efficient-easel 0.99.1 → 0.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +281 -36
  2. package/dist/autosave/index.d.ts +12 -6
  3. package/dist/blocks/index.d.ts +2 -2
  4. package/dist/canvas/index.d.ts +3 -3
  5. package/dist/canvas/index.js +1 -1
  6. package/dist/chrome/chrome.css +96 -29
  7. package/dist/chrome/index.d.ts +72 -19
  8. package/dist/chrome/index.js +17 -21
  9. package/dist/chunk-3EDSDPIP.js +1 -0
  10. package/dist/chunk-67GNT6PF.js +1 -0
  11. package/dist/{chunk-PI26YXYD.js → chunk-6EIVTKZZ.js} +21 -14
  12. package/dist/chunk-A5UK76FC.js +6 -0
  13. package/dist/chunk-AZT7V4UM.js +10 -0
  14. package/dist/chunk-DICUMYMG.js +12 -0
  15. package/dist/chunk-HDVD27VB.js +1 -0
  16. package/dist/chunk-JQM3V7XQ.js +1 -0
  17. package/dist/{chunk-TDA4HH54.js → chunk-JUQ6YJCG.js} +1 -1
  18. package/dist/chunk-LA54QLP5.js +1 -0
  19. package/dist/chunk-LGKQXSKO.js +16 -0
  20. package/dist/chunk-M6F6LS3H.js +1 -0
  21. package/dist/chunk-MH5GUZFQ.js +9 -0
  22. package/dist/chunk-P6O363EE.js +6 -0
  23. package/dist/chunk-PAELDYYS.js +1 -0
  24. package/dist/{chunk-JYPOOHII.js → chunk-QKVR3K73.js} +3 -3
  25. package/dist/chunk-SAJUCHOX.js +1 -0
  26. package/dist/chunk-TRVZ7C23.js +1 -0
  27. package/dist/chunk-V7GIIXFB.js +16 -0
  28. package/dist/{chunk-IBOFSS56.js → chunk-WZ7TGAAT.js} +1 -1
  29. package/dist/chunk-ZJEBF4NB.js +1 -0
  30. package/dist/copy/copy.css +388 -5
  31. package/dist/copy/index.d.ts +271 -8
  32. package/dist/copy/index.js +31 -10
  33. package/dist/data/index.d.ts +4 -4
  34. package/dist/drag/drag.css +10 -1
  35. package/dist/drag/index.d.ts +102 -11
  36. package/dist/drag/index.js +18 -17
  37. package/dist/{editor/editor.css → easel/easel.css} +16 -1
  38. package/dist/{editor → easel}/index.d.ts +146 -51
  39. package/dist/easel/index.js +9 -0
  40. package/dist/edit-lock/index.d.ts +3 -3
  41. package/dist/edit-lock/index.js +1 -1
  42. package/dist/fields/fields.css +103 -26
  43. package/dist/fields/index.d.ts +51 -13
  44. package/dist/fields/index.js +30 -46
  45. package/dist/gold/index.js +1 -1
  46. package/dist/icons/index.d.ts +10 -1
  47. package/dist/icons/index.js +1 -1
  48. package/dist/index.d.ts +3 -3
  49. package/dist/index.js +1 -1
  50. package/dist/markup/index.d.ts +94 -10
  51. package/dist/markup/index.js +1 -1
  52. package/dist/menu/index.d.ts +61 -6
  53. package/dist/menu/index.js +15 -15
  54. package/dist/menu/menu.css +17 -6
  55. package/dist/node-ops/index.d.ts +9 -9
  56. package/dist/node-ops/index.js +1 -1
  57. package/dist/panel/index.d.ts +73 -5
  58. package/dist/panel/index.js +18 -16
  59. package/dist/panel/panel.css +56 -1
  60. package/dist/pick/index.d.ts +42 -4
  61. package/dist/pick/index.js +7 -14
  62. package/dist/pick/pick.css +14 -0
  63. package/dist/rail/index.d.ts +6 -6
  64. package/dist/rail/index.js +1 -1
  65. package/dist/rail/rail.css +1 -1
  66. package/dist/strings/catalogue.d.ts +24 -40
  67. package/dist/strings/catalogue.js +1 -1
  68. package/dist/strings/index.d.ts +25 -23
  69. package/dist/strings/index.js +2 -2
  70. package/dist/style-clipboard/index.d.ts +5 -5
  71. package/dist/style-clipboard/index.js +1 -1
  72. package/dist/text-styles/index.d.ts +328 -0
  73. package/dist/text-styles/index.js +38 -0
  74. package/dist/text-styles/text-styles.css +309 -0
  75. package/dist/tree/index.d.ts +44 -3
  76. package/dist/tree/index.js +1 -1
  77. package/dist/tree/tree.css +71 -0
  78. package/dist/{types-CnRzp9oc.d.ts → types-DGcCsxs8.d.ts} +79 -12
  79. package/dist/{types-DNrn_w4V.d.ts → types-DWS-s9z-.d.ts} +10 -10
  80. package/dist/{types-pGBXevBU.d.ts → types-Djdq6Xkd.d.ts} +15 -8
  81. package/dist/{types-Dy6oR6Z2.d.ts → types-E3HrTPg2.d.ts} +20 -14
  82. package/dist/{types-C7HFvdtk.d.ts → types-VqL6t5HH.d.ts} +82 -5
  83. package/dist/{watch-DmasQA61.d.ts → watch-BTYOJpQU.d.ts} +19 -2
  84. package/dist/{wordFit-9B9nqyf3.d.ts → wordFit-D4S36PvB.d.ts} +1 -1
  85. package/dist/{words-DagJdwIA.d.ts → words-AHHfiaRY.d.ts} +31 -2
  86. package/dist/{words-D9ChvbNC.d.ts → words-BDignMl_.d.ts} +1 -1
  87. package/dist/{words-Cqmyey0s.d.ts → words-BMzV04b1.d.ts} +2 -2
  88. package/dist/words-CJjUKcV5.d.ts +37 -0
  89. package/dist/words-CRYvAiHX.d.ts +105 -0
  90. package/dist/{words-B2YLaKEr.d.ts → words-CnnHb9jo.d.ts} +1 -1
  91. package/dist/{words-CD3WADyb.d.ts → words-CzoyDNHm.d.ts} +4 -0
  92. package/dist/{words-bOMsUJk9.d.ts → words-DBq7vfHy.d.ts} +59 -6
  93. package/dist/{words-DOhF-4D4.d.ts → words-DMG4dYq2.d.ts} +1 -1
  94. package/dist/{words-BK-npta-.d.ts → words-DgHcY5qB.d.ts} +10 -4
  95. package/dist/{words-B87GIFWH.d.ts → words-is_Vy6hn.d.ts} +1 -1
  96. package/package.json +14 -8
  97. package/dist/chunk-66CKSTXP.js +0 -14
  98. package/dist/chunk-67VZRQLG.js +0 -13
  99. package/dist/chunk-6PS6STGA.js +0 -1
  100. package/dist/chunk-7A7O2JWQ.js +0 -1
  101. package/dist/chunk-H4B72XCJ.js +0 -1
  102. package/dist/chunk-JE63JHKO.js +0 -1
  103. package/dist/chunk-KDIVA6WD.js +0 -1
  104. package/dist/chunk-MCLBHENY.js +0 -1
  105. package/dist/chunk-QB3BZIRA.js +0 -5
  106. package/dist/chunk-SJQG5ENJ.js +0 -1
  107. package/dist/chunk-SUO5627P.js +0 -1
  108. package/dist/chunk-ZT7M24XD.js +0 -1
  109. package/dist/editor/index.js +0 -9
  110. /package/dist/{chunk-GGTPNGFJ.js → chunk-AELNNAFM.js} +0 -0
