From 1eea45ae77cdd45f3b0b88618a9577110ede4c0c Mon Sep 17 00:00:00 2001 From: tommy230 Date: Mon, 28 Sep 2026 19:34:17 +0000 Subject: [PATCH] fix(import): keep the content a compound submit button wraps base.submit was a leaf whose only content was the label string, with a 'Submit' fallback for buttons with no text. Submit buttons that carry an icon instead of text (an inline svg, an icon-font , an ) lost the icon on import and published as the literal word "Submit". base.submit now accepts children and renders them when it has any, falling back to the label when it has none (the base.link contract, shared between the publisher render and the canvas via forms/content.ts). The importer recurses into submit buttons with element children instead of flattening them to a label. Co-Authored-By: Claude Opus 5.5 --- docs/features/html-import.md | 4 +- docs/features/modules.md | 2 +- docs/reference/module-engine.md | 2 + .../htmlImport/structurePreservation.test.ts | 6 +- .../htmlImport/submitButtonChildren.test.ts | 99 +++++++++++++++++++ src/core/htmlImport/rules.ts | 9 +- src/modules/base/forms/FormControls.tsx | 6 +- src/modules/base/forms/content.ts | 24 +++++ src/modules/base/forms/index.ts | 19 +++- 9 files changed, 154 insertions(+), 17 deletions(-) create mode 100644 src/__tests__/htmlImport/submitButtonChildren.test.ts create mode 100644 src/modules/base/forms/content.ts diff --git a/docs/features/html-import.md b/docs/features/html-import.md index 7cb7bf493..a9a2815d6 100644 --- a/docs/features/html-import.md +++ b/docs/features/html-import.md @@ -108,7 +108,7 @@ Callers splice the fragment into the page tree via `insertImportedNodes(parentId | `select` | `base.select` | `fieldId`, `name`, `id`, `required`, `disabled`, `multiple` | Yes | | `optgroup` | `base.option-group` | `label`, `disabled` | Yes | | `option` | `base.option` | `value`, `label`, `selected`, `disabled` | No | -| `button` | `base.button`, `base.submit` when submit-type / inside a form without a type, or `base.container` tagged `button` when a non-submit button wraps element children | `label`, `disabled` | No for text-only and for submit buttons; yes for compound non-submit buttons | +| `button` | `base.button`, `base.submit` when submit-type / inside a form without a type, or `base.container` tagged `button` when a non-submit button wraps element children | `label`, `disabled` | No for text-only; yes when the button wraps element children | | `ul`, `ol` | `base.container` | `tag` = tag name | Yes | | `div`, `section`, `article`, `main`, `header`, `footer`, `nav`, `aside` | `base.container` | `tag` = tag name | Yes | | `area`, `base`, `br`, `col`, `embed`, `hr`, `link`, `meta`, `param`, `source`, `track`, `wbr` (void elements) | `base.container` | `tag: 'custom'`, `customTag` = tag name | **No** | @@ -122,7 +122,7 @@ Callers splice the fragment into the page tree via `insertImportedNodes(parentId - **Direct text inside a recursing container is preserved.** The walker iterates `childNodes` (not just `children`): element children route through the rules, and each significant text node becomes a synthesized `base.text` child with `tag: 'none'` in document order. That no-wrapper text mode publishes back to bare text, so `
98%
` and `
  • Buy milk
  • ` import as containers holding their original text without adding selector-visible wrapper elements. Whitespace-only text (indentation between tags) is skipped; internal whitespace runs collapse to single spaces, and boundary spaces are kept when the text run sits between element siblings. - **`` metadata is preserved separately.** Classes, safe HTML attributes (`id`, ARIA, `data-*`, etc.), and harvested inline styles on `` are returned as `fragment.body` rather than inserted into `rootIds`. Full-site import applies them to `base.body`; paste-style HTML import can ignore them without changing the fragment structure. - `base.link` uses the prop `text` (not `label`). `base.button` uses `label` (not `text`). These match the module source. -- **Button-like elements keep what they wrap.** `base.button` is `canHaveChildren: false`, so an `a.btn` or a `