fix(password-field): align input chrome with system + add Form/SIUM demo
Style fix — input chrome was hand-rolled with raw color tokens
(`--color-neutral-border`, `--ring-width`) instead of consuming the
recipe layer like every other text input. Now matches search-field and
number-field exactly:
- Added `password-field` recipe to `src/uix/eidos/lib/recipes/base.ts`
with the same surface as `search-field` (height, px, gap, font-size
per size; border, focus, invalid, bg, color, focus-shadow tokens).
- Regenerated `src/uix/eidos/generated/base.css`.
- Rewrote `password-field.css` to consume the new tokens — same focus
ring (`focus-shadow`), same border colors, same disabled opacity,
same transition tokens. VisibilityTrigger gets its own border/bg/
color-on-hover scheme aligned with search-field's clear button.
- Added per-size icon + caps font-size tokens so the trigger and caps
pill scale with the input.
Form + SIUM demo — answers "how does PasswordField validate?":
Added a new subsection in the Live tab with three working forms wired
through `<Form>` + `<Field>` and SIUM schemas:
1. Login (purpose='current', non-empty password)
2. Registration (purpose='new', complexity rules via regex pipes —
uppercase + digit + symbol + 8+ chars, plus the visual StrengthMeter
as a companion to the schema)
3. Password change (3 fields with a cross-field `refine()` so new ===
confirm; demonstrates SIUM's refine pattern)
The section explicitly documents that PasswordField has no built-in
complexity rules — validation is schema-first. The schema decides
what's "correct"; PasswordField's flags propagate via Field's OR-merge
of `formFieldState.isInvalid`.
svelte-check: 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
import {
|
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
WORDS_DOCUMENT_VERSION,
|
|
|
|
|
createHeading,
|
fix(password-field): align input chrome with system + add Form/SIUM demo
Style fix — input chrome was hand-rolled with raw color tokens
(`--color-neutral-border`, `--ring-width`) instead of consuming the
recipe layer like every other text input. Now matches search-field and
number-field exactly:
- Added `password-field` recipe to `src/uix/eidos/lib/recipes/base.ts`
with the same surface as `search-field` (height, px, gap, font-size
per size; border, focus, invalid, bg, color, focus-shadow tokens).
- Regenerated `src/uix/eidos/generated/base.css`.
- Rewrote `password-field.css` to consume the new tokens — same focus
ring (`focus-shadow`), same border colors, same disabled opacity,
same transition tokens. VisibilityTrigger gets its own border/bg/
color-on-hover scheme aligned with search-field's clear button.
- Added per-size icon + caps font-size tokens so the trigger and caps
pill scale with the input.
Form + SIUM demo — answers "how does PasswordField validate?":
Added a new subsection in the Live tab with three working forms wired
through `<Form>` + `<Field>` and SIUM schemas:
1. Login (purpose='current', non-empty password)
2. Registration (purpose='new', complexity rules via regex pipes —
uppercase + digit + symbol + 8+ chars, plus the visual StrengthMeter
as a companion to the schema)
3. Password change (3 fields with a cross-field `refine()` so new ===
confirm; demonstrates SIUM's refine pattern)
The section explicitly documents that PasswordField has no built-in
complexity rules — validation is schema-first. The schema decides
what's "correct"; PasswordField's flags propagate via Field's OR-merge
of `formFieldState.isInvalid`.
svelte-check: 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
getInlineText,
|
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
createLink,
|
|
|
|
|
createList,
|
|
|
|
|
createListItem,
|
|
|
|
|
createParagraph,
|
|
|
|
|
createQuote,
|
|
|
|
|
createText,
|
|
|
|
|
normalizeMarks,
|
fix(password-field): align input chrome with system + add Form/SIUM demo
Style fix — input chrome was hand-rolled with raw color tokens
(`--color-neutral-border`, `--ring-width`) instead of consuming the
recipe layer like every other text input. Now matches search-field and
number-field exactly:
- Added `password-field` recipe to `src/uix/eidos/lib/recipes/base.ts`
with the same surface as `search-field` (height, px, gap, font-size
per size; border, focus, invalid, bg, color, focus-shadow tokens).
- Regenerated `src/uix/eidos/generated/base.css`.
- Rewrote `password-field.css` to consume the new tokens — same focus
ring (`focus-shadow`), same border colors, same disabled opacity,
same transition tokens. VisibilityTrigger gets its own border/bg/
color-on-hover scheme aligned with search-field's clear button.
- Added per-size icon + caps font-size tokens so the trigger and caps
pill scale with the input.
Form + SIUM demo — answers "how does PasswordField validate?":
Added a new subsection in the Live tab with three working forms wired
through `<Form>` + `<Field>` and SIUM schemas:
1. Login (purpose='current', non-empty password)
2. Registration (purpose='new', complexity rules via regex pipes —
uppercase + digit + symbol + 8+ chars, plus the visual StrengthMeter
as a companion to the schema)
3. Password change (3 fields with a cross-field `refine()` so new ===
confirm; demonstrates SIUM's refine pattern)
The section explicitly documents that PasswordField has no built-in
complexity rules — validation is schema-first. The schema decides
what's "correct"; PasswordField's flags propagate via Field's OR-merge
of `formFieldState.isInvalid`.
svelte-check: 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
type WordsBlock,
|
|
|
|
|
type WordsDocument,
|
|
|
|
|
type WordsInline,
|
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
type WordsListItem,
|
|
|
|
|
type WordsMark,
|
|
|
|
|
type WordsTextAlign
|
fix(password-field): align input chrome with system + add Form/SIUM demo
Style fix — input chrome was hand-rolled with raw color tokens
(`--color-neutral-border`, `--ring-width`) instead of consuming the
recipe layer like every other text input. Now matches search-field and
number-field exactly:
- Added `password-field` recipe to `src/uix/eidos/lib/recipes/base.ts`
with the same surface as `search-field` (height, px, gap, font-size
per size; border, focus, invalid, bg, color, focus-shadow tokens).
- Regenerated `src/uix/eidos/generated/base.css`.
- Rewrote `password-field.css` to consume the new tokens — same focus
ring (`focus-shadow`), same border colors, same disabled opacity,
same transition tokens. VisibilityTrigger gets its own border/bg/
color-on-hover scheme aligned with search-field's clear button.
- Added per-size icon + caps font-size tokens so the trigger and caps
pill scale with the input.
Form + SIUM demo — answers "how does PasswordField validate?":
Added a new subsection in the Live tab with three working forms wired
through `<Form>` + `<Field>` and SIUM schemas:
1. Login (purpose='current', non-empty password)
2. Registration (purpose='new', complexity rules via regex pipes —
uppercase + digit + symbol + 8+ chars, plus the visual StrengthMeter
as a companion to the schema)
3. Password change (3 fields with a cross-field `refine()` so new ===
confirm; demonstrates SIUM's refine pattern)
The section explicitly documents that PasswordField has no built-in
complexity rules — validation is schema-first. The schema decides
what's "correct"; PasswordField's flags propagate via Field's OR-merge
of `formFieldState.isInvalid`.
svelte-check: 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
} from './document';
|
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
import { normalizeDocument, sanitizeWordsUrl } from './normalize';
|
fix(password-field): align input chrome with system + add Form/SIUM demo
Style fix — input chrome was hand-rolled with raw color tokens
(`--color-neutral-border`, `--ring-width`) instead of consuming the
recipe layer like every other text input. Now matches search-field and
number-field exactly:
- Added `password-field` recipe to `src/uix/eidos/lib/recipes/base.ts`
with the same surface as `search-field` (height, px, gap, font-size
per size; border, focus, invalid, bg, color, focus-shadow tokens).
- Regenerated `src/uix/eidos/generated/base.css`.
- Rewrote `password-field.css` to consume the new tokens — same focus
ring (`focus-shadow`), same border colors, same disabled opacity,
same transition tokens. VisibilityTrigger gets its own border/bg/
color-on-hover scheme aligned with search-field's clear button.
- Added per-size icon + caps font-size tokens so the trigger and caps
pill scale with the input.
Form + SIUM demo — answers "how does PasswordField validate?":
Added a new subsection in the Live tab with three working forms wired
through `<Form>` + `<Field>` and SIUM schemas:
1. Login (purpose='current', non-empty password)
2. Registration (purpose='new', complexity rules via regex pipes —
uppercase + digit + symbol + 8+ chars, plus the visual StrengthMeter
as a companion to the schema)
3. Password change (3 fields with a cross-field `refine()` so new ===
confirm; demonstrates SIUM's refine pattern)
The section explicitly documents that PasswordField has no built-in
complexity rules — validation is schema-first. The schema decides
what's "correct"; PasswordField's flags propagate via Field's OR-merge
of `formFieldState.isInvalid`.
svelte-check: 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
|
|
|
|
|
const MARK_TAGS = {
|
|
|
|
|
bold: 'strong',
|
|
|
|
|
italic: 'em',
|
|
|
|
|
underline: 'u',
|
|
|
|
|
strike: 's',
|
|
|
|
|
code: 'code'
|
|
|
|
|
} as const satisfies Record<WordsMark, string>;
|
|
|
|
|
|
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
type HtmlNode = HtmlElementNode | HtmlTextNode;
|
|
|
|
|
|
|
|
|
|
type HtmlElementNode = {
|
|
|
|
|
readonly type: 'element';
|
|
|
|
|
readonly name: string;
|
|
|
|
|
readonly attrs: Record<string, string | true>;
|
|
|
|
|
readonly children: HtmlNode[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type HtmlTextNode = {
|
|
|
|
|
readonly type: 'text';
|
|
|
|
|
readonly text: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const BLOCK_TAGS = new Set([
|
|
|
|
|
'article',
|
|
|
|
|
'blockquote',
|
|
|
|
|
'div',
|
|
|
|
|
'h1',
|
|
|
|
|
'h2',
|
|
|
|
|
'h3',
|
|
|
|
|
'li',
|
|
|
|
|
'ol',
|
|
|
|
|
'p',
|
|
|
|
|
'section',
|
|
|
|
|
'ul'
|
|
|
|
|
]);
|
|
|
|
|
const RAW_TEXT_TAGS = new Set(['script', 'style']);
|
|
|
|
|
const VOID_TAGS = new Set([
|
|
|
|
|
'area',
|
|
|
|
|
'base',
|
|
|
|
|
'br',
|
|
|
|
|
'col',
|
|
|
|
|
'embed',
|
|
|
|
|
'hr',
|
|
|
|
|
'img',
|
|
|
|
|
'input',
|
|
|
|
|
'link',
|
|
|
|
|
'meta'
|
|
|
|
|
]);
|
|
|
|
|
const HTML_ENTITIES: Record<string, string> = {
|
|
|
|
|
amp: '&',
|
|
|
|
|
lt: '<',
|
|
|
|
|
gt: '>',
|
|
|
|
|
quot: '"',
|
|
|
|
|
apos: "'",
|
|
|
|
|
nbsp: ' '
|
|
|
|
|
};
|
|
|
|
|
|
fix(password-field): align input chrome with system + add Form/SIUM demo
Style fix — input chrome was hand-rolled with raw color tokens
(`--color-neutral-border`, `--ring-width`) instead of consuming the
recipe layer like every other text input. Now matches search-field and
number-field exactly:
- Added `password-field` recipe to `src/uix/eidos/lib/recipes/base.ts`
with the same surface as `search-field` (height, px, gap, font-size
per size; border, focus, invalid, bg, color, focus-shadow tokens).
- Regenerated `src/uix/eidos/generated/base.css`.
- Rewrote `password-field.css` to consume the new tokens — same focus
ring (`focus-shadow`), same border colors, same disabled opacity,
same transition tokens. VisibilityTrigger gets its own border/bg/
color-on-hover scheme aligned with search-field's clear button.
- Added per-size icon + caps font-size tokens so the trigger and caps
pill scale with the input.
Form + SIUM demo — answers "how does PasswordField validate?":
Added a new subsection in the Live tab with three working forms wired
through `<Form>` + `<Field>` and SIUM schemas:
1. Login (purpose='current', non-empty password)
2. Registration (purpose='new', complexity rules via regex pipes —
uppercase + digit + symbol + 8+ chars, plus the visual StrengthMeter
as a companion to the schema)
3. Password change (3 fields with a cross-field `refine()` so new ===
confirm; demonstrates SIUM's refine pattern)
The section explicitly documents that PasswordField has no built-in
complexity rules — validation is schema-first. The schema decides
what's "correct"; PasswordField's flags propagate via Field's OR-merge
of `formFieldState.isInvalid`.
svelte-check: 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
export function serializeWordsHtml(document: WordsDocument): string {
|
|
|
|
|
return document.children.map(serializeBlockHtml).join('');
|
|
|
|
|
}
|
|
|
|
|
|
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
export function parseWordsHtml(html: string): WordsDocument {
|
|
|
|
|
const root = parseHtmlTree(html.replace(/\u0000/g, ''));
|
|
|
|
|
const children = htmlNodesToBlocks(root.children);
|
|
|
|
|
return normalizeDocument({
|
|
|
|
|
version: WORDS_DOCUMENT_VERSION,
|
|
|
|
|
children: children.length ? children : [createParagraph()]
|
|
|
|
|
}).document;
|
|
|
|
|
}
|
|
|
|
|
|
fix(password-field): align input chrome with system + add Form/SIUM demo
Style fix — input chrome was hand-rolled with raw color tokens
(`--color-neutral-border`, `--ring-width`) instead of consuming the
recipe layer like every other text input. Now matches search-field and
number-field exactly:
- Added `password-field` recipe to `src/uix/eidos/lib/recipes/base.ts`
with the same surface as `search-field` (height, px, gap, font-size
per size; border, focus, invalid, bg, color, focus-shadow tokens).
- Regenerated `src/uix/eidos/generated/base.css`.
- Rewrote `password-field.css` to consume the new tokens — same focus
ring (`focus-shadow`), same border colors, same disabled opacity,
same transition tokens. VisibilityTrigger gets its own border/bg/
color-on-hover scheme aligned with search-field's clear button.
- Added per-size icon + caps font-size tokens so the trigger and caps
pill scale with the input.
Form + SIUM demo — answers "how does PasswordField validate?":
Added a new subsection in the Live tab with three working forms wired
through `<Form>` + `<Field>` and SIUM schemas:
1. Login (purpose='current', non-empty password)
2. Registration (purpose='new', complexity rules via regex pipes —
uppercase + digit + symbol + 8+ chars, plus the visual StrengthMeter
as a companion to the schema)
3. Password change (3 fields with a cross-field `refine()` so new ===
confirm; demonstrates SIUM's refine pattern)
The section explicitly documents that PasswordField has no built-in
complexity rules — validation is schema-first. The schema decides
what's "correct"; PasswordField's flags propagate via Field's OR-merge
of `formFieldState.isInvalid`.
svelte-check: 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
function serializeBlockHtml(block: WordsBlock): string {
|
|
|
|
|
if (block.type === 'list') {
|
|
|
|
|
const tag = block.kind === 'ordered' ? 'ol' : 'ul';
|
|
|
|
|
const children = block.children
|
|
|
|
|
.map((item) => {
|
|
|
|
|
const checkbox =
|
|
|
|
|
block.kind === 'check'
|
|
|
|
|
? `<input type="checkbox"${item.checked === true ? ' checked' : ''} disabled> `
|
|
|
|
|
: '';
|
|
|
|
|
const indentStyle = item.indent ? ` style="margin-left: ${item.indent * 1.5}em"` : '';
|
|
|
|
|
return `<li${indentStyle}>${checkbox}${serializeInlinesHtml(item.children)}</li>`;
|
fix(password-field): align input chrome with system + add Form/SIUM demo
Style fix — input chrome was hand-rolled with raw color tokens
(`--color-neutral-border`, `--ring-width`) instead of consuming the
recipe layer like every other text input. Now matches search-field and
number-field exactly:
- Added `password-field` recipe to `src/uix/eidos/lib/recipes/base.ts`
with the same surface as `search-field` (height, px, gap, font-size
per size; border, focus, invalid, bg, color, focus-shadow tokens).
- Regenerated `src/uix/eidos/generated/base.css`.
- Rewrote `password-field.css` to consume the new tokens — same focus
ring (`focus-shadow`), same border colors, same disabled opacity,
same transition tokens. VisibilityTrigger gets its own border/bg/
color-on-hover scheme aligned with search-field's clear button.
- Added per-size icon + caps font-size tokens so the trigger and caps
pill scale with the input.
Form + SIUM demo — answers "how does PasswordField validate?":
Added a new subsection in the Live tab with three working forms wired
through `<Form>` + `<Field>` and SIUM schemas:
1. Login (purpose='current', non-empty password)
2. Registration (purpose='new', complexity rules via regex pipes —
uppercase + digit + symbol + 8+ chars, plus the visual StrengthMeter
as a companion to the schema)
3. Password change (3 fields with a cross-field `refine()` so new ===
confirm; demonstrates SIUM's refine pattern)
The section explicitly documents that PasswordField has no built-in
complexity rules — validation is schema-first. The schema decides
what's "correct"; PasswordField's flags propagate via Field's OR-merge
of `formFieldState.isInvalid`.
svelte-check: 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
})
|
|
|
|
|
.join('');
|
|
|
|
|
return `<${tag}>${children}</${tag}>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const align = block.textAlign && block.textAlign !== 'left' ? ` style="text-align: ${block.textAlign}"` : '';
|
|
|
|
|
|
fix(password-field): align input chrome with system + add Form/SIUM demo
Style fix — input chrome was hand-rolled with raw color tokens
(`--color-neutral-border`, `--ring-width`) instead of consuming the
recipe layer like every other text input. Now matches search-field and
number-field exactly:
- Added `password-field` recipe to `src/uix/eidos/lib/recipes/base.ts`
with the same surface as `search-field` (height, px, gap, font-size
per size; border, focus, invalid, bg, color, focus-shadow tokens).
- Regenerated `src/uix/eidos/generated/base.css`.
- Rewrote `password-field.css` to consume the new tokens — same focus
ring (`focus-shadow`), same border colors, same disabled opacity,
same transition tokens. VisibilityTrigger gets its own border/bg/
color-on-hover scheme aligned with search-field's clear button.
- Added per-size icon + caps font-size tokens so the trigger and caps
pill scale with the input.
Form + SIUM demo — answers "how does PasswordField validate?":
Added a new subsection in the Live tab with three working forms wired
through `<Form>` + `<Field>` and SIUM schemas:
1. Login (purpose='current', non-empty password)
2. Registration (purpose='new', complexity rules via regex pipes —
uppercase + digit + symbol + 8+ chars, plus the visual StrengthMeter
as a companion to the schema)
3. Password change (3 fields with a cross-field `refine()` so new ===
confirm; demonstrates SIUM's refine pattern)
The section explicitly documents that PasswordField has no built-in
complexity rules — validation is schema-first. The schema decides
what's "correct"; PasswordField's flags propagate via Field's OR-merge
of `formFieldState.isInvalid`.
svelte-check: 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
if (block.type === 'heading') {
|
|
|
|
|
return `<h${block.level}${align}>${serializeInlinesHtml(block.children)}</h${block.level}>`;
|
fix(password-field): align input chrome with system + add Form/SIUM demo
Style fix — input chrome was hand-rolled with raw color tokens
(`--color-neutral-border`, `--ring-width`) instead of consuming the
recipe layer like every other text input. Now matches search-field and
number-field exactly:
- Added `password-field` recipe to `src/uix/eidos/lib/recipes/base.ts`
with the same surface as `search-field` (height, px, gap, font-size
per size; border, focus, invalid, bg, color, focus-shadow tokens).
- Regenerated `src/uix/eidos/generated/base.css`.
- Rewrote `password-field.css` to consume the new tokens — same focus
ring (`focus-shadow`), same border colors, same disabled opacity,
same transition tokens. VisibilityTrigger gets its own border/bg/
color-on-hover scheme aligned with search-field's clear button.
- Added per-size icon + caps font-size tokens so the trigger and caps
pill scale with the input.
Form + SIUM demo — answers "how does PasswordField validate?":
Added a new subsection in the Live tab with three working forms wired
through `<Form>` + `<Field>` and SIUM schemas:
1. Login (purpose='current', non-empty password)
2. Registration (purpose='new', complexity rules via regex pipes —
uppercase + digit + symbol + 8+ chars, plus the visual StrengthMeter
as a companion to the schema)
3. Password change (3 fields with a cross-field `refine()` so new ===
confirm; demonstrates SIUM's refine pattern)
The section explicitly documents that PasswordField has no built-in
complexity rules — validation is schema-first. The schema decides
what's "correct"; PasswordField's flags propagate via Field's OR-merge
of `formFieldState.isInvalid`.
svelte-check: 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (block.type === 'quote') {
|
|
|
|
|
return `<blockquote${align}>${serializeInlinesHtml(block.children)}</blockquote>`;
|
fix(password-field): align input chrome with system + add Form/SIUM demo
Style fix — input chrome was hand-rolled with raw color tokens
(`--color-neutral-border`, `--ring-width`) instead of consuming the
recipe layer like every other text input. Now matches search-field and
number-field exactly:
- Added `password-field` recipe to `src/uix/eidos/lib/recipes/base.ts`
with the same surface as `search-field` (height, px, gap, font-size
per size; border, focus, invalid, bg, color, focus-shadow tokens).
- Regenerated `src/uix/eidos/generated/base.css`.
- Rewrote `password-field.css` to consume the new tokens — same focus
ring (`focus-shadow`), same border colors, same disabled opacity,
same transition tokens. VisibilityTrigger gets its own border/bg/
color-on-hover scheme aligned with search-field's clear button.
- Added per-size icon + caps font-size tokens so the trigger and caps
pill scale with the input.
Form + SIUM demo — answers "how does PasswordField validate?":
Added a new subsection in the Live tab with three working forms wired
through `<Form>` + `<Field>` and SIUM schemas:
1. Login (purpose='current', non-empty password)
2. Registration (purpose='new', complexity rules via regex pipes —
uppercase + digit + symbol + 8+ chars, plus the visual StrengthMeter
as a companion to the schema)
3. Password change (3 fields with a cross-field `refine()` so new ===
confirm; demonstrates SIUM's refine pattern)
The section explicitly documents that PasswordField has no built-in
complexity rules — validation is schema-first. The schema decides
what's "correct"; PasswordField's flags propagate via Field's OR-merge
of `formFieldState.isInvalid`.
svelte-check: 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return `<p${align}>${serializeInlinesHtml(block.children)}</p>`;
|
fix(password-field): align input chrome with system + add Form/SIUM demo
Style fix — input chrome was hand-rolled with raw color tokens
(`--color-neutral-border`, `--ring-width`) instead of consuming the
recipe layer like every other text input. Now matches search-field and
number-field exactly:
- Added `password-field` recipe to `src/uix/eidos/lib/recipes/base.ts`
with the same surface as `search-field` (height, px, gap, font-size
per size; border, focus, invalid, bg, color, focus-shadow tokens).
- Regenerated `src/uix/eidos/generated/base.css`.
- Rewrote `password-field.css` to consume the new tokens — same focus
ring (`focus-shadow`), same border colors, same disabled opacity,
same transition tokens. VisibilityTrigger gets its own border/bg/
color-on-hover scheme aligned with search-field's clear button.
- Added per-size icon + caps font-size tokens so the trigger and caps
pill scale with the input.
Form + SIUM demo — answers "how does PasswordField validate?":
Added a new subsection in the Live tab with three working forms wired
through `<Form>` + `<Field>` and SIUM schemas:
1. Login (purpose='current', non-empty password)
2. Registration (purpose='new', complexity rules via regex pipes —
uppercase + digit + symbol + 8+ chars, plus the visual StrengthMeter
as a companion to the schema)
3. Password change (3 fields with a cross-field `refine()` so new ===
confirm; demonstrates SIUM's refine pattern)
The section explicitly documents that PasswordField has no built-in
complexity rules — validation is schema-first. The schema decides
what's "correct"; PasswordField's flags propagate via Field's OR-merge
of `formFieldState.isInvalid`.
svelte-check: 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function serializeInlinesHtml(inlines: readonly WordsInline[]): string {
|
|
|
|
|
return inlines.map(serializeInlineHtml).join('');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function serializeInlineHtml(inline: WordsInline): string {
|
|
|
|
|
if (inline.type === 'link') {
|
|
|
|
|
const href = sanitizeWordsUrl(inline.href);
|
|
|
|
|
if (!href) return escapeHtml(inline.children.map(getInlineText).join(''));
|
|
|
|
|
const title = inline.title ? ` title="${escapeHtmlAttr(inline.title)}"` : '';
|
|
|
|
|
return `<a href="${escapeHtmlAttr(href)}"${title}>${serializeInlinesHtml(inline.children)}</a>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return wrapMarks(escapeHtml(inline.text), inline.marks ?? []);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function wrapMarks(value: string, marks: readonly WordsMark[]): string {
|
|
|
|
|
return marks.reduce((html, mark) => {
|
|
|
|
|
const tag = MARK_TAGS[mark];
|
|
|
|
|
return `<${tag}>${html}</${tag}>`;
|
|
|
|
|
}, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function escapeHtml(value: string): string {
|
|
|
|
|
return value
|
|
|
|
|
.replace(/&/g, '&')
|
|
|
|
|
.replace(/</g, '<')
|
|
|
|
|
.replace(/>/g, '>')
|
|
|
|
|
.replace(/"/g, '"')
|
|
|
|
|
.replace(/'/g, ''');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function escapeHtmlAttr(value: string): string {
|
|
|
|
|
return escapeHtml(value).replace(/`/g, '`');
|
|
|
|
|
}
|
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
|
|
|
|
|
function parseHtmlTree(html: string): HtmlElementNode {
|
|
|
|
|
const root = createHtmlElement('root');
|
|
|
|
|
const stack: HtmlElementNode[] = [root];
|
|
|
|
|
let skippedRawTag: string | null = null;
|
|
|
|
|
const tokens = html.match(/<!--[\s\S]*?-->|<\/?[^>]+>|[^<]+/g) ?? [];
|
|
|
|
|
|
|
|
|
|
for (const token of tokens) {
|
|
|
|
|
if (token.startsWith('<!--')) continue;
|
|
|
|
|
if (token.startsWith('<')) {
|
|
|
|
|
const tag = parseHtmlTag(token);
|
|
|
|
|
if (!tag) continue;
|
|
|
|
|
if (skippedRawTag) {
|
|
|
|
|
if (tag.closing && tag.name === skippedRawTag) skippedRawTag = null;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (RAW_TEXT_TAGS.has(tag.name)) {
|
|
|
|
|
if (!tag.closing) skippedRawTag = tag.name;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (tag.closing) {
|
|
|
|
|
popHtmlStackToTag(stack, tag.name);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const node = createHtmlElement(tag.name, tag.attrs);
|
|
|
|
|
stack[stack.length - 1]?.children.push(node);
|
|
|
|
|
if (!tag.selfClosing && !VOID_TAGS.has(tag.name)) stack.push(node);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!skippedRawTag) {
|
|
|
|
|
stack[stack.length - 1]?.children.push({ type: 'text', text: decodeHtml(token) });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return root;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function parseHtmlTag(
|
|
|
|
|
token: string
|
|
|
|
|
): {
|
|
|
|
|
name: string;
|
|
|
|
|
attrs: Record<string, string | true>;
|
|
|
|
|
closing: boolean;
|
|
|
|
|
selfClosing: boolean;
|
|
|
|
|
} | null {
|
|
|
|
|
const match = token.match(/^<\s*(\/)?\s*([a-zA-Z][\w:-]*)([\s\S]*?)(\/)?\s*>$/);
|
|
|
|
|
if (!match) return null;
|
|
|
|
|
return {
|
|
|
|
|
name: (match[2] ?? '').toLowerCase(),
|
|
|
|
|
attrs: parseHtmlAttrs(match[3] ?? ''),
|
|
|
|
|
closing: Boolean(match[1]),
|
|
|
|
|
selfClosing: Boolean(match[4])
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function parseHtmlAttrs(raw: string): Record<string, string | true> {
|
|
|
|
|
const attrs: Record<string, string | true> = {};
|
|
|
|
|
for (const match of raw.matchAll(
|
|
|
|
|
/([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>/]+)))?/g
|
|
|
|
|
)) {
|
|
|
|
|
const name = (match[1] ?? '').toLowerCase();
|
|
|
|
|
if (!name) continue;
|
|
|
|
|
attrs[name] =
|
|
|
|
|
match[2] || match[3] || match[4] ? decodeHtml(match[2] ?? match[3] ?? match[4] ?? '') : true;
|
|
|
|
|
}
|
|
|
|
|
return attrs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createHtmlElement(
|
|
|
|
|
name: string,
|
|
|
|
|
attrs: Record<string, string | true> = {}
|
|
|
|
|
): HtmlElementNode {
|
|
|
|
|
return { type: 'element', name, attrs, children: [] };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function popHtmlStackToTag(stack: HtmlElementNode[], name: string) {
|
|
|
|
|
for (let index = stack.length - 1; index > 0; index -= 1) {
|
|
|
|
|
if (stack[index]?.name !== name) continue;
|
|
|
|
|
stack.length = index;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function htmlNodesToBlocks(nodes: readonly HtmlNode[]): WordsBlock[] {
|
|
|
|
|
const blocks: WordsBlock[] = [];
|
|
|
|
|
let inlineBuffer: WordsInline[] = [];
|
|
|
|
|
|
|
|
|
|
const flushInlineBuffer = () => {
|
|
|
|
|
if (!inlineBuffer.length) return;
|
|
|
|
|
if (inlineBuffer.some((inline) => getInlineText(inline).trim().length > 0)) {
|
|
|
|
|
blocks.push(createParagraph(inlineBuffer));
|
|
|
|
|
}
|
|
|
|
|
inlineBuffer = [];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (const node of nodes) {
|
|
|
|
|
if (node.type === 'text') {
|
|
|
|
|
if (node.text.trim()) inlineBuffer.push(createText(collapseHtmlWhitespace(node.text)));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!BLOCK_TAGS.has(node.name)) {
|
|
|
|
|
inlineBuffer.push(...htmlNodesToInlines([node]));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
flushInlineBuffer();
|
|
|
|
|
blocks.push(...htmlElementToBlocks(node));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
flushInlineBuffer();
|
|
|
|
|
return blocks;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function textAlignFromStyle(node: HtmlElementNode): WordsTextAlign | undefined {
|
|
|
|
|
const style = typeof node.attrs.style === 'string' ? node.attrs.style : '';
|
|
|
|
|
const match = style.match(/text-align\s*:\s*(left|center|right|justify)/i);
|
|
|
|
|
const align = match?.[1]?.toLowerCase();
|
|
|
|
|
if (align === 'left' || align === 'center' || align === 'right' || align === 'justify') {
|
|
|
|
|
return align;
|
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
function htmlElementToBlocks(node: HtmlElementNode): WordsBlock[] {
|
|
|
|
|
if (node.name === 'h1' || node.name === 'h2' || node.name === 'h3') {
|
|
|
|
|
return [
|
|
|
|
|
createHeading(
|
|
|
|
|
Number(node.name.slice(1)) as 1 | 2 | 3,
|
|
|
|
|
htmlNodesToInlines(node.children),
|
|
|
|
|
textAlignFromStyle(node)
|
|
|
|
|
)
|
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (node.name === 'blockquote') {
|
|
|
|
|
return [createQuote(htmlNodesToInlines(node.children), textAlignFromStyle(node))];
|
|
|
|
|
}
|
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
|
|
|
|
|
if (node.name === 'ul' || node.name === 'ol') {
|
|
|
|
|
return [htmlListToBlock(node)];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (node.name === 'li') return [createParagraph(htmlNodesToInlines(node.children))];
|
|
|
|
|
|
|
|
|
|
if (node.children.some((child) => child.type === 'element' && BLOCK_TAGS.has(child.name))) {
|
|
|
|
|
return htmlNodesToBlocks(node.children);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [createParagraph(htmlNodesToInlines(node.children), textAlignFromStyle(node))];
|
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function htmlListToBlock(node: HtmlElementNode): WordsBlock {
|
|
|
|
|
const items = node.children.filter(
|
|
|
|
|
(child): child is HtmlElementNode => child.type === 'element' && child.name === 'li'
|
|
|
|
|
);
|
|
|
|
|
const kind =
|
|
|
|
|
node.name === 'ol' ? 'ordered' : items.some(hasCheckboxInput) ? 'check' : 'unordered';
|
|
|
|
|
return createList(
|
|
|
|
|
kind,
|
|
|
|
|
items.map((item) => htmlListItemToWords(item))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function htmlListItemToWords(node: HtmlElementNode): WordsListItem {
|
|
|
|
|
const hasCheckbox = hasCheckboxInput(node);
|
|
|
|
|
const inlines = htmlNodesToInlines(node.children);
|
|
|
|
|
const indent = indentFromStyle(node);
|
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
return createListItem(
|
|
|
|
|
hasCheckbox ? trimLeadingInlineWhitespace(inlines) : inlines,
|
|
|
|
|
hasCheckedInput(node),
|
|
|
|
|
indent
|
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function indentFromStyle(node: HtmlElementNode): number | undefined {
|
|
|
|
|
const style = typeof node.attrs.style === 'string' ? node.attrs.style : '';
|
|
|
|
|
const match = style.match(/margin-left\s*:\s*([\d.]+)em/);
|
|
|
|
|
if (!match) return undefined;
|
|
|
|
|
const em = Number.parseFloat(match[1] ?? '');
|
|
|
|
|
if (!Number.isFinite(em) || em <= 0) return undefined;
|
|
|
|
|
const level = Math.round(em / 1.5);
|
|
|
|
|
return level > 0 && level <= 8 ? level : undefined;
|
|
|
|
|
}
|
|
|
|
|
|
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
function htmlNodesToInlines(
|
|
|
|
|
nodes: readonly HtmlNode[],
|
|
|
|
|
marks: readonly WordsMark[] = [],
|
|
|
|
|
options: { insideLink?: boolean } = {}
|
|
|
|
|
): WordsInline[] {
|
|
|
|
|
const out: WordsInline[] = [];
|
|
|
|
|
for (const node of nodes) {
|
|
|
|
|
if (node.type === 'text') {
|
|
|
|
|
out.push(createText(collapseHtmlWhitespace(node.text), marks));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (node.name === 'br') {
|
|
|
|
|
out.push(createText('\n', marks));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (node.name === 'input') continue;
|
|
|
|
|
|
|
|
|
|
const nextMarks = marksForHtmlElement(node.name, marks);
|
|
|
|
|
if (node.name === 'a' && !options.insideLink) {
|
|
|
|
|
const href =
|
|
|
|
|
typeof node.attrs.href === 'string' ? sanitizeWordsUrl(node.attrs.href) : undefined;
|
|
|
|
|
const children = htmlNodesToInlines(node.children, nextMarks, { insideLink: true });
|
|
|
|
|
if (href) {
|
|
|
|
|
out.push(
|
|
|
|
|
createLink(
|
|
|
|
|
href,
|
|
|
|
|
children,
|
|
|
|
|
typeof node.attrs.title === 'string' ? node.attrs.title : undefined
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
out.push(...children);
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out.push(...htmlNodesToInlines(node.children, nextMarks, options));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return out.length ? out : [createText('', marks)];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function marksForHtmlElement(name: string, marks: readonly WordsMark[]): readonly WordsMark[] {
|
|
|
|
|
switch (name) {
|
|
|
|
|
case 'strong':
|
|
|
|
|
case 'b':
|
|
|
|
|
return normalizeMarks([...marks, 'bold']);
|
|
|
|
|
case 'em':
|
|
|
|
|
case 'i':
|
|
|
|
|
return normalizeMarks([...marks, 'italic']);
|
|
|
|
|
case 'u':
|
|
|
|
|
return normalizeMarks([...marks, 'underline']);
|
|
|
|
|
case 's':
|
|
|
|
|
case 'strike':
|
|
|
|
|
case 'del':
|
|
|
|
|
return normalizeMarks([...marks, 'strike']);
|
|
|
|
|
case 'code':
|
|
|
|
|
return normalizeMarks([...marks, 'code']);
|
|
|
|
|
default:
|
|
|
|
|
return marks;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function hasCheckboxInput(node: HtmlElementNode): boolean {
|
|
|
|
|
return node.children.some(
|
|
|
|
|
(child) =>
|
|
|
|
|
child.type === 'element' &&
|
|
|
|
|
child.name === 'input' &&
|
|
|
|
|
(typeof child.attrs.type !== 'string' || child.attrs.type.toLowerCase() === 'checkbox')
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function hasCheckedInput(node: HtmlElementNode): boolean {
|
|
|
|
|
return node.children.some(
|
|
|
|
|
(child) =>
|
|
|
|
|
child.type === 'element' &&
|
|
|
|
|
child.name === 'input' &&
|
|
|
|
|
(typeof child.attrs.type !== 'string' || child.attrs.type.toLowerCase() === 'checkbox') &&
|
|
|
|
|
'checked' in child.attrs
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function collapseHtmlWhitespace(value: string): string {
|
|
|
|
|
return value.replace(/\s+/g, ' ');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function trimLeadingInlineWhitespace(inlines: readonly WordsInline[]): readonly WordsInline[] {
|
|
|
|
|
const [first, ...rest] = inlines;
|
|
|
|
|
if (!first || first.type !== 'text') return inlines;
|
|
|
|
|
return [createText(first.text.replace(/^\s+/, ''), first.marks), ...rest];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function decodeHtml(value: string): string {
|
|
|
|
|
return value.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (entity, body: string) => {
|
|
|
|
|
if (body.startsWith('#x') || body.startsWith('#X')) {
|
|
|
|
|
return codepointToString(Number.parseInt(body.slice(2), 16), entity);
|
|
|
|
|
}
|
|
|
|
|
if (body.startsWith('#')) return codepointToString(Number.parseInt(body.slice(1), 10), entity);
|
|
|
|
|
return HTML_ENTITIES[body] ?? entity;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function codepointToString(value: number, fallback: string): string {
|
|
|
|
|
return Number.isFinite(value) ? String.fromCodePoint(value) : fallback;
|
|
|
|
|
}
|