efficient-easel 24.1.0 → 25.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +43 -2
  2. package/dist/{CanvasDragLayer-NZpaX6aq.d.ts → CanvasDragLayer-DXeQGrwJ.d.ts} +42 -3
  3. package/dist/canvas/index.d.ts +2 -139
  4. package/dist/canvas/index.js +1 -1
  5. package/dist/chrome/index.js +1 -1
  6. package/dist/{chunk-6NJHYXJM.js → chunk-3XECMGXX.js} +1 -1
  7. package/dist/chunk-4OMAA45Q.js +5 -0
  8. package/dist/{chunk-G67G63O5.js → chunk-4Y3TK65H.js} +1 -1
  9. package/dist/chunk-53ME3GAI.js +16 -0
  10. package/dist/{chunk-FRAIEWRC.js → chunk-62UGN7VM.js} +1 -1
  11. package/dist/{chunk-HNKB2ZYR.js → chunk-6ATIYLUL.js} +1 -1
  12. package/dist/{chunk-Y53EKN35.js → chunk-6EDVZBUO.js} +1 -1
  13. package/dist/chunk-7UFKEX72.js +1 -0
  14. package/dist/chunk-CEC5XLN4.js +1 -0
  15. package/dist/chunk-CWELHADJ.js +1 -0
  16. package/dist/chunk-DX3VILZ3.js +6 -0
  17. package/dist/chunk-ERB6FAZT.js +2 -0
  18. package/dist/{chunk-TGZCD6IP.js → chunk-EUFDQ4KL.js} +1 -1
  19. package/dist/chunk-FR7EEKFF.js +1 -0
  20. package/dist/{chunk-75C6WFNF.js → chunk-FYV6GZXP.js} +1 -1
  21. package/dist/chunk-GDO4QJ4G.js +1 -0
  22. package/dist/chunk-GEPSJJWW.js +1 -0
  23. package/dist/{chunk-GMCZXMOV.js → chunk-GR3XRILS.js} +1 -1
  24. package/dist/chunk-H6BER2EU.js +1 -0
  25. package/dist/chunk-JIP5JNRB.js +1 -0
  26. package/dist/{chunk-AGWKB77Q.js → chunk-K53PQAMP.js} +1 -1
  27. package/dist/{chunk-CLOS6SSX.js → chunk-KO47WAWR.js} +1 -1
  28. package/dist/chunk-KTV7ORWF.js +64 -0
  29. package/dist/chunk-LUG22U5L.js +1 -0
  30. package/dist/{chunk-DDH2DYQP.js → chunk-LUOY3A3Q.js} +1 -1
  31. package/dist/{chunk-OITNWE52.js → chunk-M4THI4OO.js} +1 -1
  32. package/dist/chunk-MBEUSPIN.js +18 -0
  33. package/dist/chunk-MIX7EGRG.js +2 -0
  34. package/dist/{chunk-2FYFAAAK.js → chunk-OXOUGEAT.js} +1 -1
  35. package/dist/chunk-PMRN5Z3T.js +1 -0
  36. package/dist/{chunk-55KKGABA.js → chunk-PVBDKBJB.js} +1 -1
  37. package/dist/{chunk-RPMSET77.js → chunk-QTHQPJWA.js} +1 -1
  38. package/dist/{chunk-ISS72IKO.js → chunk-QZJKWKGH.js} +1 -1
  39. package/dist/{chunk-IYHO4XQX.js → chunk-S6YOIRXQ.js} +1 -1
  40. package/dist/{chunk-5PXVT7JY.js → chunk-SBVIW3KJ.js} +1 -1
  41. package/dist/chunk-SC6E3UUS.js +13 -0
  42. package/dist/chunk-SSDKT4AY.js +9 -0
  43. package/dist/chunk-TOKMFZPB.js +1 -0
  44. package/dist/chunk-U3LTJSBU.js +20 -0
  45. package/dist/{chunk-WYTPBF3Q.js → chunk-VBEVZZTV.js} +11 -11
  46. package/dist/chunk-VIUES5R6.js +8 -0
  47. package/dist/{chunk-4ESG53BH.js → chunk-YFHKE54W.js} +1 -1
  48. package/dist/chunk-YYD257TD.js +1 -0
  49. package/dist/chunk-ZLOMH2PD.js +1 -0
  50. package/dist/{chunk-CE5SGCN2.js → chunk-ZYOQXBJF.js} +3 -3
  51. package/dist/copy/index.js +13 -13
  52. package/dist/copy-place-CQIemdtU.d.ts +25 -0
  53. package/dist/data/index.d.ts +4 -1
  54. package/dist/data/index.js +1 -1
  55. package/dist/dom-BpxMcHFP.d.ts +200 -0
  56. package/dist/drag/index.d.ts +2 -3
  57. package/dist/drag/index.js +1 -1
  58. package/dist/draw/index.d.ts +1 -1
  59. package/dist/draw/index.js +1 -1
  60. package/dist/easel/index.d.ts +67 -1
  61. package/dist/easel/index.js +1 -1
  62. package/dist/edit-lock/index.d.ts +0 -46
  63. package/dist/edit-lock/index.js +1 -1
  64. package/dist/fields/fields.css +6 -40
  65. package/dist/fields/index.d.ts +1 -6
  66. package/dist/fields/index.js +1 -1
  67. package/dist/node-ops/index.js +1 -1
  68. package/dist/page-copy/index.d.ts +110 -0
  69. package/dist/page-copy/index.js +5 -0
  70. package/dist/panel/index.d.ts +29 -34
  71. package/dist/panel/index.js +1 -1
  72. package/dist/panel/panel.css +0 -78
  73. package/dist/screen/components/AddBlockRow.js +1 -1
  74. package/dist/screen/components/AddSidePanel.d.ts +37 -57
  75. package/dist/screen/components/AddSidePanel.js +1 -1
  76. package/dist/screen/components/NodeContextMenu.d.ts +19 -11
  77. package/dist/screen/components/NodeContextMenu.js +1 -1
  78. package/dist/screen/components/PageEditGuardNotices.js +1 -1
  79. package/dist/screen/components/SaveControls.d.ts +10 -11
  80. package/dist/screen/components/SaveControls.js +1 -1
  81. package/dist/screen/components/ScreenChrome.js +1 -1
  82. package/dist/screen/components/ScreenDocumentContext.js +1 -1
  83. package/dist/screen/components/ScreenMount.d.ts +5 -6
  84. package/dist/screen/components/ScreenMount.js +1 -1
  85. package/dist/screen/components/ScreenStoreProvider.d.ts +8 -9
  86. package/dist/screen/components/ScreenStoreProvider.js +1 -1
  87. package/dist/screen/components/SectionScreenShell.js +17 -17
  88. package/dist/screen/components/SidePanelHead.js +1 -1
  89. package/dist/screen/components/panel-widths.js +1 -1
  90. package/dist/screen/components/usePartBridge.d.ts +10 -11
  91. package/dist/screen/components/usePartBridge.js +1 -1
  92. package/dist/screen/index.d.ts +9 -10
  93. package/dist/screen/index.js +1 -1
  94. package/dist/screen/lib/document/block-set-host.d.ts +55 -0
  95. package/dist/screen/lib/document/block-set-host.js +1 -0
  96. package/dist/screen/lib/document/block-types-in.js +1 -1
  97. package/dist/screen/lib/document/box-fields.d.ts +70 -3
  98. package/dist/screen/lib/document/box-fields.js +1 -9
  99. package/dist/screen/lib/document/device-fields.js +1 -1
  100. package/dist/screen/lib/document/palette-sections.d.ts +33 -97
  101. package/dist/screen/lib/document/palette-sections.js +1 -1
  102. package/dist/screen/lib/document/preview-dom.js +1 -1
  103. package/dist/screen/lib/document/section-doc.js +1 -1
  104. package/dist/screen/lib/document/style-clipboard.d.ts +10 -11
  105. package/dist/screen/lib/document/style-clipboard.js +1 -1
  106. package/dist/screen/lib/document/use-section-ops.js +1 -1
  107. package/dist/screen/lib/document-switch.js +1 -1
  108. package/dist/screen/lib/failure.js +1 -1
  109. package/dist/screen/lib/host-inbound.js +1 -1
  110. package/dist/screen/lib/key-scope.js +1 -1
  111. package/dist/screen/lib/media-picker.js +1 -1
  112. package/dist/screen/lib/page-lock.js +1 -1
  113. package/dist/screen/lib/screen-parts.d.ts +265 -13
  114. package/dist/screen/lib/screen-parts.js +1 -1
  115. package/dist/screen/screen.css +6 -0
  116. package/dist/screen/words.d.ts +9 -0
  117. package/dist/screen/words.js +1 -1
  118. package/dist/strings/index.d.ts +4 -1
  119. package/dist/strings/index.js +2 -2
  120. package/dist/style-clipboard/index.d.ts +0 -32
  121. package/dist/style-clipboard/index.js +1 -1
  122. package/dist/text-styles/index.d.ts +7 -6
  123. package/dist/text-styles/index.js +37 -36
  124. package/dist/text-styles/text-styles.css +5 -53
  125. package/dist/tree/index.d.ts +2 -2
  126. package/dist/{types-DBy3L69b.d.ts → types-CoUTWUwB.d.ts} +1 -1
  127. package/dist/words-Dl8Qja4K.d.ts +32 -0
  128. package/package.json +15 -3
  129. package/dist/chunk-3FIS2FRS.js +0 -1
  130. package/dist/chunk-7RSE6KKY.js +0 -1
  131. package/dist/chunk-AAAZ2CQR.js +0 -1
  132. package/dist/chunk-CMPFUJ26.js +0 -8
  133. package/dist/chunk-CZAAGBVL.js +0 -7
  134. package/dist/chunk-DSKNJXB4.js +0 -1
  135. package/dist/chunk-FJTCSQWK.js +0 -16
  136. package/dist/chunk-H4Z2V56N.js +0 -21
  137. package/dist/chunk-K7MMLCTT.js +0 -2
  138. package/dist/chunk-KRL3IUV3.js +0 -1
  139. package/dist/chunk-LF3FUSCQ.js +0 -1
  140. package/dist/chunk-LN43DP2A.js +0 -18
  141. package/dist/chunk-MRWVYSU5.js +0 -64
  142. package/dist/chunk-QORC32JO.js +0 -5
  143. package/dist/chunk-THWO5YVZ.js +0 -1
  144. package/dist/chunk-TOHOGVAE.js +0 -15
  145. package/dist/chunk-V7U33NF2.js +0 -1
  146. package/dist/chunk-VKPOH6MK.js +0 -1
  147. package/dist/chunk-YU3WHLGI.js +0 -1
  148. package/dist/repeat-D65bJS8d.d.ts +0 -49
  149. package/dist/session-EsjORLQ0.d.ts +0 -43
