efficient-easel 0.0.0 → 0.96.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 (119) hide show
  1. package/LICENSE +10 -0
  2. package/README.md +723 -4
  3. package/dist/autosave/index.d.ts +101 -0
  4. package/dist/autosave/index.js +1 -0
  5. package/dist/blocks/index.d.ts +295 -0
  6. package/dist/blocks/index.js +0 -0
  7. package/dist/buckets-1vRlQceY.d.ts +13 -0
  8. package/dist/canvas/index.d.ts +194 -0
  9. package/dist/canvas/index.js +1 -0
  10. package/dist/chrome/chrome.css +570 -0
  11. package/dist/chrome/index.d.ts +219 -0
  12. package/dist/chrome/index.js +21 -0
  13. package/dist/chunk-2LUSK32B.js +1 -0
  14. package/dist/chunk-2OAZYSWJ.js +1 -0
  15. package/dist/chunk-67VZRQLG.js +13 -0
  16. package/dist/chunk-6JDII7N3.js +1 -0
  17. package/dist/chunk-6OPSJGAI.js +1 -0
  18. package/dist/chunk-6WY6ZXJG.js +1 -0
  19. package/dist/chunk-7A7O2JWQ.js +1 -0
  20. package/dist/chunk-BZ5ASXOL.js +1 -0
  21. package/dist/chunk-CERTFXCV.js +13 -0
  22. package/dist/chunk-EFTTSIMK.js +1 -0
  23. package/dist/chunk-FI23ALVJ.js +1 -0
  24. package/dist/chunk-FJE4IDBQ.js +1 -0
  25. package/dist/chunk-GGTPNGFJ.js +1 -0
  26. package/dist/chunk-H4B72XCJ.js +1 -0
  27. package/dist/chunk-H6DDIBQ4.js +1 -0
  28. package/dist/chunk-KDIVA6WD.js +1 -0
  29. package/dist/chunk-LPRQDDQO.js +1 -0
  30. package/dist/chunk-MCLBHENY.js +1 -0
  31. package/dist/chunk-MFIRYGHW.js +1 -0
  32. package/dist/chunk-NKB3KVAB.js +1 -0
  33. package/dist/chunk-O2NLQBFX.js +1 -0
  34. package/dist/chunk-OGZFDJ6G.js +1 -0
  35. package/dist/chunk-OWBO4WKA.js +0 -0
  36. package/dist/chunk-QB3BZIRA.js +5 -0
  37. package/dist/chunk-RE4LP46F.js +1 -0
  38. package/dist/chunk-STFMZJKU.js +14 -0
  39. package/dist/chunk-SUO5627P.js +1 -0
  40. package/dist/chunk-TDA4HH54.js +1 -0
  41. package/dist/chunk-WK3AFPXY.js +1 -0
  42. package/dist/chunk-YIMRTIBN.js +3 -0
  43. package/dist/chunk-YNZZYHA4.js +450 -0
  44. package/dist/chunk-ZAOM6PFH.js +1 -0
  45. package/dist/chunk-ZNCEOSXO.js +1 -0
  46. package/dist/copy/copy.css +282 -0
  47. package/dist/copy/index.d.ts +336 -0
  48. package/dist/copy/index.js +10 -0
  49. package/dist/data/index.d.ts +208 -0
  50. package/dist/data/index.js +1 -0
  51. package/dist/drag/drag.css +360 -0
  52. package/dist/drag/index.d.ts +379 -0
  53. package/dist/drag/index.js +18 -0
  54. package/dist/edit-lock/index.d.ts +354 -0
  55. package/dist/edit-lock/index.js +1 -0
  56. package/dist/editor/editor.css +58 -0
  57. package/dist/editor/index.d.ts +361 -0
  58. package/dist/editor/index.js +9 -0
  59. package/dist/fields/fields.css +207 -0
  60. package/dist/fields/index.d.ts +273 -0
  61. package/dist/fields/index.js +46 -0
  62. package/dist/gold/gold.css +196 -0
  63. package/dist/gold/index.d.ts +280 -0
  64. package/dist/gold/index.js +1 -0
  65. package/dist/icons/index.d.ts +474 -0
  66. package/dist/icons/index.js +1 -0
  67. package/dist/index.d.ts +6 -0
  68. package/dist/index.js +1 -0
  69. package/dist/markup/index.d.ts +452 -0
  70. package/dist/markup/index.js +1 -0
  71. package/dist/menu/index.d.ts +49 -0
  72. package/dist/menu/index.js +15 -0
  73. package/dist/menu/menu.css +254 -0
  74. package/dist/node-ops/index.d.ts +91 -0
  75. package/dist/node-ops/index.js +1 -0
  76. package/dist/panel/index.d.ts +1088 -0
  77. package/dist/panel/index.js +16 -0
  78. package/dist/panel/panel.css +1460 -0
  79. package/dist/pick/index.d.ts +153 -0
  80. package/dist/pick/index.js +14 -0
  81. package/dist/pick/pick.css +235 -0
  82. package/dist/plan-DtNw2VuI.d.ts +88 -0
  83. package/dist/rail/index.d.ts +145 -0
  84. package/dist/rail/index.js +1 -0
  85. package/dist/rail/rail.css +175 -0
  86. package/dist/render/index.d.ts +117 -0
  87. package/dist/render/index.js +5 -0
  88. package/dist/resolve-DpH8YQOA.d.ts +57 -0
  89. package/dist/shell/index.d.ts +159 -0
  90. package/dist/shell/index.js +1 -0
  91. package/dist/shell/shell.css +122 -0
  92. package/dist/strings/catalogue.d.ts +141 -0
  93. package/dist/strings/catalogue.js +1 -0
  94. package/dist/strings/index.d.ts +56 -0
  95. package/dist/strings/index.js +2 -0
  96. package/dist/style-clipboard/index.d.ts +184 -0
  97. package/dist/style-clipboard/index.js +1 -0
  98. package/dist/tree/index.d.ts +173 -0
  99. package/dist/tree/index.js +1 -0
  100. package/dist/tree/tree.css +618 -0
  101. package/dist/types-CEqUFH2j.d.ts +129 -0
  102. package/dist/types-CuNiSnX6.d.ts +204 -0
  103. package/dist/types-DNrn_w4V.d.ts +88 -0
  104. package/dist/types-DziKCwsN.d.ts +105 -0
  105. package/dist/types-_Vayx8ov.d.ts +153 -0
  106. package/dist/types-pGBXevBU.d.ts +131 -0
  107. package/dist/useNodeOps-6IAej9jy.d.ts +68 -0
  108. package/dist/watch-DmasQA61.d.ts +60 -0
  109. package/dist/wordFit-9B9nqyf3.d.ts +35 -0
  110. package/dist/words-B2YLaKEr.d.ts +30 -0
  111. package/dist/words-B87GIFWH.d.ts +86 -0
  112. package/dist/words-BK-npta-.d.ts +27 -0
  113. package/dist/words-CD3WADyb.d.ts +51 -0
  114. package/dist/words-CsRWXkY4.d.ts +129 -0
  115. package/dist/words-D9ChvbNC.d.ts +52 -0
  116. package/dist/words-DOhF-4D4.d.ts +35 -0
  117. package/dist/words-DagJdwIA.d.ts +78 -0
  118. package/dist/words-DfxEp_RH.d.ts +142 -0
  119. package/package.json +202 -3
