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
@@ -1,26 +1,33 @@
1
1
  import { DataInput } from '../data/index.js';
2
2
  import { N as NodeSelection } from '../plan-DChbUXdS.js';
3
3
  export { I as ItemSelector } from '../plan-DChbUXdS.js';
4
- import { E as EditorConfig, a as EditorView, b as EditorAction, P as Permissions, c as EditorCore, d as EditorUi } from '../types-Dy6oR6Z2.js';
5
- export { A as AppState, e as EditorHistory, H as HistoryEntry, S as SetDataPatch } from '../types-Dy6oR6Z2.js';
4
+ import { E as EaselConfig, a as EaselView, b as EaselAction, P as Permissions, c as EaselCore, d as EaselUi } from '../types-E3HrTPg2.js';
5
+ export { A as AppState, e as EaselHistory, H as HistoryEntry, S as SetDataPatch } from '../types-E3HrTPg2.js';
6
6
  import { S as SlotAddress, D as Data, P as Place } from '../types-41704JE4.js';
7
7
  import { c as ResolveTrigger, a as ResolveCache, R as ResolvableItem } from '../resolve-D68pb3Bx.js';
8
8
  import * as react from 'react';
9
9
  import { ReactNode, CSSProperties, ComponentType, ReactElement } from 'react';
10
10
  import { b as NodeOpsHost, C as ClipboardEntry } from '../useNodeOps-BF0fQsif.js';
11
- import { I as InspectorFields } from '../types-C7HFvdtk.js';
11
+ import { I as InspectorFields } from '../types-VqL6t5HH.js';
12
12
  import '../buckets-CuLM47h9.js';
13
13
 