@@ -0,0 +1,200 @@
1
+ /**
2
+ * A TEMPLATE DRAWN MANY TIMES (section parts plan §3.2, row B5, 2026-10-23).
3
+ *
4
+ * A data block (a product grid, a list of posts) holds ONE `template` slot of
5
+ * real blocks and draws it once per data row. So the same block ids are in the
6
+ * canvas several times. The host wraps each drawn copy:
7
+ *
8
+ * <div data-sf-repeat="<ownerId>:template" data-sf-repeat-index={i}>
9
+ *
10
+ * Index 0 is the MAIN copy. The blocks inside keep their real
11
+ * `data-easel-block` / `data-easel-dnd` ids in every copy, so a click on any
12
+ * copy selects the template block. What the canvas does with the copies:
13
+ * - the hover and the selected outline are drawn on every copy;
14
+ * - only the main copy's lists are drop targets (the others are pictures);
15
+ * - a lookup of one element by id reads the main copy.
16
+ *
17
+ * The two names are written HERE, once. The website draws the wrappers with
18
+ * them (it imports them from `efficient-easel/draw`, a render entry), so the
19
+ * string is never typed twice. Pure DOM, no import, nothing run at import.
20
+ *
21
+ * A page with no `data-sf-repeat` gets the same answers as before: every
22
+ * helper below falls back to the first match.
23
+ */
24
+ /** The wrapper round one drawn copy of a template: `"<ownerId>:<slot>"`. */
25
+ declare const REPEAT_ATTRIBUTE = "data-sf-repeat";
26
+ /** Which copy the wrapper holds: `"0"` is the main copy. */
27
+ declare const REPEAT_INDEX_ATTRIBUTE = "data-sf-repeat-index";
28
+ /**
29
+ * Is `el` in a copy that is NOT the main one — inside (or itself) a wrapper
30
+ * whose index is not 0, at any depth (a template inside a copy of an outer
31
+ * template is a copy too)? False for a block in no wrapper at all.
32
+ */
33
+ declare function isRepeatCopy(el: Element): boolean;
34
+ /** The main copy among `matches`: the first that is not a copy, else the first. */
35
+ declare function mainCopyOf<T extends Element>(matches: Iterable<T>): T | null;
36
+ /**
37
+ * `root.querySelector(selector)`, but the main copy when a template is drawn
38
+ * many times. With no `data-sf-repeat` it is exactly `querySelector`.
39
+ */
40
+ declare function queryMainCopy<T extends Element = Element>(root: ParentNode, selector: string): T | null;
41
+ /**
42
+ * The element for `selector` in every drawn copy: the main copy first, then
43
+ * each other copy in page order. With no `data-sf-repeat` it is the first
44
+ * match alone, as `querySelector` answers — a second element with the same id
45
+ * that is not a drawn copy is never added.
46
+ */
47
+ declare function queryAllCopies<T extends Element = Element>(root: ParentNode, selector: string): T[];
48
+ /**
49
+ * THE WRAPPER ROUND ONE DRAWN COPY (card copy typing, 2026-10-30): the
50
+ * nearest element with BOTH names (a wrapper missing its index is passed
51
+ * over), its zone key as written (`"<ownerId>:<slot>"`) and its index — or
52
+ * null outside every copy, or where the index is not a whole number. The
53
+ * zone key is read by the data layer's one parser (`efficient-easel/data`
54
+ * `parseZoneKey`, in `easel/copy-place`): this file imports nothing, because
55
+ * `draw` reads it.
56
+ */
57
+ declare function repeatWrapperAt(el: Element): {
58
+ wrapper: Element;
59
+ zone: string;
60
+ index: number;
61
+ } | null;
62
+
63
+ /**
64
+ * The small facts every canvas surface needs, written once — pure DOM, no
65
+ * React, nothing run at import.
66
+ *
67
+ * Each answers a question both editors used to answer for themselves, and
68
+ * sometimes differently (the blueprint's §2 rows 6–10):
69
+ *
70
+ * isTypingTarget is this keystroke someone TYPING, so a shortcut must stay out?
71
+ * nodeIdAt which block did this pointer event land on?
72
+ * frameScale how much is the canvas frame scaled (a phone view drawn small)?
73
+ * frameRectToHost where is a box measured INSIDE the frame, on the host page?
74
+ * fitCanvas how wide is the canvas, and at what scale does it fit?
75
+ * canvas styles a stylesheet that lives in the canvas document ONLY.
76
+ */
77
+ /**
78
+ * Is the event's target somewhere a person TYPES — a text field, a textarea, a
79
+ * native select, an editable region? A shortcut (undo, delete, copy style) must
80
+ * not fire there: the key belongs to the field. Duck-typed rather than
81
+ * `instanceof`, because a canvas element belongs to the frame's own realm and
82
+ * is not an instance of the host's `HTMLElement`.
83
+ */
84
+ declare function isTypingTarget(target: EventTarget | null): boolean;
85
+ /** The attribute on every block the canvas draws. */
86
+ declare const BLOCK_ATTRIBUTE = "data-easel-block";
87
+
88
+ /**
89
+ * The id of the block a pointer event landed on: the NEAREST ancestor (or the
90
+ * target itself) carrying `attribute`. `null` on bare canvas.
91
+ */
92
+ declare function nodeIdAt(target: EventTarget | null, attribute?: string): string | null;
93
+ /**
94
+ * The box a block DREW, in its frame's viewport (8.2.0). Most blocks draw inside
95
+ * the canvas's wrapper, and the wrapper's own box is the answer. A block whose
96
+ * drawing leaves the wrapper's flow (the website's header bar is
97
+ * `position: fixed`) leaves the wrapper with no height; then the union of what
98
+ * it drew is the box: each child element with a box of its own, and inside a
99
+ * child with none (a host's own wrapper divs round the bar), what that child
100
+ * drew, the same way. Never null: a wrapper with nothing drawn is its own empty
101
+ * box.
102
+ */
103
+ declare function drawnBox(el: Element): Box;
104
+ /** Whether one element holds the other (or is it): the same words, however the host stamps them. */
105
+ declare function related(a: Element, b: Element): boolean;
106
+ /** How far the box round typed or selected words stands clear of them, in px
107
+ * (Lewis 2026-10-14). The one home: the canvas sets `--es-words-clear` from
108
+ * it (`.is-typing`, `.is-selected.is-words` read that, easel.css) and the
109
+ * drag layer measures the handle's corner from it. */
110
+ declare const WORDS_CLEAR = 4;
111
+ interface Box {
112
+ left: number;
113
+ top: number;
114
+ width: number;
115
+ height: number;
116
+ }
117
+ /**
118
+ * How much the frame is drawn scaled on the host page: its on-screen width over
119
+ * its layout width. The storefront draws tablet and phone at their real widths
120
+ * and scales the frame down to fit the pane; the campaign designer does not
121
+ * scale. 1 when the frame has no layout width yet.
122
+ */
123
+ declare function frameScale(frame: HTMLElement): number;
124
+ /**
125
+ * A box measured INSIDE the frame (an element's getBoundingClientRect in the
126
+ * frame's own viewport) as a box on the HOST page, relative to `origin` — the
127
+ * overlay layer the box will be drawn in — and through the frame's scale. The
128
+ * scale is the term both apps' own conversions left out on a scaled view.
129
+ */
130
+ declare function frameRectToHost(frame: HTMLElement, box: Box, origin?: Element | null): Box;
131
+ /**
132
+ * How much `el` is drawn scaled on screen: its on-screen width over its layout
133
+ * width. 1 when it is not scaled, or has no layout width yet. An overlay that
134
+ * shares a scaled ancestor with the frame (the storefront scales the whole
135
+ * device box, frame and overlay together) is scaled too, and its own CSS pixels
136
+ * are then that much smaller or larger than the screen's.
137
+ */
138
+ declare function elementScale(el: Element): number;
139
+ /**
140
+ * A box measured inside the frame as a box in `layer`'s OWN CSS coordinates —
141
+ * what an absolutely positioned child of `layer` is placed with. The same as
142
+ * `frameRectToHost` when the layer is not scaled; when it shares a scaled
143
+ * ancestor with the frame, every screen distance is divided back into the
144
+ * layer's pixels, or everything it draws lands the scale's factor away.
145
+ */
146
+ declare function frameRectToLayer(frame: HTMLElement, box: Box, layer: Element): Box;
147
+ /** A pointer position on the HOST page (clientX/clientY there) in `layer`'s own CSS coordinates. */
148
+ declare function clientPointToLayer(layer: Element, x: number, y: number): {
149
+ x: number;
150
+ y: number;
151
+ };
152
+ /** A pointer position inside the frame (clientX/clientY there) on the host page. */
153
+ declare function framePointToHost(frame: HTMLElement, x: number, y: number, origin?: Element | null): {
154
+ x: number;
155
+ y: number;
156
+ };
157
+ interface CanvasFit {
158
+ /** The canvas's layout width — the device's, or the pane's for a fluid view. */
159
+ width: number;
160
+ /** The scale that fits `width` into the pane, capped at `maxScale`. */
161
+ scale: number;
162
+ /** What the canvas occupies on screen: `width * scale`. */
163
+ shownWidth: number;
164
+ }
165
+ /**
166
+ * The canvas width and scale for a pane `available` px wide. A device with no
167
+ * width of its own (desktop, an email's inbox view) takes the pane's width at
168
+ * scale 1; a device width that does not fit is scaled down to fit, and one that
169
+ * does is scaled up no further than `maxScale` (the storefront allows 2; pass 1
170
+ * for "never enlarge").
171
+ */
172
+ declare function fitCanvas({ available, width, maxScale, }: {
173
+ available: number;
174
+ width?: number | null;
175
+ maxScale?: number;
176
+ }): CanvasFit;
177
+ /**
178
+ * Say which CSP nonce the stylesheets this module puts in `doc` must carry —
179
+ * for a host whose policy allows inline styles by nonce (`style-src
180
+ * 'nonce-…'`), where a `<style>` without it is refused. Every sheet
181
+ * `upsertCanvasStyle` / `useCanvasStyle` creates in `doc` afterwards wears it.
182
+ *
183
+ * `CanvasFrame` (`efficient-easel/easel`) calls this with its `nonce` prop as
184
+ * it builds the frame, so a host says it once; a host that builds its own
185
+ * frame calls it itself. `undefined` (or `''`) forgets it.
186
+ */
187
+ declare function setCanvasNonce(doc: Document, nonce: string | undefined): void;
188
+ /**
189
+ * Put `css` in the canvas document's `<head>` under `key`, replacing what that
190
+ * key held before. It lives in the canvas ONLY — not mirrored from the host
191
+ * head, so it cannot leak into the host page, and not dependent on anyone
192
+ * noticing a change to it. Writes nothing when the text is already current.
193
+ * A new sheet carries the document's nonce, when it was given one
194
+ * ({@link setCanvasNonce}).
195
+ */
196
+ declare function upsertCanvasStyle(doc: Document, key: string, css: string): HTMLStyleElement;
197
+ /** Take `key`'s stylesheet out of the canvas document, if it is there. */
198
+ declare function removeCanvasStyle(doc: Document, key: string): void;
199
+
200
+ export { BLOCK_ATTRIBUTE as B, type CanvasFit as C, REPEAT_ATTRIBUTE as R, WORDS_CLEAR as W, REPEAT_INDEX_ATTRIBUTE as a, type Box as b, clientPointToLayer as c, drawnBox as d, elementScale as e, fitCanvas as f, framePointToHost as g, frameRectToHost as h, frameRectToLayer as i, frameScale as j, isRepeatCopy as k, isTypingTarget as l, mainCopyOf as m, nodeIdAt as n, queryMainCopy as o, removeCanvasStyle as p, queryAllCopies as q, related as r, repeatWrapperAt as s, setCanvasNonce as t, upsertCanvasStyle as u };
@@ -1,9 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { HTMLAttributes, CSSProperties, ReactNode } from 'react';
3
3
  import { P as Place } from '../types-Dq180WRV.js';