@@ -0,0 +1 @@
1
+ var e={colour:{notSet:"Not set",swatch:({label:e,shown:a})=>`${e}: ${a}`,dialog:({label:e})=>`${e} colour`,theme:"Theme colours",themeSwatchTitle:({name:e,hex:a})=>`${e} · ${a}`,themeSwatch:({name:e,hex:a})=>`${e} ${a}`,square:"Saturation and brightness",squareValue:({saturation:e,brightness:a})=>`Saturation ${e}, brightness ${a}`,hue:"Hue",hex:({label:e})=>`${e} hex code`},image:{choosing:"Choosing…",failed:"The library could not be opened. Try again.",chooseImage:"Choose an image",chooseVideo:"Choose a video",empty:({label:e,kind:a,placeholder:o})=>`${e}: ${o??"choose "+("video"===a?"a video":"an image")}`,hint:"From the library, or upload one",named:({label:e,name:a})=>`${e}: ${a}`,replace:"Replace",replaceNamed:({label:e})=>`Replace ${e.toLowerCase()}`,remove:"Remove",removeNamed:({label:e})=>`Remove ${e.toLowerCase()}`}};export{e as DEFAULT_PICK_WORDS};
@@ -0,0 +1 @@
1
+ var t=/* @__PURE__ */new Set(["button","checkbox","color","file","hidden","image","radio","range","reset","submit"]);function e(e){const n=e;if(!n||"string"!=typeof n.tagName)return!1;const o=n.tagName.toUpperCase();if("TEXTAREA"===o||"SELECT"===o)return!0;if("INPUT"===o)return!t.has(String(n.type??"text").toLowerCase());if(n.isContentEditable)return!0;const i=n.closest?.("[contenteditable]");return!(!i||"false"===i.getAttribute("contenteditable"))||null!=n.closest?.('[role="textbox"]')}var n="data-puck-component";function o(t,e=n){const o=t;return o&&"function"==typeof o.closest?o.closest(`[${e}]`)?.getAttribute(e)??null:null}function i(t){const e=t.offsetWidth;if(!e)return 1;const n=t.getBoundingClientRect().width;return n>0?n/e:1}function r(t,e,n){const o=t.getBoundingClientRect(),r=n?.getBoundingClientRect()??{left:0,top:0},c=i(t);return{left:o.left-r.left+e.left*c,top:o.top-r.top+e.top*c,width:e.width*c,height:e.height*c}}function c(t){const e=t.offsetWidth;if(!e)return 1;const n=t.getBoundingClientRect().width;return n>0?n/e:1}function u(t,e,n){const o=r(t,e,n),i=c(n);return{left:o.left/i,top:o.top/i,width:o.width/i,height:o.height/i}}function l(t,e,n){const o=t.getBoundingClientRect(),i=c(t);return{x:(e-o.left)/i,y:(n-o.top)/i}}function s(t,e,n,o){const{left:i,top:c}=r(t,{left:e,top:n,width:0,height:0},o);return{x:i,y:c}}function f({available:t,width:e,maxScale:n=1}){const o=Math.max(0,t);if(!e||e<=0)return{width:o,scale:1,shownWidth:o};const i=o>0?Math.min(o/e,n):1;return{width:e,scale:i,shownWidth:e*i}}var a="data-es-canvas-style",h=/* @__PURE__ */new WeakMap;function d(t,e){e?h.set(t,e):h.delete(t)}function g(t,e,n){const o=t.head??t.documentElement;let i=w(t,e);if(!i){i=t.createElement("style"),i.setAttribute(a,e);const n=h.get(t);n&&i.setAttribute("nonce",n),o.appendChild(i)}return i.textContent!==n&&(i.textContent=n),i}function p(t,e){w(t,e)?.remove()}function w(t,e){for(const n of Array.from(t.querySelectorAll(`style[${a}]`)))if(n.getAttribute(a)===e)return n;return null}export{n as BLOCK_ATTRIBUTE,l as clientPointToLayer,c as elementScale,f as fitCanvas,s as framePointToHost,r as frameRectToHost,u as frameRectToLayer,i as frameScale,e as isTypingTarget,o as nodeIdAt,p as removeCanvasStyle,d as setCanvasNonce,g as upsertCanvasStyle};
@@ -0,0 +1,282 @@
1
+ /*
2
+ * efficient-easel — the copy control's own chrome.
3
+ *
4
+ * Only the BAR and the frame are dressed here. The box inside it is
5
+ * `efficient-easel/gold.css` and the direction strip is the panel grammar's own
6
+ * chip (`efficient-easel/panel.css`) — this sheet must never restate either, or
7
+ * there are two places a chip can change.
8
+ *
9
+ * Declared on `.es-copy`, never on `:root`, for the reason gold.css gives: a
10
+ * sheet imported by an editor must not leak a variable into the page being
11
+ * edited.
12
+ *
13
+ * Import once from a client entry / global stylesheet:
14
+ * import 'efficient-easel/copy.css';
15
+ */
16
+
17
+ .es-copy {
18
+ --es-copy-gap: 4px;
19
+ /* efficient-shell's IconButton at its default size, md (design v5, 0.94.0). */
20
+ --es-copy-mark-size: 32px;
21
+ --es-copy-mark-ink: var(--es-text-muted, #6b7280);
22
+ --es-copy-mark-ink-hover: var(--es-text, #111827);
23
+ --es-copy-mark-hover: var(--es-hover, rgba(0, 0, 0, 0.06));
24
+ --es-copy-rule: var(--es-border, #e5e7eb);
25
+ --es-copy-field-border: var(--es-field-border, #d1d5db);
26
+ --es-copy-field-bg: var(--es-field-bg, #ffffff);
27
+ /** The field's inner inset. On the SURFACES, never on the box — see
28
+ * `.es-copy-box` below for the caret bug that rule exists to stop. */
29
+ --es-copy-field-pad: 4px 6px;
30
+ --es-copy-field-ring: var(--es-accent, #2563eb);
31
+ --es-copy-radius: 4px;
32
+
33
+ display: block;
34
+ /* A container, so a host can query the box's own width. Never on
35
+ `.es-copy-bar`: that would contain the very box whose children measure. */
36
+ container-type: inline-size;
37
+ }
38
+
39
+ /* — the bar — */
40
+
41
+ .es-copy-bar {
42
+ display: flex;
43
+ flex-wrap: wrap;
44
+ align-items: center;
45
+ gap: 2px;
46
+ margin-bottom: var(--es-copy-gap);
47
+ }
48
+
49
+ /*
50
+ * The direction sits on ITS OWN LINE, under the marks (0.94.0, design v5:
51
+ * "下面一行是对齐"). Until 0.93 it sat at the far end of the marks' row; seven
52
+ * 32px buttons leave no room beside them at any panel width, and a strip
53
+ * squeezed into the remainder is a strip drawn broken. The marks act on the
54
+ * SELECTION and this on the whole text, so a line of its own also says they
55
+ * are different kinds of thing.
56
+ *
57
+ * ⚠️ THE FLEX VALUES ARE NOT TASTE. THE STRIP IS 0px WIDE WITHOUT THEM.
58
+ *
59
+ * `.es-segs-fit` is `container-type: inline-size` + `flex: none` (panel.css).
60
+ * In a flex parent `flex: none` means "size to your own content", and size
61
+ * containment means there IS no content width to size to. Both declarations
62
+ * are right where they were written and they collide in any flex slot — which
63
+ * this is. `panel.css` carries the full account under card 66516 and the same
64
+ * exception for `.es-field-ctl`; it is not restated here, only obeyed.
65
+ *
66
+ * So the wrapper takes a basis from the bar, and hands one to the fit wrapper
67
+ * inside it. `min-width: 0` on both because a flex item's automatic minimum is
68
+ * its content size, which containment makes meaningless.
69
+ */
70
+ .es-copy-bar-end {
71
+ display: flex;
72
+ align-items: center;
73
+ flex: 1 1 100%;
74
+ min-width: 0;
75
+ }
76
+
77
+ /* Not the whole line (design v14: the Segmented "does not fill a row"). A
78
+ FIXED basis, never `auto`: this wrapper is a size container, so `auto` is
79
+ its content width, which containment makes 0. */
80
+ .es-copy-bar-end > .es-segs-fit {
81
+ flex: 0 1 240px;
82
+ min-width: 0;
83
+ }
84
+
85
+ .es-copy-bar-split {
86
+ width: 1px;
87
+ align-self: stretch;
88
+ margin: 2px 4px;
89
+ background: var(--es-copy-rule);
90
+ }
91
+
92
+ .es-copy-mark {
93
+ height: var(--es-copy-mark-size);
94
+ min-width: var(--es-copy-mark-size);
95
+ padding: 0 5px;
96
+ border: 0;
97
+ border-radius: 6px;
98
+ background: transparent;
99
+ color: var(--es-copy-mark-ink);
100
+ font: inherit;
101
+ font-size: 11px;
102
+ line-height: 1;
103
+ cursor: pointer;
104
+ }
105
+
106
+ .es-copy-mark:hover:not(:disabled) {
107
+ background: var(--es-copy-mark-hover);
108
+ color: var(--es-copy-mark-ink-hover);
109
+ }
110
+
111
+ .es-copy-mark:disabled {
112
+ opacity: 0.6;
113
+ cursor: default;
114
+ }
115
+
116
+ /* A button carrying a drawing — the set's glyph (0.94.0) or the host's own —
117
+ centres it and gets none of the letter styling below: an SVG must not be
118
+ emboldened or slanted by us. */
119
+ .es-copy-mark.has-glyph {
120
+ display: inline-flex;
121
+ align-items: center;
122
+ justify-content: center;
123
+ }
124
+
125
+ /* The glyph looks like what it does. Safe here and NOT in the gold layer: a
126
+ button is not sitting over a caret, so a wider glyph moves nothing. */
127
+ /* PRESSED: the selection already wears this mark, so a press takes it off —
128
+ or, for the link button, its window is open. efficient-shell has no pressed
129
+ IconButton, so the design (v5) takes its PopupMenu's selected item:
130
+ bg-blue-50, text-blue-700. */
131
+ /* `:hover` too: the hover rule above is (0,3,0) and outranks a bare
132
+ `.is-active`, so a pressed button turned grey under the very pointer that
133
+ pressed it (seen in the 0.94.0 click-through). */
134
+ .es-copy-mark.is-active,
135
+ .es-copy-mark.is-active:hover:not(:disabled) {
136
+ background: var(--es-accent-tint, #eff6ff);
137
+ color: var(--es-accent-text, #1d4ed8);
138
+ }
139
+
140
+ .es-copy-mark.is-bold { font-weight: 700; }
141
+ .es-copy-mark.is-italic { font-style: italic; }
142
+ .es-copy-mark.is-strike { text-decoration: line-through; }
143
+
144
+ /* — the frame around the box — */
145
+
146
+ /*
147
+ * The box is `GoldTextInput`, which draws no border of its own: it inherits its
148
+ * type from whatever it is rendered inside, and a border here is what makes it
149
+ * read as a field. The focus ring is on the WRAPPER, because the textarea
150
+ * itself is transparent and its outline is suppressed by gold.css.
151
+ */
152
+ /*
153
+ * ⚠️ THE FIELD'S INSET IS HANDED TO GOLD, NOT APPLIED HERE — and that is the
154
+ * whole of a caret bug reported from production on 2026-09-26: "the caret is
155
+ * not at the right position".
156
+ *
157
+ * `.es-gold-input` is `position: absolute; inset: 0`, and for an absolutely
158
+ * positioned child `inset: 0` resolves to the containing block's PADDING box.
159
+ * The layer underneath is IN FLOW, so it starts at the CONTENT box. So
160
+ * `padding` here — on the WRAPPER — moved the painted glyphs and left the
161
+ * caret behind: measured in the campaign designer, the input sat at x=1222
162
+ * w=232 and the layer at x=1228 w=220, every character 6px right of its caret
163
+ * and 4px down.
164
+ *
165
+ * The inset is a GOLD metric, so gold.css declares it on all three surfaces
166
+ * at once (`--es-gold-pad`) — the rule it already states about glyph-moving
167
+ * properties. This sheet only says what the value is, which is the boundary
168
+ * this file's header sets: the frame is dressed here, the box never is.
169
+ */
170
+ /*
171
+ * ⚠️ `.es-gold` TOO, AND THAT IS NOT DECORATION. gold.css defaults
172
+ * `--es-gold-pad: 0` on `.es-gold`, and the element carries BOTH classes —
173
+ * so at equal specificity (0,1,0 each) the winner is whichever sheet is
174
+ * loaded last. In the campaign designer gold.css comes second, the default
175
+ * won, and 0.65.0 shipped the field with NO inner inset: the text sat against
176
+ * its own border. Measured live, `--es-gold-pad` computed to `0` and the
177
+ * surfaces had `padding: 0px`.
178
+ *
179
+ * Two classes is (0,2,0) and beats the default whatever the order, which is
180
+ * the only version of this that does not depend on how a host bundles CSS.
181
+ */
182
+ .es-gold.es-copy-box {
183
+ --es-gold-pad: var(--es-copy-field-pad);
184
+ }
185
+
186
+ .es-copy-box {
187
+
188
+ border: 1px solid var(--es-copy-field-border);
189
+ border-radius: var(--es-copy-radius);
190
+ background: var(--es-copy-field-bg);
191
+ font-size: 12px;
192
+ }
193
+
194
+ .es-copy-box:focus-within {
195
+ border-color: var(--es-copy-field-ring);
196
+ }
197
+
198
+ .es-copy-box.es-gold-is-off {
199
+ background: var(--es-field-bg-off, #f9fafb);
200
+ }
201
+
202
+ /* — the link button's window (blueprint 4.9) — */
203
+
204
+ /*
205
+ * Under the box, in its flow: a settings panel scrolls, and a floating window
206
+ * is clipped by the edge it scrolls under (the colour window's rule). Drawn on
207
+ * the panel's own raised surface, so it reads as part of this field.
208
+ */
209
+ .es-copy-link {
210
+ display: flex;
211
+ flex-direction: column;
212
+ gap: 8px;
213
+ margin-top: var(--es-copy-gap);
214
+ padding: 12px;
215
+ border: 1px solid var(--es-copy-rule);
216
+ border-radius: var(--es-row-radius, 8px);
217
+ background: var(--es-raised, var(--es-copy-field-bg));
218
+ box-shadow: var(--es-raised-shadow, 0 4px 12px rgba(0, 0, 0, 0.08));
219
+ }
220
+
221
+ .es-copy-link-label {
222
+ font-size: 11px;
223
+ color: var(--es-copy-mark-ink);
224
+ }
225
+
226
+ .es-copy-link-input {
227
+ box-sizing: border-box;
228
+ width: 100%;
229
+ height: 32px;
230
+ padding: 0 10px;
231
+ border: 1px solid var(--es-copy-field-border);
232
+ border-radius: var(--es-field-radius, 6px);
233
+ background: var(--es-copy-field-bg);
234
+ color: var(--es-text, #111827);
235
+ font: inherit;
236
+ font-size: 12px;
237
+ }
238
+
239
+ .es-copy-link-input:focus {
240
+ outline: none;
241
+ border-color: var(--es-copy-field-ring);
242
+ }
243
+
244
+ .es-copy-link-input[aria-invalid='true'] {
245
+ border-color: var(--es-danger, #dc2626);
246
+ }
247
+
248
+ .es-copy-link-why {
249
+ margin: 0;
250
+ font-size: 11px;
251
+ line-height: 1.45;
252
+ color: var(--es-danger, #dc2626);
253
+ }
254
+
255
+ .es-copy-link-actions {
256
+ display: flex;
257
+ justify-content: flex-end;
258
+ gap: 8px;
259
+ }
260
+
261
+ .es-copy-link-btn {
262
+ height: 28px;
263
+ padding: 0 10px;
264
+ border: 1px solid var(--es-btn-border, var(--es-copy-field-border));
265
+ border-radius: var(--es-field-radius, 6px);
266
+ background: var(--es-raised, #fff);
267
+ color: var(--es-btn-text, var(--es-text, #111827));
268
+ font: inherit;
269
+ font-size: 12px;
270
+ cursor: pointer;
271
+ }
272
+
273
+ .es-copy-link-btn.is-primary {
274
+ border-color: var(--es-accent, #2563eb);
275
+ background: var(--es-accent, #2563eb);
276
+ color: var(--es-on-accent, #fff);
277
+ }
278
+
279
+ .es-copy-link-btn:disabled {
280
+ opacity: 0.5;
281
+ cursor: default;
282
+ }
@@ -0,0 +1,336 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { MarkStyle, MarkupTool, WrapResult, InlineRule } from '../markup/index.js';
4
+ import { a as CopyAlign } from '../words-DfxEp_RH.js';
5
+ export { b as COPY_ALIGNS, c as COPY_ALIGN_FIT, d as COPY_ALIGN_GLYPHS, e as COPY_ALIGN_LABELS, C as CopyWords, f as copyAlignOptions } from '../words-DfxEp_RH.js';
6
+
7
+ /** `'b'` → `Meta+B`, `'shift+x'` → `Meta+Shift+X` — the ARIA spelling of a
8
+ * shortcut. Meta is what the tooltips promise (⌘); Ctrl works too. */
9
+ declare function ariaShortcut(shortcut: string | undefined): string | undefined;
10
+ interface MarkBarProps {
11
+ /**
12
+ * Which buttons to show.
13
+ *
14
+ * TWO FORMS, and the second is not a convenience — it is the only way a
15
+ * product with marks of its OWN can use this bar at all.
16
+ *
17
+ * `MarkStyle[]` resolved against the shared grammar's `MARKUP_TOOLS`, in
18
+ * that order. What the campaign designer passes.
19
+ * `MarkupTool[]` used verbatim. What `efficient-shop` passes.
20
+ *
21
+ * ⚠️ WHY VERBATIM DESCRIPTORS HAD TO EXIST. The storefront offered SEVEN
22
+ * marks: the grammar's four, plus its three word styles (`@@`, `++`, `^^`),
23
+ * which then lived only in the shop because the grammar was
24
+ * `efficient-shell/markup`. Resolving through `markupTools` can only ever
25
+ * return marks the grammar knows, so it silently DROPPED all three — the bar
26
+ * rendered four buttons where the panel had seven, and nothing failed to
27
+ * compile. Since 2026-09-28 the grammar is ours and holds Quiet (`^^`) and
28
+ * reads `@@` / `++` as Emphasis and Strong, but a host may still hand in
29
+ * marks of its own.
30
+ *
31
+ * So a host that has its own marks builds its own descriptors (the shop
32
+ * already did: `shopMarkupTools()`) and hands them over whole. The bar's job
33
+ * is the ROW — the buttons, the focus guard, the naming, the group — and that
34
+ * job does not depend on who defined the mark.
35
+ */
36
+ tools: readonly MarkStyle[] | readonly MarkupTool[];
37
+ onApply: (style: MarkStyle) => void;
38
+ disabled?: boolean;
39
+ /** Buttons that write no mark, after a divider — see `CopyBoxProps.actions`. */
40
+ actions?: ReactNode;
41
+ /** The alignment strip, already built by the control. */
42
+ align?: ReactNode;
43
+ /**
44
+ * WHAT A BUTTON IS DRAWN AS, per style. Default: the grammar's own letter —
45
+ * `B`, `I`, `S`, `◆` — which is what the campaign designer has always shown.
46
+ *
47
+ * ⚠️ THIS IS NOT A THEME KNOB, AND IT IS NOT OPTIONAL POLISH. This package
48
+ * already says that WHICH buttons and WHAT they write are the grammar's, while
49
+ * how they LOOK is the host's, "because this bar has to belong to the
50
+ * storefront inspector's chrome and the campaign designer's bar has to belong
51
+ * to the portal's". Colour was delegated by tokens on day one; the DRAWING was
52
+ * not, and a letter is a drawing. `efficient-shop` renders each mark as a 14px
53
+ * stroked SVG at the weight of its other inspector glyphs, on the stated
54
+ * ground that the row should read "as part of the field rather than as
55
+ * pasted-in icons" — so without this it could not adopt the bar without losing
56
+ * that, which is a regression dressed as a refactor.
57
+ *
58
+ * The set now HAS a glyph for every mark (0.94.0: bold, italic, strike,
59
+ * emphasis, quiet, clear-marks — the owner's call, made 2026-10-02), and they
60
+ * are the default. This map stays so a host can still draw its own.
61
+ *
62
+ * A style the map does not name falls back to the icon set's glyph for it
63
+ * (`SET_GLYPH`, 0.94.0, when the owner added the mark glyphs to the set) and
64
+ * then to the letter, so a host may override one button and leave the rest.
65
+ */
66
+ glyphs?: Partial<Record<MarkStyle, ReactNode>>;
67
+ /**
68
+ * What the group is CALLED. Defaults to "Formatting" (`EditorStrings.copy.formatting`).
69
+ *
70
+ * A bar bound to one box needs nothing more. A bar SHARED by several rows has
71
+ * to say which row it is aimed at, because the visible cue — the marked row —
72
+ * is not readable. `efficient-shop` has that case today.
73
+ */
74
+ ariaLabel?: string;
75
+ /**
76
+ * The marks the selection already wears — a button drawn pressed is one
77
+ * whose press takes the mark OFF (`markedAt`, the same test `applyMark`
78
+ * unwraps by), so the two cannot disagree. A style not named is not pressed;
79
+ * with no map at all no button says either way (`aria-pressed` is left off),
80
+ * which is what every host that does not track the selection still gets.
81
+ */
82
+ pressed?: Partial<Record<MarkStyle, boolean>>;
83
+ /** The style whose own window is open — the link button while its "Link
84
+ * to" window is showing. Drawn pressed, and named as expanded. */
85
+ open?: MarkStyle | null;
86
+ }
87
+ declare function MarkBar({ tools, onApply, disabled, actions, align, glyphs, ariaLabel, pressed, open, }: MarkBarProps): react.JSX.Element | null;
88
+
89
+ /**
90
+ * Just enough of a text control to read a selection off it. Narrowed to these
91
+ * two fields so a caller may hand in a plain object in a test.
92
+ *
93
+ * A `<textarea>` OR an `<input>` (blueprint 4.9). An input's selection is
94
+ * `number | null` where a textarea's is `number`, so the type had to be the
95
+ * wider of the two: typed to the textarea, every one-line row in the website
96
+ * editor needed a cast or a copy of this file, and the shop kept a copy.
97
+ */
98
+ type SelectionSource = {
99
+ readonly selectionStart: number | null;
100
+ readonly selectionEnd: number | null;
101
+ } | null;
102
+ /**
103
+ * How a mark is written. Defaults to the shared grammar's own `applyMark`.
104
+ *
105
+ * INJECTED RATHER THAN ASSUMED, because the storefront legitimately writes marks
106
+ * the shell's grammar does not know: its three word styles (`@@`, `++`, `^^`)
107
+ * go through `applyShopMark`, which delegates here for everything else. Making
108
+ * the writer a parameter is what lets one control serve both products without
109
+ * this package learning a vocabulary that belongs to one of them.
110
+ */
111
+ type MarkWriter = (text: string, start: number, end: number, style: MarkStyle) => WrapResult;
112
+ /** Apply a style to whatever the field has selected. */
113
+ declare function markSelection(value: string, style: MarkStyle, el: SelectionSource, write?: MarkWriter): WrapResult;
114
+ /**
115
+ * Put a literal string where the caret is.
116
+ *
117
+ * The caret lands AFTER the inserted text and selects nothing, because the thing
118
+ * inserted is finished — a merge tag is not a placeholder to type over, and
119
+ * leaving it selected invites the next keystroke to delete it.
120
+ */
121
+ declare function insertSelection(value: string, text: string, el: SelectionSource): WrapResult;
122
+
123
+ /**
124
+ * A handle on the box's textarea.
125
+ *
126
+ * DECLARED STRUCTURALLY rather than as React's `RefObject`, and the reason is
127
+ * the package's `react >=18` peer range. React 18 types read `RefObject<T>` as
128
+ * `{ readonly current: T | null }`, React 19's `useRef<T>(null)` produces
129
+ * `RefObject<T | null>`, and the interface is covariant in `T` — so the two
130
+ * spellings of the same object do not assign to each other, and a package that
131
+ * has to compile against both cannot name either one. The shape is the whole
132
+ * contract; naming it here costs one line and no version branch.
133
+ */
134
+ type TextAreaHandle = {
135
+ current: HTMLTextAreaElement | null;
136
+ };
137
+ /**
138
+ * A handle on ANY text control the caret machinery can drive — a `<textarea>`
139
+ * or a one-line `<input>` (blueprint 4.9). `useCaretEdit` takes this, so a host
140
+ * with its own input rows (the website editor's list lines) uses the same
141
+ * twelve lines instead of a copy of them. A {@link TextAreaHandle} is one.
142
+ */
143
+ type TextBoxHandle = {
144
+ current: HTMLInputElement | HTMLTextAreaElement | null;
145
+ };
146
+ /**
147
+ * What a host may put UNDER the box, and what it needs to draw it.
148
+ *
149
+ * A render prop rather than a plain node, for one reason that is not style: the
150
+ * campaign's merge-tag chips have to insert at the caret, and only this control
151
+ * knows where the caret is. Handing the host `insert` is what lets the chips
152
+ * live in the app — where the tag list, the audience call and the product's
153
+ * vocabulary belong — while the caret machinery stays here, written once.
154
+ *
155
+ * `focused` is passed for the same reason the portal tracked it locally: the row
156
+ * appears while the merchant is typing in THIS box and not in its neighbour.
157
+ */
158
+ interface CopyBoxFooter {
159
+ focused: boolean;
160
+ /** Put a literal string where the caret is, replacing any selection. */
161
+ insert: (text: string) => void;
162
+ }
163
+ /**
164
+ * The alignment strip, when the host has an alignment to offer.
165
+ *
166
+ * GENERIC IN THE DIRECTIONS IT ALLOWS, so `onChange` RECEIVES only what the
167
+ * host said it could honour. Before this it received the whole union, and a
168
+ * host allowing three directions had to narrow a `justify` it had just
169
+ * declared impossible before storing. Raised by the strip's first consumer,
170
+ * which did write that narrowing; the next one might not.
171
+ *
172
+ * `A` is inferred from `allow`, so nothing has to be spelled at the call site.
173
+ *
174
+ * ⚠️ IT DOES NOT FORBID A WIDER HANDLER, and cannot: a function taking the
175
+ * whole `CopyAlign` is assignable where one taking three is wanted, because
176
+ * parameters are contravariant — and that direction is SAFE, since only the
177
+ * three can ever arrive. What this buys is that the narrow handler now
178
+ * type-checks without a cast, which is what makes writing the safe one the
179
+ * path of least resistance.
180
+ */
181
+ interface CopyBoxAlign<A extends CopyAlign = CopyAlign> {
182
+ value: A | '';
183
+ onChange: (next: A) => void;
184
+ /**
185
+ * Which directions this host can honour. Required and with no default: a
186
+ * renderer that cannot justify must not be given a Justify button because a
187
+ * default handed it one, which is the shape of mistake this whole card exists
188
+ * to stop. It is also what `A` above is inferred from.
189
+ */
190
+ allow: readonly A[];
191
+ /** The strip exists but cannot apply right now. Distinct from the whole
192
+ * control being read-only. */
193
+ disabled?: boolean;
194
+ }
195
+ interface CopyBoxProps {
196
+ /** The authored string, delimiters and all. The single source of truth. */
197
+ value: string;
198
+ /** Every keystroke, and every button press. */
199
+ onChange: (value: string) => void;
200
+ /** The edit is finished and may be written: on blur, and on Enter when
201
+ * {@link multiline} is false. */
202
+ onCommit?: (value: string) => void;
203
+ /** Escape. The argument is the value the box was given when it gained focus;
204
+ * the HOST puts it back, because the host owns the string. */
205
+ onCancel?: (restored: string) => void;
206
+ /** What Enter means — NOT how the text wraps. Both modes soft-wrap. */
207
+ multiline?: boolean;
208
+ placeholder?: string;
209
+ /** Accessible name for the box. */
210
+ ariaLabel?: string;
211
+ className?: string;
212
+ /** Nothing may be typed or pressed. */
213
+ readOnly?: boolean;
214
+ /**
215
+ * The grammar's rule set — `CAMPAIGN_MARKUP` in the designer,
216
+ * `STOREFRONT_MARKUP` in the shop, both from `efficient-easel/markup`. It
217
+ * decides what the LAYER paints; {@link tools} decides what the BAR offers,
218
+ * and a host that lets those two disagree gets a button whose mark draws as
219
+ * plain text.
220
+ */
221
+ rules?: readonly InlineRule[];
222
+ /**
223
+ * Which mark buttons to show — styles resolved against the shared grammar, or
224
+ * the host's own descriptors used verbatim. See `MarkBarProps.tools`. Empty
225
+ * draws no bar.
226
+ */
227
+ tools?: MarkBarProps['tools'];
228
+ /**
229
+ * How a mark is written. Defaults to the shared grammar's `applyMark`; the
230
+ * storefront passes its own so its three word styles reach the same bar.
231
+ */
232
+ writeMark?: MarkWriter;
233
+ /**
234
+ * Buttons that are NOT marks, drawn after a divider at the end of the bar.
235
+ *
236
+ * The storefront's "Clear formatting" is the one that exists: it writes no
237
+ * mark at all, so it is not in the grammar's tool list and must not be added
238
+ * to it. Drawing it here keeps `MARKUP_TOOLS` meaning "what a mark MEANS".
239
+ */
240
+ actions?: ReactNode;
241
+ /** The alignment strip. Omitted = no strip. */
242
+ align?: CopyBoxAlign<CopyAlign>;
243
+ /** What each mark button is DRAWN as — see `MarkBarProps.glyphs`. Default is
244
+ * the grammar's own letter. */
245
+ glyphs?: MarkBarProps['glyphs'];
246
+ /** What the formatting group is CALLED — see `MarkBarProps.ariaLabel`. */
247
+ barLabel?: string;
248
+ /** What goes under the box — see {@link CopyBoxFooter}. */
249
+ renderFooter?: (ctx: CopyBoxFooter) => ReactNode;
250
+ /**
251
+ * When the bar is drawn. `'always'` (the default) keeps it over the box at
252
+ * rest. `'focus'` draws it only while this box is being typed in — the text
253
+ * box design for 4.9: a column of text settings at rest is a column of
254
+ * words, not a column of toolbars.
255
+ */
256
+ barShows?: 'always' | 'focus';
257
+ /** A handle on the textarea, for a host that needs one — and the SAME object
258
+ * the control uses, so there is never a second ref to reconcile. A plain prop
259
+ * rather than `ref`, for the reason `GoldLayer.layerRef` gives. */
260
+ inputRef?: TextAreaHandle;
261
+ }
262
+
263
+ declare function CopyBox({ value, onChange, onCommit, onCancel, multiline, placeholder, ariaLabel, className, readOnly, rules, tools, writeMark, actions, align, glyphs, barLabel, renderFooter, inputRef, barShows, }: CopyBoxProps): react.JSX.Element;
264
+
265
+ interface PasteData {
266
+ /** The clipboard's `text/html`, when there is one. */
267
+ html?: string;
268
+ /** The clipboard's `text/plain`. */
269
+ text?: string;
270
+ }
271
+ interface PasteOptions {
272
+ /** The document the box lives in — the HTML is read in an inert template
273
+ * of it. */
274
+ doc: Document;
275
+ /** A one-line box joins every line with a space. */
276
+ multiline: boolean;
277
+ /** The host's rules, which the result must read back under. */
278
+ rules?: readonly InlineRule[];
279
+ }
280
+ /** Turn a clipboard's contents into the grammar, filtered. */
281
+ declare function pasteToMarkup(data: PasteData, opts: PasteOptions): string;
282
+
283
+ /**
284
+ * THE BOX'S OWN UNDO (0.94.0) — what ⌘Z and ⇧⌘Z step through while a copy
285
+ * box has the caret.
286
+ *
287
+ * A browser's own text undo cannot do it: it forgets everything the moment a
288
+ * value is set from script, and every mark, link, clear and paste is set from
289
+ * script. And the editor's undo (`efficient-easel/editor` `bindUndoKeys`)
290
+ * steps the whole document, a keystroke at a time. So the box keeps the steps
291
+ * IT made, with the selection each one started from, and answers ⌘Z itself —
292
+ * the key never reaches the editor while the caret is in the box, so one key
293
+ * is never answered twice.
294
+ *
295
+ * Typing is one step per burst: keystrokes less than a second apart are one
296
+ * step, as every word processor groups them. A press of a mark is always its
297
+ * own step.
298
+ *
299
+ * Plain functions, no React, so a spec can pin it without a box.
300
+ */
301
+ /** A value and where its selection was. */
302
+ interface Snapshot {
303
+ text: string;
304
+ start: number;
305
+ end: number;
306
+ }
307
+ interface History {
308
+ /** Remember `before`, the state about to be replaced. `type` coalesces with
309
+ * the previous `type` step when it came less than {@link TYPING_BURST_MS}
310
+ * after it. Any new step forgets what could be redone. */
311
+ record(before: Snapshot, kind: 'type' | 'edit', now: number): void;
312
+ /** The state to go back to, or null. `current` becomes redoable. */
313
+ undo(current: Snapshot): Snapshot | null;
314
+ /** The state to go forward to, or null. `current` becomes undoable. */
315
+ redo(current: Snapshot): Snapshot | null;
316
+ /** Forget everything — the host put a different value in the box. */
317
+ reset(): void;
318
+ }
319
+ /** Keystrokes closer together than this are one undo step. */
320
+ declare const TYPING_BURST_MS = 1000;
321
+ declare function createHistory(limit?: number): History;
322
+
323
+ interface CaretEdit {
324
+ /** The field being edited — a textarea or an input. Its live selection IS
325
+ * the argument, so nothing here holds state that could fall out of step with
326
+ * the box. */
327
+ target: TextBoxHandle;
328
+ onChange: (next: string) => void;
329
+ }
330
+ /**
331
+ * Returns a runner: hand it a function that produces the next value and where
332
+ * the caret goes, and it writes both.
333
+ */
334
+ declare function useCaretEdit({ target, onChange }: CaretEdit): (edit: (el: HTMLInputElement | HTMLTextAreaElement | null) => WrapResult) => void;
335
+
336
+ export { type CaretEdit, CopyAlign, CopyBox, type CopyBoxAlign, type CopyBoxFooter, type CopyBoxProps, type History, MarkBar, type MarkBarProps, type MarkWriter, type PasteData, type PasteOptions, type SelectionSource, type Snapshot, TYPING_BURST_MS, type TextAreaHandle, type TextBoxHandle, ariaShortcut, createHistory, insertSelection, markSelection, pasteToMarkup, useCaretEdit };
@@ -0,0 +1,10 @@
1
+ import{DEFAULT_COPY_WORDS as e}from"../chunk-2LUSK32B.js";import{SettingsSegmented as t}from"../chunk-QB3BZIRA.js";import{GoldTextInput as n}from"../chunk-CERTFXCV.js";import{applyMark as l,STANDARD_MARKUP as r,writeMarkedLines as o,sameMarks as i,markedLines as a,spanFor as s,markupTools as c,findLink as u,markedAt as d,removeLink as p,writeLink as h,isSafeLinkHref as f}from"../chunk-6OPSJGAI.js";import{useWords as m}from"../chunk-MFIRYGHW.js";import"../chunk-BZ5ASXOL.js";import{AlignSeparateIcon as y,AlignRightIcon as b,AlignCenterIcon as g,AlignLeftIcon as k,ClearMarksIcon as v,LinkIcon as x,QuietIcon as C,EmphasisIcon as w,StrikeIcon as S,ItalicIcon as N,BoldIcon as D}from"../chunk-YNZZYHA4.js";import{useRef as j,useLayoutEffect as E,useState as L,useEffect as K,useId as P}from"react";import{jsx as A,jsxs as T,Fragment as z}from"react/jsx-runtime";var B=["left","center","right","justify"],R=e.align,$={left:/* @__PURE__ */A(k,{}),center:/* @__PURE__ */A(g,{}),right:/* @__PURE__ */A(b,{}),justify:/* @__PURE__ */A(y,{})},M={three:{labelsPx:94.5,widestLabelPx:39.94},four:{labelsPx:140,widestLabelPx:44}};function F(e,t=R){return B.filter(t=>e.includes(t)).map(e=>({id:e,label:t[e],icon:$[e],title:t[e]}))}var Z=/[\u1100-\u11ff\u2e80-\u9fff\uac00-\ud7af\uf900-\ufaff\uff00-\uffef]/;function I(e){if(e.every(e=>e.label===R[e.id]))return e.length>3?M.four:M.three;const t=e.map(e=>[...e.label].reduce((e,t)=>e+(Z.test(t)?13:8),0));return{labelsPx:t.reduce((e,t)=>e+t,0),widestLabelPx:Math.max(0,...t)}}function Y(e,t){const n=t?.selectionStart??e.length;return{start:n,end:t?.selectionEnd??n}}function q(e,t,n,r=l){const{start:o,end:i}=Y(e,n);return r(e,o,i,t)}function H(e,t,n){const{start:l,end:r}=Y(e,n),o=l+t.length;return{text:e.slice(0,l)+t+e.slice(r),selectionStart:o,selectionEnd:o}}var O=1e3;function U(e=200){let t=[],n=[],l=null,r=0;return{record(o,i,a){const s="type"===i&&"type"===l&&a-r<1e3;l=i,r=a,n=[],s||(t.push(o),t.length>e&&t.shift())},undo(e){const r=t.pop();return r?(n.push(e),l=null,r):null},redo(e){const r=n.pop();return r?(t.push(e),l=null,r):null},reset(){t=[],n=[],l=null}}}function G({href:t,editing:n,onApply:l,onRemove:r,onClose:o}){const i=m("copy",e).link,a=P(),[s,c]=L(t),[u,d]=L(!1),p=j(null);K(()=>{p.current?.focus(),p.current?.select()},[]);const h=()=>{const e=s.trim();f(e)?l(e):d(!0)};/* @__PURE__ */
2
+ return T("div",{role:"dialog","aria-label":i.title,className:"es-copy-link",onKeyDown:e=>{"Enter"===e.key?(e.preventDefault(),h()):"Escape"===e.key&&(e.preventDefault(),e.stopPropagation(),o())},children:[
3
+ /* @__PURE__ */A("label",{htmlFor:a,className:"es-copy-link-label",children:i.label}),
4
+ /* @__PURE__ */A("input",{ref:p,id:a,type:"text",className:"es-copy-link-input",value:s,placeholder:i.placeholder,spellCheck:!1,autoComplete:"off","aria-invalid":u||void 0,"aria-describedby":u?`${a}-why`:void 0,onChange:e=>{c(e.target.value),d(!1)}}),u?/* @__PURE__ */A("p",{id:`${a}-why`,role:"alert",className:"es-copy-link-why",children:i.refused}):null,
5
+ /* @__PURE__ */T("div",{className:"es-copy-link-actions",children:[n?/* @__PURE__ */A("button",{type:"button",className:"es-copy-link-btn",onClick:r,children:i.remove}):null,
6
+ /* @__PURE__ */A("button",{type:"button",className:"es-copy-link-btn is-primary",onClick:h,disabled:""===s.trim(),children:i.apply})]})]})}var W=/* @__PURE__ */new Set(["script","style","head","title","meta","link","template","noscript","svg","math","object","iframe"]),X=/* @__PURE__ */new Set(["p","div","li","ul","ol","dl","dt","dd","h1","h2","h3","h4","h5","h6","blockquote","pre","table","thead","tbody","tfoot","tr","section","article","header","footer","aside","nav","figure","figcaption","address","hr"]);function J(e,t){const n=t.rules??r,l=e.html&&""!==e.html.trim()?function(e,t,n){const l=t.createElement("template");l.innerHTML=e;const r=[[]],s=()=>r[r.length-1],c=()=>{const e=s();for(;e.length>0&&" "===e[e.length-1].ch;)e.pop();e.length>0&&r.push([])},u=(e,t,n,l)=>{if(3===e.nodeType){for(const r of e.textContent??""){const e=/\s/.test(r)?" ":r,o=s();(" "!==e||0!==o.length&&" "!==o[o.length-1].ch)&&o.push({ch:e,bold:t,italic:n,href:l})}return}if(1!==e.nodeType&&11!==e.nodeType)return;const o=e,i=1===e.nodeType?o.localName:"";if(W.has(i))return;if("br"===i)return void r.push([]);let a=t,d=n,p=l;if("b"!==i&&"strong"!==i||(a=!0),"i"!==i&&"em"!==i||(d=!0),"a"===i){const e=(o.getAttribute("href")??"").trim();p=f(e)?e:null}const h=1===e.nodeType?o.style:null;if(h){const e=h.fontWeight;"bold"===e||"bolder"===e||Number(e)>=600?a=!0:("normal"===e||"lighter"===e||""!==e&&Number(e)<600)&&(a=!1),"italic"===h.fontStyle||"oblique"===h.fontStyle?d=!0:"normal"===h.fontStyle&&(d=!1)}const m=X.has(i);m&&c();for(const t of[...e.childNodes])u(t,a,d,p);m&&c()};u(l.content,!1,!1,null);for(;r.length>1&&0===r[r.length-1].length;)r.pop();return r.map(e=>function(e,t){const n=t=>e.map((n,l)=>n[t]&&(" "!==n.ch||Boolean(e[l-1]?.[t])&&Boolean(e[l+1]?.[t]))),l=[{bold:!0,italic:!0,links:!0},{bold:!0,italic:!1,links:!0},{bold:!1,italic:!1,links:!0},{bold:!1,italic:!1,links:!1}];for(const r of l){const l=Q(e,r.bold?n("bold"):null,r.italic?n("italic"):null,r.links),s=o([l]).text;if(i([l],a(s,t)))return s}return e.map(e=>e.ch).join("")}(e,n))}(e.html,t.doc,n):(e.text??"").replace(/\r\n?/g,"\n").split("\n");return t.multiline?l.join("\n"):l.map(e=>e.trim()).filter(e=>""!==e).join(" ")}function Q(e,t,n,l){let r=0,o=null,i=null,a=null;return{bullet:!1,chars:e.map((e,c)=>{const u=l?e.href:null;return o=null===u?null:o&&o.href===u?o:s(++r,"link",u),i=t?.[c]?i??s(++r,"bold"):null,a=n?.[c]?a??s(++r,"italic"):null,{ch:e.ch,at:-1,marks:[o,i,a].filter(e=>null!==e)}})}}var V={bold:/* @__PURE__ */A(D,{size:16}),italic:/* @__PURE__ */A(N,{size:16}),strike:/* @__PURE__ */A(S,{size:16}),highlight:/* @__PURE__ */A(w,{size:16}),quiet:/* @__PURE__ */A(C,{size:16}),link:/* @__PURE__ */A(x,{size:16}),clear:/* @__PURE__ */A(v,{size:16})};function _(e){if(!e)return;const t=e.split("+"),n=t.pop().toUpperCase();return["Meta",...t.map(e=>e[0].toUpperCase()+e.slice(1)),n].join("+")}var ee={bold:"is-bold",italic:"is-italic",strike:"is-strike"};function te({tools:t,onApply:n,disabled:l=!1,actions:r,align:o,glyphs:i,ariaLabel:a,pressed:s,open:u=null}){const d=m("copy",e),p="string"==typeof t[0]?c(t).map(e=>({...e,...d.marks[e.style]})):t;return 0!==p.length||r||o?/* @__PURE__ */T("div",{className:"es-copy-bar",role:"group","aria-label":a??d.formatting,children:[p.map(e=>{const t=i?.[e.style]??V[e.style],r=u===e.style||Boolean(s?.[e.style]);/* @__PURE__ */
7
+ return A("button",{type:"button",onMouseDown:e=>e.preventDefault(),onClick:()=>n(e.style),disabled:l,title:e.title,"aria-label":e.name??e.title,"aria-keyshortcuts":_(e.shortcut),"aria-pressed":s||u?r:void 0,"aria-haspopup":"link"===e.style&&s?"dialog":void 0,"aria-expanded":"link"===e.style&&s?"link"===u:void 0,className:`es-copy-mark${t?" has-glyph":ee[e.style]?` ${ee[e.style]}`:""}${r?" is-active":""}`,children:t??e.label},e.style)}),r?/* @__PURE__ */T(z,{children:[
8
+ /* @__PURE__ */A("span",{className:"es-copy-bar-split","aria-hidden":"true"}),r]}):null,o?/* @__PURE__ */A("span",{className:"es-copy-bar-end",children:o}):null]}):null}function ne({target:e,onChange:t}){const n=j(null);return E(()=>{const t=n.current,l=e.current;t&&l&&l.value===t.text&&(n.current=null,l.focus(),l.setSelectionRange(t.start,t.end))}),l=>{const r=l(e.current);t(r.text),n.current={start:r.selectionStart,end:r.selectionEnd,text:r.text}}}function le({value:o,onChange:i,onCommit:a,onCancel:s,multiline:f=!1,placeholder:y,ariaLabel:b,className:g,readOnly:k=!1,rules:v,tools:x=[],writeMark:C,actions:w,align:S,glyphs:N,barLabel:D,renderFooter:E,inputRef:P,barShows:z="always"}){const B=m("copy",e),R=j(null),$=P??R,[M,Z]=L(!1),[Y,O]=L(null),W=j(U()).current,X=j(null);K(()=>{const e=X.current;e&&o===e.to?X.current=null:e&&o===e.from||(X.current=null,W.reset())},[o,W]);const Q=e=>{X.current={from:X.current?.from??o,to:e},i(e)},V=e=>({text:o,start:e?.selectionStart??Y?.start??o.length,end:e?.selectionEnd??Y?.end??o.length}),_=ne({target:$,onChange:Q}),ee=e=>(O({start:e.selectionStart,end:e.selectionEnd}),e),le=e=>_(t=>(W.record(V(t),"edit",Date.now()),ee(e(t)))),[re,oe]=L(null),ie=v??r,ae="string"==typeof x[0]?c(x):x,se=ae.map(e=>e.style),ce={};if(Y)for(const e of se)ce[e]="link"===e?null!==u(o,Y.start,Y.end):d(o,Y.start,Y.end,e);const ue=e=>{if("clear"===e)return void le(t=>q(o,e,t,(e,t,n)=>l(e,t,n,"clear",ie)));if("link"!==e){const t=C??((e,t,n,o)=>l(e,t,n,o,r,B.placeholders));return void le(n=>q(o,e,n,t))}const t=$.current,n=t?.selectionStart??o.length,i=t?.selectionEnd??n;oe({start:n,end:i,link:u(o,n,i)})},de=S?F(S.allow,B.align):[];/* @__PURE__ */
9
+ return T("div",{className:"es-copy"+(g?` ${g}`:""),onFocus:()=>Z(!0),onBlur:e=>{e.currentTarget.contains(e.relatedTarget)||Z(!1)},onKeyDown:e=>{if(k||e.target!==$.current)return;if(e.altKey||e.metaKey===e.ctrlKey)return;const t=e.key.toLowerCase(),n="KeyZ"===e.code||"z"===t,l=("KeyY"===e.code||"y"===t)&&!e.shiftKey;if(n||l)return e.preventDefault(),e.stopPropagation(),r=l||e.shiftKey?"redo":"undo",void _(e=>{const t=V(e),n=W[r](t);return ee(n?{text:n.text,selectionStart:n.start,selectionEnd:n.end}:{text:t.text,selectionStart:t.start,selectionEnd:t.end})});var r;const o=`${e.shiftKey?"shift+":""}${t}`,i=ae.find(e=>e.shortcut===o);i&&(e.preventDefault(),e.stopPropagation(),ue(i.style))},onPaste:e=>{const t=$.current;if(k||!t||e.target!==t)return;e.preventDefault();const n=J({html:e.clipboardData.getData("text/html"),text:e.clipboardData.getData("text/plain")},{doc:t.ownerDocument,multiline:f,rules:ie});le(e=>H(o,n,e))},onSelect:()=>{const e=$.current;e&&e.ownerDocument.activeElement===e&&O({start:e.selectionStart??o.length,end:e.selectionEnd??o.length})},children:["always"===z||M||null!==re?/* @__PURE__ */A(te,{tools:x,disabled:k,actions:w,glyphs:N,ariaLabel:D,pressed:ce,open:re?"link":null,onApply:ue,align:S?/* @__PURE__ */A(t,{options:de,value:S.value,onChange:S.onChange,ariaLabel:B.alignment,disabled:k||S.disabled,fit:I(de)}):null}):null,
10
+ /* @__PURE__ */A(n,{value:o,onInput:e=>{W.record(V(null),"type",Date.now()),Q(e)},onCommit:a,onCancel:s,multiline:f,placeholder:y,ariaLabel:b,rules:v,disabled:k,inputRef:$,className:"es-copy-box"}),re?/* @__PURE__ */A(G,{href:re.link?.href??"",editing:null!==re.link,onApply:e=>{const t=re;oe(null),le(()=>h(o,t.start,t.end,e))},onRemove:()=>{const e=re.link;oe(null),e&&le(()=>p(o,e))},onClose:()=>{oe(null),$.current?.focus()}}):null,E?E({focused:M,insert:e=>le(t=>H(o,e,t))}):null]})}export{B as COPY_ALIGNS,M as COPY_ALIGN_FIT,$ as COPY_ALIGN_GLYPHS,R as COPY_ALIGN_LABELS,le as CopyBox,te as MarkBar,O as TYPING_BURST_MS,_ as ariaShortcut,F as copyAlignOptions,U as createHistory,H as insertSelection,q as markSelection,J as pasteToMarkup,ne as useCaretEdit};