efficient-easel 0.99.1 → 0.100.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.
Files changed (110) hide show
  1. package/README.md +281 -36
  2. package/dist/autosave/index.d.ts +12 -6
  3. package/dist/blocks/index.d.ts +2 -2
  4. package/dist/canvas/index.d.ts +3 -3
  5. package/dist/canvas/index.js +1 -1
  6. package/dist/chrome/chrome.css +96 -29
  7. package/dist/chrome/index.d.ts +72 -19
  8. package/dist/chrome/index.js +17 -21
  9. package/dist/chunk-3EDSDPIP.js +1 -0
  10. package/dist/chunk-67GNT6PF.js +1 -0
  11. package/dist/{chunk-PI26YXYD.js → chunk-6EIVTKZZ.js} +21 -14
  12. package/dist/chunk-A5UK76FC.js +6 -0
  13. package/dist/chunk-AZT7V4UM.js +10 -0
  14. package/dist/chunk-DICUMYMG.js +12 -0
  15. package/dist/chunk-HDVD27VB.js +1 -0
  16. package/dist/chunk-JQM3V7XQ.js +1 -0
  17. package/dist/{chunk-TDA4HH54.js → chunk-JUQ6YJCG.js} +1 -1
  18. package/dist/chunk-LA54QLP5.js +1 -0
  19. package/dist/chunk-LGKQXSKO.js +16 -0
  20. package/dist/chunk-M6F6LS3H.js +1 -0
  21. package/dist/chunk-MH5GUZFQ.js +9 -0
  22. package/dist/chunk-P6O363EE.js +6 -0
  23. package/dist/chunk-PAELDYYS.js +1 -0
  24. package/dist/{chunk-JYPOOHII.js → chunk-QKVR3K73.js} +3 -3
  25. package/dist/chunk-SAJUCHOX.js +1 -0
  26. package/dist/chunk-TRVZ7C23.js +1 -0
  27. package/dist/chunk-V7GIIXFB.js +16 -0
  28. package/dist/{chunk-IBOFSS56.js → chunk-WZ7TGAAT.js} +1 -1
  29. package/dist/chunk-ZJEBF4NB.js +1 -0
  30. package/dist/copy/copy.css +388 -5
  31. package/dist/copy/index.d.ts +271 -8
  32. package/dist/copy/index.js +31 -10
  33. package/dist/data/index.d.ts +4 -4
  34. package/dist/drag/drag.css +10 -1
  35. package/dist/drag/index.d.ts +102 -11
  36. package/dist/drag/index.js +18 -17
  37. package/dist/{editor/editor.css → easel/easel.css} +16 -1
  38. package/dist/{editor → easel}/index.d.ts +146 -51
  39. package/dist/easel/index.js +9 -0
  40. package/dist/edit-lock/index.d.ts +3 -3
  41. package/dist/edit-lock/index.js +1 -1
  42. package/dist/fields/fields.css +103 -26
  43. package/dist/fields/index.d.ts +51 -13
  44. package/dist/fields/index.js +30 -46
  45. package/dist/gold/index.js +1 -1
  46. package/dist/icons/index.d.ts +10 -1
  47. package/dist/icons/index.js +1 -1
  48. package/dist/index.d.ts +3 -3
  49. package/dist/index.js +1 -1
  50. package/dist/markup/index.d.ts +94 -10
  51. package/dist/markup/index.js +1 -1
  52. package/dist/menu/index.d.ts +61 -6
  53. package/dist/menu/index.js +15 -15
  54. package/dist/menu/menu.css +17 -6
  55. package/dist/node-ops/index.d.ts +9 -9
  56. package/dist/node-ops/index.js +1 -1
  57. package/dist/panel/index.d.ts +73 -5
  58. package/dist/panel/index.js +18 -16
  59. package/dist/panel/panel.css +56 -1
  60. package/dist/pick/index.d.ts +42 -4
  61. package/dist/pick/index.js +7 -14
  62. package/dist/pick/pick.css +14 -0
  63. package/dist/rail/index.d.ts +6 -6
  64. package/dist/rail/index.js +1 -1
  65. package/dist/rail/rail.css +1 -1
  66. package/dist/strings/catalogue.d.ts +24 -40
  67. package/dist/strings/catalogue.js +1 -1
  68. package/dist/strings/index.d.ts +25 -23
  69. package/dist/strings/index.js +2 -2
  70. package/dist/style-clipboard/index.d.ts +5 -5
  71. package/dist/style-clipboard/index.js +1 -1
  72. package/dist/text-styles/index.d.ts +328 -0
  73. package/dist/text-styles/index.js +38 -0
  74. package/dist/text-styles/text-styles.css +309 -0
  75. package/dist/tree/index.d.ts +44 -3
  76. package/dist/tree/index.js +1 -1
  77. package/dist/tree/tree.css +71 -0
  78. package/dist/{types-CnRzp9oc.d.ts → types-DGcCsxs8.d.ts} +79 -12
  79. package/dist/{types-DNrn_w4V.d.ts → types-DWS-s9z-.d.ts} +10 -10
  80. package/dist/{types-pGBXevBU.d.ts → types-Djdq6Xkd.d.ts} +15 -8
  81. package/dist/{types-Dy6oR6Z2.d.ts → types-E3HrTPg2.d.ts} +20 -14
  82. package/dist/{types-C7HFvdtk.d.ts → types-VqL6t5HH.d.ts} +82 -5
  83. package/dist/{watch-DmasQA61.d.ts → watch-BTYOJpQU.d.ts} +19 -2
  84. package/dist/{wordFit-9B9nqyf3.d.ts → wordFit-D4S36PvB.d.ts} +1 -1
  85. package/dist/{words-DagJdwIA.d.ts → words-AHHfiaRY.d.ts} +31 -2
  86. package/dist/{words-D9ChvbNC.d.ts → words-BDignMl_.d.ts} +1 -1
  87. package/dist/{words-Cqmyey0s.d.ts → words-BMzV04b1.d.ts} +2 -2
  88. package/dist/words-CJjUKcV5.d.ts +37 -0
  89. package/dist/words-CRYvAiHX.d.ts +105 -0
  90. package/dist/{words-B2YLaKEr.d.ts → words-CnnHb9jo.d.ts} +1 -1
  91. package/dist/{words-CD3WADyb.d.ts → words-CzoyDNHm.d.ts} +4 -0
  92. package/dist/{words-bOMsUJk9.d.ts → words-DBq7vfHy.d.ts} +59 -6
  93. package/dist/{words-DOhF-4D4.d.ts → words-DMG4dYq2.d.ts} +1 -1
  94. package/dist/{words-BK-npta-.d.ts → words-DgHcY5qB.d.ts} +10 -4
  95. package/dist/{words-B87GIFWH.d.ts → words-is_Vy6hn.d.ts} +1 -1
  96. package/package.json +14 -8
  97. package/dist/chunk-66CKSTXP.js +0 -14
  98. package/dist/chunk-67VZRQLG.js +0 -13
  99. package/dist/chunk-6PS6STGA.js +0 -1
  100. package/dist/chunk-7A7O2JWQ.js +0 -1
  101. package/dist/chunk-H4B72XCJ.js +0 -1
  102. package/dist/chunk-JE63JHKO.js +0 -1
  103. package/dist/chunk-KDIVA6WD.js +0 -1
  104. package/dist/chunk-MCLBHENY.js +0 -1
  105. package/dist/chunk-QB3BZIRA.js +0 -5
  106. package/dist/chunk-SJQG5ENJ.js +0 -1
  107. package/dist/chunk-SUO5627P.js +0 -1
  108. package/dist/chunk-ZT7M24XD.js +0 -1
  109. package/dist/editor/index.js +0 -9
  110. /package/dist/{chunk-GGTPNGFJ.js → chunk-AELNNAFM.js} +0 -0