4
- import { D as DragSession } from '../session-EsjORLQ0.js';
5
- export { a as DragHeld, c as createDragSession } from '../session-EsjORLQ0.js';
6
- export { B as Box, a as CanvasDragLayer, b as CanvasDragLayerProps, C as CanvasDragModel, c as CanvasDropTarget, d as CanvasSeam, e as CanvasSource, f as CanvasZone, E as EMPTY_MEASURE, H as HANDLE_GRACE_MS, M as MeasuredCanvas, g as MeasuredNode, h as canvasTarget, i as contains, j as gapInZone, k as gapLine, l as insertSeams, m as measureCanvas, p as pickSource, s as seamsBetween, n as sourcesAt } from '../CanvasDragLayer-NZpaX6aq.js';
4
+ import { D as DragSession } from '../CanvasDragLayer-DXeQGrwJ.js';
5
+ export { B as Box, a as CanvasDragLayer, b as CanvasDragLayerProps, C as CanvasDragModel, c as CanvasDropTarget, d as CanvasSeam, e as CanvasSource, f as CanvasZone, g as DragHeld, E as EMPTY_MEASURE, H as HANDLE_GRACE_MS, M as MeasuredCanvas, h as MeasuredNode, i as canvasTarget, j as contains, k as createDragSession, l as gapInZone, m as gapLine, n as insertSeams, o as measureCanvas, p as pickSource, s as seamsBetween, q as sourcesAt } from '../CanvasDragLayer-DXeQGrwJ.js';
7
6
  import { D as DragWords } from '../words-SD4neL0n.js';