14
- interface EditorOptions<C extends EditorConfig = EditorConfig> {
14
+ interface EaselOptions<C extends EaselConfig = EaselConfig> {
15
15
  config: C;
16
16
  /** The document — Puck 0.20's shape, unchanged. */
17
17
  data: DataInput | Data;
18
18
  /** The host's own UI keys to start with (and an `itemSelector`, if any). */
19
19
  ui?: Record<string, unknown>;
20
- /** Called with the new document after every change that changed it — once
21
- * per `batch`, once per undo / redo. Not called for `load`. */
20
+ /**
21
+ * Called with the new document after every change that changed it — once
22
+ * per `batch`, once per undo / redo. Not called for `load`.
23
+ *
24
+ * THIS is what feeds autosave (`autosave.push`), not `subscribe`: a
25
+ * subscriber is also told of a `load()`, a selection and a permission, and a
26
+ * host that pushed every notification would save the document it had just
27
+ * loaded as if it were an edit.
28
+ */
22
29
  onChange?: (data: Data) => void;
23
- /** Editor-wide permissions, under each block's own (Puck's `permissions`). */
30
+ /** Easel-wide permissions, under each block's own (Puck's `permissions`). */
24
31
  permissions?: Partial<Permissions>;
25
32
  /**
26
33
  * Changes recorded closer together than this collapse into ONE undo step —
@@ -32,12 +39,22 @@ interface EditorOptions<C extends EditorConfig = EditorConfig> {
32
39
  /** The clock, for tests. */
33
40
  now?: () => number;
34
41
  }
35
- interface Editor<C extends EditorConfig = EditorConfig> {
36
- /** What `useEditor` reads. The same object until the state changes. */
37
- getState: () => EditorView<C>;
42
+ /**
43
+ * `EaselProvider` takes an `Easel<EaselConfig>`, and `createEasel` infers `C`
44
+ * from the config it is handed. A config typed more narrowly than
45
+ * `EaselConfig` (a `Config<…>` from `efficient-easel/blocks`) therefore infers
46
+ * an `Easel<ThatConfig>`, which the provider does not accept (`setConfig`
47
+ * takes a `C`). Say the type: `createEasel<EaselConfig>({ config, … })`.
48
+ */
49
+ interface Easel<C extends EaselConfig = EaselConfig> {
50
+ /** What `useEasel` reads. The same object until the state changes. */
51
+ getState: () => EaselView<C>;
38
52
  subscribe: (listener: () => void) => () => void;
39
- /** One Puck action. Outside a `batch`, one undo step when it records. */
40
- dispatch: (action: EditorAction) => void;
53
+ /**
54
+ * One Puck action. Outside a `batch`, one undo step when it records.
55
+ * Permissions are not checked here (see `setPermissions`).
56
+ */
57
+ dispatch: (action: EaselAction) => void;
41
58
  /**
42
59
  * Run several changes as ONE: one undo step, one `onChange`, and one
43
60
  * notification to subscribers. Each dispatch inside sees the ones before it.
@@ -65,20 +82,57 @@ interface Editor<C extends EditorConfig = EditorConfig> {
65
82
  }) => void;
66
83
  undo: () => void;
67
84
  redo: () => void;
85
+ /**
86
+ * Replace the easel-wide permissions (not merged over the last ones: name
87
+ * every key you mean). Subscribers are told, and `getPermissions` becomes a
88
+ * new function, so a reader of either re-renders — a settings panel reading
89
+ * `getPermissions().edit` follows an edit lock as it is claimed and lost.
90
+ * The same permissions again tell nobody, so it is safe in an effect.
91
+ *
92
+ * These are what the PARTS read (the settings panel, the menus, the undo
93
+ * keys). The store itself enforces none of them: `dispatch`, `run`, `undo`
94
+ * and `redo` write whatever they are asked to. A host that must not write —
95
+ * somebody else holds the document — gates its own calls.
96
+ */
68
97
  setPermissions: (permissions: Partial<Permissions>) => void;
69
98
  /**
70
99
  * Run every block's `resolveData` over the document, as `<Puck>` did when it
71
100
  * mounted (trigger `'load'`), and write what changed as ONE change folded
72
101
  * into the step it happens in — a resolver bringing a stale document up to
73
102
  * date is not an undo step (Puck made it one, which undid back to the stale
74
- * document). `onChange` hears it, as Puck's did. `EditorProvider` runs this
103
+ * document). `onChange` hears it, as Puck's did. `EaselProvider` runs this
75
104
  * on mount; a node changed by hand while it ran is left as the hand left it.
76
105
  */
77
106
  resolveAll: (options?: {
78
107
  trigger?: ResolveTrigger;
79
108
  metadata?: Record<string, unknown>;
80
109
  }) => Promise<void>;
81
- /** This editor's memory of the last change each node was resolved for —
110
+ /**
111
+ * Run ONE node's `resolveData`, and that of everything in its slots, and
112
+ * write what changed folded into the step it happens in — `resolveAll`, for
113
+ * one node. Resolves to whether anything was written.
114
+ *
115
+ * It is what Puck's own insert ran: a block dropped from its component list
116
+ * was resolved with trigger `'insert'` (`insertComponent`), which is how a
117
+ * block seeds its children or stamps a default the moment it arrives. The
118
+ * `insert` ACTION never did — Puck's reducer was pure, and so is this one —
119
+ * and the engine has no drop of its own: the Add list's click and the drag's
120
+ * drop are the host's. So a host that puts a new block in, by `dispatch` or
121
+ * by `run`, follows it with this (second host, 2026-10-07):
122
+ *
123
+ * easel.dispatch({ type: 'insert', componentType, destinationZone, destinationIndex, id });
124
+ * void easel.resolveNode(id, { trigger: 'insert' });
125
+ *
126
+ * ONE undo step with the insert (Puck made the resolver's write a second
127
+ * one, and a single undo left a block that had never been resolved). A node
128
+ * changed by hand, or taken out, while its resolver ran is left as it is.
129
+ * `trigger` is `'replace'` when not given, as `resolveNodeData`'s is.
130
+ */
131
+ resolveNode: (id: string, options?: {
132
+ trigger?: ResolveTrigger;
133
+ metadata?: Record<string, unknown>;
134
+ }) => Promise<boolean>;
135
+ /** This easel's memory of the last change each node was resolved for —
82
136
  * Puck kept one per editor, shared by every write that resolves. */
83
137
  readonly resolveCache: ResolveCache;
84
138
  /**
@@ -98,11 +152,11 @@ interface Editor<C extends EditorConfig = EditorConfig> {
98
152
  */
99
153
  declare const DEFAULT_UI: Record<string, unknown>;
100
154
  /**
101
- * THE EDITOR STORE (blueprint step 5.1): the document, the selection by id and
155
+ * THE EASEL STORE (blueprint step 5.1): the document, the selection by id and
102
156
  * the host's UI keys, with Puck's actions, Puck's history and a `batch` Puck
103
- * never had. Framework-free — `useEditor` (./react) is the React binding.
157
+ * never had. Framework-free — `useEasel` (./react) is the React binding.
104
158
  */
105
- declare function createEditor<C extends EditorConfig>(options: EditorOptions<C>): Editor<C>;
159
+ declare function createEasel<C extends EaselConfig>(options: EaselOptions<C>): Easel<C>;
106
160
 
107
161
  /**
108
162
  * Puck 0.20's reducer, on `efficient-easel/data` — blueprint step 5.1.
@@ -123,31 +177,39 @@ declare function createEditor<C extends EditorConfig>(options: EditorOptions<C>)
123
177
  */
124
178
  /** Whether Puck records an action in history when the action does not say:
125
179
  * everything but the state-setters. */
126
- declare function recordsByDefault(action: EditorAction): boolean;
180
+ declare function recordsByDefault(action: EaselAction): boolean;
127
181
  /** The UI as Puck shows it: the host's keys, with the selection translated. */
128
- declare function uiView(core: EditorCore, config: EditorConfig): EditorUi;
182
+ declare function uiView(core: EaselCore, config: EaselConfig): EaselUi;
129
183
  /** A selection that still points at something; otherwise nothing. */
130
- declare function validSelection(data: Data, config: EditorConfig, selection: NodeSelection): NodeSelection;
184
+ declare function validSelection(data: Data, config: EaselConfig, selection: NodeSelection): NodeSelection;
131
185
  /** One action, applied. Pure: the same core and action give the same core. */
132
- declare function reduce(core: EditorCore, action: EditorAction, config: EditorConfig): EditorCore;
186
+ declare function reduce(core: EaselCore, action: EaselAction, config: EaselConfig): EaselCore;
133
187
 
134
- /** Puts one editor in reach of everything under it — `<Puck>`'s provider
135
- * half — and, as `<Puck>` did, binds undo / redo to the page's keyboard
136
- * (`hotkeys={false}` for a host that binds its own) and runs every block's
137
- * `resolveData` once on mount (`resolveOnMount={false}` to skip). */
138
- declare function EditorProvider({ editor, hotkeys, resolveOnMount, children, }: {
139
- editor: Editor;
188
+ interface EaselProviderProps {
189
+ easel: Easel;
190
+ /**
191
+ * Bind undo / redo: to the page here, and in the canvas frame
192
+ * (`EaselCanvas`, whose own `hotkeys` can say otherwise for the frame
193
+ * alone). `false` binds NEITHER, for a host that binds its own. On by
194
+ * default. Whatever this says, the keys do nothing while the easel-wide
195
+ * `edit` permission is off (`bindUndoKeys`).
196
+ */
140
197
  hotkeys?: boolean;
141
- /** Run every block's `resolveData` once the editor mounts, as `<Puck>` did
142
- * (`editor.resolveAll`). On by default. */
198
+ /** Run every block's `resolveData` once the easel mounts, as `<Puck>` did
199
+ * (`easel.resolveAll`). On by default. */
143
200
  resolveOnMount?: boolean;
144
201
  children?: ReactNode;
145
- }): react.JSX.Element;
146
- /** The editor itself, for handlers that read the state at the moment they run
202
+ }
203
+ /** Puts one easel in reach of everything under it — `<Puck>`'s provider
204
+ * half — and, as `<Puck>` did, binds undo / redo to the page's keyboard
205
+ * (`hotkeys={false}` for a host that binds its own) and runs every block's
206
+ * `resolveData` once on mount (`resolveOnMount={false}` to skip). */
207
+ declare function EaselProvider({ easel, hotkeys, resolveOnMount, children }: EaselProviderProps): react.JSX.Element;
208
+ /** The easel itself, for handlers that read the state at the moment they run
147
209
  * (`useGetPuck`'s job) and for `dispatch` / `batch` / `select`. */
148
- declare function useEditorApi<C extends EditorConfig = EditorConfig>(): Editor<C>;
210
+ declare function useEaselApi<C extends EaselConfig = EaselConfig>(): Easel<C>;
149
211
  /**
150
- * Read a slice of the editor and re-render when it changes — `usePuck` /
212
+ * Read a slice of the easel and re-render when it changes — `usePuck` /
151
213
  * `createUsePuck()`'s selector form, over the same state shape, so the apps'
152
214
  * selectors move over by renaming the hook.
153
215
  *
@@ -155,12 +217,12 @@ declare function useEditorApi<C extends EditorConfig = EditorConfig>(): Editor<C
155
217
  * per selector, so React sees the same value for the same state and does not
156
218
  * loop. A primitive or an unchanged reference does not re-render.
157
219
  */
158
- declare function useEditor<T, C extends EditorConfig = EditorConfig>(selector: (view: EditorView<C>) => T): T;
220
+ declare function useEasel<T, C extends EaselConfig = EaselConfig>(selector: (view: EaselView<C>) => T): T;
159
221
  /**
160
- * A typed `useEditor` for one config — `createUsePuck<Config>()`'s shape, so
222
+ * A typed `useEasel` for one config — `createUsePuck<Config>()`'s shape, so
161
223
  * a file that made its own typed hook keeps the one line that made it.
162
224
  */
163
- declare function createUseEditor<C extends EditorConfig = EditorConfig>(): <T>(selector: (view: EditorView<C>) => T) => T;
225
+ declare function createUseEasel<C extends EaselConfig = EaselConfig>(): <T>(selector: (view: EaselView<C>) => T) => T;
164
226
 
165
227
  /** What a slot component accepts — Puck's. */
166
228
  interface CanvasSlotProps {
@@ -177,10 +239,12 @@ interface CanvasFrameProps {
177
239
  id?: string;
178
240
  /**
179
241
  * The frame's accessible name. Default "Canvas", in the strings provider's
180
- * language (`EditorStrings.canvas.frame`).
242
+ * language (`EaselStrings.canvas.frame`).
181
243
  */
182
244
  title?: string;
245
+ /** Added to the frame's own classes (`es-canvas-frame`, and `is-fill` when it fills). */
183
246
  className?: string;
247
+ /** The host's own style for the frame. The engine writes none: see the note on `CanvasFrame`. */
184
248
  style?: CSSProperties;
185
249
  /** Copy the host page's stylesheets into the frame, and keep them in step.
186
250
  * On by default: the blocks are styled by the app's own CSS. */
@@ -208,7 +272,7 @@ interface CanvasFrameProps {
208
272
  nonce?: string;
209
273
  }
210
274
  /**
211
- * An iframe of the editor's own, with the content portalled in. No `src`: the
275
+ * An iframe of the easel's own, with the content portalled in. No `src`: the
212
276
  * frame's first document is used — there to write into at once, with no load
213
277
  * to wait for and nothing fetched. The host's stylesheets are copied into its
214
278
  * head and copied again whenever the host's head changes (a route's CSS
@@ -221,12 +285,21 @@ interface CanvasFrameProps {
221
285
  * empty slot grew to the frame's full height), tables stop inheriting the
222
286
  * page's font, and an image's line box changes. (0.89.0–0.92.0 shipped the
223
287
  * quirks-mode frame.)
288
+ *
289
+ * ── Its look is a CLASS, not a `style` (second host, 2026-10-06) ───────────
290
+ * No border, the pane's width, and the pane's height when it fills are rules
291
+ * in easel.css (`.es-canvas-frame`, `.is-fill`). They were a `style` prop,
292
+ * which a server renders as a `style=""` attribute — and a Content-Security-
293
+ * Policy without `'unsafe-inline'` for styles refuses those: a server-rendered
294
+ * editor showed a bordered 300×150 frame, and hydration does not put a refused
295
+ * attribute right. The measured height of an `auto` frame is still written to
296
+ * `style`, on the client, through the CSSOM, which no policy governs.
224
297
  */
225
298
  declare function CanvasFrame({ children, id, title, className, style, mirrorStyles, onDocument, height, nonce, }: CanvasFrameProps): react.JSX.Element;
226
299
  /**
227
300
  * The whole document, for editing. Works in a `CanvasFrame` or on its own.
228
301
  *
229
- * `metadata` is the editor-wide metadata: the root's `puck.metadata`, and —
302
+ * `metadata` is the easel-wide metadata: the root's `puck.metadata`, and —
230
303
  * under each block's own — every block's (see the header).
231
304
  */
232
305
  declare function EditDocument({ metadata }: {
@@ -240,13 +313,13 @@ type CanvasOverlay = ComponentType<{
240
313
  kind: 'selected' | 'hover';
241
314
  children: ReactNode;
242
315
  }>;
243
- interface EditorCanvasProps {
244
- /** Draw in an iframe of the editor's own (default), or straight into the page. */
316
+ interface EaselCanvasProps {
317
+ /** Draw in an iframe of the easel's own (default), or straight into the page. */
245
318
  frame?: boolean;
246
319
  className?: string;
247
320
  style?: CSSProperties;
248
321
  /**
249
- * The editor-wide metadata — what the published page passes `Render` as
322
+ * The easel-wide metadata — what the published page passes `Render` as
250
323
  * `metadata`. Every block reads it as `puck.metadata` (its own `metadata`
251
324
  * laid over it) and the root as it is. Change it whenever the host's data
252
325
  * changes: the blocks redraw in place, nothing remounts, and it never enters
@@ -265,6 +338,16 @@ interface EditorCanvasProps {
265
338
  title?: string;
266
339
  /** The page's CSP nonce for the frame's stylesheets — see `CanvasFrameProps.nonce`. Unused with `frame={false}`. */
267
340
  nonce?: string;
341
+ /**
342
+ * Bind undo / redo in the frame's document — a key pressed in the frame
343
+ * never reaches the page. Left out, it follows `EaselProvider`'s `hotkeys`,
344
+ * so one switch turns the easel's keys off everywhere. Say `true` for a
345
+ * host that binds the PAGE's keys itself (to its own root, because the page
346
+ * is another app's) and still wants the frame's; `false` for the frame
347
+ * alone. Either way the keys do nothing while the easel-wide `edit`
348
+ * permission is off (`bindUndoKeys`). Unused with `frame={false}`.
349
+ */
350
+ hotkeys?: boolean;
268
351
  onDocument?: (doc: Document | null) => void;
269
352
  /**
270
353
  * Wraps everything drawn in the frame, and is handed the frame's document —
@@ -282,9 +365,9 @@ interface EditorCanvasProps {
282
365
  }
283
366
  /**
284
367
  * `<Puck.Preview>`, ours: the document for editing, its outlines, and the
285
- * clicks that select. Must sit under an `EditorProvider`.
368
+ * clicks that select. Must sit under an `EaselProvider`.
286
369
  */
287
- declare function EditorCanvas({ frame, className, style, metadata, mirrorStyles, onDocument, height, title, nonce, frameWrapper: FrameWrapper, overlay, children, }: EditorCanvasProps): ReactElement;
370
+ declare function EaselCanvas({ frame, className, style, metadata, mirrorStyles, onDocument, height, title, nonce, hotkeys, frameWrapper: FrameWrapper, overlay, children, }: EaselCanvasProps): ReactElement;
288
371
 
289
372
  /**
290
373
  * THE PARTS, ON THE STORE (blueprint step 5.2).
@@ -297,7 +380,7 @@ declare function EditorCanvas({ frame, className, style, metadata, mirrorStyles,
297
380
  */
298
381
  /** `useNodeOps` with the store as its host: reads the document, and writes one
299
382
  * `run` — one undo step, the selection travelling with it. */
300
- declare function useEditorNodeOps(options?: Omit<NodeOpsHost, 'data' | 'config' | 'commit'>): {
383
+ declare function useEaselNodeOps(options?: Omit<NodeOpsHost, 'data' | 'config' | 'commit'>): {
301
384
  held: ClipboardEntry | null;
302
385
  remove: (id: string) => boolean;
303
386
  duplicate: (id: string) => boolean;
@@ -331,7 +414,7 @@ interface SelectedFields {
331
414
  * - fields resolve through `useResolvedFields` (Puck's
332
415
  * `useRegisterFieldsSlice`: `changed`, `lastFields`, `appState`, `parent`);
333
416
  * - a change runs the block's `resolveData` (`resolveNodeData`, Puck's
334
- * `resolveComponentData`, with this editor's own memory of the last
417
+ * `resolveComponentData`, with this easel's own memory of the last
335
418
  * change) and then writes ONE `replace` of the node, or ONE `replaceRoot`.
336
419
  *
337
420
  * The write reads the node AT THE MOMENT IT RUNS, by id: a field changed while
@@ -349,13 +432,25 @@ declare function useSelectedFields(): SelectedFields;
349
432
  * a layout that moves Z still undoes and ⌥⌘Z does nothing.
350
433
  *
351
434
  * ⚠️ PUCK FIRED THESE IN A TEXT FIELD TOO, and so does this: ⌘Z while typing
352
- * in a setting undoes the editor's last step — which, since a field writes as
435
+ * in a setting undoes the easel's last step — which, since a field writes as
353
436
  * it is typed, is the typing — and the browser's own text undo is prevented,
354
437
  * so there are never two undo histories answering one key.
355
438
  *
356
- * Bound per DOCUMENT: the host page (`EditorProvider`) and the canvas frame
357
- * (`EditorCanvas`), because a key pressed in the frame never reaches the page.
439
+ * Bound per DOCUMENT: the host page (`EaselProvider`) and the canvas frame
440
+ * (`EaselCanvas`), because a key pressed in the frame never reaches the page.
441
+ * ONE switch turns both off (`EaselProvider hotkeys={false}`); the frame's
442
+ * alone is `EaselCanvas hotkeys`.
443
+ *
444
+ * ── Held with editing (second host, 2026-10-06) ─────────────────────────────
445
+ * While the easel-wide `edit` permission is off — somebody else holds the
446
+ * document, a save was refused, a read-only view — the keys do NOTHING: an
447
+ * undo is a write, and the screen may be the only copy of what it would
448
+ * rewind. The key is not taken either (no `preventDefault`), so a text field
449
+ * outside the document keeps the browser's own undo. Read at each key press,
450
+ * so a binding made once follows `setPermissions`. A block's own
451
+ * `permissions.edit` is that block's fields and is not asked. Handed only
452
+ * `undo` / `redo` (no `getState`), there is nothing to ask and the keys work.
358
453
  */
359
- declare function bindUndoKeys(doc: Document, editor: Pick<Editor, 'undo' | 'redo'>): () => void;
454
+ declare function bindUndoKeys(doc: Document, easel: Pick<Easel, 'undo' | 'redo'> & Partial<Pick<Easel, 'getState'>>): () => void;
360
455
 
361
- export { CanvasFrame, type CanvasFrameProps, type CanvasOverlay, type CanvasSlotProps, DEFAULT_UI, EditDocument, type Editor, EditorAction, EditorCanvas, type EditorCanvasProps, EditorConfig, EditorCore, type EditorOptions, EditorProvider, EditorUi, EditorView, NodeSelection, Permissions, type SelectedFields, bindUndoKeys, createEditor, createUseEditor, recordsByDefault, reduce, uiView, useEditor, useEditorApi, useEditorNodeOps, useSelectedFields, validSelection };
456
+ export { CanvasFrame, type CanvasFrameProps, type CanvasOverlay, type CanvasSlotProps, DEFAULT_UI, type Easel, EaselAction, EaselCanvas, type EaselCanvasProps, EaselConfig, EaselCore, type EaselOptions, EaselProvider, type EaselProviderProps, EaselUi, EaselView, EditDocument, NodeSelection, Permissions, type SelectedFields, bindUndoKeys, createEasel, createUseEasel, recordsByDefault, reduce, uiView, useEasel, useEaselApi, useEaselNodeOps, useSelectedFields, validSelection };
@@ -0,0 +1,9 @@
1
+ import{DEFAULT_CANVAS_WORDS as e}from"../chunk-AELNNAFM.js";import{useResolvedFields as t}from"../chunk-YRLPFZW3.js";import{toItemSelector as n,fromItemSelector as o,useNodeOps as r}from"../chunk-D2BCHTQR.js";import{useWords as a}from"../chunk-MFIRYGHW.js";import{setCanvasNonce as i}from"../chunk-ZNCEOSXO.js";import{find as s,ROOT_ID as d,getList as l,remove as c,parseZoneKey as u,move as p,duplicate as f,replaceNode as h,createNode as m,insert as v,normalizeData as y,createResolveCache as g,resolveNodeData as k,ROOT_SLOT as b}from"../chunk-KRXQM65X.js";import{topLevelSlotKeys as w,fieldsFrom as S,defaultsFrom as I}from"../chunk-NKB3KVAB.js";import{createContext as C,useEffect as O,useContext as x,useRef as E,useSyncExternalStore as A,useState as D,useMemo as z,useCallback as M,useLayoutEffect as Z}from"react";import{jsx as N,jsxs as $}from"react/jsx-runtime";import{createPortal as j}from"react-dom";function R(e){return!["setData","setUi","set"].includes(e.type)}var B=(e,t,n,o)=>{const r=l(e,t,u(n));return r?.[o]??null},P=(e,t,n,o,r)=>{const a=B(e,t,n,o);if(!a)throw new Error(`${r}: nothing at ${n}[${o}]`);return a};function L(e,t){const{selection:o}=e;return{...e.ui,itemSelector:n(e.data,t,o),selectedSlot:o&&"slot"===o.kind?{parentId:o.parentId,slot:o.slot}:null}}function K(e,t,n){return n?"node"===n.kind?s(e,t,n.id)?n:null:n.parentId===d?l(e,t,n)?n:null:s(e,t,n.parentId)&&l(e,t,n)?n:null:null}function F(e,t,n,r=!1){const{itemSelector:a,selectedSlot:i,...s}=n;let d=e.selection;var l;return"itemSelector"in n&&(d=o(e.data,t,a??null)),"selectedSlot"in n&&(i?d=(l=i)?{kind:"slot",parentId:l.parentId,slot:l.slot}:null:"slot"===d?.kind&&(d=null)),{...e,selection:d,ui:r?s:{...e.ui,...s}}}function H(e,t,n){const o=V(e,t,n);if(o===e)return e;const r=K(o.data,n,o.selection);return r===o.selection?o:{...o,selection:r}}function V(e,t,n){const{data:o}=e;switch(t.type){case"setData":{const n="function"==typeof t.data?t.data(o):t.data;return{...e,data:{...o,...n}}}case"setUi":return F(e,n,"function"==typeof t.ui?t.ui(L(e,n)):t.ui);case"set":{const r={data:o,ui:L(e,n)},a="function"==typeof t.state?t.state(r):t.state;let i=a.data?{...e,data:a.data}:e;return a.ui&&(i=F(i,n,a.ui,!0)),i}case"insert":{const r={...u(t.destinationZone),index:t.destinationIndex},a=m(n,t.componentType,{},t.id?{id:t.id}:{});return{...e,data:v(o,n,r,a)}}case"replace":{const r=P(o,n,t.destinationZone,t.destinationIndex,"replace");if(r.props.id!==t.data.props.id)throw new Error("Can't change the id during a replace action. Use remove and insert for a new node.");return{...e,data:h(o,n,r.props.id,t.data)}}case"replaceRoot":{const n={...o.root,props:{...o.root.props,...t.root.props},readOnly:t.root.readOnly};return{...e,data:{...o,root:n}}}case"duplicate":{const r=P(o,n,t.sourceZone,t.sourceIndex,"duplicate"),a=f(o,n,r.props.id),i=B(a,n,t.sourceZone,t.sourceIndex+1);return{...e,data:a,selection:i?{kind:"node",id:i.props.id}:e.selection}}case"reorder":return V(e,{...t,type:"move",sourceZone:t.destinationZone},n);case"move":{if(t.sourceZone===t.destinationZone&&t.sourceIndex===t.destinationIndex)return e;const r=B(o,n,t.sourceZone,t.sourceIndex);if(!r)return e;const a={...u(t.destinationZone),index:t.destinationIndex};return{...e,data:p(o,n,r.props.id,a)}}case"remove":{const r=P(o,n,t.zone,t.index,"remove");return{...e,data:c(o,n,r.props.id)}}default:return e}}var T={drag:!0,duplicate:!0,delete:!0,edit:!0,insert:!0},q={leftSideBarVisible:!0,rightSideBarVisible:!0,arrayState:{},componentList:{},isDragging:!1,previewMode:"edit",viewports:{current:{width:360,height:"auto"},options:[],controlsVisible:!0},field:{focus:null}},Y=0,U=()=>`history-${Y+=1}`;function W(e){let t=e.config;const r=e.now??(()=>Date.now()),a=e.historyCoalesceMs??250;let i=e.permissions??{};const d=y(e.data,t);let c={data:d,selection:null,ui:{...q}};if(e.ui){const{itemSelector:n,selectedSlot:r,...a}=e.ui;c={data:d,selection:r?{kind:"slot",...r}:o(d,t,n??null),ui:{...q,...a}}}let u=[{core:c,at:-1/0,sealed:!0,id:U()}],p=0;const f=e=>{const t=u[p],n=r();!e&&p===u.length-1&&p>0&&!t.sealed&&n-t.at<a?u=[...u.slice(0,p),{...t,core:c,at:n}]:(u=[...u.slice(0,p+1),{core:c,at:n,sealed:e,id:U()}],p=u.length-1)},m=/* @__PURE__ */new Set;let v=null,b=null,w=0,S=null,I=!1;const C=()=>{for(const e of[...m])e()},O=()=>{u=[...u.slice(0,p),{...u[p],core:c},...u.slice(p+1)]},x=(t,n)=>{w>0?I=I||n:(n?f(!1):c!==t&&O(),c.data!==t.data&&e.onChange?.(c.data),c!==t&&C())},E=e=>{const n=c;if(c=H(c,e,t),c===n)return;const o=e.recordHistory??R(e);x(n,o&&c.data!==n.data)},A=t=>{0===w&&(S=c,I=!1),w+=1;try{t()}catch(e){throw w-=1,0===w&&S&&(c=S,S=null),e}if(w-=1,w>0||!S)return;const n=S;S=null,I&&c.data!==n.data?f(!0):c!==n&&O(),c.data!==n.data&&e.onChange?.(c.data),c!==n&&C()},D=e=>{const n=c,o=K(c.data,t,e);var r,a;(r=o,a=c.selection,r===a||r&&a&&r.kind===a.kind&&("node"===r.kind&&"node"===a.kind?r.id===a.id:"slot"===r.kind&&"slot"===a.kind&&r.parentId===a.parentId&&r.slot===a.slot))||(c={...c,selection:o},x(n,!1))},z=t=>{const n=c;p=t,c=u[p].core,c.data!==n.data&&e.onChange?.(c.data),c!==n&&C()},M=()=>(e={})=>{const n=e.root?t.root?.permissions:t.components[e.item?.type??e.type??""]?.permissions;return{...T,...i,...n}};let Z=M();const N=g(),$={resolveCache:N,async resolveAll({trigger:e="load",metadata:n}={}){const o=c.data,r={type:"root",props:{...o.root.props??{}}},a=[...o.content,...Object.values(o.zones??{}).flat()],[i,...d]=await Promise.all([k(r,t,{cache:N,trigger:e,metadata:n}),...a.map(o=>k(o,t,{cache:N,trigger:e,metadata:n}))]);A(()=>{const e=c;let n=c.data;d.forEach((e,r)=>{if(!e.didChange)return;const i=a[r].props.id,d=s(n,t,i)?.node;d&&d===s(o,t,i)?.node&&(n=h(n,t,i,e.node))}),i.didChange&&n.root===o.root&&(n={...n,root:{...n.root,props:i.node.props,readOnly:i.node.readOnly??n.root.readOnly}}),n!==c.data&&(c={...c,data:n,selection:K(n,t,c.selection)},x(e,!1))})},async resolveNode(e,{trigger:n="replace",metadata:o}={}){const r=s(c.data,t,e)?.node;if(!r)return!1;const a=await k(r,t,{cache:N,trigger:n,metadata:o});if(!a.didChange)return!1;let i=!1;return A(()=>{const n=c;if(s(c.data,t,e)?.node!==r)return;const o=h(c.data,t,e,a.node);o!==c.data&&(c={...c,data:o,selection:K(o,t,c.selection)},x(n,!1),i=!0)}),i},get config(){return t},getState(){if(v&&b===c)return v;const e=c,o={data:e.data,ui:L(e,t)},r="node"===e.selection?.kind?s(e.data,t,e.selection.id)?.node??null:null;return v={appState:o,selection:e.selection,selectedItem:r,config:t,dispatch:E,history:{back:()=>$.undo(),forward:()=>$.redo(),hasPast:p>0,hasFuture:p<u.length-1,histories:u.map(e=>({id:e.id,state:{data:e.core.data,ui:L(e.core,t)}})),index:p,setHistoryIndex:e=>{e>=0&&e<u.length&&e!==p&&z(e)}},getPermissions:Z,getItemById:n=>s(e.data,t,n)?.node,getItemBySelector:n=>function(e,t,n){const[o,r]=(n.zone??"root:default-zone").split(/:(.*)/s);return l(e,t,{parentId:o,slot:r})?.[n.index]}(e.data,t,n),getSelectorForId:o=>{const r=n(e.data,t,{kind:"node",id:o});return r?{index:r.index,zone:r.zone??"root:default-zone"}:void 0}},b=e,v},subscribe:e=>(m.add(e),()=>{m.delete(e)}),dispatch:E,batch:A,select:e=>D(e?{kind:"node",id:e}:null),selectSlot:e=>D(e?{kind:"slot",parentId:e.parentId,slot:e.slot}:null),run(e){A(()=>{const n=c,o=void 0===e.select?c.selection:e.select;c={...c,data:e.data,selection:K(e.data,t,o)},x(n,e.data!==n.data)})},load(e,n={}){const o=c,r=y(e,t);c={...c,data:r,selection:K(r,t,n.selection??c.selection)},n.keepHistory||(u=[{core:c,at:-1/0,sealed:!0,id:U()}],p=0),c!==o&&C()},undo(){p>0&&z(p-1)},redo(){p<u.length-1&&z(p+1)},setPermissions(e){(function(e,t){if(e===t)return!0;const n=Object.keys(e);return n.length===Object.keys(t).length&&n.every(n=>e[n]===t[n])})(e,i)||(i=e,Z=M(),v=null,C())},setConfig(e){e!==t&&(t=e,Z=M(),v=null,C())}};return $}function X(e,t){const n=e=>{if(e.altKey)return;if(!(e.metaKey!==e.ctrlKey))return;const n="KeyZ"===e.code,o="KeyY"===e.code&&!e.shiftKey;(n||o)&&!1!==t.getState?.().getPermissions().edit&&(e.preventDefault(),o||e.shiftKey?t.redo():t.undo())};return e.addEventListener("keydown",n),()=>e.removeEventListener("keydown",n)}var Q=C(null),G=C(!0);function J({easel:e,hotkeys:t=!0,resolveOnMount:n=!0,children:o}){return O(()=>{n&&e.resolveAll()},[e,n]),O(()=>{if(t&&"undefined"!=typeof document)return X(document,e)},[e,t]),/* @__PURE__ */N(Q.Provider,{value:e,children:/* @__PURE__ */N(G.Provider,{value:t,children:o})})}function _(){const e=x(Q);if(!e)throw new Error("useEasel: no <EaselProvider> above this component");return e}function ee(e){const t=_(),n=E({view:null,selector:null,value:void 0,has:!1}),o=()=>{const o=t.getState(),r=n.current;if(r.has&&r.view===o&&r.selector===e)return r.value;const a=e(o);return r.has&&Object.is(r.value,a)?(n.current={view:o,selector:e,value:r.value,has:!0},r.value):(n.current={view:o,selector:e,value:a,has:!0},a)};return A(t.subscribe,o,o)}function te(){return function(e){return ee(e)}}var ne="data-puck-component",oe="data-puck-dropzone",re={},ae="data-es-mirror",ie="undefined"==typeof window?O:Z;function se(e,t,n){for(const e of Array.from(t.head.querySelectorAll(`[${ae}]`)))e.remove();for(const o of Array.from(e.head.querySelectorAll('style, link[rel="stylesheet"]'))){const e=o.cloneNode(!0);e.setAttribute(ae,""),n&&e.setAttribute("nonce",n),t.head.appendChild(e)}}function de({children:t,id:n="preview-frame",title:o,className:r,style:s,mirrorStyles:d=!0,onDocument:l,height:c="auto",nonce:u}){const p=a("canvas",e),f=E(null),[h,m]=D(null);return ie(()=>{const e=f.current,t=h?.ownerDocument.defaultView;if("auto"!==c||!e||!h||!t)return;const n=()=>{e.style.height=`${Math.ceil(h.getBoundingClientRect().height)}px`};n();const o=t.ResizeObserver,r=o?new o(n):null;return r?.observe(h),()=>r?.disconnect()},[h,c]),ie(()=>{let e=f.current?.contentDocument;if(!e)return;"CSS1Compat"!==e.compatMode&&(e.open(),e.write("<!DOCTYPE html><html><head></head><body></body></html>"),e.close(),e=f.current?.contentDocument??e),e.body||e.documentElement.appendChild(e.createElement("body")),e.body.style.margin="0";const t=e.createElement("div");t.setAttribute("data-puck-entry",""),t.id="frame-root","fill"===c&&t.classList.add("es-frame-fill"),e.body.appendChild(t),i(e,u),m(t),l?.(e);let n=null;const o=f.current?.ownerDocument;if(d&&o){se(o,e,u);const t=o.defaultView;t&&"MutationObserver"in t&&(n=new t.MutationObserver(()=>se(o,e,u)),n.observe(o.head,{childList:!0,subtree:!0,characterData:!0}))}return()=>{n?.disconnect(),t.remove(),m(null),l?.(null)}},[]),/* @__PURE__ */N("iframe",{ref:f,id:n,title:o??p.frame,className:["es-canvas-frame","fill"===c?"is-fill":"",r??""].filter(Boolean).join(" "),style:s,children:h?j(t,h):null})}var le=C(null),ce=C(re),ue=[];function pe({parentId:e,slot:t,className:n,style:o,minEmptyHeight:r=128}){const a=_(),i=ee(n=>l(n.appState.data,a.config,{parentId:e,slot:t})??ue),s=ee(t=>"node"===t.selection?.kind&&t.selection.id===e&&e!==d),c=ee(n=>"slot"===n.selection?.kind&&n.selection.parentId===e&&n.selection.slot===t),u=x(le),p=["es-zone"];return i.length&&p.push("has-children"),e===d&&p.push("is-root"),(s||c)&&p.push("is-area-selected"),null!==u&&u===e&&e!==d&&p.push("is-hovering-area"),n&&p.push(n),/* @__PURE__ */N("div",{className:p.join(" "),[oe]:`${e}:${t}`,style:{...o,"--min-empty-height":`${r}px`},children:i.map(e=>/* @__PURE__ */N(me,{node:e},e.props.id))})}function fe(e,t,n,o){const r="root"===t?e.root:e.components[t],a={...o};for(const e of w(S(r)))a[e]=function(t={}){/* @__PURE__ */
2
+ return N(pe,{parentId:n,slot:e,...t})};return a}function he(e){return function({zone:t,...n}){/* @__PURE__ */
3
+ return N(pe,{parentId:e,slot:t,...n})}}function me({node:e}){const t=_().config,n=t.components[e.type],o=e.props.id,r=function(e){const t=E(null);return z(()=>n=>{const o=t.current;o&&o!==n&&(o.removeAttribute(ne),o.removeAttribute("data-puck-dnd")),t.current=n,n&&(n.setAttribute(ne,e),n.setAttribute("data-puck-dnd",e),n.style.position="relative")},[e])}(o),a=x(ce);if(!n?.render)return null;const i=n.render,s=fe(t,e.type,o,{...I(n),...e.props}),d={renderDropZone:he(o),isEditing:!0,dragRef:n.inline?r:null,metadata:{...a,...n.metadata}};return n.inline?/* @__PURE__ */N(i,{...s,puck:d,editMode:!0}):/* @__PURE__ */N("div",{[ne]:o,"data-puck-dnd":o,style:{position:"relative"},children:/* @__PURE__ */N(i,{...s,puck:d,editMode:!0})})}function ve({metadata:e}){const t=ee(e=>e.config),n=ee(e=>e.appState.data.root).props??{},o=t.root?.render,r=/* @__PURE__ */N(pe,{parentId:d,slot:b}),a=o?/* @__PURE__ */N(o,{id:"puck-root",...fe(t,"root",d,n),puck:{renderDropZone:he(d),isEditing:!0,dragRef:null,metadata:e??{}},editMode:!0,children:r}):r;/* @__PURE__ */
4
+ return N(ce.Provider,{value:e??re,children:a})}function ye({id:e,kind:t,overlay:n}){const o=E(null),[r,a]=D(null);ie(()=>{const t=o.current?.ownerDocument,n=t?.defaultView;if(!t||!n||!e)return void a(null);let r=0;const i=()=>{r=0;const i=function(e,t){if(!e||!t)return null;const n=e.getBoundingClientRect(),o=t.getBoundingClientRect();return{top:n.top-o.top,left:n.left-o.left,width:n.width,height:n.height}}(t.querySelector(`[${ne}="${n.CSS?.escape?n.CSS.escape(e):e}"]`),o.current?.parentElement??null);a(e=>{return(t=e)===(n=i)||t&&n&&t.top===n.top&&t.left===n.left&&t.width===n.width&&t.height===n.height?e:i;var t,n})},s=()=>{r||(r=(n.requestAnimationFrame??(e=>n.setTimeout(e,16)))(i))};i();const d="MutationObserver"in n?new n.MutationObserver(s):null;d?.observe(t.body,{childList:!0,subtree:!0,attributes:!0,characterData:!0});const l="ResizeObserver"in n?new n.ResizeObserver(s):null;return l?.observe(t.documentElement),n.addEventListener("resize",s),()=>{d?.disconnect(),l?.disconnect(),n.removeEventListener("resize",s),r&&(n.cancelAnimationFrame??n.clearTimeout)(r)}},[e]);const i=/* @__PURE__ */N("div",{ref:o,"aria-hidden":"true",className:`es-canvas-outline is-${t}`,style:r?{position:"absolute",top:r.top,left:r.left,width:r.width,height:r.height}:{display:"none"}});return n&&e?/* @__PURE__ */N(n,{componentId:e,kind:t,children:i}):i}function ge({frame:e=!0,className:t,style:n,metadata:o,mirrorStyles:r,onDocument:a,height:i,title:s,nonce:d,hotkeys:l,frameWrapper:c,overlay:u,children:p}){const f=_(),h=x(G),m=l??h,[v,y]=D(void 0),g=ee(e=>"node"===e.selection?.kind?e.selection.id:null),[k,b]=D(null),w=/* @__PURE__ */$("div",{className:"es-canvas-content",style:{position:"relative"},onClick:e=>{const t=e.target;t.closest?.("a[href]")&&e.preventDefault();const n=t.closest?.(`[${ne}]`);n?f.select(n.getAttribute(ne)):t.hasAttribute?.(oe)||f.select(null)},onMouseOver:e=>{const t=e.target.closest?.(`[${ne}]`),n=t?.getAttribute(ne)??null;n!==k&&b(n)},onMouseLeave:()=>b(null),children:[
5
+ /* @__PURE__ */N(le.Provider,{value:k,children:/* @__PURE__ */N(ve,{metadata:o})}),
6
+ /* @__PURE__ */$("div",{className:"es-canvas-outlines",style:{position:"absolute",top:0,left:0,pointerEvents:"none"},children:[
7
+ /* @__PURE__ */N(ye,{id:k&&k!==g?k:null,kind:"hover",overlay:u}),
8
+ /* @__PURE__ */N(ye,{id:g,kind:"selected",overlay:u})]}),p]}),S=c?/* @__PURE__ */N(c,{document:v,children:w}):w;O(()=>{if(v&&m)return X(v,f)},[v,m,f]);/* @__PURE__ */
9
+ return N("div",{className:"es-canvas"+(t?` ${t}`:""),style:n,"data-puck-preview":"",children:e?/* @__PURE__ */N(de,{mirrorStyles:r,onDocument:e=>{y(e??void 0),a?.(e)},height:i,title:s,nonce:d,children:S}):/* @__PURE__ */N("div",{id:"preview-frame","data-puck-entry":"",children:S})})}function ke(e={}){const t=_(),n=ee(e=>e.appState.data);return r({...e,data:n,config:t.config,commit:(e,{select:n})=>t.run({data:e,select:n})})}function be(){const e=_(),n=ee(e=>e.config),o=e.resolveCache,r=ee(e=>e.selectedItem),a=ee(e=>e.appState.data.root),i=r?r.props.id:"root",d=a.props??{},l=z(()=>r||{type:"root",props:d},[r,d]),c=r?n.components[r.type]:n.root,u=e.getState(),p=r?s(u.appState.data,n,i):null,f=p&&"root"!==p.place.parentId?p.place.parentId:null,h=f?u.getItemById(f)??null:null,{fields:m}=t({id:i,node:l,block:c,parent:h,appState:u.appState}),v=ee(e=>!1===e.getPermissions(r?{item:r}:{root:!0}).edit),y=M((t,r)=>{(async()=>{const a=e.getState();if("root"!==i){const s=a.getItemById(i),d=a.getSelectorForId(i);if(!s||!d)return;const l={...s,props:{...s.props,[t]:r}},{node:c}=await k(l,n,{cache:o,trigger:"replace"}),u=e.getState().getSelectorForId(i);if(!u)return;return void e.dispatch({type:"replace",destinationZone:u.zone??"root:default-zone",destinationIndex:u.index,data:c})}const s=a.appState.data.root.props??{},{node:d}=await k({type:"root",props:{...s,[t]:r}},n,{cache:o,trigger:"replace"});e.dispatch({type:"replaceRoot",root:{props:d.props,readOnly:d.readOnly},recordHistory:!0})})()},[e,n,o,i]);return{id:i,node:l,fields:m,values:l.props,readOnly:v,onChange:y}}export{de as CanvasFrame,q as DEFAULT_UI,ge as EaselCanvas,J as EaselProvider,ve as EditDocument,X as bindUndoKeys,W as createEasel,te as createUseEasel,R as recordsByDefault,H as reduce,L as uiView,ee as useEasel,_ as useEaselApi,ke as useEaselNodeOps,be as useSelectedFields,K as validSelection};
@@ -177,7 +177,7 @@ declare const documentPermissions: (canEdit: boolean) => DocumentPermissions;
177
177
  /**
178
178
  * "just now" / "2 minutes ago" — one phrasing, shared by every notice.
179
179
  *
180
- * `words` is the host's language (`useEditorStrings().lock`, 0.95.0); left out,
180
+ * `words` is the host's language (`useEaselStrings().lock`, 0.95.0); left out,
181
181
  * it is the English it always was. The thresholds are not words and do not
182
182
  * move: under 45 seconds is "just now", then whole minutes, then whole hours.
183
183
  */
@@ -192,7 +192,7 @@ declare const nameOrSomeone: (name: string, words?: LockWords) => string;
192
192
  * a reload IS a new editor session, and a remembered id would let a closed tab's
193
193
  * claim look alive.
194
194
  */
195
- declare function newEditorClientId(): string;
195
+ declare function newEaselClientId(): string;
196
196
  /**
197
197
  * The `code` of a document write refused because somebody else TOOK the
198
198
  * document from this tab (backend `efficient/editlock.py`,
@@ -351,4 +351,4 @@ type EditLock = {
351
351
  /** Bind the decisions to one editor's HTTP client and document type. */
352
352
  declare function createEditLock(adapter: EditLockAdapter): EditLock;
353
353
 
354
- export { type ClaimBeatDeps, type DocumentPermissions, EDIT_LOCK_TAKEN, type EditAccess, type EditLock, type EditLockAdapter, type EditLockBody, type EditLockFailure, type HeldWrite, type LockHolder, type LockWords, type SaveConflict, accessForNewDocument, canWriteDocument, createEditLock, describeAge, documentPermissions, documentVersionMoved, holderOf, nameOrSomeone, newEditorClientId, readLockHolder, reloadIfVersionMoved, shouldReleaseHeldWrite, shouldReloadAfterTakeover };
354
+ export { type ClaimBeatDeps, type DocumentPermissions, EDIT_LOCK_TAKEN, type EditAccess, type EditLock, type EditLockAdapter, type EditLockBody, type EditLockFailure, type HeldWrite, type LockHolder, type LockWords, type SaveConflict, accessForNewDocument, canWriteDocument, createEditLock, describeAge, documentPermissions, documentVersionMoved, holderOf, nameOrSomeone, newEaselClientId, readLockHolder, reloadIfVersionMoved, shouldReleaseHeldWrite, shouldReloadAfterTakeover };
@@ -1 +1 @@
1
- export{EDIT_LOCK_TAKEN,accessForNewDocument,canWriteDocument,createEditLock,describeAge,documentPermissions,documentVersionMoved,holderOf,nameOrSomeone,newEditorClientId,readLockHolder,reloadIfVersionMoved,shouldReleaseHeldWrite,shouldReloadAfterTakeover}from"../chunk-TDA4HH54.js";import"../chunk-RE4LP46F.js";
1
+ export{EDIT_LOCK_TAKEN,accessForNewDocument,canWriteDocument,createEditLock,describeAge,documentPermissions,documentVersionMoved,holderOf,nameOrSomeone,newEaselClientId,readLockHolder,reloadIfVersionMoved,shouldReleaseHeldWrite,shouldReloadAfterTakeover}from"../chunk-JUQ6YJCG.js";import"../chunk-RE4LP46F.js";
@@ -35,8 +35,13 @@
35
35
  border-color: var(--es-accent);
36
36
  box-shadow: 0 0 0 3px var(--es-accent-ring);
37
37
  }
38
+ /* A value nobody may change is still a value somebody has to READ: the grey
39
+ ground says the state, and the ink stays legible on it. It was `--es-muted`,
40
+ 4.39:1 on this ground at 12px — a hair under the 4.5:1 WCAG AA asks (second
41
+ host, axe-core, 2026-10-07). The number cell's read-only state below is the
42
+ same rule. Held by ratio in tests/contrast-small-text.test.ts. */
38
43
  .es-fin[readonly],
39
- .es-fin:disabled { background: var(--es-track-bg); color: var(--es-muted); }
44
+ .es-fin:disabled { background: var(--es-track-bg); color: var(--es-text-secondary); }
40
45
 
41
46
  .es-fin--area {
42
47
  height: auto;
@@ -75,7 +80,7 @@
75
80
  border-color: var(--es-focus-line, #60a5fa);
76
81
  box-shadow: 0 0 0 2px var(--es-focus-ring, rgba(96, 165, 250, 0.3));
77
82
  }
78
- .es-fnum.is-readonly { background: var(--es-track-bg); color: var(--es-muted); }
83
+ .es-fnum.is-readonly { background: var(--es-track-bg); color: var(--es-text-secondary); }
79
84
 
80
85
  .es-fnum-icon { display: flex; align-items: center; flex: none; color: var(--es-field-placeholder); }
81
86
  .es-fnum-icon svg { width: 14px; height: 14px; }
@@ -239,6 +244,90 @@
239
244
  .es-fsel-option.is-active { background: var(--es-hover); }
240
245
  .es-fsel-option.is-chosen { color: var(--es-accent-text); font-weight: 600; }
241
246
  .es-fsel-option[aria-disabled='true'] { color: var(--es-disabled); cursor: default; }
247
+ /* A heading over a run of options (`FieldOption.group`): a quiet label above
248
+ its options, and not an option itself — no hover, no pointer. */
249
+ .es-fsel-group-list { margin: 0; padding: 0; list-style: none; }
250
+ .es-fsel-group + .es-fsel-group,
251
+ .es-fsel-option + .es-fsel-group { margin-top: 4px; }
252
+ .es-fsel-group-head {
253
+ padding: 8px 8px 4px;
254
+ font-size: 11px;
255
+ font-weight: 600;
256
+ color: var(--es-muted);
257
+ cursor: default;
258
+ }
259
+ .es-fsel-group-count { margin-left: 4px; font-weight: 400; }
260
+
261
+ /* The editor's one pop-up list (card [MERGE] One Pop-up List For Editor And
262
+ One For Shoppers): what the website editor's own lists drew, here once.
263
+ A row with more than its label lays its parts out in a line. */
264
+ .es-fsel-option--rich { display: flex; align-items: center; gap: 8px; }
265
+ .es-fsel-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
266
+ .es-fsel-meta { flex: none; font-size: 11px; font-weight: 400; color: var(--es-muted); }
267
+ .es-fsel-thumb {
268
+ flex: none;
269
+ width: 26px;
270
+ height: 26px;
271
+ overflow: hidden;
272
+ border-radius: var(--es-field-radius);
273
+ background: var(--es-track-bg);
274
+ }
275
+ .es-fsel-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
276
+ /* `multiple`: a tick box per row, drawn (the row is the option, not an <input>). */
277
+ .es-fsel-tick {
278
+ flex: none;
279
+ position: relative;
280
+ box-sizing: border-box;
281
+ width: 14px;
282
+ height: 14px;
283
+ border: 1.5px solid var(--es-input-border);
284
+ border-radius: 3px;
285
+ background: var(--es-field-bg);
286
+ }
287
+ .es-fsel-tick.is-on { border-color: var(--es-accent); background: var(--es-accent); }
288
+ .es-fsel-tick.is-on::after {
289
+ content: '';
290
+ position: absolute;
291
+ left: 3.5px;
292
+ top: 0.5px;
293
+ width: 3.5px;
294
+ height: 7px;
295
+ border-right: 1.6px solid var(--es-on-accent);
296
+ border-bottom: 1.6px solid var(--es-on-accent);
297
+ transform: rotate(45deg);
298
+ }
299
+ /* A tick box says "on" itself, so a ticked row keeps the ordinary ink. */
300
+ .es-fsel-option--rich.is-chosen:has(.es-fsel-tick) { color: var(--es-text); font-weight: 400; }
301
+ /* `searchable`: the box and the list float together; the list inside is plain. */
302
+ .es-fsel-pop {
303
+ position: absolute;
304
+ z-index: 20;
305
+ top: calc(100% + 4px);
306
+ left: 0;
307
+ right: 0;
308
+ display: flex;
309
+ flex-direction: column;
310
+ gap: 4px;
311
+ padding: 4px;
312
+ border: 1px solid var(--es-line);
313
+ border-radius: var(--es-row-radius);
314
+ background: var(--es-raised);
315
+ box-shadow: var(--es-panel-shadow);
316
+ }
317
+ .es-fsel-pop .es-fsel-list {
318
+ position: static;
319
+ padding: 0;
320
+ border: 0;
321
+ border-radius: 0;
322
+ background: none;
323
+ box-shadow: none;
324
+ }
325
+ .es-fsel-empty { padding: 8px; font-size: 12px; color: var(--es-muted); }
326
+ /* `inline`: the list opens in flow and pushes the rows below it down, so a
327
+ panel that scrolls never clips it. */
328
+ .es-fsel--inline .es-fsel-list { position: static; margin-top: 4px; }
329
+ .es-fsel--inline .es-fsel-pop { position: static; margin-top: 4px; }
330
+ .es-fsel--inline .es-fsel-pop .es-fsel-list { margin-top: 0; }
242
331
 
243
332
  /* ── the panel body: the tab strip and the rows (Inspector) ───────────────── */
244
333
 
@@ -274,6 +363,16 @@
274
363
  /* The rows are Layers' rows (tree.css); the list lets them reach the panel's
275
364
  edges the way Layers' do, so a row's hover reads as the same row. */
276
365
  .es-list-tree { margin: 0 -8px; }
366
+ /* …but the list is NOT a scroll box, as Layers is (`.es-tree { overflow-y:
367
+ auto }`): the panel around it scrolls. A row's drag hint hangs 4px under the
368
+ row, 22px tall, and under the LAST row the tree has less room than that: it
369
+ clipped the hint to 8px (second host, 2026-10-07). What overflowed also
370
+ made the list a scroller of its own inside the panel: a wheel over it moved
371
+ the rows 14px, and it was the nearest box that scrolled, so a drag in the
372
+ list scrolled IT at its edges, not the panel. Measured in Chrome
373
+ (scripts/second-host-clickthrough.mjs). Two classes, so it wins whichever
374
+ sheet loads last. */
375
+ .es-tree.es-list-tree { overflow: visible; }
277
376
 
278
377
  .es-list-add {
279
378
  height: 32px;
@@ -289,31 +388,9 @@
289
388
  .es-list-add:disabled { color: var(--es-disabled); cursor: default; }
290
389
  .es-list-add:focus-visible { outline: 2px solid var(--es-accent); outline-offset: 1px; }
291
390
 
292
- /* An item opened: the way back, its name, its own settings, its two actions. */
391
+ /* An item opened: the way back (chrome's InspectorUpLevel, inline), its name,
392
+ its own settings, its two actions. */
293
393
  .es-list.is-open { gap: 12px; }
294
- .es-list-back {
295
- align-self: flex-start;
296
- display: inline-flex;
297
- align-items: center;
298
- gap: 8px;
299
- height: 28px;
300
- padding: 0 8px;
301
- border: 0;
302
- border-radius: var(--es-field-radius);
303
- background: var(--es-hover);
304
- color: var(--es-text-secondary);
305
- font: inherit;
306
- font-size: 12px;
307
- cursor: pointer;
308
- }
309
- .es-list-back:focus-visible { outline: 2px solid var(--es-accent); outline-offset: 1px; }
310
- .es-list-back-chev {
311
- width: 6px;
312
- height: 6px;
313
- border-left: 1.6px solid currentColor;
314
- border-bottom: 1.6px solid currentColor;
315
- transform: rotate(45deg);
316
- }
317
394
  .es-list-title { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
318
395
  .es-list-name { font-size: 14px; font-weight: 600; color: var(--es-text); }
319
396
  .es-list-item { min-width: 0; }