package/README.md CHANGED
@@ -21,7 +21,7 @@ it since 0.93.0.
21
21
  The storefront editor (`efficient-storefront`, Next 16 + Puck) and the campaign
22
22
  designer (`efficient-admin-portal`, Vite/React) each hand-rolled their own narrow
23
23
  left icon-rail. They had already drifted — button 34px vs 36px, icon 18px vs
24
- 20px, radius 9 vs 8, a CSS-var accent vs a Tailwind one. `EditorRail` is the one
24
+ 20px, radius 9 vs 8, a CSS-var accent vs a Tailwind one. `EaselRail` is the one
25
25
  rail they converge on.
26
26
 
27
27
  ## The leaf
@@ -36,14 +36,14 @@ it.
36
36
  // A client component in any editor:
37
37
  'use client';
38
38
  import {
39
- EditorRail,
39
+ EaselRail,
40
40
  PagesIcon, LayersIcon, StylesIcon, MediaIcon, SitemapIcon, AddIcon,
41
41
  } from 'efficient-easel/rail';
42
42
  import 'efficient-easel/rail.css'; // once, from a client/global entry
43
43
 
44
44
  export function LeftRail({ view, onView, onAdd }) {
45
45
  return (
46
- <EditorRail
46
+ <EaselRail
47
47
  ariaLabel="Left panel views"
48
48
  items={[
49
49
  { id: 'pages', label: 'Pages', icon: <PagesIcon />, active: view === 'pages', onSelect: () => onView('pages') },
@@ -59,14 +59,14 @@ export function LeftRail({ view, onView, onAdd }) {
59
59
 
60
60
  ## API
61
61
 
62
- ### `<EditorRail items ariaLabel? className? orientation? />`
62
+ ### `<EaselRail items ariaLabel? className? orientation? />`
63
63
 
64
64
  A **pure presentational** toolbar. It owns the look, never the app state — you
65
65
  decide which item is `active` and what each `onSelect` does.
66
66
 
67
67
  | prop | type | default | notes |
68
68
  |---|---|---|---|
69
- | `items` | `EditorRailItem[]` | — | buttons, in render order |
69
+ | `items` | `EaselRailItem[]` | — | buttons, in render order |
70
70
  | `ariaLabel` | `string` | `"Editor"` | names the toolbar for assistive tech |
71
71
  | `className` | `string` | — | merged onto `.es-rail` |
72
72
  | `orientation` | `'column' \| 'row'` | `'column'` | which way the buttons run |
@@ -85,17 +85,17 @@ changes. A row is 34px tall, so a 52px bar centres it with 9px to spare.
85
85
 
86
86
  ```tsx
87
87
  // The same rail, in the top bar's empty middle.
88
- <EditorRail orientation="row" ariaLabel="Editor views" items={items} />
88
+ <EaselRail orientation="row" ariaLabel="Editor views" items={items} />
89
89
  ```
90
90
 
91
91
  `aria-orientation` follows the layout (`vertical` / `horizontal`); the `toolbar`
92
92
  role does not change, because a rail that mixes view toggles with plain actions
93
93
  is a toolbar whichever way it runs.
94
94
 
95
- ### `EditorRailItem`
95
+ ### `EaselRailItem`
96
96
 
97
97
  ```ts
98
- interface EditorRailItem {
98
+ interface EaselRailItem {
99
99
  id: string; // React key + selection correlation
100
100
  label: string; // aria-label + hover title
101
101
  icon: React.ReactNode; // an 18px currentColor glyph — use the shipped icons
@@ -109,7 +109,7 @@ A **view item** passes `active: true | false` and renders as a toggle
109
109
  (`aria-pressed`, `.is-active` highlight). An **action item** (the quick-add "+")
110
110
  omits `active` and renders as a plain button.
111
111
 
112
- `EditorRailButton` — the single-button building block `EditorRail` maps to — is
112
+ `EaselRailButton` — the single-button building block `EaselRail` maps to — is
113
113
  exported too, for bespoke layouts.
114
114
 
115
115
  ## Tokens
@@ -216,7 +216,7 @@ email. Framework-free: no React, no DOM, no imports.
216
216
  | `- ` at the start of a line | a bullet line, one level | yes |
217
217
  | `1. ` at the start of a line | a numbered line, one level: 1-3 digits, `. `, words; the number is kept as typed | yes |
218
218
  | `[x]{underline}`, `[x]{sup}`, `[x]{sub}` | underline, superscript, subscript (tree only) | not yet — `applyMark` writes them; the buttons come with the final text box |
219
- | `[x]{color=name}` | a theme colour, by name only (`isColorName`; never a hex) (tree only) | not yet — `applyColor` writes it |
219
+ | `[x]{color=name}` / `[x]{color=#rrggbb}` | a theme colour by name (`isColorName`), or any colour as exactly six hex digits (`isColorValue`; saved lowercase) (tree only) | not yet — `applyColor` writes it |
220
220
  | `@@x@@`, `++x++` | Emphasis, Strong (the storefront's old word styles) | no — read only |
221
221
  | `*x*`, `#x#`, `[x]` | legacy accent, bold, italic (per product set) | no — read only |
222
222
 
@@ -235,7 +235,7 @@ not learned them prints the delimiters.
235
235
  | `isSafeLinkHref(href)` | The link rule. Call it before writing any `href`. |
236
236
  | `tokenizeInline(value, rules)`, `stripInline` | The flat reading older renderers use: runs only, no links, no nesting. |
237
237
  | `applyMark(text, start, end, style)`, `MARKUP_TOOLS`, `markupTools`, `COPY_FIELD_TOOLS` | The writer behind a toolbar button, and the button descriptors. |
238
- | `applyColor(text, start, end, name)`, `isColorName`, `spanFor` | A colour carries a name, so it has its own writer: it writes, renames and (`null`) takes off `[x]{color=name}`, and changes nothing for a name `isColorName` refuses. |
238
+ | `applyColor(text, start, end, name)`, `isColorName`, `isColorValue`, `spanFor` | A colour carries a value, so it has its own writer: it writes, changes and (`null`) takes off `[x]{color=name}` — a theme name or a 6-digit hex, saved lowercase — and changes nothing for a value `isColorValue` refuses (`#f00`, `rgb()`, a typo). |
239
239
  | `STANDARD_MARKUP`, `STOREFRONT_MARKUP`, `CAMPAIGN_MARKUP` | The rule sets. The last two add each product's legacy runs. |
240
240
 
241
241
  ## The edit lock — two operators, one document
@@ -263,7 +263,7 @@ export const pageEditLock = createEditLock({
263
263
 
264
264
  The plain half needs no binding: `canWriteDocument`, `holderOf`,
265
265
  `readLockHolder`, `accessForNewDocument`, `documentPermissions`, `describeAge`,
266
- `nameOrSomeone`, `newEditorClientId`, `shouldReleaseHeldWrite`,
266
+ `nameOrSomeone`, `newEaselClientId`, `shouldReleaseHeldWrite`,
267
267
  `shouldReloadAfterTakeover`, `documentVersionMoved`, `reloadIfVersionMoved`.
268
268
  The bound half —
269
269
  `accessAfterClaimFailure`, `accessAfterTakeoverFailure`,
@@ -296,16 +296,16 @@ the editor owns the device, the save status, the widths and which panels are ope
296
296
 
297
297
  ```tsx
298
298
  import {
299
- EditorTopBar, TopBarCluster, TopBarDivider, EditorIconButton, DeviceSwitch,
300
- SaveState, PanelResizeHandle, InspectorHead, DEFAULT_PANEL_W, panelWidthVars,
299
+ EaselTopBar, TopBarCluster, TopBarDivider, EaselIconButton, DeviceSwitch,
300
+ SaveState, PanelResizeHandle, InspectorHead, DEFAULT_PANEL_W, panelWidthVars, setPanelWidthVars,
301
301
  } from 'efficient-easel/chrome';
302
302
  import 'efficient-easel/chrome.css'; // once, after shell.css
303
303
 
304
304
  <div className="es-editor" style={panelWidthVars(widths)}> {/* the vars go on the ROOT */}
305
- <EditorTopBar>
306
- <TopBarCluster kind="rail"><EditorRail orientation="row" items={items} /></TopBarCluster>
305
+ <EaselTopBar>
306
+ <TopBarCluster kind="rail"><EaselRail orientation="row" items={items} /></TopBarCluster>
307
307
  <TopBarCluster>
308
- <EditorIconButton label="Undo" onClick={undo}><UndoIcon /></EditorIconButton>
308
+ <EaselIconButton label="Undo" onClick={undo}><UndoIcon /></EaselIconButton>
309
309
  <TopBarDivider />
310
310
  <DeviceSwitch devices={devices} value={device} onChange={setDevice} />
311
311
  </TopBarCluster>
@@ -313,18 +313,29 @@ import 'efficient-easel/chrome.css'; // once, after shell.css
313
313
  <SaveState tone="saved" label="" />
314
314
  <button className="es-btn-primary">Done</button>
315
315
  </TopBarCluster>
316
- </EditorTopBar>
316
+ </EaselTopBar>
317
317
  …
318
318
  </div>
319
319
  ```
320
320
 
321
+ A server renders that `style` prop as a `style=""` attribute. Under a
322
+ Content-Security-Policy with no `'unsafe-inline'` for styles the browser refuses
323
+ it, so a **server-rendered** editor writes the widths through the CSSOM instead:
324
+
325
+ ```tsx
326
+ <div className="es-editor" ref={(el) => { if (el) setPanelWidthVars(el, widths); }}>
327
+ ```
328
+
329
+ Until that runs, chrome.css draws the panels at `DEFAULT_PANEL_W`. See
330
+ [Server rendering and a strict Content-Security-Policy](#server-rendering-and-a-strict-content-security-policy).
331
+
321
332
  ## The Layers tree — one row at every depth
322
333
 
323
334
  `efficient-easel/tree` (added in 0.42.0) is the Layers tree both editors draw. Every
324
335
  row has the same shape; only `Subtree` says depth.
325
336
 
326
337
  ```tsx
327
- import { LayersTree, TreeGroupLabel, Subtree, TreeRow, TreeRowAction } from 'efficient-easel/tree';
338
+ import { LayersTree, TreeGroupLabel, Subtree, TreeRow, TreeRowAction, TreeEmptySlot } from 'efficient-easel/tree';
328
339
  import 'efficient-easel/tree.css';
329
340
 
330
341
  <LayersTree>
@@ -335,9 +346,17 @@ import 'efficient-easel/tree.css';
335
346
  <Subtree>
336
347
  <TreeRow icon={<TextLinesIcon />} name="Text" preview="BACK IN STOCK NOW" />
337
348
  </Subtree>
349
+ <Subtree>
350
+ <TreeEmptySlot onAdd={addToColumn} /> {/* a list with nothing in it */}
351
+ </Subtree>
338
352
  </LayersTree>
339
353
  ```
340
354
 
355
+ `TreeEmptySlot` is an **empty list as a place**: a dashed box the size of a row
356
+ that says "Add a block", takes a drop and says "Drop here" while one may land.
357
+ With no `onAdd` (a read-only tree) it says "No blocks yet". The three words are
358
+ `tree.addBlock`, `tree.dropHere` and `tree.emptySlot` in the strings catalogue.
359
+
341
360
  ## Copy style, paste style — one rule for both editors
342
361
 
343
362
  `efficient-easel/style-clipboard` (added in 0.50.0) is the right-click menu's
@@ -566,9 +585,9 @@ editors' own polling loops, host-head style injection and hand-written
566
585
  coordinate maths.
567
586
 
568
587
  ```tsx
569
- import { useCanvasFrame, useCanvasEvent, useCanvasStyle, useCanvasLayout, frameRectToHost, isTypingTarget, nodeIdAt, BLOCK_SELECTOR } from 'efficient-easel/canvas';
588
+ import { useCanvasFrame, useCanvasEvent, useCanvasStyle, useCanvasLayout, frameRectToHost, isTypingTarget, nodeIdAt, LIST_SELECTOR } from 'efficient-easel/canvas';
570
589
 
571
- const canvas = useCanvasFrame(editorRootRef, { readySelector: BLOCK_SELECTOR });
590
+ const canvas = useCanvasFrame(editorRootRef, { readySelector: LIST_SELECTOR }); // see the note below
572
591
  useCanvasStyle(canvas, 'hidden-blocks', hiddenBlockCss(ids)); // canvas document only
573
592
  useCanvasEvent(canvas, 'contextmenu', (e) => open(nodeIdAt(e.target))); // follows a document swap
574
593
  const tick = useCanvasLayout(canvas); // re-measure when this changes
@@ -576,7 +595,7 @@ const tick = useCanvasLayout(canvas); // re-m
576
595
 
577
596
  | Export | What it answers |
578
597
  |---|---|
579
- | `useCanvasFrame(rootRef, { selector?, readySelector? })` | `{ frame, document, window }` or `null`. Found by a MutationObserver and the frame's `load` event — never a timer. `readySelector` waits for the blocks to arrive in a loaded-but-empty document. |
598
+ | `useCanvasFrame(rootRef, { selector?, readySelector? })` | `{ frame, document, window }` or `null`. Found by a MutationObserver and the frame's `load` event — never a timer. `readySelector` waits for the canvas to be drawn in a loaded-but-empty document. |
580
599
  | `useCanvasEvent(canvas, type, handler, { on?, capture? })` | A listener on the canvas document (or window) that moves to a replacement document and always calls the latest handler, without re-binding on every render. |
581
600
  | `useCanvasStyle(canvas, key, css)` | A `<style>` in the canvas document's head only — re-injected into a replacement, put back if something empties the head (Puck's AutoFrame does, once), removed on unmount, never written to the host page. |
582
601
  | `useCanvasLayout(canvas)` | A number that changes when the canvas scrolls, resizes, is replaced, or the host scrolls or resizes (plus two settle passes). |
@@ -587,6 +606,13 @@ const tick = useCanvasLayout(canvas); // re-m
587
606
  | `watchCanvasFrame(root, onChange, options)` | The watcher the hook wraps, for code outside React. |
588
607
  | `setCanvasNonce(document, nonce)` | The CSP nonce every `<style>` created in that canvas document carries (0.95.0). `CanvasFrame`'s `nonce` prop sets it; a host that builds its own frame calls it. |
589
608
 
609
+ ⚠️ **Wait for a list, not for a block.** `LIST_SELECTOR` matches every list
610
+ the canvas draws, and the page's own is there with nothing in it.
611
+ `readySelector: BLOCK_SELECTOR` means "ready once the canvas holds a block", so
612
+ the canvas of a page with **no blocks** is never published: the drag layer,
613
+ canvas stylesheets and context menus do nothing on the one page a first block
614
+ has to be dropped on. `BLOCK_SELECTOR` is for a canvas that is never empty.
615
+
590
616
  ## Node operations — `efficient-easel/node-ops`
591
617
 
592
618
  `efficient-easel/node-ops` (added in 0.71.0, blueprint step 2.2) is what can be
@@ -612,7 +638,7 @@ const items = nodeMenuItems({
612
638
  });
613
639
 
614
640
  const up = ops.parentOf(selection); // null → render nothing
615
- {up && <InspectorUpLevel name={nameOf(up)} onSelect={() => select(up)} icon={<UpGlyph />} />}
641
+ {up && <InspectorUpLevel name={nameOf(up)} onSelect={() => select(up)} />} // ← Section
616
642
  ```
617
643
 
618
644
  - **One action, one commit.** Every operation (remove, duplicate, move, step,
@@ -677,6 +703,14 @@ onChange={(doc) => canWrite && autosave.push(doc)}
677
703
  (after saving or discarding it); a save still out for the old document can
678
704
  no longer change the new one's state.
679
705
 
706
+ - **Feed it from `createEasel({ onChange })`, not from `easel.subscribe`.**
707
+ `onChange` is called for a change to the document and never for `load()`. A
708
+ subscriber is also told of a load, a selection and a permission, and pushing
709
+ each of those saves a document nobody edited.
710
+ - **`autosave.indicator` is always English.** Autosave does not read the
711
+ strings provider. In another language, build the pill from the state:
712
+ `<SaveState {...saveIndicator(autosave.state, useEaselStrings().save)} />`.
713
+
680
714
  `createAutosave` is the same engine without React, with the clock passed in —
681
715
  the specs run minutes of debounce and backoff on a fake one.
682
716
 
@@ -691,7 +725,7 @@ land (`canDrop`) and what it writes (`onDrop`, `onStep`).
691
725
 
692
726
  ```tsx
693
727
  import { useTreeDrag, gapToMoveIndex } from 'efficient-easel/drag';
694
- import { LayersTree, TreeRow, TreeSeam } from 'efficient-easel/tree';
728
+ import { LayersTree, Subtree, TreeEmptySlot, TreeRow, TreeSeam } from 'efficient-easel/tree';
695
729
 
696
730
  const drag = useTreeDrag<Item>({
697
731
  keyOf: (item) => item.id,
@@ -704,6 +738,9 @@ const drag = useTreeDrag<Item>({
704
738
  <LayersTree rootProps={drag.rootProps()}>
705
739
  <TreeSeam {...drag.seam({ place: { ...list, index: 0 } })} onAdd={() => openAdd(list, 0)} label="Add block here" />
706
740
  <TreeRow {...drag.row({ item, place, into: containerPlace, pinned })} icon={…} name={…} onSelect={…} />
741
+ <Subtree> {/* a list with nothing in it: not a seam */}
742
+ <TreeEmptySlot {...drag.seam({ place: { ...column, index: 0 } })} onAdd={() => openAdd(column, 0)} />
743
+ </Subtree>
707
744
  ```
708
745
 
709
746
  - **The whole row is picked up**, and the drag picture is the row, raised.
@@ -718,6 +755,17 @@ const drag = useTreeDrag<Item>({
718
755
  - **⌥↑ / ⌥↓** step a row. The list **scrolls at its edges**.
719
756
  - **A seam is the gap:** seams in every gap of a list, or in none. The list's
720
757
  spacing does not change either way.
758
+ - **An empty list is not a seam.** A seam hands its height back to the rows
759
+ either side of it; alone in a list it is a strip a few pixels tall, and a
760
+ drop aimed at "into this empty column" lands on whatever is really under the
761
+ pointer. Draw `TreeEmptySlot` (`efficient-easel/tree`) there, with the same
762
+ `drag.seam()`.
763
+ - **A target names an effect its source allowed.** A row or a block picked up
764
+ is a move; a tile from the Add list is a copy (`effectAllowed = 'copy'`), and
765
+ a browser refuses a drop whose `dropEffect` the source did not allow: the
766
+ line is drawn and `drop` never fires. The tree and the canvas both answer
767
+ with `dropEffectFor(e.dataTransfer.effectAllowed)`; a drop target of a
768
+ host's own in the same session should too.
721
769
 
722
770
  **The canvas and the Add list** (0.75.0, step 3.2) join the same drag through a
723
771
  shared session, so a thing picked up on one surface can land on another:
@@ -740,12 +788,172 @@ const drag = useTreeDrag({ …, session }); // the tree
740
788
  blocks. Your `model` says what may be picked up (leave out what cannot move)
741
789
  and where "+ Add section" goes (`seamsBetween`).
742
790
  - **One handle,** on the smallest box under the pointer, never unmounted
743
- mid-drag.
791
+ mid-drag. It follows the CONTENT, not the last mouse move: it is picked
792
+ again whenever the canvas is measured again (a wheel in a frame that scrolls
793
+ itself, a block added above) and when a drag ends, so a host has no reason
794
+ to nudge it with a mouse move of its own.
744
795
  - **The line** runs across a list, or down between columns (a slot laid out
745
796
  across).
746
797
  - **An empty slot** is a dashed "+ Add block" cell that also takes drops.
798
+ - **A drop the innermost list refuses falls through to the list outside it.**
799
+ A section held over a column lands between sections, and the line is drawn
800
+ where it will land before it is let go. Without that a page covered by its
801
+ inner lists could take a section only on the few pixels between sections.
802
+ A host for whom a refusal is final says so per drop:
803
+ `fallThrough={(item, refused, from) => …}` is asked only about a place
804
+ `canDrop` has just refused, and `false` ends it there: no line, nothing
805
+ lands. For example, a Columns block over a column, or a block about to
806
+ leave the container it was picked up in (`from`). Left out, every refusal
807
+ falls through. A drop back where the thing already is never falls through.
808
+ - **The canvas scrolls at its edges,** like the tree: the host's pane around a
809
+ frame as tall as its content, and the frame's own document when the frame
810
+ fills its pane and scrolls itself (`EaselCanvas height="fill"`).
811
+ - **One tile can be off:** `AddTile.disabled`, with `disabledReason` as its
812
+ tooltip ("A page has one header"). The tile stays in the list, dimmed, is
813
+ still found by search and counted; a click adds nothing and a drag does not
814
+ start. It is `aria-disabled`, so it keeps the focus and its reason can be
815
+ reached. `readOnly` is still the whole panel's.
747
816
  - Styles: `import 'efficient-easel/drag.css'`.
748
817
 
818
+ ## The store, its keys and its permissions — `efficient-easel/easel`
819
+
820
+ ```tsx
821
+ import { createEasel, EaselProvider, EaselCanvas, useSelectedFields } from 'efficient-easel/easel';
822
+ import 'efficient-easel/easel.css';
823
+
824
+ const easel = createEasel<EaselConfig>({ config, data, onChange: (doc) => autosave.push(doc) });
825
+
826
+ <EaselProvider easel={easel} hotkeys={ownsThePage}>
827
+ <EaselCanvas height="fill" />
828
+ </EaselProvider>
829
+ ```
830
+
831
+ - **Say the type: `createEasel<EaselConfig>(…)`.** `createEasel` infers its
832
+ type from the config it is handed, and a config typed more narrowly (a
833
+ `Config<…>` from `efficient-easel/blocks`) gives an `Easel<ThatConfig>`,
834
+ which `EaselProvider` does not accept.
835
+ - **The store enforces no permission.** `dispatch`, `run`, `undo` and `redo`
836
+ write whatever they are asked to. Permissions are what the PARTS read: the
837
+ settings panel (`useSelectedFields().readOnly`), the menus, the undo keys. A
838
+ host that must not write, because somebody else holds the document, gates
839
+ its own calls.
840
+ - **`setPermissions` replaces, and tells its readers.** It is not merged over
841
+ the last value (name every key: `documentPermissions` in
842
+ `efficient-easel/edit-lock` does). A panel reading
843
+ `useSelectedFields().readOnly` or `useEasel((s) => s.getPermissions)`
844
+ re-renders when the permissions change, and only then; the same permissions
845
+ again tell nobody, so it is safe to call from an effect.
846
+ - **Undo and redo on the keyboard.** `EaselProvider` binds ⌘Z / Ctrl+Z and
847
+ the redo keys to the page, and `EaselCanvas` binds them in the frame's own
848
+ document, because a key pressed in the frame never reaches the page.
849
+ - `hotkeys={false}` on the provider turns **both** off, for a host that binds
850
+ its own.
851
+ - `<EaselCanvas hotkeys>` says otherwise for the frame alone: a host that
852
+ binds the page's keys itself (to its own root, because the page is another
853
+ app's) and still wants the frame's passes `hotkeys` there.
854
+ - Whatever those say, the keys do nothing while the easel-wide `edit`
855
+ permission is off. An undo is a write. `bindUndoKeys(target, editor)`, for
856
+ a host's own target, is held the same way.
857
+ - **`planFields().rows` holds `slot` rows.** They draw nothing and are there so
858
+ the slot's name has a bucket. Filter `row.field.type !== 'slot'` before
859
+ drawing or counting rows of your own.
860
+ - **Inserting a block runs no resolver; `easel.resolveNode` does.** The
861
+ `insert` action puts the block in with its `defaultProps`, as Puck's reducer
862
+ did. What ran a new block's `resolveData` in Puck was its own drop from the
863
+ component list, and the engine has no drop of its own: the Add list's click
864
+ and the drag's drop are the host's. So follow an insert with it:
865
+
866
+ ```ts
867
+ easel.dispatch({ type: 'insert', componentType, destinationZone, destinationIndex, id });
868
+ void easel.resolveNode(id, { trigger: 'insert' }); // or after `easel.run(…)`
869
+ ```
870
+
871
+ The block is told `trigger: 'insert'`, what it holds is resolved with it,
872
+ and what changed is written into the insert's own undo step. A block
873
+ changed by hand, or taken out, while its resolver ran is left as it is.
874
+ - **`InspectorUpLevel` draws its own `←`** (the one up-level look, Lewis
875
+ 2026-10-06): a blue text link, the arrow and the parent's name, no box and
876
+ no hover change. `icon` is ignored. Inside a panel body, pass `inline`. The
877
+ icon set has no back arrow, on purpose: nothing needs one now, and a glyph
878
+ enters the set by the owner's icon standard, not for a host.
879
+
880
+ ### What an accessibility audit of a second host found
881
+
882
+ A host built only from the parts, audited with axe-core in a real Chromium.
883
+ What follows is how the parts answer it; `scripts/second-host-clickthrough.mjs`
884
+ runs the same rules (`--axe <axe.min.js>`).
885
+
886
+ - **The right-click menu lives on `<body>`, and a host may move it into its
887
+ own landmarks.** On the body the box is page content outside every landmark
888
+ (axe `region`, a best-practice rule). It is not wrapped in a region or a
889
+ dialog to satisfy that: a menu is neither. A host that wants it inside its
890
+ landmarks portals it into its editor's root, through the check:
891
+
892
+ ```tsx
893
+ import { EaselContextMenu, menuPortalTarget } from 'efficient-easel/menu';
894
+
895
+ <EaselContextMenu portalTo={menuPortalTarget(rootRef.current)} … />
896
+ ```
897
+
898
+ The box is `position: fixed` at the pointer, and an ancestor that is
899
+ transformed, filtered or contained places and clips a fixed box inside it.
900
+ `menuPortalTarget` gives the root back only when none does, and `null` (the
901
+ body) otherwise, so a root that is scaled on one screen and not on another
902
+ is right on both. The body stays the default because it is the one place
903
+ that cannot go wrong. The root's stacking is the host's: the box is
904
+ `z-index: 9999` inside it.
905
+ - **A dropdown's open list has a name.** In a settings row
906
+ (`InspectorField`) the list is `aria-labelledby` the row's own label. A
907
+ `FieldSelect` on its own is named by `ariaLabelledBy` or `ariaLabel`, and
908
+ with neither the list takes its button's name, so it is never nameless.
909
+ - **Small grey words can be read.** The Add panel's group count, a menu
910
+ row's shortcut, a read-only or disabled field's value and an empty slot's
911
+ note are at least 4.5:1 on their ground (WCAG AA), held by ratio in
912
+ `tests/contrast-small-text.test.ts`. A host that overrode
913
+ `.es-add-group-count` or `--es-menu-shortcut-fg` can drop the override.
914
+ - **The rail says which words it draws.** The width decides in CSS between
915
+ every word, the open panel's word, and glyphs only, so a longer language
916
+ folds sooner (German "Zurück", "Ebenen"). `<TopBarRail onWords={…}>` is told
917
+ `'all' | 'chosen' | 'none'`, read back from what the stylesheet drew, when
918
+ it changes. Every button keeps its name as `title` and `aria-label`.
919
+ - **A list's drag hint is drawn whole under its last row.** A list in the
920
+ settings panel (`ListField`) is not a scroll box of its own; the panel
921
+ around it scrolls.
922
+
923
+ ### Server rendering and a strict Content-Security-Policy
924
+
925
+ A policy with no `'unsafe-inline'` for styles (`style-src 'self'`, or by nonce)
926
+ refuses every `style=""` **attribute** in the HTML a server sends. It does not
927
+ govern the CSSOM, which is how React writes a `style` prop in the browser, so a
928
+ client-rendered editor is not affected. And React does not repair it when it
929
+ hydrates: an attribute it finds matching is left alone, so a refused style stays
930
+ refused until its value changes.
931
+
932
+ So the frame's parts write no `style` of their own into a server's HTML:
933
+
934
+ - **The canvas frame's look is a class** (`.es-canvas-frame`, `.is-fill`, in
935
+ `efficient-easel/easel.css`): no border, the pane's width, and the pane's
936
+ height when it fills.
937
+ - **The panel widths have their default in the stylesheet** wherever chrome.css
938
+ reads them, and a host writes its own with `setPanelWidthVars(el, widths)`
939
+ from a ref rather than `style={panelWidthVars(widths)}`.
940
+ - **Stylesheets in the frame** take the page's nonce: `<EaselCanvas nonce>`.
941
+
942
+ What still writes a `style`, and where it matters:
943
+
944
+ | Part | When | Under such a policy |
945
+ |---|---|---|
946
+ | The drag layer's handle, line and cells; context menus; the rail's measured words | In the browser only, from state or a measurement | Nothing to do: never in a server's HTML |
947
+ | Everything inside the canvas frame (lists, block wrappers, outlines) | In the browser only: the frame is filled once it mounts | Nothing to do |
948
+ | `EaselCanvas frame={false}` | Server-rendered with the page | Mount it on the client |
949
+ | `ColourPicker`'s swatch, when it has a value | Server-rendered with a settings panel that holds a colour | Mount that panel on the client |
950
+ | `SaveState` with `counting` and `waitMs` | Only while a save waits, which a server render is not | Nothing to do |
951
+ | A `style` or a slot's `style` you pass (`CanvasFrame`, `TopBarCluster`, `Render`) | As you wrote it | Yours to move to a class |
952
+
953
+ `tests/csp-server-render.test.tsx` server-renders a frame built from the parts
954
+ and fails on any `style` attribute; `scripts/second-host-clickthrough.mjs`
955
+ serves one under a real policy in Chrome.
956
+
749
957
  ## The engine's words — `efficient-easel/strings`
750
958
 
751
959
  `efficient-easel/strings` (added in 0.95.0) is every word the engine itself
@@ -754,24 +962,24 @@ typed catalogue, so a host in another language can translate it. A host that
754
962
  does nothing sees the English it always saw.
755
963
 
756
964
  ```tsx
757
- import { EditorStringsProvider, useEditorStrings, type DeepPartial, type EditorStrings } from 'efficient-easel/strings';
965
+ import { EaselStringsProvider, useEaselStrings, type DeepPartial, type EaselStrings } from 'efficient-easel/strings';
758
966
 
759
- const german: DeepPartial<EditorStrings> = {
967
+ const german: DeepPartial<EaselStrings> = {
760
968
  save: { failed: 'Speichern fehlgeschlagen' },
761
969
  lock: { minutesAgo: ({ count }) => `vor ${count} Min.` }, // a value or a plural: a function
762
970
  };
763
971
 
764
- <EditorStringsProvider value={german} locale="de">
972
+ <EaselStringsProvider value={german} locale="de">
765
973
  <Editor />
766
- </EditorStringsProvider>;
974
+ </EaselStringsProvider>;
767
975
  ```
768
976
 
769
- - **`EditorStrings`** is grouped by the module that says the word (`notice`,
977
+ - **`EaselStrings`** is grouped by the module that says the word (`notice`,
770
978
  `lock`, `save`, `chrome`, `rail`, `tree`, `panel`, `fields`, `pick`, `drag`,
771
979
  `menu`, `styleClipboard`, `copy`, `canvas`). A plain word is a `string`;
772
980
  anything with a value in it is a function, so the host places the value and
773
- does its own plural. `DEFAULT_EDITOR_STRINGS` is the English.
774
- - **What is left out falls back** (`mergeEditorStrings`), and a provider inside
981
+ does its own plural. `DEFAULT_EASEL_STRINGS` is the English.
982
+ - **What is left out falls back** (`mergeEaselStrings`), and a provider inside
775
983
  another lays its words over the outer one's.
776
984
  - **The provider reaches the parts that have hooks:** the edit notice, the device
777
985
  switch, "up one level", the panel grip and the top-bar rail; the copy box, its
@@ -784,9 +992,9 @@ const german: DeepPartial<EditorStrings> = {
784
992
  argument:
785
993
 
786
994
  ```tsx
787
- const strings = useEditorStrings();
995
+ const strings = useEaselStrings();
788
996
 
789
- <EditorRail items={items} ariaLabel={strings.rail.label} />
997
+ <EaselRail items={items} ariaLabel={strings.rail.label} />
790
998
  <TreeRow … words={strings.tree} />
791
999
  <ColourPicker … words={strings.pick.colour} />
792
1000
  <ImageBox … words={strings.pick.image} />
@@ -799,17 +1007,44 @@ const german: DeepPartial<EditorStrings> = {
799
1007
  styleMenuItems({ …, words: strings.styleClipboard });
800
1008
  pasteMessage(plan, source, target, strings.styleClipboard);
801
1009
  ```
802
- - **`useEditorLocale()`** is the provider's `locale` (`'en'` with none), for a
1010
+ - **`useEaselLocale()`** is the provider's `locale` (`'en'` with none), for a
803
1011
  host's own blocks and fields. The engine does not read it.
804
1012
  - **A server file may import it.** Importing the entry runs nothing — the React
805
1013
  context is made on first use. `efficient-easel/strings/catalogue` is the
806
- catalogue, its types and `mergeEditorStrings` with no React in its graph at
1014
+ catalogue, its types and `mergeEaselStrings` with no React in its graph at
807
1015
  all: what a host's own test imports to check its languages against the
808
1016
  engine's keys.
809
1017
 
810
1018
  What a host supplies was never the engine's and is not in the catalogue: a
811
1019
  block's name, a field's label, a tile's caption, the document's `noun`.
812
1020
 
1021
+ ## The names before the Easel rename
1022
+
1023
+ The package took the name `efficient-easel` at 0.96.0. Its exports followed at
1024
+ 0.99.2: every `Editor…` name is an `Easel…` one, and the store's subpath is
1025
+ `efficient-easel/easel`. From 0.99.2 to 0.99.x the old names were kept as
1026
+ deprecated aliases. **They are removed in 0.100.0**: a host must be on the new
1027
+ names, and on `efficient-web-builder` 0.7.1 or later, before it takes 0.100.0.
1028
+
1029
+ | Subpath | Was | Is |
1030
+ | --- | --- | --- |
1031
+ | `efficient-easel/editor` | the subpath itself, and `editor.css` | `efficient-easel/easel`, `easel.css` |
1032
+ | `/easel` | `createEditor`, `Editor`, `EditorOptions` | `createEasel`, `Easel`, `EaselOptions` |
1033
+ | `/easel` | `EditorProvider` (prop `editor`) | `EaselProvider` (prop `easel`) |
1034
+ | `/easel` | `useEditor`, `useEditorApi`, `createUseEditor`, `useEditorNodeOps` | `useEasel`, `useEaselApi`, `createUseEasel`, `useEaselNodeOps` |
1035
+ | `/easel` | `EditorCanvas`, `EditorCanvasProps` | `EaselCanvas`, `EaselCanvasProps` |
1036
+ | `/easel` | `EditorConfig`, `EditorView`, `EditorUi`, `EditorAction`, `EditorCore`, `EditorHistory` | `EaselConfig`, `EaselView`, `EaselUi`, `EaselAction`, `EaselCore`, `EaselHistory` |
1037
+ | `/rail` | `EditorRail`, `EditorRailButton`, `EditorRailItem`, `EditorRailProps`, `EditorRailButtonProps`, `EditorRailOrientation` | `EaselRail`, `EaselRailButton`, `EaselRailItem`, `EaselRailProps`, `EaselRailButtonProps`, `EaselRailOrientation` |
1038
+ | `/menu` | `EditorContextMenu`, `EditorContextMenuItem`, `EditorContextMenuProps`, `EditorContextMenuSkin` | `EaselContextMenu`, `EaselContextMenuItem`, `EaselContextMenuProps`, `EaselContextMenuSkin` |
1039
+ | `/chrome` | `EditorTopBar`, `EditorIconButton`, `EditorIconButtonProps` | `EaselTopBar`, `EaselIconButton`, `EaselIconButtonProps` |
1040
+ | `/strings` | `EditorStrings`, `DEFAULT_EDITOR_STRINGS`, `mergeEditorStrings`, `EditorStringsProvider`, `EditorStringsProviderProps`, `useEditorStrings`, `useEditorLocale` | `EaselStrings`, `DEFAULT_EASEL_STRINGS`, `mergeEaselStrings`, `EaselStringsProvider`, `EaselStringsProviderProps`, `useEaselStrings`, `useEaselLocale` |
1041
+ | `/edit-lock` | `newEditorClientId` | `newEaselClientId` |
1042
+
1043
+ Not renamed, because other code holds them: the `.es-editor` class and every
1044
+ other `es-` class, the `--es-*` variables, the `editor-shell:style-clipboard`
1045
+ storage key, and the rail's default label, the word "Editor", which a screen
1046
+ reader says to the person using a host's editor.
1047
+
813
1048
  ## Develop
814
1049
 
815
1050
  Node is native on the dev Mac (v26.8.1) — run these in your own worktree; a
@@ -826,3 +1061,13 @@ docker run --rm -v "$PWD":/w -w /w node:26-slim \
826
1061
  jsdom window in `tests/_dom-env.ts` and runs in its own process.
827
1062
  - `npm run build` — `tsup` → `dist/` (root + the `rail`, `shell` and `gold`
828
1063
  subpaths) + the copied CSS.
1064
+ - `node scripts/second-host-clickthrough.mjs [out-dir]` — what jsdom cannot
1065
+ hold. It server-renders a lean editor built only from the parts
1066
+ (`tests/fixtures/second-host-page.tsx`), serves it under a
1067
+ Content-Security-Policy with no `'unsafe-inline'`, and drives it in headless
1068
+ Chrome with real drags and keys: a tile dropped on the tree, the canvas
1069
+ scrolling under a drag, undo keys with editing held, a permission reaching
1070
+ the settings panel, no refused `style` attribute, an empty slot taking a
1071
+ drop. One PASS or FAIL per step and a screenshot of each. Not part of
1072
+ `npm test`: CI has no Chrome. Run it when you touch `drag`, `editor`,
1073
+ `chrome` or `tree`.
@@ -1,8 +1,8 @@
1
- import { A as Autosave, a as AutosaveSnapshot, b as SaveIndicator, c as AutosaveOptions } from '../words-Cqmyey0s.js';
2
- export { d as AutosaveClock, S as SaveWords, e as createAutosave, s as saveIndicator } from '../words-Cqmyey0s.js';
3
- import '../types-CnRzp9oc.js';
4
- import '../types-DNrn_w4V.js';
5
- import '../wordFit-9B9nqyf3.js';
1
+ import { A as Autosave, a as AutosaveSnapshot, b as SaveIndicator, c as AutosaveOptions } from '../words-BMzV04b1.js';
2
+ export { d as AutosaveClock, S as SaveWords, e as createAutosave, s as saveIndicator } from '../words-BMzV04b1.js';
3
+ import '../types-DGcCsxs8.js';
4
+ import '../types-DWS-s9z-.js';
5
+ import '../wordFit-D4S36PvB.js';
6
6
  import 'react';
7
7
  import '../edit-lock/index.js';
8
8
 
@@ -16,7 +16,13 @@ type UseAutosaveOptions<T> = Omit<AutosaveOptions<T>, 'save' | 'onFailure'> & {
16
16
  };
17
17
  type UseAutosave<T> = Omit<Autosave<T>, 'setSave' | 'getSnapshot' | 'subscribe' | 'dispose'> & {
18
18
  state: AutosaveSnapshot;
19
- /** The pill's tone and words — `<SaveState {...indicator} />`. */
19
+ /**
20
+ * The pill's tone and words — `<SaveState {...indicator} />`.
21
+ *
22
+ * ⚠️ ALWAYS ENGLISH. Autosave is held to its own lane and does not read the
23
+ * strings provider. A host in another language builds the pill from the
24
+ * state instead: `saveIndicator(autosave.state, useEaselStrings().save)`.
25
+ */
20
26
  indicator: SaveIndicator;
21
27
  };
22
28
  declare function useAutosave<T>(options: UseAutosaveOptions<T>): UseAutosave<T>;
@@ -1,5 +1,5 @@
1
1
  import { ReactElement, FC, CSSProperties, ReactNode, JSX } from 'react';
2
- import { P as Permissions, A as AppState } from '../types-Dy6oR6Z2.js';
2
+ import { P as Permissions, A as AppState } from '../types-E3HrTPg2.js';
3
3
  import '../data/index.js';
4
4
  import '../types-41704JE4.js';
5
5
  import '../resolve-D68pb3Bx.js';
@@ -21,7 +21,7 @@ import '../plan-DChbUXdS.js';
21
21
  * - the `ConfigParams` form of `Config<{ components, root, fields }>` and the
22
22
  * user field-type extension it carries (neither app uses it);
23
23
  * - everything about Puck's own editor — its store, overrides, plugins and
24
- * history. Those are the engine's now (`efficient-easel/editor`).
24
+ * history. Those are the engine's now (`efficient-easel/easel`).
25
25
  *
26
26
  * The `puck` prop keeps its name: it is what every block's `render` destructures
27
27
  * (`puck.isEditing`, `puck.renderDropZone`), and renaming it is the block rewrite
@@ -1,6 +1,6 @@
1
1
  import { RefObject } from 'react';
2
- import { C as CanvasTarget, W as WatchOptions } from '../watch-DmasQA61.js';
3
- export { B as BLOCK_SELECTOR, a as CANVAS_SELECTOR, i as isFrameReady, w as watchCanvasFrame } from '../watch-DmasQA61.js';
2
+ import { C as CanvasTarget, W as WatchOptions } from '../watch-BTYOJpQU.js';
3
+ export { B as BLOCK_SELECTOR, a as CANVAS_SELECTOR, L as LIST_SELECTOR, i as isFrameReady, w as watchCanvasFrame } from '../watch-BTYOJpQU.js';
4
4
 
5
5
  /**
6
6
  * The small facts every canvas surface needs, written once — pure DOM, no
@@ -103,7 +103,7 @@ declare function fitCanvas({ available, width, maxScale, }: {
103
103
  * 'nonce-…'`), where a `<style>` without it is refused. Every sheet
104
104
  * `upsertCanvasStyle` / `useCanvasStyle` creates in `doc` afterwards wears it.
105
105
  *
106
- * `CanvasFrame` (`efficient-easel/editor`) calls this with its `nonce` prop as
106
+ * `CanvasFrame` (`efficient-easel/easel`) calls this with its `nonce` prop as
107
107
  * it builds the frame, so a host says it once; a host that builds its own
108
108
  * frame calls it itself. `undefined` (or `''`) forgets it.
109
109
  */
@@ -1 +1 @@
1
- export{BLOCK_SELECTOR,CANVAS_SELECTOR,isFrameReady,useCanvasEvent,useCanvasFrame,useCanvasLayout,useCanvasStyle,watchCanvasFrame}from"../chunk-MCLBHENY.js";export{BLOCK_ATTRIBUTE,clientPointToLayer,elementScale,fitCanvas,framePointToHost,frameRectToHost,frameRectToLayer,frameScale,isTypingTarget,nodeIdAt,removeCanvasStyle,setCanvasNonce,upsertCanvasStyle}from"../chunk-ZNCEOSXO.js";
1
+ export{BLOCK_SELECTOR,CANVAS_SELECTOR,LIST_SELECTOR,isFrameReady,useCanvasEvent,useCanvasFrame,useCanvasLayout,useCanvasStyle,watchCanvasFrame}from"../chunk-PAELDYYS.js";export{BLOCK_ATTRIBUTE,clientPointToLayer,elementScale,fitCanvas,framePointToHost,frameRectToHost,frameRectToLayer,frameScale,isTypingTarget,nodeIdAt,removeCanvasStyle,setCanvasNonce,upsertCanvasStyle}from"../chunk-ZNCEOSXO.js";