8
7
  import '../watch-Unf6SfUf.js';
9
8
 
@@ -1 +1 @@
1
- export{AddPanel,CanvasDragLayer,CanvasInsert,EMPTY_MEASURE,HANDLE_GRACE_MS,canvasTarget,contains,gapInZone,gapLine,insertSeams,measureCanvas,pickSource,seamsBetween,sourcesAt}from"../chunk-LN43DP2A.js";import"../chunk-TQ5Y6S2X.js";export{AUTO_SCROLL_EDGE,AUTO_SCROLL_MAX_SPEED,autoScrollDelta,autoScrollSpeed,createDragSession,dropEffectFor,dropOnRow,frameScrollBox,gapToMoveIndex,isNoOpDrop,scrollBoxOf,scrollParent,startAutoScroll,stepGap,useTreeDrag}from"../chunk-6NJHYXJM.js";import"../chunk-P2VEMZH5.js";import"../chunk-YU3WHLGI.js";import"../chunk-LF3FUSCQ.js";import"../chunk-TGZCD6IP.js";import"../chunk-KXM6YRG2.js";
1
+ export{AddPanel,CanvasDragLayer,CanvasInsert,EMPTY_MEASURE,HANDLE_GRACE_MS,canvasTarget,contains,gapInZone,gapLine,insertSeams,measureCanvas,pickSource,seamsBetween,sourcesAt}from"../chunk-MBEUSPIN.js";import"../chunk-TQ5Y6S2X.js";export{AUTO_SCROLL_EDGE,AUTO_SCROLL_MAX_SPEED,autoScrollDelta,autoScrollSpeed,createDragSession,dropEffectFor,dropOnRow,frameScrollBox,gapToMoveIndex,isNoOpDrop,scrollBoxOf,scrollParent,startAutoScroll,stepGap,useTreeDrag}from"../chunk-3XECMGXX.js";import"../chunk-P2VEMZH5.js";import"../chunk-EUFDQ4KL.js";import"../chunk-KXM6YRG2.js";import"../chunk-TOKMFZPB.js";import"../chunk-FR7EEKFF.js";import"../chunk-7UFKEX72.js";
@@ -6,6 +6,6 @@ export { RowNameRule, rootRowName, sectionRenameProp, slotChildName, slotChildRe
6
6
  export { FOOTER_TYPE, HEADER_TYPE, NodeLocation, ROOT_ZONE, SlotConfigView, TREE_MAX_DEPTH, TreeItem, blockNodeActions, childSlotsOf, locateNode, missingPinnedTypes, nestsInTree, newNodeId, pinnedDuplicateIds, pinnedDuplicateIndices, slotChildrenIn, slotFieldsOf } from '../screen/lib/document/content-tree.js';
7
7
  export { AUTO_RULES, AutoRule, FOLLOW_RULES, VIEW_DESKTOP_MIN, VIEW_TABLET_MIN, VIEW_TIERS, VIEW_TIER_LABEL, ViewTier, ViewValueKind, ViewValues, isAuto, phoneKey, resolveViewValue, tabletKey, viewTierForWidth } from '../screen/lib/content/view-tiers.js';
8
8
  export { HERO_SLIDER_GOTO_EVENT, HeroSliderGotoDetail } from '../screen/lib/document/preview-events.js';
9
- export { R as REPEAT_ATTRIBUTE, a as REPEAT_INDEX_ATTRIBUTE } from '../repeat-D65bJS8d.js';
9
+ export { B as BLOCK_ATTRIBUTE, R as REPEAT_ATTRIBUTE, a as REPEAT_INDEX_ATTRIBUTE } from '../dom-BpxMcHFP.js';
10
10
  export { TEXT_STYLE_CASES, TEXT_STYLE_CASE_CSS, TextStyleCase, TextStyleCaseCss, isTextStyleCase, textStyleCaseCss, textStyleCaseOfCss } from '../text-styles/model.js';
11
11
  import '../words-B5Gb7KFq.js';
@@ -1 +1 @@
1
- export{SCALAR_TEXT_CONTROLS,TEXT_CONTROL_KINDS}from"../chunk-I6SK6ZDT.js";export{AUTO_RULES,FOLLOW_RULES,VIEW_DESKTOP_MIN,VIEW_TABLET_MIN,VIEW_TIERS,VIEW_TIER_LABEL,isAuto,phoneKey,resolveViewValue,tabletKey,viewTierForWidth}from"../chunk-E52W3M5I.js";export{rootRowName,sectionRenameProp,slotChildName,slotChildRenameProp,slotChildRowName}from"../chunk-5NHQ452C.js";export{hidableText,readHidable}from"../chunk-6UAEFOJ4.js";export{HERO_SLIDER_GOTO_EVENT}from"../chunk-AJ2SUSET.js";export{ANCHOR_FIELD_KEYS,FOLD_FIELD_MARKER,LINK_FIELD_MARKER,MEDIA_FIELD_MARKER,SCAFFOLD_FIELD_MARKER,TEXT_FIELD_MARKER,foldMetaOf,textFieldIdentifier}from"../chunk-ANPOR6EY.js";export{PART_SEP,decodePartId,encodePartId}from"../chunk-R4AUZVA3.js";export{FOOTER_TYPE,HEADER_TYPE,ROOT_ZONE,TREE_MAX_DEPTH,blockNodeActions,childSlotsOf,locateNode,missingPinnedTypes,nestsInTree,newNodeId,pinnedDuplicateIds,pinnedDuplicateIndices,slotChildrenIn,slotFieldsOf}from"../chunk-TCAAOYQY.js";export{TEXT_STYLE_CASES,TEXT_STYLE_CASE_CSS,isTextStyleCase,textStyleCaseCss,textStyleCaseOfCss}from"../chunk-YHZSBL2N.js";export{REPEAT_ATTRIBUTE,REPEAT_INDEX_ATTRIBUTE}from"../chunk-LF3FUSCQ.js";
1
+ export{SCALAR_TEXT_CONTROLS,TEXT_CONTROL_KINDS}from"../chunk-I6SK6ZDT.js";export{AUTO_RULES,FOLLOW_RULES,VIEW_DESKTOP_MIN,VIEW_TABLET_MIN,VIEW_TIERS,VIEW_TIER_LABEL,isAuto,phoneKey,resolveViewValue,tabletKey,viewTierForWidth}from"../chunk-E52W3M5I.js";export{HERO_SLIDER_GOTO_EVENT}from"../chunk-AJ2SUSET.js";export{rootRowName,sectionRenameProp,slotChildName,slotChildRenameProp,slotChildRowName}from"../chunk-5NHQ452C.js";export{hidableText,readHidable}from"../chunk-6UAEFOJ4.js";export{ANCHOR_FIELD_KEYS,FOLD_FIELD_MARKER,LINK_FIELD_MARKER,MEDIA_FIELD_MARKER,SCAFFOLD_FIELD_MARKER,TEXT_FIELD_MARKER,foldMetaOf,textFieldIdentifier}from"../chunk-ANPOR6EY.js";export{PART_SEP,decodePartId,encodePartId}from"../chunk-R4AUZVA3.js";export{FOOTER_TYPE,HEADER_TYPE,ROOT_ZONE,TREE_MAX_DEPTH,blockNodeActions,childSlotsOf,locateNode,missingPinnedTypes,nestsInTree,newNodeId,pinnedDuplicateIds,pinnedDuplicateIndices,slotChildrenIn,slotFieldsOf}from"../chunk-TCAAOYQY.js";export{TEXT_STYLE_CASES,TEXT_STYLE_CASE_CSS,isTextStyleCase,textStyleCaseCss,textStyleCaseOfCss}from"../chunk-YHZSBL2N.js";export{BLOCK_ATTRIBUTE}from"../chunk-FR7EEKFF.js";export{REPEAT_ATTRIBUTE,REPEAT_INDEX_ATTRIBUTE}from"../chunk-7UFKEX72.js";
@@ -8,6 +8,8 @@ import { c as ResolveTrigger, a as ResolveCache, R as ResolvableItem } from '../
8
8
  import * as react from 'react';
9
9
  import { ReactNode, CSSProperties, ComponentType, ReactElement } from 'react';
10
10
  import { TextToolbarProps } from 'efficient-shell/ui';
11
+ import { R as RepeatCopy } from '../copy-place-CQIemdtU.js';
12
+ export { r as repeatCopyAt } from '../copy-place-CQIemdtU.js';
11
13
  import { C as CopyBoxLastTool, a as CopyThemeColor, b as CanvasTypingEnd } from '../canvas-typing-DOBx8dD3.js';
12
14
  import { InlineRule } from 'efficient-shell/markup';
13
15
  import { b as NodeOpsHost, C as ClipboardEntry } from '../useNodeOps-igxrnEhq.js';
@@ -452,6 +454,13 @@ interface CanvasTypingTarget {
452
454
  skip?: string;
453
455
  /** The host's ONE writer for these words — the panel's text box's. */
454
456
  write: (next: string) => void;
457
+ /**
458
+ * The prop of the words' block these words are saved in (`text`). Given,
459
+ * words typed in a drawn copy of a card template go to the host's
460
+ * `CanvasTyping.onCopyText` with it; left out, they are written through
461
+ * `write` as before.
462
+ */
463
+ key?: string;
455
464
  /** Draw the words afresh (after an IME composition wrote into them). */
456
465
  remount?: () => void;
457
466
  /**
@@ -500,6 +509,58 @@ interface CanvasTyping {
500
509
  atSelection?: () => CanvasTypingTarget | null;
501
510
  /** Typing started or ended: the host's text toolbar comes to the words, its panel follows. */
502
511
  onChange?: (state: CanvasTypingState) => void;
512
+ /**
513
+ * Words typed in a DRAWN COPY of a card template (card copy typing,
514
+ * 2026-10-30). A copy is drawn for a row of its owner's data, so what it
515
+ * shows is the row's, not the template's: each write of the session (every
516
+ * edit step, an undo in the words included) comes here with the copy's
517
+ * place instead of going through the target's `write`. Asked only when the
518
+ * words are inside a `data-sf-repeat` wrapper (`repeatCopyAt`) — the
519
+ * main copy, row 0, too — and the target names its `key`. The session
520
+ * starts from `copyValue`'s answer; with none, from the template's string
521
+ * when the copy draws it, else from the words the copy SHOWS (plain: their
522
+ * marks are not known). Enter after a click on a copy types that copy.
523
+ *
524
+ * Return `false` to say these words are the template's own (the block is
525
+ * not bound to the row): easel writes them through the target's `write`,
526
+ * as before. Left out, typing in a copy is what it was.
527
+ */
528
+ onCopyText?: (edit: CanvasCopyText) => boolean | void;
529
+ /**
530
+ * The copy's words AS SAVED, in the string's grammar (`rules`): the row's
531
+ * value with its marks (bold, a link, a colour), for the session to start
532
+ * from, so the first key keeps them. Asked when a copy session starts.
533
+ * `undefined`, or left out: the session starts as `onCopyText` says.
534
+ * Return `undefined` wherever `onCopyText` would return `false` (words
535
+ * that are the template's own): a row's string there would start the
536
+ * session from words `write` does not hold.
537
+ */
538
+ copyValue?: (copy: CanvasCopyPlace) => string | undefined;
539
+ }
540
+ /** A drawn copy's words: the copy's place (`RepeatCopy`, its `outer` chain
541
+ * included), the template block the words are in and the prop they are
542
+ * saved in (`CanvasTypingTarget.key`). */
543
+ type CanvasCopyPlace = RepeatCopy & {
544
+ nodeId: string;
545
+ key: string;
546
+ };
547
+ /** Words typed in a drawn copy (`CanvasTyping.onCopyText`): where, and what. */
548
+ interface CanvasCopyText {
549
+ /** The block whose slot is drawn once per row. */
550
+ ownerId: string;
551
+ /** That slot (`template`). */
552
+ slot: string;
553
+ /** The row the copy is drawn for; 0 is the main copy. */
554
+ index: number;
555
+ /** The copy of an outer template this copy's owner is drawn in, when
556
+ * there is one (`RepeatCopy.outer`): the inner index alone names no row. */
557
+ outer?: RepeatCopy;
558
+ /** The template block the words are in. */
559
+ nodeId: string;
560
+ /** The prop the words are saved in (`CanvasTypingTarget.key`). */
561
+ key: string;
562
+ /** The words, in the string's grammar (`rules`), after this edit. */
563
+ text: string;
503
564
  }
504
565
  interface EaselCanvasProps {
505
566
  /** Draw in an iframe of the easel's own (default), or straight into the page. */
@@ -632,6 +693,11 @@ interface SelectedFields {
632
693
  * selected when the change began.
633
694
  */
634
695
  declare function useSelectedFields(): SelectedFields;
696
+ /** The selected node's host, or `null` outside an editor (a guard, a harness),
697
+ * where `useSelectedFields` throws: a row then reads nothing and writes
698
+ * nothing. The ONE place that catch is made. Called unconditionally, so hook
699
+ * order never changes. */
700
+ declare function useSelectedHost(): SelectedFields | null;
635
701
  /**
636
702
  * The ROOT's fields and write, whatever is selected: `useSelectedFields` with
637
703
  * nothing selected, for a panel that edits the page beside a selection — the
@@ -679,4 +745,4 @@ declare function bindUndoKeys(doc: Document, easel: Pick<Easel, 'undo' | 'redo'>
679
745
  leaveTyping?: boolean;
680
746
  }): () => void;
681
747
 
682
- export { CHROME_ATTR, type CanvasChrome, CanvasChromeContext, CanvasFrame, type CanvasFrameProps, type CanvasOverlay, type CanvasPart, type CanvasParts, CanvasPartsContext, CanvasPassThroughContext, CanvasSharedContext, type CanvasSlotProps, type CanvasTyping, type CanvasTypingState, type CanvasTypingTarget, 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, partHasOwnBox, recordsByDefault, reduce, uiView, useEasel, useEaselApi, useEaselNodeOps, useRootFields, useSelectedFields, validSelection };
748
+ export { CHROME_ATTR, type CanvasChrome, CanvasChromeContext, type CanvasCopyPlace, type CanvasCopyText, CanvasFrame, type CanvasFrameProps, type CanvasOverlay, type CanvasPart, type CanvasParts, CanvasPartsContext, CanvasPassThroughContext, CanvasSharedContext, type CanvasSlotProps, type CanvasTyping, type CanvasTypingState, type CanvasTypingTarget, DEFAULT_UI, type Easel, EaselAction, EaselCanvas, type EaselCanvasProps, EaselConfig, EaselCore, type EaselOptions, EaselProvider, type EaselProviderProps, EaselUi, EaselView, EditDocument, NodeSelection, Permissions, RepeatCopy, type SelectedFields, bindUndoKeys, createEasel, createUseEasel, partHasOwnBox, recordsByDefault, reduce, uiView, useEasel, useEaselApi, useEaselNodeOps, useRootFields, useSelectedFields, useSelectedHost, validSelection };
@@ -1 +1 @@
1
- export{CHROME_ATTR,CanvasChromeContext,CanvasFrame,CanvasPartsContext,CanvasPassThroughContext,CanvasSharedContext,DEFAULT_UI,EaselCanvas,EaselProvider,EditDocument,bindUndoKeys,createEasel,createUseEasel,partHasOwnBox,recordsByDefault,reduce,uiView,useEasel,useEaselApi,useEaselNodeOps,useRootFields,useSelectedFields,validSelection}from"../chunk-FJTCSQWK.js";import"../chunk-TBY3DFKS.js";import"../chunk-AGWKB77Q.js";import"../chunk-RPMSET77.js";import"../chunk-HZSZT3BE.js";import"../chunk-KRL3IUV3.js";import"../chunk-MFIRYGHW.js";import"../chunk-YU3WHLGI.js";import"../chunk-LF3FUSCQ.js";import"../chunk-TGZCD6IP.js";import"../chunk-KXM6YRG2.js";
1
+ export{CHROME_ATTR,CanvasChromeContext,CanvasFrame,CanvasPartsContext,CanvasPassThroughContext,CanvasSharedContext,DEFAULT_UI,EaselCanvas,EaselProvider,EditDocument,bindUndoKeys,createEasel,createUseEasel,partHasOwnBox,recordsByDefault,reduce,uiView,useEasel,useEaselApi,useEaselNodeOps,useRootFields,useSelectedFields,useSelectedHost,validSelection}from"../chunk-53ME3GAI.js";export{repeatCopyAt}from"../chunk-LUG22U5L.js";import"../chunk-TBY3DFKS.js";import"../chunk-K53PQAMP.js";import"../chunk-QTHQPJWA.js";import"../chunk-HZSZT3BE.js";import"../chunk-PMRN5Z3T.js";import"../chunk-MFIRYGHW.js";import"../chunk-EUFDQ4KL.js";import"../chunk-KXM6YRG2.js";import"../chunk-TOKMFZPB.js";import"../chunk-FR7EEKFF.js";import"../chunk-7UFKEX72.js";
@@ -20,52 +20,6 @@ type LockWords = {
20
20
  someone: string;
21
21
  };
22
22
 
23
- /**
24
- * Who may WRITE the open document, and what to tell the operator when they may
25
- * not — for EVERY editor in the house.
26
- *
27
- * This module is the merge of two files that had grown side by side and said
28
- * the same things in two places (card 11161):
29
- *
30
- * * `efficient-admin-portal/src/components/email/campaignEditLock.ts` — the
31
- * campaign designer's copy, which alone had the TAKE-OVER half;
32
- * * `efficient-storefront/lib/editor-page-lock.ts` — the storefront editor's copy,
33
- * which alone had the claim/heartbeat driver, the auth-rejection rule and
34
- * the "a template has no lock" rule.
35
- *
36
- * Neither was a subset of the other, so this file keeps EVERY capability of
37
- * both, and both editors now import it instead of carrying a copy.
38
- *
39
- * TWO INDEPENDENT PROTECTIONS, and it matters which is which:
40
- *
41
- * * the SAVE-TIME version check is what actually protects work. The backend
42
- * refuses a document write whose base version has moved on, so no amount of
43
- * client-side confusion can erase a colleague's edits. Nothing here can
44
- * weaken it — the token travels with the write and the server decides.
45
- * * this advisory LOCK only stops two people STARTING, and is soft on
46
- * purpose: it expires, it can always be taken over, and — see
47
- * `accessAfterClaimFailure` — it FAILS OPEN. A lock that could refuse to
48
- * let anybody edit would be worse than no lock at all. A save consults it
49
- * in one case only: a tab somebody TOOK the document from is refused
50
- * (`EDIT_LOCK_TAKEN`, read by `readLockTaken`), so it stops at its next
51
- * save instead of its next heartbeat.
52
- *
53
- * The backend rule is one rule shared by both (efficient/editlock.py). These
54
- * are two CONSUMERS of one server-side contract, which is exactly why one
55
- * client module can serve them.
56
- *
57
- * WHAT IS NOT SHARED, and why there is a factory rather than plain functions:
58
- * the two editors talk to the backend through different HTTP clients, so a
59
- * failure arrives as an `AxiosError` in one and as an `ApiError` in the other,
60
- * and the refused-save payload carries a different `code`. Those two facts —
61
- * and nothing else — are handed in by the consumer through `createEditLock`.
62
- * The module itself imports no HTTP client and knows about no repo.
63
- *
64
- * LEAF DISCIPLINE: pure TypeScript. No React, no `document`/`window`, no
65
- * dependency at all. `crypto` is touched inside a function, never at module
66
- * eval, so a Next server component may import this module safely.
67
- */
68
-
69
23
  /** The `edit-lock` body — identical for every editable document. */
70
24
  type EditLockBody = {
71
25
  held_by_you: boolean;
@@ -1 +1 @@
1
- export{CLAIM_FAIL_OPEN_MS,EDIT_LOCK_TAKEN,FAIL_OPEN_RETRY_MS,accessForNewDocument,canWriteDocument,createEditLock,describeAge,documentPermissions,documentVersionMoved,holderOf,nameOrSomeone,newEaselClientId,openUnansweredClaim,readLockHolder,reloadIfVersionMoved,shouldReleaseHeldWrite,shouldReloadAfterTakeover}from"../chunk-THWO5YVZ.js";import"../chunk-RE4LP46F.js";
1
+ export{CLAIM_FAIL_OPEN_MS,EDIT_LOCK_TAKEN,FAIL_OPEN_RETRY_MS,accessForNewDocument,canWriteDocument,createEditLock,describeAge,documentPermissions,documentVersionMoved,holderOf,nameOrSomeone,newEaselClientId,openUnansweredClaim,readLockHolder,reloadIfVersionMoved,shouldReleaseHeldWrite,shouldReloadAfterTakeover}from"../chunk-YYD257TD.js";import"../chunk-RE4LP46F.js";import"../chunk-EUFDQ4KL.js";import"../chunk-KXM6YRG2.js";
@@ -129,46 +129,12 @@ span:has(> .es-fnum) { display: flex; min-width: 0; }
129
129
  .es-list-row-main { flex: 1 1 auto; min-width: 0; min-height: 32px; display: flex; align-items: center; font-size: var(--text-xs, 12px); color: var(--es-text); }
130
130
  .es-list-row-main > * { flex: 1 1 auto; min-width: 0; }
131
131
 
132
- /* A many-field item: its name, its summary, a chevron that turns when open. */
133
- .es-list-open {
134
- flex: 1 1 auto;
135
- min-width: 0;
136
- display: flex;
137
- align-items: center;
138
- gap: 4px;
139
- height: 32px;
140
- padding: 0;
141
- border: 0;
142
- background: none;
143
- color: var(--es-text);
144
- font: inherit;
145
- font-size: var(--text-xs, 12px);
146
- text-align: left;
147
- cursor: inherit;
148
- }
149
- .es-list-open:focus-visible { outline: 2px solid var(--es-accent); outline-offset: 1px; border-radius: var(--radius-sm); }
150
- .es-list-thumb { flex: none; display: inline-flex; align-items: center; }
151
- .es-list-row-name { flex: none; font-weight: 600; white-space: nowrap; }
152
- .es-list-row-summary { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--es-muted); }
153
- .es-list-chev { flex: none; color: var(--es-icon); transition: transform 0.12s; }
154
- .es-list-row.is-open .es-list-chev { transform: rotate(90deg); }
155
-
156
- .es-list-x {
157
- flex: none;
158
- width: 28px;
159
- height: 32px;
160
- border: 0;
161
- border-radius: var(--es-row-radius);
162
- background: transparent;
163
- color: var(--es-muted);
164
- font: inherit;
165
- font-size: var(--text-base, 16px);
166
- line-height: 1;
167
- cursor: pointer;
168
- }
169
- .es-list-x:hover:not(:disabled) { background: var(--es-danger-tint); color: var(--es-danger); }
170
- .es-list-x:disabled { color: var(--es-disabled); cursor: default; }
171
- .es-list-x:focus-visible { outline: 2px solid var(--es-accent); outline-offset: 2px; }
132
+ /* A many-field item: the shell's fold button (`ListRow` `title` / `onToggle`)
133
+ holds its name and, after it, its summary — quiet beside the bold name. */
134
+ .es-list-row-name,
135
+ .es-list-row-summary { font-size: var(--text-xs, 12px); }
136
+ .es-list-row-summary { margin-left: var(--es-space-4, 4px); font-weight: 400; color: var(--es-muted); }
137
+
172
138
  .es-list-lock {
173
139
  flex: none;
174
140
  width: 28px;
@@ -433,11 +433,6 @@ declare function insertAt<T>(items: readonly T[], index: number, item: T): T[];
433
433
  declare function moveTo<T>(items: readonly T[], from: number, to: number): T[];
434
434
  /** The list without the item at `index`. */
435
435
  declare function removeAt<T>(items: readonly T[], index: number): T[];
436
- /**
437
- * A copy of an item: a deep copy of plain data, so editing the copy never
438
- * writes through to the original (a list item is JSON — the old library stored it so).
439
- */
440
- declare function copyItem<T>(item: T): T;
441
436
  declare function ListField<T>({ label, icon, noun: ownNoun, items, onChange, newItem, summary, itemName, thumb, min, max, readOnly, open, onOpen, renderItem, renderRow, canRemove: removable, lockedReason, fixed, removeOnRow, canDuplicate, above, head, }: ListFieldProps<T>): react.JSX.Element;
442
437
 
443
438
  /**
@@ -552,4 +547,4 @@ declare function optionFor<V>(options: readonly FieldOption<V>[], value: V | und
552
547
  declare function FieldSelect<V>(props: FieldSelectManyProps<V>): ReactNode;
553
548
  declare function FieldSelect<V>(props: FieldSelectOneProps<V>): ReactNode;
554
549
 
555
- export { FieldNumber, FieldNumberProps, FieldOption, type FieldPlan, FieldRenderer, FieldSelect, FieldSelectManyProps, FieldSelectOneProps, FieldSlider, FieldSliderProps, FieldText, FieldTextProps, type FieldsBlock, Inspector, InspectorField, InspectorFieldDef, type InspectorFieldProps, InspectorFields, type InspectorProps, ListField, type ListFieldProps, type PlannedFold, type PlannedRow, type ResolvedFieldsInput, SettingsBucket, type ShowWhenInput, bucketsOf, copyItem, fieldIsShown, fieldShownInView, insertAt, labelOf, moveTo, optionFor, planFields, removeAt, shownFields, useResolvedFields };
550
+ export { FieldNumber, FieldNumberProps, FieldOption, type FieldPlan, FieldRenderer, FieldSelect, FieldSelectManyProps, FieldSelectOneProps, FieldSlider, FieldSliderProps, FieldText, FieldTextProps, type FieldsBlock, Inspector, InspectorField, InspectorFieldDef, type InspectorFieldProps, InspectorFields, type InspectorProps, ListField, type ListFieldProps, type PlannedFold, type PlannedRow, type ResolvedFieldsInput, SettingsBucket, type ShowWhenInput, bucketsOf, fieldIsShown, fieldShownInView, insertAt, labelOf, moveTo, optionFor, planFields, removeAt, shownFields, useResolvedFields };
@@ -1 +1 @@
1
- export{FieldText,Inspector,InspectorField,LinkPicker,ListField,bucketsOf,copyItem,fieldShownInView,insertAt,labelOf,moveTo,planFields,removeAt}from"../chunk-H4Z2V56N.js";export{fieldIsShown,showWhenOf,shownFields,withShowWhen}from"../chunk-BPXEFVSQ.js";import"../chunk-YCSZAP7T.js";import"../chunk-WHS6Y55E.js";import"../chunk-K56YDHYQ.js";export{FieldSlider}from"../chunk-ZEH6QLBH.js";export{useResolvedFields}from"../chunk-AGWKB77Q.js";import"../chunk-FIVU2COQ.js";import"../chunk-FM6JO4WF.js";import"../chunk-KFV7N2DJ.js";export{FieldNumber}from"../chunk-Y2T3JX7K.js";export{FieldSelect,optionFor}from"../chunk-KFWNGPW3.js";import"../chunk-JACEEVVF.js";import"../chunk-CZAAGBVL.js";import"../chunk-MFIRYGHW.js";import"../chunk-6NJHYXJM.js";import"../chunk-P2VEMZH5.js";import"../chunk-TGZCD6IP.js";import"../chunk-KXM6YRG2.js";
1
+ export{FieldText,Inspector,InspectorField,LinkPicker,ListField,bucketsOf,fieldShownInView,insertAt,labelOf,moveTo,planFields,removeAt}from"../chunk-U3LTJSBU.js";export{fieldIsShown,showWhenOf,shownFields,withShowWhen}from"../chunk-BPXEFVSQ.js";import"../chunk-YCSZAP7T.js";export{FieldSlider}from"../chunk-ZEH6QLBH.js";import"../chunk-WHS6Y55E.js";import"../chunk-FM6JO4WF.js";import"../chunk-K56YDHYQ.js";import"../chunk-KFV7N2DJ.js";export{useResolvedFields}from"../chunk-K53PQAMP.js";import"../chunk-FIVU2COQ.js";export{FieldNumber}from"../chunk-Y2T3JX7K.js";export{FieldSelect,optionFor}from"../chunk-KFWNGPW3.js";import"../chunk-MIX7EGRG.js";import"../chunk-DX3VILZ3.js";import"../chunk-JACEEVVF.js";import"../chunk-MFIRYGHW.js";import"../chunk-3XECMGXX.js";import"../chunk-P2VEMZH5.js";import"../chunk-EUFDQ4KL.js";import"../chunk-KXM6YRG2.js";
@@ -1 +1 @@
1
- export{createNodeClipboard,nodeMenuItems}from"../chunk-P5VYGWYO.js";import"../chunk-WK3AFPXY.js";export{canStep,clickPick,clickPickPart,fromItemSelector,nextInChain,nodeChain,parentStep,planDuplicate,planMove,planPaste,planRemove,planRestore,planSetProps,planStep,planSwap,toItemSelector,useNodeOps}from"../chunk-RPMSET77.js";import"../chunk-TGZCD6IP.js";import"../chunk-KXM6YRG2.js";
1
+ export{createNodeClipboard,nodeMenuItems}from"../chunk-P5VYGWYO.js";import"../chunk-WK3AFPXY.js";export{canStep,clickPick,clickPickPart,fromItemSelector,nextInChain,nodeChain,parentStep,planDuplicate,planMove,planPaste,planRemove,planRestore,planSetProps,planStep,planSwap,toItemSelector,useNodeOps}from"../chunk-QTHQPJWA.js";import"../chunk-EUFDQ4KL.js";import"../chunk-KXM6YRG2.js";
@@ -0,0 +1,110 @@
1
+ import * as react from 'react';
2
+ export { D as DEFAULT_PAGE_COPY_WORDS, P as PageCopyWords } from '../words-Dl8Qja4K.js';
3
+
4
+ /** The longest title a page copy may take (the backend's title field). */
5
+ declare const DUPLICATE_TITLE_MAX = 255;
6
+ /** Whether a row may be copied: its kind is copyable and the host does not call
7
+ * it a system page (`isSystem`: the host's own list of system-page handles). */
8
+ declare function canDuplicatePage(page: {
9
+ kind?: string | null;
10
+ slug: string;
11
+ }, isSystem?: (slug: string) => boolean): boolean;
12
+ /** A URL handle as a person TYPES it: the shell's `slugify`, plus the one
13
+ * trailing hyphen a word break leaves, so "summer " shows "summer-" and the
14
+ * next word flows on. The value is always a backend-valid SlugField (spaces
15
+ * and caps would 400). A handle made from a whole title is `slugify` itself
16
+ * (no trailing hyphen). */
17
+ declare function slugWhileTyping(typed: string): string;
18
+ /** The first handle no listed page uses: `base`, else `base-2`, `base-3` … (the
19
+ * backend's per-site / per-template unique constraint). */
20
+ declare function firstFreeSlug(base: string, taken: Iterable<string>): string;
21
+ /** A handle no listed page uses, made from any name. */
22
+ declare function uniqueSlug(base: string, taken: Iterable<string>): string;
23
+ /** Whether another listed page already uses `slug`. */
24
+ declare function urlHandleTaken(slug: string, taken: Iterable<string>): boolean;
25
+ /** What the window starts with: "<title> copy" and the first free "<slug>-copy". */
26
+ declare function copySuggestion(page: {
27
+ title: string;
28
+ slug: string;
29
+ }, taken: Iterable<string>): {
30
+ title: string;
31
+ slug: string;
32
+ };
33
+
34
+ type PageCopyFormInput<Copy> = {
35
+ /** The page being copied. */
36
+ source: {
37
+ title: string;
38
+ slug: string;
39
+ };
40
+ /** Every URL handle the site's pages use. */
41
+ taken: Iterable<string>;
42
+ /** The backend's copy: ONE server call with the new title and handle. */
43
+ duplicate: (input: {
44
+ title: string;
45
+ slug: string;
46
+ }) => Promise<Copy>;
47
+ /** The copy exists: the caller reloads its list and opens it. */
48
+ onDuplicated: (copy: Copy) => void;
49
+ };
50
+ /**
51
+ * The state of the Duplicate page form, for a host that draws its own window
52
+ * around it (efficient-admin-portal's `PopupForm`, which adds the window's
53
+ * dirty mark and undo) and for `DuplicatePageDialog`.
54
+ *
55
+ * It starts on "<title> copy" and the first free "<slug>-copy". Editing the
56
+ * title moves the URL handle until the handle is edited by hand. A handle
57
+ * another page uses blocks the submit; the backend refuses it too. A failed
58
+ * call keeps what was typed so the person can retry.
59
+ */
60
+ declare function usePageCopyForm<Copy>({ source, taken, duplicate, onDuplicated }: PageCopyFormInput<Copy>): {
61
+ title: string;
62
+ slug: string;
63
+ clash: boolean;
64
+ canSubmit: boolean;
65
+ busy: boolean;
66
+ failed: boolean;
67
+ /** Something was typed: closing should ask first. */
68
+ touched: boolean;
69
+ titleMax: number;
70
+ onTitleChange: (value: string) => void;
71
+ onSlugChange: (value: string) => void;
72
+ submit: () => Promise<void>;
73
+ };
74
+ type PageCopyForm = ReturnType<typeof usePageCopyForm>;
75
+
76
+ /**
77
+ * The Duplicate page form's two fields, Title and URL handle (the page form's
78
+ * own names, so the console and the website editor say one thing). Every
79
+ * control is the shell's. Enter in either field submits.
80
+ *
81
+ * `host` is the site's domain the URL hint shows ("inovit.com/the-handle").
82
+ * The submit button is the window's: `DuplicatePageDialog` puts it in its
83
+ * footer, and a host with its own window does the same.
84
+ */
85
+ declare function DuplicatePageFields({ form, host }: {
86
+ form: PageCopyForm;
87
+ host: string;
88
+ }): react.JSX.Element;
89
+
90
+ type DuplicatePageDialogProps<Copy> = PageCopyFormInput<Copy> & {
91
+ /** The site's domain the URL hint shows. */
92
+ host: string;
93
+ onClose: () => void;
94
+ };
95
+ /**
96
+ * "Duplicate page" as a dialog (Lewis, 2026-10-21: design final via GG; one
97
+ * dialog for both products, B9 2026-10-28): the shell's `Dialog`, size sm, one
98
+ * primary button. Esc, the cross and a click outside close it, the shell asking
99
+ * "Discard changes?" first once something is typed (needs the host's
100
+ * `ConfirmProvider`, as every shell dialog that asks does). The copy is the backend's:
101
+ * this window never reads or writes a layout.
102
+ *
103
+ * It is drawn open: a host mounts it for the page being copied and unmounts it
104
+ * on `onClose` or `onDuplicated`. A host with a window system of its own
105
+ * (efficient-admin-portal's `PopupForm`) uses `usePageCopyForm` and
106
+ * `DuplicatePageFields` inside its own window instead.
107
+ */
108
+ declare function DuplicatePageDialog<Copy>({ host, onClose, ...input }: DuplicatePageDialogProps<Copy>): react.JSX.Element;
109
+
110
+ export { DUPLICATE_TITLE_MAX, DuplicatePageDialog, type DuplicatePageDialogProps, DuplicatePageFields, type PageCopyForm, type PageCopyFormInput, canDuplicatePage, copySuggestion, firstFreeSlug, slugWhileTyping, uniqueSlug, urlHandleTaken, usePageCopyForm };
@@ -0,0 +1,5 @@
1
+ import{DEFAULT_PAGE_COPY_WORDS as t}from"../chunk-CEC5XLN4.js";export{DEFAULT_PAGE_COPY_WORDS}from"../chunk-CEC5XLN4.js";import{useWords as e}from"../chunk-MFIRYGHW.js";import{slugify as n}from"efficient-shell/format";import{useState as o,useId as i}from"react";import{FormField as l,Input as r,Dialog as a,Button as u}from"efficient-shell/ui";import{jsxs as s,jsx as c}from"react/jsx-runtime";var h=255,f=/* @__PURE__ */new Set(["normal","dynamic"]);function m(t,e){return(!t.kind||f.has(t.kind))&&!e?.(t.slug)}function g(t){const e=n(t);return e&&/[^a-z0-9]$/.test(t.toLowerCase())?`${e}-`:e}function d(t,e){const n=new Set(e);if(!n.has(t))return t;let o=2;for(;n.has(`${t}-${o}`);)o+=1;return`${t}-${o}`}function p(t,e){return d(n(t)||"page",e)}function C(t,e){return new Set(e).has(t)}function y(t,e){return{title:`${t.title||t.slug} copy`,slug:p(`${t.slug}-copy`,e)}}function b({source:t,taken:e,duplicate:i,onDuplicated:l}){const[r]=o(()=>y(t,e)),[a,u]=o(r.title),[s,c]=o(r.slug),[h,f]=o(!1),[m,d]=o(!1),[p,b]=o(!1),[v,k]=o(!1),x=C(s,e),S=Boolean(a.trim()&&s&&!x&&!p);return{title:a,slug:s,clash:x,canSubmit:S,busy:p,failed:v,touched:m,titleMax:255,onTitleChange:t=>{d(!0),u(t),h||c(n(t))},onSlugChange:t=>{d(!0),f(!0),c(g(t))},submit:async()=>{if(S){b(!0),k(!1);try{l(await i({title:a.trim(),slug:s}))}catch{k(!0)}finally{b(!1)}}}}}function v({form:n,host:o}){const a=e("pageCopy",t),u=i(),h=i(),f=t=>{"Enter"===t.key&&n.submit()};/* @__PURE__ */
2
+ return s("div",{className:"flex flex-col gap-4",children:[
3
+ /* @__PURE__ */c(l,{label:a.titleLabel,htmlFor:u,error:n.failed?a.failed:void 0,hint:a.hint,children:/* @__PURE__ */c(r,{id:u,autoFocus:!0,value:n.title,maxLength:n.titleMax,onChange:t=>n.onTitleChange(t.target.value),onKeyDown:f})}),
4
+ /* @__PURE__ */c(l,{label:a.slugLabel,htmlFor:h,error:n.clash?a.clash:void 0,hint:a.address({host:o,slug:n.slug}),children:/* @__PURE__ */c(r,{id:h,value:n.slug,invalid:n.clash,onChange:t=>n.onSlugChange(t.target.value),onKeyDown:f})})]})}function k({host:n,onClose:o,...i}){const l=e("pageCopy",t),r=b(i),s=i.source.title||i.source.slug;/* @__PURE__ */
5
+ return c(a,{open:!0,dirty:r.touched,onClose:()=>{r.busy||o()},title:l.title({name:s}),size:"sm",footer:/* @__PURE__ */c(u,{disabled:!r.canSubmit,loading:r.busy,onClick:()=>{r.submit()},children:l.submit}),children:/* @__PURE__ */c(v,{form:r,host:n})})}export{h as DUPLICATE_TITLE_MAX,k as DuplicatePageDialog,v as DuplicatePageFields,m as canDuplicatePage,y as copySuggestion,d as firstFreeSlug,g as slugWhileTyping,p as uniqueSlug,C as urlHandleTaken,b as usePageCopyForm};