kerfjs 3.0.0-beta.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,6 +33,124 @@ import { B as Binding } from './bindings-CYwoJpQb.js';
33
33
  * undefined / `SafeHtml`. Event-handler props (`onClick` etc.) are
34
34
  * deliberately omitted: kerf renders to strings, so inline handlers do
35
35
  * nothing. Use `delegate()` / `delegateCapture()` instead.
36
+ *
37
+ * ---
38
+ *
39
+ * ## Provenance — where these types come from
40
+ *
41
+ * Attribute names, value sets, and per-element membership are taken from the
42
+ * **WHATWG HTML Living Standard** (and **SVG 2** for the SVG interfaces), with
43
+ * MDN used only as a readable index into them. They are NOT derived from
44
+ * `@types/react`, `lib.dom.d.ts`, or any other framework's table — those model
45
+ * a *property* surface (`HTMLElement.draggable: boolean`), and kerf emits
46
+ * *content attributes* into an HTML string, which is a different contract in
47
+ * exactly the places that bite (see the enumerated-attribute rule below).
48
+ *
49
+ * Coverage is deliberately focused rather than exhaustive: the ~100 most-used
50
+ * elements and their commonly-authored attributes. A missing attribute is a
51
+ * gap to fill, not a statement that it's invalid — extend via declaration
52
+ * merging (above) until it lands here.
53
+ *
54
+ * ## The rule that governs every value type
55
+ *
56
+ * `boolean` in an attribute type means **HTML boolean attribute** — one whose
57
+ * *presence* is the whole signal. `foo={true}` renders ` foo` and `foo={false}`
58
+ * renders nothing, so only attributes with those exact semantics may accept a
59
+ * boolean.
60
+ *
61
+ * HTML's **enumerated** attributes look boolean but are not: they take the
62
+ * literal *strings* `"true"` / `"false"`, and their missing-value default is a
63
+ * third state. Typing one as `boolean` produces markup that silently means the
64
+ * opposite of what was written:
65
+ *
66
+ * - `draggable={true}` → `<div draggable>` → empty value is invalid for
67
+ * `draggable`, so the element falls to the **auto** state — which for a
68
+ * `<div>` means **not draggable**. The attribute that was supposed to turn
69
+ * dragging on turns nothing on.
70
+ * - `draggable={false}` → attribute omitted → **auto** again, and auto for
71
+ * `<img>` / `<a href>` is *draggable*. The disable never happens either.
72
+ * - `spellCheck={false}` / `contentEditable={false}` → omitted → the
73
+ * **inherit** default, not the false state. (Their `true` direction happens
74
+ * to work: the empty string is a spec keyword for the true state on those
75
+ * two, unlike `draggable`.)
76
+ *
77
+ * So these are typed as string literal unions and reject `boolean` outright.
78
+ * Six attributes have this shape; they do NOT all share one type, because the
79
+ * keywords differ — `EnumeratedBool` (`"true"`/`"false"`) covers `draggable`,
80
+ * `spellcheck` and `writingsuggestions`, `ContentEditableValue` adds
81
+ * `plaintext-only`, and `translate` (`"yes"`/`"no"`) and `autocorrect`
82
+ * (`"on"`/`"off"`) spell their keywords differently again. Grep `EnumeratedBool`
83
+ * for the largest group. The fix is at the type
84
+ * level rather than in the runtime on purpose: translating `{true}` →
85
+ * `="true"` would require the renderer to carry a list of every enumerated
86
+ * attribute in HTML, and any attribute *missing* from that list would silently
87
+ * regress to precisely this bug. A per-attribute type keeps the knowledge where
88
+ * the spec knowledge already lives and costs nothing at runtime. The tradeoff
89
+ * is a compile error on `draggable={true}` — which is the point.
90
+ *
91
+ * One residual hole this cannot close: a signal-valued attribute
92
+ * (`draggable={sig}`) is `ReadonlySignal<unknown>`, so a boolean inside it is
93
+ * invisible to the type system. Put the string in the signal: `signal('true')`.
94
+ *
95
+ * A third shape sits between the two and accepts `boolean | string`
96
+ * legitimately: **presence-or-value** attributes, where the presence carries
97
+ * the meaning and a value refines it. `download` is the clearest case — bare,
98
+ * it means "download this and let the server name the file"; with a value, the
99
+ * value is the filename; absent, it's ordinary navigation. `capture` on
100
+ * `<input type="file">` is the same shape (bare = the default capture device,
101
+ * `user`/`environment` pick one).
102
+ *
103
+ * The test that separates this shape from the enumerated one: ask what
104
+ * `{true}` and `{false}` each render, then what those markups MEAN. Here all
105
+ * three states are real and distinct, so both forms are typed. For `draggable`
106
+ * they collapse onto the same `auto` state, which is why `boolean` is rejected
107
+ * there.
108
+ *
109
+ * ## Deliberate deviations from the spec
110
+ *
111
+ * Each of these is a knowing departure, kept because removing it would cost
112
+ * more than it buys:
113
+ *
114
+ * - **Lowercase aliases** (`class`, `for`, `tabindex`, `autofocus`,
115
+ * `spellcheck`, `contenteditable`, `autocomplete`) sit alongside the
116
+ * camelCase forms. Both spellings are accepted because the migration docs
117
+ * tell incoming developers to write the real HTML name.
118
+ * - **`contentEditable="inherit"`** is accepted but is *not* a spec keyword.
119
+ * It lands on the inherit state only via the invalid-value default. Kept
120
+ * for React parity; omitting the attribute is the spec-correct way to
121
+ * inherit.
122
+ * - **`tabindex` / `autofocus` / `capture`** accept a widened value set
123
+ * (string ints, plain `boolean`) matching what the parser actually honors.
124
+ * - **`cellPadding` / `cellSpacing`** are obsolete presentational attributes,
125
+ * marked `@deprecated` rather than removed so legacy markup still compiles.
126
+ * - **`xlink:*`** attributes are deprecated in SVG 2 but still typed — real
127
+ * documents and icon sprites still carry them.
128
+ * - **`data-morph-skip` / `-skip-children` / `-preserve`** are kerf's own
129
+ * `data-*` attributes, valid HTML by the `data-*` rule.
130
+ * - **`<meta property>`** is Open Graph vocabulary, not an attribute in the
131
+ * HTML standard. Typed anyway because it is universal in real documents —
132
+ * every social-preview `<head>` carries `og:*` meta tags.
133
+ *
134
+ * Attributes that are *not* typed because they do nothing when rendered as
135
+ * markup: `value` / `defaultValue` on `<select>` and `<textarea>` (neither
136
+ * element has a `value` content attribute — a select's selection comes from
137
+ * `<option selected>`, a textarea's value is its child text), any `on*`
138
+ * handler prop (rejected at runtime — use `delegate()`), and the declarative
139
+ * shadow DOM attributes on `<template>` (`shadowrootmode` and its
140
+ * `shadowrootdelegatesfocus` / `shadowrootclonable` companions): the fragment
141
+ * parsing behind `innerHTML` — kerf's entire client render path — never
142
+ * instantiates declarative shadow roots (only the document parser and
143
+ * `setHTMLUnsafe` do), so through `mount()` the template would sit inert in
144
+ * the DOM instead of becoming a shadow root. Declaration-merge them if you
145
+ * emit full-document SSR strings, where the document parser does honor them.
146
+ *
147
+ * Attributes outside the Living Standard stay out even when browsers ship
148
+ * them: `<video disablepictureinpicture / disableremoteplayback>` live in
149
+ * separate W3C specs and only appear in video-player code, `<iframe
150
+ * credentialless>` is engine-specific (in the standard only as a COEP value,
151
+ * not an iframe attribute), and `<iframe allowpaymentrequest>` was removed
152
+ * from the standard outright. Declaration merging is the path for all of
153
+ * them.
36
154
  */
37
155
 
38
156
  /**
@@ -44,6 +162,45 @@ import { B as Binding } from './bindings-CYwoJpQb.js';
44
162
  type AttrValue = string | number | boolean | null | undefined | SafeHtml | ReadonlySignal<unknown>;
45
163
  /** A typed-narrowing helper: `AttrLike<'a'|'b'>` accepts the literals plus the runtime fall-throughs. */
46
164
  type AttrLike<T = string> = T | SafeHtml | null | undefined | ReadonlySignal<unknown>;
165
+ /**
166
+ * An HTML **enumerated** attribute whose keywords are the literal strings
167
+ * `"true"` / `"false"` — NOT a boolean attribute.
168
+ *
169
+ * This is the shape that has to be got right more than any other in this file,
170
+ * because getting it wrong is silent: omitting the attribute selects a third
171
+ * state (inherit, or auto), so `boolean` would render markup meaning something
172
+ * other than what was written. `draggable`, `spellcheck`, `contenteditable`,
173
+ * `translate`, `autocorrect`, and `writingsuggestions` are all this shape, and
174
+ * each was typed as a boolean at some point before being corrected.
175
+ *
176
+ * Naming it makes the classification greppable — `EnumeratedBool` answers
177
+ * "which attributes are in this family?" in one command, which is a question
178
+ * the docs repeatedly got wrong while it was six separate inline unions.
179
+ *
180
+ * The per-attribute JSDoc stays at the attribute: this alias says *what shape
181
+ * it is*, the attribute says *what omitting it means for that attribute*.
182
+ */
183
+ type EnumeratedBool = AttrLike<'true' | 'false'>;
184
+ /**
185
+ * `contenteditable`'s value set — the enumerated-bool keywords plus
186
+ * `plaintext-only`, and `inherit`, which is NOT a spec keyword (it reaches the
187
+ * inherit state only via the invalid-value default; kept for React parity).
188
+ */
189
+ type ContentEditableValue = AttrLike<'true' | 'false' | 'inherit' | 'plaintext-only'>;
190
+ /** Resource-loading priority hint: `<img>` / `<link>` / `<script>`. */
191
+ type FetchPriority = AttrLike<'high' | 'low' | 'auto'>;
192
+ /**
193
+ * The `blocking` token list. `render` is the only token the standard defines
194
+ * today — a union rather than a plain string so a typo fails to compile, and a
195
+ * one-line edit when the set grows.
196
+ */
197
+ type BlockingToken = AttrLike<'render'>;
198
+ /** Lazy-loading behavior: `<img>` / `<iframe>`. */
199
+ type LoadingBehavior = AttrLike<'eager' | 'lazy'>;
200
+ /** Form submission method, on `<form method>` and the `form*` overrides. */
201
+ type FormMethod = AttrLike<'get' | 'post' | 'dialog'>;
202
+ /** What a popover invoker does to its target: `<button>` / `<input>`. */
203
+ type PopoverTargetAction = AttrLike<'toggle' | 'show' | 'hide'>;
47
204
  /**
48
205
  * `data-*` and `aria-*` index signatures. Applied via `KerfBaseAttrs` so
49
206
  * every typed element accepts them without per-element enumeration.
@@ -68,26 +225,48 @@ interface KerfBaseAttrs extends DataAriaAttrs {
68
225
  title?: AttrLike;
69
226
  lang?: AttrLike;
70
227
  dir?: AttrLike<'ltr' | 'rtl' | 'auto'>;
71
- hidden?: AttrLike<boolean>;
72
- draggable?: AttrLike<boolean>;
73
- contentEditable?: AttrLike<boolean | 'true' | 'false' | 'inherit' | 'plaintext-only'>;
74
228
  /**
75
- * Lowercase HTML form accepted alongside `contentEditable` (same shape as
76
- * `class` / `tabindex` / `autofocus` / `spellcheck`). The HTML spec defines
77
- * `contenteditable` as a string-valued enumerated attribute; an HTML-savvy
78
- * developer typing the lowercase form will reach for `contenteditable="false"`.
229
+ * A genuine HTML boolean attribute, so `hidden={true}` → ` hidden` is
230
+ * correct. `'until-found'` is the one non-boolean keyword: the element is
231
+ * hidden but still findable by find-in-page and fragment navigation, which
232
+ * reveals it.
233
+ */
234
+ hidden?: AttrLike<boolean | 'until-found'>;
235
+ /**
236
+ * Enumerated, NOT boolean — write `draggable="true"` / `draggable="false"`.
237
+ * `boolean` is rejected because both directions would render the wrong
238
+ * state: `{true}` emits an empty value (invalid → the **auto** state, which
239
+ * for most elements means *not* draggable) and `{false}` omits the attribute
240
+ * (auto again — and auto for `<img>` / `<a href>` is *draggable*). Omit the
241
+ * attribute to mean auto. See the enumerated-attribute rule in this file's
242
+ * header.
79
243
  */
80
- contenteditable?: AttrLike<boolean | 'true' | 'false' | 'inherit' | 'plaintext-only'>;
244
+ draggable?: EnumeratedBool;
245
+ /**
246
+ * Enumerated, NOT boolean — write `contentEditable="true"` / `="false"`.
247
+ * `contentEditable={false}` would omit the attribute, which means *inherit*,
248
+ * not false — so a child of an editable region would stay editable.
249
+ *
250
+ * `'inherit'` is not a spec keyword; it reaches the inherit state only
251
+ * through the invalid-value default. Accepted for React parity, but omitting
252
+ * the attribute is the spec-correct way to inherit.
253
+ */
254
+ contentEditable?: ContentEditableValue;
255
+ /**
256
+ * KF-191 — lowercase HTML form accepted alongside `contentEditable` (same
257
+ * shape as `class` / `tabindex` / `autofocus` / `spellcheck`), with the same
258
+ * enumerated value set.
259
+ */
260
+ contenteditable?: ContentEditableValue;
81
261
  inputMode?: AttrLike<'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'>;
82
- spellCheck?: AttrLike<boolean>;
83
262
  /**
84
- * KF-183 — lowercase HTML form accepted alongside `spellCheck`. Widened
85
- * to also accept the literal string values `'true'` / `'false'` because
86
- * the HTML spec defines `spellcheck` as a string-valued enumerated
87
- * attribute, and an HTML-savvy developer typing the lowercase form
88
- * will naturally reach for `spellcheck="false"`.
263
+ * Enumerated, NOT boolean — write `spellCheck="false"` to turn spellchecking
264
+ * off. `spellCheck={false}` would omit the attribute, which means *inherit
265
+ * the default*, not off; the disable would silently never happen.
89
266
  */
90
- spellcheck?: AttrLike<boolean | 'true' | 'false'>;
267
+ spellCheck?: EnumeratedBool;
268
+ /** KF-183 — lowercase HTML form accepted alongside `spellCheck`. */
269
+ spellcheck?: EnumeratedBool;
91
270
  tabIndex?: AttrLike<number>;
92
271
  /**
93
272
  * KF-191 — lowercase HTML form accepted alongside `tabIndex`. Widened to
@@ -102,14 +281,63 @@ interface KerfBaseAttrs extends DataAriaAttrs {
102
281
  autoCapitalize?: AttrLike<'off' | 'none' | 'on' | 'sentences' | 'words' | 'characters'>;
103
282
  autoFocus?: AttrLike<boolean>;
104
283
  /**
105
- * KF-191 — lowercase HTML form accepted alongside `autoFocus`. Widened
106
- * to also accept `'true'` / `'false'` strings (same rationale as KF-183
107
- * for `spellcheck` — HTML boolean-attribute parsing allows the string
108
- * forms, and the canonical HTML attribute name lets developers reach
109
- * for either spelling).
284
+ * KF-191 — lowercase HTML form accepted alongside `autoFocus`.
285
+ *
286
+ * A real boolean attribute, so only `boolean` is accepted. The string forms
287
+ * are deliberately NOT allowed: `autofocus="false"` is *present*, and a
288
+ * present boolean attribute is true regardless of its value — the spelling
289
+ * that reads like "off" turns autofocus **on**. Use `{false}` or omit it.
110
290
  */
111
- autofocus?: AttrLike<boolean | 'true' | 'false'>;
291
+ autofocus?: AttrLike<boolean>;
112
292
  accessKey?: AttrLike;
293
+ /**
294
+ * A genuine HTML boolean attribute (the subtree becomes non-interactive and
295
+ * invisible to assistive tech), so `inert={true}` → ` inert` is correct.
296
+ */
297
+ inert?: AttrLike<boolean>;
298
+ /**
299
+ * Enumerated: `auto` / `hint` / `manual` — but the boolean forms are ALSO
300
+ * accepted, because both land on spec states, unlike `draggable`:
301
+ * `popover={true}` renders the bare attribute, whose empty value is a spec
302
+ * keyword for the **auto** state (`<div popover>` is the canonical
303
+ * spelling), and `popover={false}` omits it — the not-a-popover state.
304
+ */
305
+ popover?: AttrLike<'auto' | 'hint' | 'manual' | boolean>;
306
+ /** CSP nonce. A global attribute — most useful on `<script>` / `<style>` / `<link>`. */
307
+ nonce?: AttrLike;
308
+ /** Shadow-DOM part name(s) this element exposes for `::part()` styling. */
309
+ part?: AttrLike;
310
+ /** All-lowercase in HTML (`exportparts`) — there is no camelCase form to alias. */
311
+ exportparts?: AttrLike;
312
+ enterKeyHint?: AttrLike<'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'>;
313
+ /**
314
+ * Enumerated, NOT boolean — write `translate="yes"` / `translate="no"`.
315
+ * `translate={false}` would omit the attribute, which means *inherit*, not
316
+ * "no" — the opt-out would silently never happen. (The keywords are
317
+ * `yes` / `no`, not `true` / `false`.) See the enumerated-attribute rule in
318
+ * this file's header.
319
+ */
320
+ translate?: AttrLike<'yes' | 'no'>;
321
+ /**
322
+ * Enumerated, NOT boolean — write `autocorrect="on"` / `autocorrect="off"`.
323
+ * `autocorrect={false}` would omit the attribute, which means *inherit the
324
+ * default* (on, for most editable elements), not off.
325
+ */
326
+ autocorrect?: AttrLike<'on' | 'off'>;
327
+ /**
328
+ * Enumerated, NOT boolean — write `writingsuggestions="false"` to turn the
329
+ * browser's writing suggestions off. `writingsuggestions={false}` would omit
330
+ * the attribute, which means *inherit the default* (on, ultimately), not
331
+ * off — the same trap as `spellcheck`. Keywords are `true` / `false`.
332
+ * All-lowercase in HTML — there is no camelCase form to alias.
333
+ */
334
+ writingsuggestions?: EnumeratedBool;
335
+ /** A genuine HTML boolean attribute: presence declares the item. */
336
+ itemScope?: AttrLike<boolean>;
337
+ itemProp?: AttrLike;
338
+ itemType?: AttrLike;
339
+ itemId?: AttrLike;
340
+ itemRef?: AttrLike;
113
341
  /** `data-morph-skip` opts a subtree out of kerf's morph. Any value (incl. `true`) is treated as set. */
114
342
  'data-morph-skip'?: AttrValue;
115
343
  /** `data-morph-skip-children` (KF-152) — morph the element's attributes but leave its children verbatim. For client-hydrated slots whose loading/state classes still need to flow through. Any value (incl. `true`) is treated as set. */
@@ -123,7 +351,14 @@ interface HTMLAnchorAttrs extends KerfBaseAttrs {
123
351
  href?: AttrLike;
124
352
  target?: AttrLike<'_self' | '_blank' | '_parent' | '_top'>;
125
353
  rel?: AttrLike;
126
- download?: AttrLike;
354
+ /**
355
+ * Presence-or-value (see the third shape in this file's header):
356
+ * `download={true}` renders the bare attribute — download the resource and
357
+ * let the server's `Content-Disposition` / the URL decide the filename —
358
+ * while a string overrides the filename. `download={false}` omits it, which
359
+ * is ordinary navigation. All three are real states, so both forms are typed.
360
+ */
361
+ download?: AttrLike<boolean | string>;
127
362
  hrefLang?: AttrLike;
128
363
  ping?: AttrLike;
129
364
  referrerPolicy?: AttrLike;
@@ -136,6 +371,10 @@ interface HTMLAreaAttrs extends KerfBaseAttrs {
136
371
  href?: AttrLike;
137
372
  target?: AttrLike;
138
373
  rel?: AttrLike;
374
+ /** Presence-or-value, same as `<a download>` — see that attribute's note. */
375
+ download?: AttrLike<boolean | string>;
376
+ ping?: AttrLike;
377
+ referrerPolicy?: AttrLike;
139
378
  }
140
379
  interface HTMLImgAttrs extends KerfBaseAttrs {
141
380
  src?: AttrLike;
@@ -144,12 +383,14 @@ interface HTMLImgAttrs extends KerfBaseAttrs {
144
383
  height?: AttrLike<number | string>;
145
384
  srcSet?: AttrLike;
146
385
  sizes?: AttrLike;
147
- loading?: AttrLike<'eager' | 'lazy'>;
386
+ loading?: LoadingBehavior;
148
387
  decoding?: AttrLike<'sync' | 'async' | 'auto'>;
149
388
  crossOrigin?: AttrLike<'anonymous' | 'use-credentials' | ''>;
150
389
  referrerPolicy?: AttrLike;
151
390
  useMap?: AttrLike;
152
- fetchPriority?: AttrLike<'high' | 'low' | 'auto'>;
391
+ fetchPriority?: FetchPriority;
392
+ /** A genuine HTML boolean attribute: inside `<a href>`, clicks send the click coordinates to the server. */
393
+ isMap?: AttrLike<boolean>;
153
394
  }
154
395
  interface HTMLInputAttrs extends KerfBaseAttrs {
155
396
  type?: AttrLike<'text' | 'password' | 'email' | 'number' | 'tel' | 'url' | 'search' | 'date' | 'datetime-local' | 'time' | 'month' | 'week' | 'color' | 'checkbox' | 'radio' | 'file' | 'hidden' | 'submit' | 'reset' | 'button' | 'image' | 'range'>;
@@ -175,7 +416,7 @@ interface HTMLInputAttrs extends KerfBaseAttrs {
175
416
  autocomplete?: AttrLike;
176
417
  form?: AttrLike;
177
418
  formAction?: AttrLike;
178
- formMethod?: AttrLike<'get' | 'post' | 'dialog'>;
419
+ formMethod?: FormMethod;
179
420
  formTarget?: AttrLike;
180
421
  formEncType?: AttrLike;
181
422
  formNoValidate?: AttrLike<boolean>;
@@ -186,6 +427,15 @@ interface HTMLInputAttrs extends KerfBaseAttrs {
186
427
  height?: AttrLike<number | string>;
187
428
  accept?: AttrLike;
188
429
  capture?: AttrLike<boolean | 'user' | 'environment'>;
430
+ /** Submits the field's text direction alongside its value, under this name. */
431
+ dirName?: AttrLike;
432
+ /**
433
+ * Popover invokers apply to button-state `<input>`s (`type="button"` /
434
+ * `"submit"` / `"reset"` / `"image"`) exactly as they do to `<button>` —
435
+ * the standard lists both elements.
436
+ */
437
+ popoverTarget?: AttrLike;
438
+ popoverTargetAction?: PopoverTargetAction;
189
439
  }
190
440
  interface HTMLButtonAttrs extends KerfBaseAttrs {
191
441
  type?: AttrLike<'button' | 'submit' | 'reset'>;
@@ -198,14 +448,39 @@ interface HTMLButtonAttrs extends KerfBaseAttrs {
198
448
  formTarget?: AttrLike;
199
449
  formEncType?: AttrLike;
200
450
  formNoValidate?: AttrLike<boolean>;
451
+ popoverTarget?: AttrLike;
452
+ popoverTargetAction?: PopoverTargetAction;
453
+ /**
454
+ * Invoker Commands: a spec keyword — `show-modal` / `close` / `request-close`
455
+ * for a `<dialog>` target, `toggle-popover` / `show-popover` / `hide-popover`
456
+ * for a popover target — or a custom command, which the spec requires to
457
+ * start with `--`.
458
+ *
459
+ * **This union is a snapshot of a moving target.** The keyword set is still
460
+ * being extended (menu elements and `interestfor` are open in the HTML
461
+ * standard's invoker pipeline; `show-picker`, media, and fullscreen commands
462
+ * are staged behind them), so a keyword can ship in browsers before it is
463
+ * listed here. When that happens the compile error is kerf being out of
464
+ * date, not your markup being wrong — please file an issue so the union can
465
+ * be updated.
466
+ *
467
+ * Two ways through in the meantime, in preference order: use the custom
468
+ * `--*` form if the behavior is yours to define, or assert the value
469
+ * (`command={'show-picker' as never}`) at the one call site. The custom-command
470
+ * arm below is part of the design, not a workaround.
471
+ */
472
+ command?: AttrLike<'show-modal' | 'close' | 'request-close' | 'toggle-popover' | 'show-popover' | 'hide-popover' | `--${string}`>;
473
+ commandFor?: AttrLike;
201
474
  }
202
475
  interface HTMLFormAttrs extends KerfBaseAttrs {
203
476
  action?: AttrLike;
204
- method?: AttrLike<'get' | 'post' | 'dialog'>;
477
+ method?: FormMethod;
205
478
  encType?: AttrLike;
206
479
  target?: AttrLike;
207
480
  name?: AttrLike;
208
481
  noValidate?: AttrLike<boolean>;
482
+ /** Link types for the form's submission navigation — `noopener` / `noreferrer` / `opener`, the pairing `target` warrants. */
483
+ rel?: AttrLike;
209
484
  acceptCharset?: AttrLike;
210
485
  autoComplete?: AttrLike;
211
486
  /** KF-183 — lowercase HTML form accepted alongside `autoComplete`. */
@@ -228,10 +503,14 @@ interface HTMLOptgroupAttrs extends KerfBaseAttrs {
228
503
  label?: AttrLike;
229
504
  disabled?: AttrLike<boolean>;
230
505
  }
506
+ /**
507
+ * No `value` / `defaultValue`: `<select>` has no `value` content attribute, so
508
+ * rendering one is inert markup. The selection is expressed on the options —
509
+ * `<option value="b" selected>` — which is also the form kerf's morph keeps in
510
+ * sync with the live `selected` property after the user has picked.
511
+ */
231
512
  interface HTMLSelectAttrs extends KerfBaseAttrs {
232
513
  name?: AttrLike;
233
- value?: AttrLike;
234
- defaultValue?: AttrLike;
235
514
  multiple?: AttrLike<boolean>;
236
515
  required?: AttrLike<boolean>;
237
516
  disabled?: AttrLike<boolean>;
@@ -241,10 +520,13 @@ interface HTMLSelectAttrs extends KerfBaseAttrs {
241
520
  /** KF-183 — lowercase HTML form accepted alongside `autoComplete`. */
242
521
  autocomplete?: AttrLike;
243
522
  }
523
+ /**
524
+ * No `value` / `defaultValue`: a `<textarea>`'s value is its child text, and
525
+ * there is no `value` content attribute to render. Write the text as a child —
526
+ * `<textarea>{draft}</textarea>` — which is what kerf's morph reconciles.
527
+ */
244
528
  interface HTMLTextareaAttrs extends KerfBaseAttrs {
245
529
  name?: AttrLike;
246
- value?: AttrLike;
247
- defaultValue?: AttrLike;
248
530
  placeholder?: AttrLike;
249
531
  rows?: AttrLike<number>;
250
532
  cols?: AttrLike<number>;
@@ -254,13 +536,17 @@ interface HTMLTextareaAttrs extends KerfBaseAttrs {
254
536
  maxLength?: AttrLike<number>;
255
537
  minLength?: AttrLike<number>;
256
538
  wrap?: AttrLike<'hard' | 'soft' | 'off'>;
539
+ /** Submits the field's text direction alongside its value, under this name. */
540
+ dirName?: AttrLike;
257
541
  autoComplete?: AttrLike;
258
542
  /** KF-183 — lowercase HTML form accepted alongside `autoComplete`. */
259
543
  autocomplete?: AttrLike;
260
544
  form?: AttrLike;
261
545
  }
262
546
  interface HTMLTableAttrs extends KerfBaseAttrs {
547
+ /** @deprecated Obsolete presentational attribute — use CSS `padding` on the cells. Typed so legacy markup still compiles. */
263
548
  cellPadding?: AttrLike<number | string>;
549
+ /** @deprecated Obsolete presentational attribute — use CSS `border-spacing`. Typed so legacy markup still compiles. */
264
550
  cellSpacing?: AttrLike<number | string>;
265
551
  }
266
552
  interface HTMLTableCellAttrs extends KerfBaseAttrs {
@@ -278,6 +564,13 @@ interface HTMLMetaAttrs extends KerfBaseAttrs {
278
564
  content?: AttrLike;
279
565
  charSet?: AttrLike;
280
566
  httpEquiv?: AttrLike;
567
+ media?: AttrLike;
568
+ /**
569
+ * Open Graph (`og:title` etc.) — NOT in the HTML standard, typed because it
570
+ * is universal in real documents. See the deviations list in this file's
571
+ * header.
572
+ */
573
+ property?: AttrLike;
281
574
  }
282
575
  interface HTMLLinkAttrs extends KerfBaseAttrs {
283
576
  href?: AttrLike;
@@ -290,7 +583,13 @@ interface HTMLLinkAttrs extends KerfBaseAttrs {
290
583
  crossOrigin?: AttrLike;
291
584
  integrity?: AttrLike;
292
585
  referrerPolicy?: AttrLike;
293
- fetchPriority?: AttrLike<'high' | 'low' | 'auto'>;
586
+ fetchPriority?: FetchPriority;
587
+ /** A genuine HTML boolean attribute on `<link>`: the stylesheet is not applied (and for a stylesheet link, not fetched) until it's removed. */
588
+ disabled?: AttrLike<boolean>;
589
+ /** For `rel="preload" as="image"`: the srcset the preload should match. */
590
+ imageSrcSet?: AttrLike;
591
+ imageSizes?: AttrLike;
592
+ blocking?: BlockingToken;
294
593
  }
295
594
  interface HTMLScriptAttrs extends KerfBaseAttrs {
296
595
  src?: AttrLike;
@@ -301,12 +600,14 @@ interface HTMLScriptAttrs extends KerfBaseAttrs {
301
600
  integrity?: AttrLike;
302
601
  crossOrigin?: AttrLike;
303
602
  referrerPolicy?: AttrLike;
304
- nonce?: AttrLike;
603
+ blocking?: BlockingToken;
604
+ fetchPriority?: FetchPriority;
305
605
  }
606
+ /** No `scoped`: the proposal was removed from the HTML standard and never shipped in any engine. */
306
607
  interface HTMLStyleAttrs extends KerfBaseAttrs {
307
608
  type?: AttrLike;
308
609
  media?: AttrLike;
309
- scoped?: AttrLike<boolean>;
610
+ blocking?: BlockingToken;
310
611
  }
311
612
  interface HTMLIframeAttrs extends KerfBaseAttrs {
312
613
  src?: AttrLike;
@@ -317,7 +618,7 @@ interface HTMLIframeAttrs extends KerfBaseAttrs {
317
618
  allowFullScreen?: AttrLike<boolean>;
318
619
  width?: AttrLike<number | string>;
319
620
  height?: AttrLike<number | string>;
320
- loading?: AttrLike<'eager' | 'lazy'>;
621
+ loading?: LoadingBehavior;
321
622
  referrerPolicy?: AttrLike;
322
623
  }
323
624
  interface HTMLMediaAttrs extends KerfBaseAttrs {
@@ -341,6 +642,9 @@ interface HTMLSourceAttrs extends KerfBaseAttrs {
341
642
  srcSet?: AttrLike;
342
643
  sizes?: AttrLike;
343
644
  media?: AttrLike;
645
+ /** Valid when the parent is `<picture>`: intrinsic dimensions for the candidate image, so layout is stable before selection. */
646
+ width?: AttrLike<number | string>;
647
+ height?: AttrLike<number | string>;
344
648
  }
345
649
  interface HTMLTrackAttrs extends KerfBaseAttrs {
346
650
  src?: AttrLike;
@@ -1,4 +1,4 @@
1
- export { Fragment, SafeHtml, assertEmittableAttrName as _assertEmittableAttrName, _renderAttrVerbatim, _toSegment, granularListSafeHtml, isSafeHtml, jsx, jsx as jsxDEV, jsx as jsxs, listSafeHtml, raw } from './chunk-JVVU2RQO.js';
1
+ export { Fragment, SafeHtml, assertEmittableAttrName as _assertEmittableAttrName, _renderAttrVerbatim, _toSegment, granularListSafeHtml, isSafeHtml, jsx, jsx as jsxDEV, jsx as jsxs, listSafeHtml, raw } from './chunk-JXAR5J54.js';
2
2
  import './chunk-GY4XV2UV.js';
3
3
  import './chunk-3APBEVHF.js';
4
4
  import './chunk-VVDJLWMP.js';
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # kerf
2
2
 
3
- > A tiny (~11 KB minified + gzipped including its one runtime dependency `@preact/signals-core`; ~12 KB with `arraySignal`) reactive UI framework — fine-grained signals + DOM morphing + JSX. No virtual DOM, no compiler. Apply the smallest possible cut to update your DOM.
3
+ > A tiny (~12 KB minified + gzipped including its one runtime dependency `@preact/signals-core`; ~13 KB with `arraySignal`) reactive UI framework — fine-grained signals + DOM morphing + JSX. No virtual DOM, no compiler. Apply the smallest possible cut to update your DOM.
4
4
 
5
5
  kerf renders JSX to a structured `SafeHtml` (string for static content; tagged "list"/"mixed" segments where `each(...)` was used) and reconciles it against the live tree with a custom segment-aware morph. Static surrounds go through a general-purpose tree-morph; list contents go through a keyed reconciler that operates directly on live children — partial-update on huge lists is O(changes), not O(rows). Reactivity is provided by [@preact/signals-core](https://github.com/preactjs/signals). It pairs well with server-rendered HTML, embedded widgets, and any UI where preserving focus / selection across re-renders matters. Public API is one import: `signal`, `computed`, `effect`, `batch`, `defineStore`, `resetAllStores`, `mount`, `morph`, `each`, `attr`, `delegate`, `delegateCapture`, `toElement`, `SafeHtml`, `isSafeHtml`, `raw`, `Fragment`. (Two more subpaths: `kerfjs/testing` exposes `clearStoreRegistry` for unit-test isolation; `kerfjs/jsx-runtime` exposes the typed JSX building blocks for declaration-merging custom-element types.) An optional subpath at `kerfjs/array-signal` adds `arraySignal()` — a granular keyed-list signal whose patch events let `each()` reconcile in O(patches) instead of O(N). An optional subpath at `kerfjs/dev` installs the development diagnostics — kerf does NOT infer dev mode, so you import it behind your own build's dev flag (`if (import.meta.env.DEV) await import('kerfjs/dev');`); omitting it is production and sheds ~4.7 KB min+gzip. Another optional subpath at `kerfjs/html` adds the `html` tagged template — JSX-identical runtime semantics with no JSX transform, so CDN/importmap projects can author kerf UIs with literally no build step.
6
6
 
@@ -30,11 +30,12 @@ kerf renders JSX to a structured `SafeHtml` (string for static content; tagged "
30
30
  - [API reference](https://github.com/brianwestphal/kerf/blob/main/docs/8-api-reference.md): every export, every option.
31
31
  - [Live demo](https://github.com/brianwestphal/kerf/blob/main/docs/9-live-demo.md): the GitHub Pages deploy of `examples/reactivity-demo`.
32
32
  - [Migrating](https://github.com/brianwestphal/kerf/blob/main/docs/10-migrating.md): the `/kerf/migrating/` comparison hub — coming-from-React/Alpine/Lit/vanjs pages with side-by-side todo-list translations.
33
- - [Dev-mode warnings](https://github.com/brianwestphal/kerf/blob/main/docs/11-dev-warnings.md): the opt-in `KERF_DEV_WARN_*` env-gated dev-warn family (rebuilt listeners, untracked signals, narrow store sets, delegate-in-effect, each-in-morph-skip, duplicate keys, value-only re-renders, stale bindings), how the diagnostics are installed via the `kerfjs/dev` subpath rather than inferred from the environment, and the rules each new warning must follow.
33
+ - [Dev-mode warnings](https://github.com/brianwestphal/kerf/blob/main/docs/11-dev-warnings.md): the opt-in `KERF_DEV_WARN_*` env-gated dev-warn family (rebuilt listeners, untracked signals, narrow store sets, delegate-in-effect, each-in-morph-skip, duplicate keys, value-only re-renders, stale bindings, list rebinds, stale row indices, parser repairs) plus the `KERF_DEV_INVARIANTS` structural checks, how the diagnostics are installed via the `kerfjs/dev` subpath rather than inferred from the environment, and the rules each new warning must follow.
34
34
  - [AI-assistant configs](https://github.com/brianwestphal/kerf/blob/main/docs/12-ai-assistant-configs.md): how the drop-in Claude Code skill + Cursor rules ship inside the `kerfjs` npm package at `ai/skill.md` / `ai/cursorrules` / `ai/manifest.json`, the version + marker contract for customization preservation, and the `kerfjs/ai-assistant-configs` ESLint rule that surfaces drift on every lint pass.
35
35
  - [Component packages](https://github.com/brianwestphal/kerf/blob/main/docs/13-component-packages.md): building and publishing reusable kerf components as npm packages — the no-instance component model, per-instance state via factories, event/cleanup patterns, and `kerfjs`-as-peer-dependency packaging modeled on `eslint-plugin-kerfjs`. Scaffold one with `npm create kerf-component@latest <dir>` (the `create-kerf-component` initializer).
36
36
  - [Feature coverage](https://github.com/brianwestphal/kerf/blob/main/docs/14-feature-coverage.md): the per-behavior coverage axis orthogonal to line coverage — an index mapping each behavior (especially list-reconciler *state transitions*) to its guarding test, enforced by `npm run check:features`.
37
37
  - [No-build example](https://github.com/brianwestphal/kerf/blob/main/docs/15-no-build-example.md): the served-as-source `live-poll` example app — plain JS + importmap + the `html` tagged template, zero tooling — and the vendor-copy contract that ships it.
38
+ - [List identity](https://github.com/brianwestphal/kerf/blob/main/docs/16-list-identity.md): why an `each()` list's call-order identity is not stable, what the source guard fixes and what it doesn't, the five constraints any scheme must survive, and the explicit-key recommendation now shipped as `each(items, render, { key })`.
38
39
 
39
40
  ## Examples
40
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kerfjs",
3
- "version": "3.0.0-beta.1",
3
+ "version": "4.0.0",
4
4
  "description": "Tiny reactive UI framework — fine-grained signals + DOM morphing + JSX. Apply the smallest possible cut to update your DOM.",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -91,7 +91,7 @@
91
91
  "bench:serve": "bash bench/site.sh",
92
92
  "lint": "eslint src tests",
93
93
  "typecheck": "node node_modules/typescript7/bin/tsc --noEmit",
94
- "check": "npm run lint && npm run typecheck && node scripts/check-doc-test-inventory.mjs && node scripts/check-doc-api-coverage.mjs && node scripts/check-doc-site-tickets.mjs && node scripts/check-feature-coverage.mjs && node scripts/check-ai-bundle.mjs && npm test && npm run build && node scripts/check-bundle-size.mjs && node scripts/check-doc-api-signatures.mjs && vitest run --config vitest.config.dist.ts && vitest run --config vitest.config.dist-full.ts && node node_modules/typescript7/bin/tsc -p tests/dist/jsx-typing/tsconfig.json && node node_modules/typescript7/bin/tsc -p site/src/examples/complete/tsconfig.json && node node_modules/typescript7/bin/tsc -p tests/dist/scaffold-typing/tsconfig.json && node scripts/check-docs-examples.mjs",
94
+ "check": "npm run lint && npm run typecheck && node scripts/check-doc-test-inventory.mjs && node scripts/check-doc-api-coverage.mjs && node scripts/check-doc-site-tickets.mjs && node scripts/check-dev-warn-docs.mjs && node scripts/check-skill-freshness.mjs && node scripts/check-design-rule-5.mjs && node scripts/check-feature-coverage.mjs && node scripts/check-ai-bundle.mjs && npm test && npm run build && node scripts/check-bundle-size.mjs && node scripts/check-doc-api-signatures.mjs && vitest run --config vitest.config.dist.ts && vitest run --config vitest.config.dist-full.ts && node node_modules/typescript7/bin/tsc -p tests/dist/jsx-typing/tsconfig.json && node node_modules/typescript7/bin/tsc -p site/src/examples/complete/tsconfig.json && node node_modules/typescript7/bin/tsc -p tests/dist/scaffold-typing/tsconfig.json && node scripts/check-docs-examples.mjs",
95
95
  "check:docs:examples": "node scripts/check-docs-examples.mjs",
96
96
  "check:full": "npm run check && playwright test",
97
97
  "check:docs:test-inventory": "node scripts/check-doc-test-inventory.mjs",
@@ -113,7 +113,10 @@
113
113
  "site:dev": "cd site && npm install && npm run dev",
114
114
  "site:dev:hmr": "cd site && npm install && npm run dev:hmr",
115
115
  "site:build": "cd site && npm install && npm run build",
116
- "check:bundle-size": "npm run build && node scripts/check-bundle-size.mjs"
116
+ "check:bundle-size": "npm run build && node scripts/check-bundle-size.mjs",
117
+ "check:skills": "node scripts/check-skill-freshness.mjs",
118
+ "check:design-rule-5": "node scripts/check-design-rule-5.mjs",
119
+ "check:docs:dev-warns": "node scripts/check-dev-warn-docs.mjs"
117
120
  },
118
121
  "dependencies": {
119
122
  "@preact/signals-core": "^1.14.1"
@@ -137,5 +140,16 @@
137
140
  "typescript": "^6.0.3",
138
141
  "typescript7": "npm:typescript@^7.0.2",
139
142
  "vitest": "^3.0.0"
143
+ },
144
+ "gitgist": {
145
+ "exclude": [
146
+ "site/public/demos/*.svg",
147
+ "site/src/content/docs/docs/*",
148
+ "site/src/content/docs/api.md",
149
+ "ai/*",
150
+ "site/public/llms.txt",
151
+ "bench/results.json",
152
+ "bench/results.md"
153
+ ]
140
154
  }
141
155
  }