@@ -0,0 +1,309 @@
1
+ /*
2
+ * efficient-easel/text-styles.css — the text-styles list (plan step E6).
3
+ *
4
+ * Email styles → Text and Site styles → Text, drawn once. The numbers are the
5
+ * design's (both pages, final 2026-10-06) and every gap is one of the six
6
+ * spacing tokens (2/4/8/12/16/32, owner ruling 2026-10-02):
7
+ *
8
+ * a row 12 above and below; name → grey line 4
9
+ * an open style its settings 12 apart, name → control 4 (the slider
10
+ * row's own), 16 below the last; indented to line up
11
+ * with the name (the 12 px chevron + 8), none under 260 px
12
+ * group → group 32; group heading → first row 4; "+ Add" 12 above
13
+ *
14
+ * No divider line between rows: this package draws none (card 66350,
15
+ * tests/panel-no-divider-lines.test.ts). The design's hairline under each
16
+ * row is the one thing on the page not drawn here; the 12 px padding
17
+ * separates them.
18
+ *
19
+ * Builds on fields.css (the sliders, the dropdown) and pick.css (the colour
20
+ * window). Light only, like the rest of the editor.
21
+ */
22
+
23
+ .es-ts {
24
+ container-type: inline-size;
25
+ display: flex;
26
+ flex-direction: column;
27
+ min-width: 0;
28
+ }
29
+
30
+ .es-ts-section {
31
+ display: flex;
32
+ flex-direction: column;
33
+ min-width: 0;
34
+ }
35
+ .es-ts-section + .es-ts-section { margin-top: var(--es-space-32, 32px); }
36
+
37
+ .es-ts-group {
38
+ margin: 0 0 var(--es-space-4, 4px);
39
+ font-size: 11px;
40
+ font-weight: 600;
41
+ letter-spacing: 0.06em;
42
+ text-transform: uppercase;
43
+ color: var(--es-muted, #6b7280);
44
+ }
45
+
46
+ /* ── a style's row: the chevron, its name in its own face, the grey line ── */
47
+
48
+ /* The row and, over its end, its actions: Delete on an added style
49
+ (`onRemove`), then the host's own (`rowActions`). As the Layers row's
50
+ (tree.css `.es-tree-actions`): they LIE OVER the end of the row and hold
51
+ no width, so a row with Delete keeps the same room for its name as one
52
+ without; they show on hover AND on focus, over a fade the grey line
53
+ dissolves into. They sit beside the button that opens the row, never
54
+ inside it (a button may not hold a button). */
55
+ .es-ts-item {
56
+ position: relative;
57
+ min-width: 0;
58
+ }
59
+ .es-ts-actions {
60
+ position: absolute;
61
+ right: 0;
62
+ top: 0;
63
+ bottom: 0;
64
+ display: flex;
65
+ align-items: center;
66
+ gap: var(--es-space-2, 2px);
67
+ padding-left: var(--es-space-16, 16px);
68
+ background: linear-gradient(to right, transparent 0, var(--es-panel, #ffffff) 16px);
69
+ opacity: 0;
70
+ pointer-events: none;
71
+ transition: opacity 0.12s;
72
+ }
73
+ .es-ts-item:hover .es-ts-actions,
74
+ .es-ts-item:focus-within .es-ts-actions {
75
+ opacity: 1;
76
+ }
77
+ /* The strip never takes the pointer; only the buttons in it do, once shown,
78
+ so a press on the faded name still opens the row. */
79
+ .es-ts-item:hover .es-ts-actions > *,
80
+ .es-ts-item:focus-within .es-ts-actions > * {
81
+ pointer-events: auto;
82
+ }
83
+ @media (prefers-reduced-motion: reduce) { .es-ts-actions { transition: none; } }
84
+
85
+ /* Delete: the Layers trash (tree.css `.es-tree-del`) — 22 px, the glyph 14,
86
+ red at 0.7, full with a red tint under the pointer. */
87
+ .es-ts-del {
88
+ all: unset;
89
+ box-sizing: border-box;
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ width: 22px;
94
+ height: 22px;
95
+ border-radius: 5px;
96
+ color: var(--es-danger, #dc2626);
97
+ opacity: 0.7;
98
+ cursor: pointer;
99
+ transition: opacity 0.12s, background 0.12s, color 0.12s;
100
+ }
101
+ .es-ts-del svg { width: 14px; height: 14px; }
102
+ .es-ts-del:hover,
103
+ .es-ts-del:focus-visible {
104
+ opacity: 1;
105
+ background: var(--es-danger-tint, #fef2f2);
106
+ color: var(--es-danger-strong, #b91c1c);
107
+ }
108
+ .es-ts-del:focus-visible { outline: 2px solid var(--es-accent, #2563eb); outline-offset: 0; }
109
+ @media (prefers-reduced-motion: reduce) { .es-ts-del { transition: none; } }
110
+
111
+ .es-ts-row {
112
+ all: unset;
113
+ box-sizing: border-box;
114
+ display: grid;
115
+ grid-template-columns: 12px minmax(0, 1fr);
116
+ column-gap: var(--es-space-8, 8px);
117
+ row-gap: var(--es-space-4, 4px);
118
+ align-items: center;
119
+ width: 100%;
120
+ padding: var(--es-space-12, 12px) 0;
121
+ cursor: pointer;
122
+ }
123
+ .es-ts-row:focus-visible { outline: 2px solid var(--es-accent, #2563eb); outline-offset: 2px; border-radius: 4px; }
124
+ .es-ts-row[aria-expanded='true'] { padding-bottom: var(--es-space-8, 8px); }
125
+
126
+ .es-ts-chev {
127
+ display: flex;
128
+ grid-row: 1;
129
+ width: 12px;
130
+ height: 12px;
131
+ color: var(--es-field-placeholder, #9ca3af);
132
+ transition: transform 0.12s ease;
133
+ }
134
+ .es-ts-row[aria-expanded='true'] .es-ts-chev { transform: rotate(90deg); }
135
+ @media (prefers-reduced-motion: reduce) { .es-ts-chev { transition: none; } }
136
+
137
+ .es-ts-name {
138
+ grid-column: 2;
139
+ min-width: 0;
140
+ overflow: hidden;
141
+ text-overflow: ellipsis;
142
+ white-space: nowrap;
143
+ line-height: 1.2;
144
+ color: var(--es-text, #111827);
145
+ }
146
+ .es-ts-meta {
147
+ grid-column: 2;
148
+ grid-row: 2;
149
+ min-width: 0;
150
+ overflow: hidden;
151
+ text-overflow: ellipsis;
152
+ white-space: nowrap;
153
+ font-size: 12px;
154
+ line-height: 1.4;
155
+ color: var(--es-muted, #6b7280);
156
+ }
157
+
158
+ /* ── an open style ────────────────────────────────────────────────────────── */
159
+
160
+ .es-ts-open {
161
+ display: flex;
162
+ flex-direction: column;
163
+ gap: var(--es-space-12, 12px);
164
+ min-width: 0;
165
+ padding: var(--es-space-4, 4px) 0 var(--es-space-16, 16px) calc(var(--es-space-12, 12px) + var(--es-space-8, 8px));
166
+ }
167
+ @container (width < 260px) {
168
+ .es-ts-open { padding-left: 0; }
169
+ }
170
+
171
+ /* One line in the style itself; a long one fades out on the right instead of
172
+ making the panel taller. */
173
+ .es-ts-preview {
174
+ box-sizing: border-box;
175
+ overflow: hidden;
176
+ padding: var(--es-space-12, 12px);
177
+ border: 1px solid var(--es-line, #e5e7eb);
178
+ border-radius: var(--es-row-radius, 8px);
179
+ background: var(--es-panel, #ffffff);
180
+ color: var(--es-text, #111827);
181
+ white-space: nowrap;
182
+ -webkit-mask-image: linear-gradient(to right, #000 85%, transparent);
183
+ mask-image: linear-gradient(to right, #000 85%, transparent);
184
+ }
185
+
186
+ .es-ts-set {
187
+ display: flex;
188
+ flex-direction: column;
189
+ gap: var(--es-space-4, 4px);
190
+ min-width: 0;
191
+ }
192
+ /* A setting's name: the slider row's name, so every name in the list is one. */
193
+ .es-ts-sub {
194
+ min-width: 0;
195
+ overflow: hidden;
196
+ text-overflow: ellipsis;
197
+ white-space: nowrap;
198
+ font-size: 12px;
199
+ font-weight: 500;
200
+ color: var(--es-text-secondary, #374151);
201
+ }
202
+ .es-ts-top {
203
+ display: flex;
204
+ align-items: baseline;
205
+ justify-content: space-between;
206
+ gap: var(--es-space-8, 8px);
207
+ min-width: 0;
208
+ }
209
+ .es-ts-colour-read {
210
+ flex: none;
211
+ font: 500 12px ui-monospace, SFMono-Regular, Menlo, monospace;
212
+ color: var(--es-muted, #6b7280);
213
+ }
214
+
215
+ /* The theme's colours in a row, then Any color — the colour window's swatches. */
216
+ .es-ts-swatches {
217
+ display: flex;
218
+ flex-wrap: wrap;
219
+ gap: var(--es-space-8, 8px);
220
+ }
221
+ /* Under 260 px the step down the scale keeps a six-colour theme and Any color
222
+ on one line (7 × 22 + 6 × 4 = 178 of the 196 px a 220 px panel has). */
223
+ @container (width < 260px) {
224
+ .es-ts-swatches { gap: var(--es-space-4, 4px); }
225
+ }
226
+ .es-ts-swatch {
227
+ box-sizing: border-box;
228
+ width: 22px;
229
+ height: 22px;
230
+ padding: 0;
231
+ border: 0;
232
+ border-radius: 6px;
233
+ box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.1);
234
+ cursor: pointer;
235
+ }
236
+ .es-ts-swatch[aria-pressed='true'] {
237
+ box-shadow:
238
+ inset 0 0 0 1px rgb(0 0 0 / 0.1),
239
+ 0 0 0 2px var(--es-panel, #ffffff),
240
+ 0 0 0 4px var(--es-accent, #2563eb);
241
+ }
242
+ .es-ts-swatch:focus-visible { outline: 2px solid var(--es-accent, #2563eb); outline-offset: 2px; }
243
+ .es-ts-swatch:disabled { cursor: default; opacity: 0.6; }
244
+ .es-ts-swatch--any { background: conic-gradient(#f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00); }
245
+
246
+ /* B · I and Letter case: the text box's 32 px buttons (react-os-shell
247
+ IconButton md); pressed = the light blue the toolbar's pressed marks wear. */
248
+ .es-ts-icons {
249
+ display: flex;
250
+ flex-wrap: wrap;
251
+ gap: var(--es-space-2, 2px);
252
+ }
253
+ .es-ts-icon {
254
+ box-sizing: border-box;
255
+ display: inline-flex;
256
+ align-items: center;
257
+ justify-content: center;
258
+ width: 32px;
259
+ height: 32px;
260
+ padding: 0;
261
+ border: 0;
262
+ border-radius: 6px;
263
+ background: transparent;
264
+ color: var(--es-text-secondary, #374151);
265
+ font: inherit;
266
+ cursor: pointer;
267
+ }
268
+ .es-ts-icon--word {
269
+ width: auto;
270
+ padding: 0 var(--es-space-8, 8px);
271
+ font-size: 13px;
272
+ font-weight: 500;
273
+ }
274
+ .es-ts-icon:hover:not(:disabled) { background: var(--es-hover, #f3f4f6); color: var(--es-text, #111827); }
275
+ .es-ts-icon[aria-pressed='true'],
276
+ .es-ts-icon[aria-checked='true'],
277
+ .es-ts-icon[aria-pressed='true']:hover:not(:disabled),
278
+ .es-ts-icon[aria-checked='true']:hover:not(:disabled) {
279
+ background: var(--es-accent-tint, #eff6ff);
280
+ color: var(--es-accent-text, #1d4ed8);
281
+ }
282
+ .es-ts-icon:focus-visible { outline: 2px solid var(--es-accent, #2563eb); outline-offset: 1px; }
283
+ .es-ts-icon:disabled { opacity: 0.6; cursor: not-allowed; }
284
+
285
+ /* The window Any color opens: under the row, in the panel's flow. */
286
+ .es-ts-set > .es-pick-window { margin-top: var(--es-space-4, 4px); }
287
+
288
+ /* ── "+ Add heading" · 3 / 7 ──────────────────────────────────────────────── */
289
+
290
+ .es-ts-add {
291
+ all: unset;
292
+ box-sizing: border-box;
293
+ display: flex;
294
+ align-items: center;
295
+ justify-content: space-between;
296
+ gap: var(--es-space-8, 8px);
297
+ padding: var(--es-space-12, 12px) 0 0;
298
+ font-size: 13px;
299
+ font-weight: 500;
300
+ color: var(--es-accent, #2563eb);
301
+ cursor: pointer;
302
+ }
303
+ .es-ts-add:focus-visible { outline: 2px solid var(--es-accent, #2563eb); outline-offset: 2px; border-radius: 4px; }
304
+ .es-ts-add:disabled { color: var(--es-disabled, #9ca3af); cursor: not-allowed; }
305
+ .es-ts-count {
306
+ font-size: 12px;
307
+ font-weight: 400;
308
+ color: var(--es-field-placeholder, #9ca3af);
309
+ }
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, HTMLAttributes, ButtonHTMLAttributes } from 'react';
3
- import { T as TreeWords } from '../words-BK-npta-.js';
3
+ import { T as TreeWords } from '../words-DgHcY5qB.js';
4
4
 
5
5
  type LayersTreeProps = {
6
6
  /** Extra classes — a host's scroll-bar class, for instance. */
@@ -86,7 +86,7 @@ type TreeRowProps = {
86
86
  rowProps?: HTMLAttributes<HTMLDivElement> & Record<`data-${string}`, string | number | undefined>;
87
87
  /**
88
88
  * The row's own words — the drag hint and the caret's — in the host's
89
- * language: `useEditorStrings().tree` (0.95.0). Left out, English. `dragHint`
89
+ * language: `useEaselStrings().tree` (0.95.0). Left out, English. `dragHint`
90
90
  * above still wins for its one line.
91
91
  */
92
92
  words?: TreeWords;
@@ -111,6 +111,25 @@ type TreeRowActionProps = {
111
111
  onClick: () => void;
112
112
  children: ReactNode;
113
113
  };
114
+ type TreeEmptySlotProps = {
115
+ /** A drag is over the slot and may land in it: it lights up and says "Drop here". */
116
+ drop?: boolean;
117
+ /** Present: the box is a button that adds here (it opens the Add panel at this place). */
118
+ onAdd?: () => void;
119
+ /**
120
+ * What the box says at rest. Default `words.addBlock` ("Add a block") with
121
+ * `onAdd`, `words.emptySlot` ("No blocks yet") without.
122
+ */
123
+ label?: string;
124
+ /**
125
+ * The drag handlers — `efficient-easel/drag`'s
126
+ * `seam({ place: { ...list, index: 0 } }).seamProps`: an empty list has one
127
+ * place, its start.
128
+ */
129
+ seamProps?: HTMLAttributes<HTMLDivElement>;
130
+ /** The box's own words in the host's language: `useEaselStrings().tree`. Left out, English. */
131
+ words?: TreeWords;
132
+ };
114
133
  type TreeSeamProps = {
115
134
  /** A drag is over this gap and may land in it: draw the drop line. */
116
135
  drop?: boolean;
@@ -167,7 +186,29 @@ declare function TreeRowAction({ kind, label, onClick, pressed, disabled, childr
167
186
  * The gap between two rows, as a place (0.74.0): a drop between them lands here,
168
187
  * and — when the host offers it — a "+" adds here. It takes exactly the space
169
188
  * the gap already had, so a list reads the same with seams or without.
189
+ *
190
+ * ⚠️ NOT FOR AN EMPTY LIST. A seam hands its height back to the rows either
191
+ * side of it; alone in a list there are none, and what is left is a strip a
192
+ * few pixels tall with nothing drawn in it. Draw `TreeEmptySlot` there.
170
193
  */
171
194
  declare function TreeSeam({ drop, onAdd, label, seamProps }: TreeSeamProps): react.JSX.Element;
195
+ /**
196
+ * AN EMPTY LIST, AS A PLACE (second host, 2026-10-06): where a slot holds
197
+ * nothing yet — a column with no blocks — a dashed box the size of a row. A
198
+ * drop on it lands in the slot, and when the host offers it the box is a
199
+ * button that adds there. While a drag may land it lights up and says so.
200
+ *
201
+ * Until then an empty list was drawn as its one seam, and a seam is a gap: a
202
+ * drop aimed at "into this empty column" missed its few pixels and landed on
203
+ * whatever row or seam was really under the pointer. The canvas has always
204
+ * drawn the same idea as its "+ Add block" cell (`efficient-easel/drag`).
205
+ *
206
+ * Lifted from the campaign designer's `.cd-tree-add` (efficient-admin-portal
207
+ * `EmailOutline`), which had drawn one of its own. Hook-free and controlled,
208
+ * like every part of the tree: the drag is `efficient-easel/drag`'s —
209
+ *
210
+ * <TreeEmptySlot {...drag.seam({ place: { ...list, index: 0 } })} onAdd={…} />
211
+ */
212
+ declare function TreeEmptySlot({ drop, onAdd, label, seamProps, words }: TreeEmptySlotProps): react.JSX.Element;
172
213
 
173
- export { LayersTree, type LayersTreeProps, Subtree, type SubtreeProps, TREE_DRAG_HINT, TreeGroupLabel, type TreeGroupLabelProps, TreeRow, TreeRowAction, type TreeRowActionProps, type TreeRowFold, type TreeRowMark, type TreeRowProps, TreeSeam, type TreeSeamProps, TreeWords };
214
+ export { LayersTree, type LayersTreeProps, Subtree, type SubtreeProps, TREE_DRAG_HINT, TreeEmptySlot, type TreeEmptySlotProps, TreeGroupLabel, type TreeGroupLabelProps, TreeRow, TreeRowAction, type TreeRowActionProps, type TreeRowFold, type TreeRowMark, type TreeRowProps, TreeSeam, type TreeSeamProps, TreeWords };
@@ -1 +1 @@
1
- export{LayersTree,Subtree,TREE_DRAG_HINT,TreeGroupLabel,TreeRow,TreeRowAction,TreeSeam}from"../chunk-66CKSTXP.js";import"../chunk-H4B72XCJ.js";import"../chunk-PI26YXYD.js";
1
+ export{LayersTree,Subtree,TREE_DRAG_HINT,TreeEmptySlot,TreeGroupLabel,TreeRow,TreeRowAction,TreeSeam}from"../chunk-V7GIIXFB.js";import"../chunk-3EDSDPIP.js";import"../chunk-6EIVTKZZ.js";
@@ -616,3 +616,74 @@
616
616
  .es-subtree > .es-tree-seam:last-child {
617
617
  margin-bottom: calc(-1 * var(--es-rhythm-between));
618
618
  }
619
+
620
+ /* ── An empty list, as a place (second host, 2026-10-06) ───────────────────
621
+ Where a slot holds nothing yet: a box the size of a row, that takes a drop
622
+ and — when the host offers it — adds there. NOT a seam: a seam is the gap
623
+ between two rows and takes back the space it adds (above), so alone in a
624
+ list it was a strip 8px tall with nothing drawn in it, and a drop aimed at
625
+ "into this empty column" landed on whatever was really under the pointer.
626
+
627
+ Dashed, because the thing is not there yet — the canvas says the same with
628
+ its "+ Add block" cell (`.es-canvas-empty`, drag.css). Read off the campaign
629
+ designer's `.cd-tree-add` (efficient-admin-portal), which drew one of its
630
+ own; 34px is a row's height (8 + 18 + 8). */
631
+ .es-tree-empty {
632
+ box-sizing: border-box;
633
+ flex: none;
634
+ min-height: 34px;
635
+ display: flex;
636
+ align-items: stretch;
637
+ border: 1px dashed var(--es-line);
638
+ border-radius: var(--es-row-radius);
639
+ color: var(--es-text-secondary);
640
+ font: 500 12px/1.3 var(--es-font);
641
+ transition: border-color 0.12s, color 0.12s, background 0.12s;
642
+ }
643
+ .es-tree-empty-add,
644
+ .es-tree-empty-note {
645
+ flex: 1;
646
+ min-width: 0;
647
+ display: flex;
648
+ align-items: center;
649
+ gap: 8px;
650
+ padding: 0 12px;
651
+ }
652
+ /* The whole box is the button. A bare one: a native button ignores an author's
653
+ background and border on some platforms, so the box draws them. */
654
+ .es-tree-empty-add {
655
+ border: none;
656
+ border-radius: inherit;
657
+ background: none;
658
+ color: inherit;
659
+ font: inherit;
660
+ text-align: left;
661
+ cursor: pointer;
662
+ }
663
+ .es-tree-empty-add:focus-visible {
664
+ outline: 2px solid var(--es-accent);
665
+ outline-offset: -2px;
666
+ }
667
+ /* Nothing to do here: the list is empty, and that is all it says. It SAYS it,
668
+ so it is a grey that can be read: it was `--es-disabled` (#9ca3af), 2.5:1 on
669
+ the panel, which axe-core failed on a read-only tree (2026-10-07). Held by
670
+ ratio in tests/contrast-small-text.test.ts. */
671
+ .es-tree-empty-note {
672
+ color: var(--es-muted);
673
+ }
674
+ .es-tree-empty.has-add:hover,
675
+ .es-tree-empty.is-drop {
676
+ border-color: var(--es-accent);
677
+ color: var(--es-accent-text);
678
+ }
679
+ .es-tree-empty.is-drop {
680
+ background: var(--es-accent-tint);
681
+ }
682
+ .es-tree-empty.is-drop .es-tree-empty-note {
683
+ color: inherit;
684
+ }
685
+ /* At the top level rows stack as blocks: keep the row's own distance from one above. */
686
+ .es-tree > .es-tree-row + .es-tree-empty,
687
+ .es-tree > .es-subtree + .es-tree-empty {
688
+ margin-top: var(--es-rhythm-between);
689
+ }
@@ -1,4 +1,4 @@
1
- import { a as EditorRailItem } from './types-DNrn_w4V.js';
1
+ import { a as EaselRailItem } from './types-DWS-s9z-.js';
2
2
  import { CSSProperties, ReactNode, Ref } from 'react';
3
3
  import { EditAccess, SaveConflict } from './edit-lock/index.js';
4
4
 
@@ -40,9 +40,30 @@ declare function clampPanelWidth(width: number): number;
40
40
  * it. The website editor learned this on 2026-09-04: the vars sat on the panel
41
41
  * row, the top bar is that row's SIBLING, and the rail column read a width with
42
42
  * no declaration in scope — which is not an error, it silently falls back to
43
- * `auto` and the column shrinks to its buttons.
43
+ * `auto` and the column shrinks to its buttons. (Since 2026-10-06 chrome.css
44
+ * falls back to `DEFAULT_PANEL_W` instead: the column keeps its width and
45
+ * simply stops following the grip.)
46
+ *
47
+ * ⚠️ A SERVER RENDERS THIS AS A `style=""` ATTRIBUTE, and a
48
+ * Content-Security-Policy without `'unsafe-inline'` for styles refuses those.
49
+ * A client render is not affected: React writes a `style` prop through the
50
+ * CSSOM. A server-rendered editor under such a policy uses
51
+ * {@link setPanelWidthVars} instead.
44
52
  */
45
53
  declare function panelWidthVars(widths: PanelWidths): CSSProperties;
54
+ /**
55
+ * The same two variables, written onto the editor root through the CSSOM
56
+ * (second host, 2026-10-06) — so the server's HTML carries no `style` attribute for a strict
57
+ * Content-Security-Policy to refuse:
58
+ *
59
+ * <div className="es-editor" ref={(el) => { if (el) setPanelWidthVars(el, widths); }}>
60
+ *
61
+ * A ref runs on the client only, before the browser paints, and again on each
62
+ * render — so it follows the widths as a grip is dragged. Until it runs (the
63
+ * server's HTML, before hydration) chrome.css draws the panels at
64
+ * `DEFAULT_PANEL_W`. Touches nothing but the element it is handed.
65
+ */
66
+ declare function setPanelWidthVars(el: HTMLElement, widths: PanelWidths): void;
46
67
 
47
68
  type TopBarProps = {
48
69
  children?: ReactNode;
@@ -51,7 +72,7 @@ type TopBarClusterProps = {
51
72
  /**
52
73
  * `rail` — the LEFT column: exactly the left panel's width (`--es-left-w`),
53
74
  * carrying the panel's hairline up through the bar. Holds the
54
- * `EditorRail` in `orientation="row"` and nothing else.
75
+ * `EaselRail` in `orientation="row"` and nothing else.
55
76
  * `right` — the actions a person takes: save state, ghost buttons, the one
56
77
  * primary button.
57
78
  * omitted — the centre: undo / redo, a divider, the device switch, the panel
@@ -61,7 +82,7 @@ type TopBarClusterProps = {
61
82
  style?: CSSProperties;
62
83
  children?: ReactNode;
63
84
  };
64
- type EditorIconButtonProps = {
85
+ type EaselIconButtonProps = {
65
86
  /** Tooltip AND accessible name. An icon-only button has no other. */
66
87
  label: string;
67
88
  onClick?: () => void;
@@ -87,7 +108,7 @@ type DeviceSwitchProps<Id extends string = string> = {
87
108
  value: Id;
88
109
  /** Asked for a different device. Never called for the current one. */
89
110
  onChange: (next: Id) => void;
90
- /** The strip's own name. Defaults to "Preview device" (`EditorStrings.chrome.device`). */
111
+ /** The strip's own name. Defaults to "Preview device" (`EaselStrings.chrome.device`). */
91
112
  ariaLabel?: string;
92
113
  };
93
114
  /**
@@ -146,19 +167,49 @@ type InspectorHeadProps = {
146
167
  /** `InspectorIconButton`s, right-aligned. Omit when the thing has no actions. */
147
168
  actions?: ReactNode;
148
169
  };
170
+ /** What a field can hold, as `MediaPanelHead` says it (`ChromeWords.mediaKinds`). */
171
+ type MediaPickKind = 'image' | 'video' | 'svg';
172
+ type MediaPanelHeadProps = {
173
+ /** The line under "Media library": which website or campaign the panel is
174
+ * open in. Also the line under "Choose …" when the field has no name. */
175
+ context: ReactNode;
176
+ /** The field choosing right now, or null while browsing. `field` is the
177
+ * host's label for it ("Background image"). */
178
+ pick: {
179
+ kind: MediaPickKind;
180
+ field?: string;
181
+ } | null;
182
+ /** Leave choosing: the field keeps what it had. */
183
+ onCancel: () => void;
184
+ /** Hide the panel. Omitted where the panel cannot be hidden, and then no
185
+ * Hide button is drawn. */
186
+ onHide?: () => void;
187
+ /** Which side panel the head is on; picks the Hide glyph and its name. */
188
+ side?: 'left' | 'right';
189
+ };
149
190
  type InspectorUpLevelProps = {
150
- /** What the level above is called — the section, the column, the group. */
191
+ /** What the level above is called — the section, the column, the group. The link's words. */
151
192
  name: string;
152
193
  /** Select the level above. */
153
194
  onSelect: () => void;
154
- /** The glyph before the name. The host's own until the icon set carries one. */
195
+ /**
196
+ * @deprecated Ignored since the one up-level look (Lewis, 2026-10-06): the
197
+ * link draws its own ← and a host glyph is a second look. Kept so a host
198
+ * that still passes one compiles; drop it from your call.
199
+ */
155
200
  icon?: ReactNode;
156
201
  /**
157
202
  * The tooltip, and the start of the accessible name ("… to ‹name›"). Default
158
- * "Go up one level" (`EditorStrings.chrome.upLevel`); how the two are joined
159
- * is `EditorStrings.chrome.upLevelTo`.
203
+ * "Go up one level" (`EaselStrings.chrome.upLevel`); how the two are joined
204
+ * is `EaselStrings.chrome.upLevelTo`.
160
205
  */
161
206
  label?: string;
207
+ /**
208
+ * Draw the link alone, without the panel-top inset — for a level inside the
209
+ * panel body (an open list item, a sub-panel). Default: the panel-top place,
210
+ * above the inspector head.
211
+ */
212
+ inline?: boolean;
162
213
  };
163
214
  type InspectorIconButtonProps = {
164
215
  /** Accessible name, and the tooltip unless `title` says something else. */
@@ -180,11 +231,27 @@ type InspectorIconButtonProps = {
180
231
  buttonRef?: Ref<HTMLButtonElement>;
181
232
  children: ReactNode;
182
233
  };
234
+ /**
235
+ * Which of its words a top-bar rail is drawing — the settings tabs' three
236
+ * states: `'all'` every button with its word, `'chosen'` the glyphs plus the
237
+ * word of the button whose panel is open, `'none'` glyphs only.
238
+ */
239
+ type RailWordsShown = 'all' | 'chosen' | 'none';
183
240
  type TopBarRailProps = {
184
- /** The rail's buttons (`EditorRail` items). */
185
- items: EditorRailItem[];
241
+ /** The rail's buttons (`EaselRail` items). */
242
+ items: EaselRailItem[];
186
243
  /** The toolbar's own name, e.g. "Left panel views". */
187
244
  ariaLabel?: string;
245
+ /**
246
+ * Told which words the rail is drawing, once they are measured and again
247
+ * whenever that changes (the panel dragged, a longer language, another panel
248
+ * opened). The width decides in CSS; this is that answer read back, for a
249
+ * host that has to know — to shorten a word, widen its panel's floor, or
250
+ * note that "Zurück" and "Ebenen" fold where "Back" and "Layers" do not.
251
+ * Every button keeps its name as `title` and `aria-label` in all three.
252
+ * Left out, nothing is observed.
253
+ */
254
+ onWords?: (shown: RailWordsShown) => void;
188
255
  };
189
256
  type EditNoticeProps = {
190
257
  /** Who may write the document now (`efficient-easel/edit-lock`). */
@@ -201,4 +268,4 @@ type EditNoticeProps = {
201
268
  onReload: () => void;
202
269
  };
203
270
 
204
- export { type DeviceSwitchProps as D, type EditorIconButtonProps as E, type InspectorHeadProps as I, MAX_PANEL_W as M, type PanelResizeHandleProps as P, type SaveTone as S, type TopBarProps as T, type TopBarClusterProps as a, type TopBarRailProps as b, type SaveStateProps as c, type SaveStateToggleProps as d, type InspectorIconButtonProps as e, type InspectorUpLevelProps as f, type EditNoticeProps as g, DEFAULT_PANEL_W as h, type DeviceOption as i, MIN_PANEL_W as j, type PanelSide as k, type PanelWidths as l, clampPanelWidth as m, panelWidthVars as p };
271
+ export { type DeviceSwitchProps as D, type EaselIconButtonProps as E, type InspectorHeadProps as I, type MediaPanelHeadProps as M, type PanelResizeHandleProps as P, type RailWordsShown as R, type SaveTone as S, type TopBarProps as T, type TopBarClusterProps as a, type TopBarRailProps as b, type SaveStateProps as c, type SaveStateToggleProps as d, type InspectorIconButtonProps as e, type InspectorUpLevelProps as f, type MediaPickKind as g, type EditNoticeProps as h, DEFAULT_PANEL_W as i, type DeviceOption as j, MAX_PANEL_W as k, MIN_PANEL_W as l, type PanelSide as m, type PanelWidths as n, clampPanelWidth as o, panelWidthVars as p, setPanelWidthVars as s };
@@ -1,4 +1,4 @@
1
- import { W as WordFit } from './wordFit-9B9nqyf3.js';
1
+ import { W as WordFit } from './wordFit-D4S36PvB.js';
2
2
  import { ReactNode } from 'react';
3
3
 
4
4
  /**
@@ -14,7 +14,7 @@ import { ReactNode } from 'react';
14
14
  * The rail is purely presentational: it owns the LOOK, never the app state. The
15
15
  * host decides which item is `active` and what each `onSelect` does.
16
16
  */
17
- interface EditorRailItem {
17
+ interface EaselRailItem {
18
18
  /** Stable identity — used as the React key and to correlate selection. */
19
19
  id: string;
20
20
  /** Accessible name; becomes both the `aria-label` and the hover `title`. */
@@ -48,11 +48,11 @@ interface EditorRailItem {
48
48
  * and active look are the same in both directions — the row re-points three
49
49
  * existing `--es-rail-*` custom properties and touches nothing else.
50
50
  */
51
- type EditorRailOrientation = 'column' | 'row';
52
- interface EditorRailProps {
51
+ type EaselRailOrientation = 'column' | 'row';
52
+ interface EaselRailProps {
53
53
  /** The buttons, top to bottom, in render order (place the "+" where you want it). */
54
- items: EditorRailItem[];
55
- /** Names the toolbar for assistive tech. Defaults to `"Editor"` (`EditorStrings.rail.label`). */
54
+ items: EaselRailItem[];
55
+ /** Names the toolbar for assistive tech. Defaults to `"Editor"` (`EaselStrings.rail.label`). */
56
56
  ariaLabel?: string;
57
57
  /** Extra class(es) merged onto the rail container. */
58
58
  className?: string;
@@ -61,7 +61,7 @@ interface EditorRailProps {
61
61
  * what every editor draws today — adding this prop changed no existing
62
62
  * output. Pass `'row'` to lay the rail into a top bar.
63
63
  */
64
- orientation?: EditorRailOrientation;
64
+ orientation?: EaselRailOrientation;
65
65
  /**
66
66
  * A ROW rail's words, measured: the sum of every label's width and the widest
67
67
  * one, in the rail's own type (`WordFit`). Given, every button carries its
@@ -74,9 +74,9 @@ interface EditorRailProps {
74
74
  */
75
75
  wordFit?: WordFit;
76
76
  }
77
- interface EditorRailButtonProps {
77
+ interface EaselRailButtonProps {
78
78
  label: string;
79
- /** Put the label beside the icon, for the stylesheet to show or clip (see `EditorRail`). */
79
+ /** Put the label beside the icon, for the stylesheet to show or clip (see `EaselRail`). */
80
80
  worded?: boolean;
81
81
  icon: ReactNode;
82
82
  active?: boolean;
@@ -85,4 +85,4 @@ interface EditorRailButtonProps {
85
85
  className?: string;
86
86
  }
87
87
 
88
- export type { EditorRailButtonProps as E, EditorRailItem as a, EditorRailOrientation as b, EditorRailProps as c };
88
+ export type { EaselRailButtonProps as E, EaselRailItem as a, EaselRailOrientation as b, EaselRailProps as c };