efficient-easel 0.105.1 → 1.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 (184) hide show
  1. package/README.md +103 -29
  2. package/dist/{LinkPicker-W2Rtxetz.d.ts → LinkPicker-hPEr1MJx.d.ts} +6 -4
  3. package/dist/autosave/index.d.ts +5 -5
  4. package/dist/chrome/chrome.css +50 -273
  5. package/dist/chrome/index.d.ts +61 -56
  6. package/dist/chrome/index.js +1 -1
  7. package/dist/chunk-2GVCAIPX.js +1 -0
  8. package/dist/chunk-4GNPKHV4.js +64 -0
  9. package/dist/chunk-5545A3PC.js +4 -0
  10. package/dist/chunk-7SQF6LZL.js +1 -0
  11. package/dist/chunk-BXMXTO36.js +1 -0
  12. package/dist/{chunk-NYKIQJT2.js → chunk-CKEPQNSQ.js} +1 -1
  13. package/dist/chunk-DJMMTWFP.js +1 -0
  14. package/dist/{chunk-PQXIFBDY.js → chunk-E2O2MRZR.js} +1 -1
  15. package/dist/chunk-EA2HTX37.js +5 -0
  16. package/dist/{chunk-SARZN4M7.js → chunk-FEK3S6HN.js} +1 -1
  17. package/dist/chunk-FPAVNX3Y.js +6 -0
  18. package/dist/chunk-HMJS344F.js +1 -0
  19. package/dist/chunk-JLTZQ4US.js +1 -0
  20. package/dist/chunk-JMGPPXNQ.js +24 -0
  21. package/dist/chunk-K56YDHYQ.js +1 -0
  22. package/dist/chunk-LOMEH6DG.js +1 -0
  23. package/dist/{chunk-V4LF7D4F.js → chunk-MAEXP3KE.js} +1 -1
  24. package/dist/chunk-MJM6SFD3.js +3 -0
  25. package/dist/chunk-NLRW4BGG.js +8 -0
  26. package/dist/chunk-OJAJVH7S.js +5 -0
  27. package/dist/chunk-ONRYLR2D.js +2 -0
  28. package/dist/chunk-ORR3Q4WV.js +1 -0
  29. package/dist/chunk-OTEXGJDU.js +5 -0
  30. package/dist/chunk-P2VEMZH5.js +1 -0
  31. package/dist/chunk-PT6V53OO.js +1 -0
  32. package/dist/chunk-PYCLHOSA.js +9 -0
  33. package/dist/chunk-Q27BB5ZC.js +14 -0
  34. package/dist/chunk-QLD57GBB.js +6 -0
  35. package/dist/{chunk-NWIW7AWH.js → chunk-QMNML7XH.js} +1 -1
  36. package/dist/chunk-QR4DLZ3P.js +5 -0
  37. package/dist/chunk-TJ7YIRWQ.js +21 -0
  38. package/dist/{chunk-3P6AMTOS.js → chunk-VPFNZ7TB.js} +1 -1
  39. package/dist/chunk-W3XX2CNW.js +16 -0
  40. package/dist/chunk-X6DQWK2C.js +1 -0
  41. package/dist/chunk-XWR5YVA7.js +9 -0
  42. package/dist/chunk-YABL4EWX.js +7 -0
  43. package/dist/chunk-ZNNATBDC.js +1 -0
  44. package/dist/chunk-ZP4G3X44.js +4 -0
  45. package/dist/copy/copy.css +66 -177
  46. package/dist/copy/index.d.ts +0 -2
  47. package/dist/copy/index.js +31 -32
  48. package/dist/drag/drag.css +31 -11
  49. package/dist/drag/index.d.ts +10 -0
  50. package/dist/drag/index.js +1 -1
  51. package/dist/easel/index.d.ts +10 -1
  52. package/dist/easel/index.js +1 -1
  53. package/dist/fields/fields.css +35 -304
  54. package/dist/fields/index.d.ts +87 -95
  55. package/dist/fields/index.js +1 -1
  56. package/dist/gold/gold.css +3 -3
  57. package/dist/gold/index.d.ts +6 -5
  58. package/dist/gold/index.js +1 -1
  59. package/dist/{host-BQ_b9Qzk.d.ts → host-CiuzZJXj.d.ts} +11 -1
  60. package/dist/index.d.ts +3 -3
  61. package/dist/index.js +1 -1
  62. package/dist/menu/index.d.ts +49 -93
  63. package/dist/menu/index.js +1 -1
  64. package/dist/menu/menu.css +37 -246
  65. package/dist/node-ops/index.d.ts +1 -1
  66. package/dist/panel/index.d.ts +46 -72
  67. package/dist/panel/index.js +17 -18
  68. package/dist/panel/panel.css +143 -372
  69. package/dist/pick/index.d.ts +52 -33
  70. package/dist/pick/index.js +7 -7
  71. package/dist/pick/pick.css +19 -150
  72. package/dist/rail/index.d.ts +48 -90
  73. package/dist/rail/index.js +1 -1
  74. package/dist/rail/rail.css +21 -108
  75. package/dist/screen/components/AddBlockRow.d.ts +8 -2
  76. package/dist/screen/components/AddBlockRow.js +1 -1
  77. package/dist/screen/components/AddChildRow.d.ts +4 -3
  78. package/dist/screen/components/AddChildRow.js +1 -1
  79. package/dist/screen/components/AddSidePanel.js +1 -1
  80. package/dist/screen/components/MediaSidePanel.d.ts +2 -2
  81. package/dist/screen/components/MediaSidePanel.js +1 -1
  82. package/dist/screen/components/NodeContextMenu.d.ts +31 -9
  83. package/dist/screen/components/NodeContextMenu.js +1 -1
  84. package/dist/screen/components/PageEditGuardNotices.js +1 -1
  85. package/dist/screen/components/ScreenChrome.js +1 -1
  86. package/dist/screen/components/ScreenDocumentContext.d.ts +2 -2
  87. package/dist/screen/components/ScreenDocumentContext.js +1 -1
  88. package/dist/screen/components/ScreenMount.d.ts +8 -8
  89. package/dist/screen/components/ScreenMount.js +1 -1
  90. package/dist/screen/components/ScreenStoreProvider.d.ts +8 -8
  91. package/dist/screen/components/ScreenStoreProvider.js +1 -1
  92. package/dist/screen/components/SectionScreenShell.js +16 -16
  93. package/dist/screen/components/SidePanelHead.d.ts +4 -2
  94. package/dist/screen/components/SidePanelHead.js +1 -1
  95. package/dist/screen/components/UiIcon.d.ts +10 -26
  96. package/dist/screen/components/UiIcon.js +2 -1
  97. package/dist/screen/components/panel-widths.d.ts +15 -1
  98. package/dist/screen/components/panel-widths.js +1 -1
  99. package/dist/screen/components/usePartBridge.d.ts +8 -8
  100. package/dist/screen/components/usePartBridge.js +1 -1
  101. package/dist/screen/index.d.ts +9 -9
  102. package/dist/screen/index.js +1 -1
  103. package/dist/screen/lib/admin-bridge.d.ts +1 -1
  104. package/dist/screen/lib/admin-bridge.js +1 -1
  105. package/dist/screen/lib/chrome-theme.d.ts +20 -12
  106. package/dist/screen/lib/chrome-theme.js +1 -1
  107. package/dist/screen/lib/document-addressing.d.ts +2 -2
  108. package/dist/screen/lib/host-chrome.d.ts +1 -1
  109. package/dist/screen/lib/host.d.ts +2 -2
  110. package/dist/screen/lib/key-scope.d.ts +31 -2
  111. package/dist/screen/lib/key-scope.js +1 -1
  112. package/dist/screen/lib/links/link-filters.d.ts +1 -1
  113. package/dist/screen/lib/media-panel.d.ts +2 -2
  114. package/dist/screen/lib/media-picker.d.ts +2 -2
  115. package/dist/screen/lib/page-lock.d.ts +1 -1
  116. package/dist/screen/lib/puck/section-doc.d.ts +5 -1
  117. package/dist/screen/lib/puck/section-doc.js +1 -1
  118. package/dist/screen/lib/puck/style-clipboard.d.ts +8 -8
  119. package/dist/screen/lib/puck/use-section-ops.js +1 -1
  120. package/dist/screen/lib/screen-parts.d.ts +47 -10
  121. package/dist/screen/lib/screen-parts.js +1 -1
  122. package/dist/screen/lib/section-templates.d.ts +1 -1
  123. package/dist/screen/lib/session-context.d.ts +1 -1
  124. package/dist/screen/lib/session.d.ts +1 -1
  125. package/dist/screen/screen.css +54 -206
  126. package/dist/screen/words.d.ts +17 -1
  127. package/dist/screen/words.js +1 -1
  128. package/dist/shell/index.d.ts +134 -118
  129. package/dist/shell/index.js +1 -1
  130. package/dist/shell/shell.css +130 -88
  131. package/dist/strings/catalogue.d.ts +5 -5
  132. package/dist/strings/catalogue.js +1 -1
  133. package/dist/strings/index.d.ts +5 -5
  134. package/dist/strings/index.js +1 -1
  135. package/dist/style-clipboard/index.d.ts +1 -1
  136. package/dist/text-styles/index.d.ts +12 -3
  137. package/dist/text-styles/index.js +33 -38
  138. package/dist/text-styles/text-styles.css +39 -71
  139. package/dist/tree/index.d.ts +49 -44
  140. package/dist/tree/index.js +1 -1
  141. package/dist/tree/tree.css +31 -653
  142. package/dist/{types-DJDBtdB2.d.ts → types-BqGnUVX5.d.ts} +32 -32
  143. package/dist/types-BsyPh6EW.d.ts +99 -0
  144. package/dist/{types-DWS-s9z-.d.ts → types-CsgMwHHa.d.ts} +14 -11
  145. package/dist/{types-CqY85foL.d.ts → types-Dt1CFR1S.d.ts} +4 -2
  146. package/dist/{wordFit-D4S36PvB.d.ts → wordFit-7XpmhcaW.d.ts} +3 -5
  147. package/dist/words-Cl9xMUYY.d.ts +41 -0
  148. package/dist/{words-D_Lnw1Gr.d.ts → words-CvFnwGSr.d.ts} +1 -1
  149. package/package.json +5 -1
  150. package/dist/chunk-2UX75JDP.js +0 -1
  151. package/dist/chunk-3EDSDPIP.js +0 -1
  152. package/dist/chunk-3QVB4AG2.js +0 -3
  153. package/dist/chunk-3WSPDAK2.js +0 -1
  154. package/dist/chunk-53G47SWO.js +0 -1
  155. package/dist/chunk-5MZ3RNQQ.js +0 -13
  156. package/dist/chunk-5ZBBXZPV.js +0 -6
  157. package/dist/chunk-7FBDZTUD.js +0 -2
  158. package/dist/chunk-7V5WZ46F.js +0 -2
  159. package/dist/chunk-BXZ6WLHC.js +0 -22
  160. package/dist/chunk-BZ5ASXOL.js +0 -1
  161. package/dist/chunk-EOJNJCMJ.js +0 -16
  162. package/dist/chunk-EWVJSRRY.js +0 -6
  163. package/dist/chunk-FMGFZNPW.js +0 -1
  164. package/dist/chunk-H24N5TMZ.js +0 -11
  165. package/dist/chunk-HLEET6NV.js +0 -65
  166. package/dist/chunk-IE4GFYKO.js +0 -1
  167. package/dist/chunk-KTVH2ZRL.js +0 -15
  168. package/dist/chunk-N7VRF7PR.js +0 -5
  169. package/dist/chunk-OWBO4WKA.js +0 -0
  170. package/dist/chunk-P67FP7PG.js +0 -9
  171. package/dist/chunk-P6O363EE.js +0 -6
  172. package/dist/chunk-Q4N7C6CT.js +0 -10
  173. package/dist/chunk-QH6LCNOX.js +0 -8
  174. package/dist/chunk-RW4XBUUW.js +0 -1
  175. package/dist/chunk-TTJ54QGG.js +0 -18
  176. package/dist/chunk-VSHV6KJQ.js +0 -5
  177. package/dist/chunk-VSOP4GUM.js +0 -1
  178. package/dist/chunk-WZ7TGAAT.js +0 -3
  179. package/dist/chunk-YC4TB3CJ.js +0 -25
  180. package/dist/chunk-ZFDCDJNZ.js +0 -16
  181. package/dist/chunk-ZNS7727Y.js +0 -12
  182. package/dist/chunk-ZTTWFAAC.js +0 -1
  183. package/dist/types-Djdq6Xkd.d.ts +0 -138
  184. package/dist/words-DgHcY5qB.d.ts +0 -33
package/README.md CHANGED
@@ -16,6 +16,45 @@ it since 0.93.0.
16
16
  > Moving from the old name without touching an import:
17
17
  > `"efficient-editor": "npm:efficient-easel@^0.96.0"`.
18
18
 
19
+ ## The shell first — efficient-shell is a peer (UI-24)
20
+
21
+ Harness UI-24 (efficient-harness#117, 2026-10-10): every surface a person
22
+ operates takes its UI from `efficient-shell`. So `efficient-shell` (`^4.163.0`)
23
+ is a **peer dependency**, and every colour, radius and the typeface in easel's
24
+ sheets is an alias of a shell token (`--es-surface`… is `var(--surface)`, the
25
+ accent is the shell's, and so on — `src/shell/shell.css` lists them all).
26
+
27
+ **A host loads the shell's CSS before any easel sheet**, or nothing resolves —
28
+ there are no hex fallbacks, because a fallback is a second palette:
29
+
30
+ ```ts
31
+ import 'efficient-shell/ui.css'; // or 'efficient-shell/styles.css' — exactly one
32
+ import 'efficient-easel/shell.css'; // then easel's sheets
33
+ import 'efficient-easel/screen.css'; // …
34
+ ```
35
+
36
+ The shell's `ui.css` is a Tailwind v4 input (its radii and face are declared in
37
+ `@theme default`), so it goes through the host's Tailwind build like every
38
+ portal's. The editor chrome then **follows the shell's theme, dark included**:
39
+ the shell stamps `data-theme="dark"` (or `.dark`) on `<html>` and easel's tokens
40
+ follow. The editor no longer pins itself light (`data-editor-theme` is gone;
41
+ `applyEaselChromeTheme` stamps the bridged theme as `data-theme`). The canvas is
42
+ output and keeps the document's own colours in every theme.
43
+
44
+ Since 1.0 (efficient-easel#199 step 4, part A) the editor's buttons, icon
45
+ buttons, switches, segmented controls, tabs, text and number boxes, the Add
46
+ block menu, the leave prompt, the toast and every explanation are
47
+ efficient-shell's components (`efficient-shell/ui`): they draw in the shell's
48
+ Tailwind classes, which **the host's Tailwind compiles from the shell's dist**
49
+ (the `@source` lines in its `ui.css`). Easel itself emits no Tailwind class.
50
+ The exported names hosts import (`EaselIconButton`, `SettingsSwitch`,
51
+ `SettingsSegmented`, `SettingsTabs`, `FieldText`, `FieldNumber`, …) stay, as
52
+ thin wrappers with the props they had.
53
+
54
+ `tests/own-ui-ratchet.test.ts` lists what easel still draws itself: since
55
+ 1.0.0 no primitive is left on it (`OWN_PRIMITIVES` is empty), and its counts —
56
+ explanatory `title=`, raw colours — only go down.
57
+
19
58
  ## Why
20
59
 
21
60
  The storefront editor (`efficient-storefront`, Next 16 + Puck) and the campaign
@@ -114,33 +153,32 @@ exported too, for bespoke layouts.
114
153
 
115
154
  ## Tokens
116
155
 
117
- The same values, two ways — `railTokens` (JS) and `rail.css` (CSS custom
118
- properties on `.es-rail`). Taken verbatim from the storefront editor's `.sf-rail`
119
- (the visual reference).
156
+ Since 1.0.0 the buttons are efficient-shell's `IconRailButton` (its look,
157
+ hover, active, focus and `Tooltip`), inside the shell's `IconRail` (its one
158
+ tab stop and arrow keys). What the tokens still describe is the rail's
159
+ CONTAINER and a measured row's words, the same values two ways — `railTokens`
160
+ (JS) and `rail.css` (CSS custom properties on `.es-rail`):
120
161
 
121
162
  | token | value | CSS var |
122
163
  |---|---|---|
123
164
  | rail width | `46px` | `--es-rail-w` |
124
165
  | rail padding (y) | `8px` | `--es-rail-pad-y` |
125
166
  | button gap | `4px` | `--es-rail-gap` |
126
- | rail radius | `12px` | `--es-rail-radius` |
127
- | button size | `34px` | `--es-rail-btn-size` |
128
- | button radius | `9px` | `--es-rail-btn-radius` |
167
+ | rail radius | `var(--radius-xl)` (12px) | `--es-rail-radius` |
168
+ | button size | `36px` (the shell's) | `--es-rail-btn-size` |
129
169
  | icon size | `18px` | `--es-rail-icon-size` |
130
- | background | `#ffffff` | `--es-rail-bg` |
131
- | idle icon | `#6b7280` | `--es-rail-fg` |
132
- | hover bg / fg | `#f3f4f6` / `#111827` | `--es-rail-hover-bg` / `--es-rail-hover-fg` |
133
- | accent (active) | `#2563eb` | `--es-rail-accent` |
134
- | accent tint (active bg) | `#eff6ff` | `--es-rail-accent-tint` |
170
+ | background | `var(--surface)` | `--es-rail-bg` |
171
+
172
+ The button colour tokens (`idle`, `hoverBg`, `hoverFg`, `accent`,
173
+ `accentTint`, `--es-rail-fg`, `--es-rail-hover-*`, `--es-rail-accent*`), the
174
+ button radius and `transition` / `disabledOpacity` were removed in 1.0.0: the
175
+ shell draws the button.
135
176
 
136
177
  `orientation="row"` adds **no new token**. It re-points three of the ones above
137
178
  — `--es-rail-bg` to `transparent`, `--es-rail-shadow` to `none`,
138
179
  `--es-rail-radius` to `0` — so the card look stays declared in one place, and it
139
180
  reuses `--es-rail-pad-y` rotated onto the row's ends and `--es-rail-gap` as-is.
140
181
 
141
- Re-theme without new CSS by overriding a var on the element:
142
- `style={{ ['--es-rail-accent']: brand }}`.
143
-
144
182
  ## The gold layer — type in place
145
183
 
146
184
  `GoldTextInput` (subpath `efficient-easel/gold`, added in 0.3.0) is the surface a
@@ -315,7 +353,7 @@ import 'efficient-easel/chrome.css'; // once, after shell.css
315
353
  </TopBarCluster>
316
354
  <TopBarCluster kind="right">
317
355
  <SaveState tone="saved" label="" />
318
- <button className="es-btn-primary">Done</button>
356
+ <Button variant="primary">Done</Button>{/* efficient-shell/ui */}
319
357
  </TopBarCluster>
320
358
  </EaselTopBar>
321
359
  …
@@ -858,6 +896,10 @@ const easel = createEasel<EaselConfig>({ config, data, onChange: (doc) => autosa
858
896
  - Whatever those say, the keys do nothing while the easel-wide `edit`
859
897
  permission is off. An undo is a write. `bindUndoKeys(target, editor)`, for
860
898
  a host's own target, is held the same way.
899
+ - `bindUndoKeys(target, editor, { leaveTyping: true })` (0.106.0) leaves a
900
+ press where a person types — a text field, a textarea, an editable
901
+ region — to that field's own undo. Left out, a press there undoes the
902
+ easel's step, as Puck's did; the provider and the canvas bind so.
861
903
  - **`planFields().rows` holds `slot` rows.** They draw nothing and are there so
862
904
  the slot's name has a bucket. Filter `row.field.type !== 'slot'` before
863
905
  drawing or counting rows of your own.
@@ -887,25 +929,28 @@ A host built only from the parts, audited with axe-core in a real Chromium.
887
929
  What follows is how the parts answer it; `scripts/second-host-clickthrough.mjs`
888
930
  runs the same rules (`--axe <axe.min.js>`).
889
931
 
890
- - **The right-click menu lives on `<body>`, and a host may move it into its
891
- own landmarks.** On the body the box is page content outside every landmark
892
- (axe `region`, a best-practice rule). It is not wrapped in a region or a
893
- dialog to satisfy that: a menu is neither. A host that wants it inside its
894
- landmarks portals it into its editor's root, through the check:
932
+ - **The right-click menu is the shell's `ContextMenu` (since 1.0.0).** It is
933
+ portalled to `<body>` by the shell and kept inside the shell window that
934
+ holds what was right-clicked (pass it as `owner`). It is only ever a
935
+ `role="menu"`, never wrapped in a region or a dialog to satisfy axe's
936
+ `region`: a menu is neither. `menuPortalTarget`, `ES_MENU_SKIN` and the
937
+ `portalTo` / `skin` / `maxHeight` behaviour are gone (the props are
938
+ accepted and ignored):
895
939
 
896
940
  ```tsx
897
- import { EaselContextMenu, menuPortalTarget } from 'efficient-easel/menu';
941
+ import { EaselContextMenu } from 'efficient-easel/menu';
898
942
 
899
- <EaselContextMenu portalTo={menuPortalTarget(rootRef.current)} … />
943
+ <EaselContextMenu x={e.clientX} y={e.clientY} owner={e.currentTarget} items={items} onClose={close} />
900
944
  ```
901
945
 
902
- The box is `position: fixed` at the pointer, and an ancestor that is
903
- transformed, filtered or contained places and clips a fixed box inside it.
904
- `menuPortalTarget` gives the root back only when none does, and `null` (the
905
- body) otherwise, so a root that is scaled on one screen and not on another
906
- is right on both. The body stays the default because it is the one place
907
- that cannot go wrong. The root's stacking is the host's: the box is
908
- `z-index: 9999` inside it.
946
+ Or the shell's own pieces, with the editors' item list as the rows:
947
+
948
+ ```tsx
949
+ import { ContextMenu, useContextMenu } from 'efficient-shell/ui';
950
+ import { EaselMenuItems } from 'efficient-easel/menu';
951
+
952
+ {menu.state && <ContextMenu {...menu.props}><EaselMenuItems items={items} onClose={menu.close} /></ContextMenu>}
953
+ ```
909
954
  - **A dropdown's open list has a name.** In a settings row
910
955
  (`InspectorField`) the list is `aria-labelledby` the row's own label. A
911
956
  `FieldSelect` on its own is named by `ariaLabelledBy` or `ariaLabel`, and
@@ -1055,6 +1100,19 @@ selected block by the menu's rules (`nodeKeys`, on unless the layer says
1055
1100
  `createEaselHost({ claimEscape })`, so the menu, Add, a field's pick and a
1056
1101
  rename box take Escape first.
1057
1102
 
1103
+ Add and the panels speak of the layer's own nodes (0.106.0). Add's groups are
1104
+ keyed apart (`AddGroup.key`, `efficient-easel/drag`), so a container may offer
1105
+ its blocks grouped under the catalogue's headings; "Adding to …" and
1106
+ "Inside …" name a node as its Layers row does where the layer says
1107
+ (`blockLook.rowName`); a block a layer lays down in a section of its own at a
1108
+ gap (`newSectionNode`) lands selected; an empty slot's canvas cell selects the
1109
+ slot as its row does, in `screen.canvasAddBlock`'s words; and the canvas's
1110
+ "Add section" pill keeps off blocks. `storageNamespace` names where widths
1111
+ and folds are kept (`<namespace>-panel-widths`,
1112
+ `<namespace>-collapsed-sections`; the website's keys without it), and
1113
+ `screen.railMedia`, `screen.leftPanel` and `screen.resizeLeftPanel` are the
1114
+ rail's Media button, the left panel's accessible name and its grip's.
1115
+
1058
1116
  Two rulings of the owner's (Victor Mau, 2026-10-09) change what every host
1059
1117
  gets, the website included: a held block PASTES ACROSS LISTS — into any list
1060
1118
  on the way up from the node pointed at that a drag of it could land in, by the
@@ -1074,6 +1132,22 @@ last word (the campaign designer's sections are named by their `label`). And a
1074
1132
  host may say the screen's toasts itself (R17, `ScreenParts.notify(message,
1075
1133
  tone)`): every line goes to it and the screen draws none.
1076
1134
 
1135
+ The parity round with the old campaign designer (0.106.0) changes what every
1136
+ host gets as well. ONE write gate: a document is read-only for the screen when
1137
+ it is frozen, somebody else holds it, or a save was refused (somebody else's
1138
+ save moved it on); then Undo and Redo are drawn DISABLED (owner ruling R5),
1139
+ their keys do nothing, the store takes no edit, the Layers rows draw no trash,
1140
+ eye, grip or rename, and the menus offer only what writes nothing (Select
1141
+ layer, Copy, Copy style). Inside another app's page (`ownsDocument` false) ⌘Z
1142
+ / ⇧⌘Z / ⌘Y are heard in the canvas frame too, and after a click on the
1143
+ editor's ground (its root takes focus, never a tab stop), and never in a text
1144
+ field or an editable region, where they are the field's own. A row's
1145
+ right-click is that row's: a pinned node's row opens its node's menu, a rung's
1146
+ row opens none, and the ground's "Page settings" is `screen.pageSettings`.
1147
+ And `blockRules.stripCopy(node)` cleans every copy the screen makes — the
1148
+ menu's Duplicate and Paste, ⌘V, a saved section put in from Add — of what
1149
+ only its original may hold (the campaign designer's baked picture).
1150
+
1077
1151
  - **Every module is a subpath of its own** (`efficient-easel/screen/lib/session`,
1078
1152
  `efficient-easel/screen/components/ScreenChrome`): hosts and their guards
1079
1153
  import them one by one, and two export different types under one name.
@@ -101,10 +101,12 @@ type LinkPickerProps = {
101
101
  * filter or value the host no longer offers is shown as "(unavailable)" and
102
102
  * kept, never silently replaced.
103
103
  *
104
- * Every list is `FieldSelect`, the editor's one pop-up list — the anchors
105
- * under one heading per page (`FieldOption.group`), a long list with a search
106
- * box — never the browser's `<select>`. The boxes stack, one under the next,
107
- * at every panel width.
104
+ * Every box is the shell's (harness UI-24, efficient-easel#199): `Select`
105
+ * for a list — the anchors under one heading per page (`SelectOptionGroup`) —
106
+ * `SearchableSelect` for a long one, and `Input` for a typed address; never
107
+ * the browser's `<select>`. The boxes stack, one under the next, at every
108
+ * panel width. A long list of anchors names each one's page as its quiet
109
+ * second line: the shell's `SearchableSelect` takes no groups.
108
110
  */
109
111
  declare function LinkPicker({ value, onChange, kinds, rules, filtersFor, label, readOnly }: LinkPickerProps): react.JSX.Element | null;
110
112
 
@@ -1,10 +1,10 @@
1
1
  import { a as Autosave, A as AutosaveSnapshot, b as AutosaveOptions } from '../engine-DBxp_v6B.js';
2
2
  export { c as AutosaveClock, d as createAutosave } from '../engine-DBxp_v6B.js';
3
- import { a as SaveIndicator } from '../words-D_Lnw1Gr.js';
4
- export { S as SaveWords, s as saveIndicator } from '../words-D_Lnw1Gr.js';
5
- import '../types-CqY85foL.js';
6
- import '../types-DWS-s9z-.js';
7
- import '../wordFit-D4S36PvB.js';
3
+ import { a as SaveIndicator } from '../words-CvFnwGSr.js';
4
+ export { S as SaveWords, s as saveIndicator } from '../words-CvFnwGSr.js';
5
+ import '../types-Dt1CFR1S.js';
6
+ import '../types-CsgMwHHa.js';
7
+ import '../wordFit-7XpmhcaW.js';
8
8
  import 'react';
9
9
  import '../edit-lock/index.js';
10
10
  import '../panelWidths-D9hTLR25.js';
@@ -20,7 +20,9 @@
20
20
  * at the read, not a declaration on a root: a host may set the variables on an
21
21
  * ancestor of its own, and a default declared lower down would shadow them.
22
22
  *
23
- * Light only (owner, 2026-08-06: no dark mode in the editor or the designer).
23
+ * It follows the shell's theme, dark included (UI-24, efficient-harness#117):
24
+ * every colour is an `--es-*` token, and those resolve to efficient-shell's.
25
+ * The 2026-08-06 "light only" ruling is reversed.
24
26
  */
25
27
 
26
28
  /* ── Top bar ─────────────────────────────────────────────────────────────── */
@@ -93,130 +95,31 @@
93
95
  margin: 0 4px;
94
96
  }
95
97
 
96
- /* ── Icon buttons ────────────────────────────────────────────────────────── */
97
- .es-iconbtn {
98
- width: 30px;
99
- height: 30px;
100
- border: none;
101
- background: transparent;
102
- border-radius: 7px;
103
- cursor: pointer;
104
- color: var(--es-icon);
105
- display: flex;
106
- align-items: center;
107
- justify-content: center;
108
- padding: 0;
109
- transition: background 0.12s, color 0.12s;
110
- text-decoration: none;
111
- }
112
- .es-iconbtn:hover:not(:disabled) {
113
- background: var(--es-hover);
114
- }
115
- .es-iconbtn:disabled {
116
- color: var(--es-disabled);
117
- cursor: default;
118
- }
119
- .es-iconbtn--active {
120
- color: var(--es-text-strong);
121
- }
122
- .es-iconbtn--inactive {
123
- color: var(--es-disabled);
124
- }
125
- .es-iconbtn svg {
98
+ /* ── Icon buttons, the device switch and the bar's buttons ──────────────── */
99
+ /* The shell's IconButton, Segmented and Button since efficient-easel issue 199 step 4
100
+ (harness UI-24): their look is the shell's, never redrawn here — a toggle's
101
+ pressed look included (`aria-pressed`, efficient-shell 4.164.0). What stays
102
+ is the glyph size inside. */
103
+ .es-iconbtn svg,
104
+ .es-insp-iconbtn svg {
126
105
  width: 16px;
127
106
  height: 16px;
128
107
  }
129
-
130
- /* ── Device switch ───────────────────────────────────────────────────────── */
131
- .es-devseg {
132
- display: flex;
133
- align-items: center;
134
- gap: 2px;
135
- background: var(--es-track-bg);
136
- border-radius: 9px;
137
- padding: 2px;
138
- }
139
- .es-devseg button {
140
- width: 30px;
141
- height: 26px;
142
- border: none;
143
- background: transparent;
144
- border-radius: 6px;
145
- cursor: pointer;
146
- color: var(--es-icon);
147
- display: flex;
148
- align-items: center;
149
- justify-content: center;
150
- padding: 0;
151
- transition: background 0.12s, color 0.12s, box-shadow 0.12s;
152
- }
153
- .es-devseg button.is-active {
154
- background: var(--es-raised);
155
- color: var(--es-text-strong);
156
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.14);
108
+ .es-devseg-glyph {
109
+ display: inline-flex;
157
110
  }
158
- .es-devseg button svg {
111
+ .es-devseg-glyph svg {
159
112
  width: 15px;
160
113
  height: 15px;
161
114
  }
162
115
 
163
- /* ── Top-bar buttons ─────────────────────────────────────────────────────── */
164
- .es-btn-ghost {
165
- display: flex;
166
- align-items: center;
167
- gap: 8px;
168
- padding: 8px 12px;
169
- border: 1px solid var(--es-btn-border);
170
- background: var(--es-panel);
171
- border-radius: 8px;
172
- cursor: pointer;
173
- font: inherit;
174
- font-size: 13px;
175
- font-weight: 500;
176
- color: var(--es-btn-text);
177
- white-space: nowrap;
178
- transition: background 0.12s;
179
- text-decoration: none;
180
- }
181
- .es-btn-ghost:hover:not(:disabled) {
182
- background: var(--es-hover);
183
- }
184
- .es-btn-ghost:disabled {
185
- color: var(--es-disabled);
186
- border-color: var(--es-btn-border);
187
- background: var(--es-panel);
188
- cursor: default;
189
- }
190
- .es-btn-ghost:focus-visible {
191
- outline: 2px solid var(--es-accent);
192
- outline-offset: 2px;
193
- }
194
- /* The ONE primary button in the bar (Publish on the website). */
195
- .es-btn-primary {
196
- padding: 8px 16px;
197
- border: none;
198
- background: var(--es-accent);
199
- border-radius: 8px;
200
- cursor: pointer;
201
- font: inherit;
202
- font-size: 13px;
203
- font-weight: 600;
204
- color: var(--es-on-accent);
205
- white-space: nowrap;
206
- box-shadow: 0 1px 2px var(--es-accent-shadow);
207
- transition: filter 0.12s;
208
- }
209
- .es-btn-primary:hover {
210
- filter: brightness(1.06);
211
- }
212
-
213
116
  /* ── Save pill ───────────────────────────────────────────────────────────── */
214
117
  .es-savestate {
215
118
  display: inline-flex;
216
119
  align-items: center;
217
120
  gap: 8px;
218
121
  padding: 4px 12px;
219
- border-radius: 8px;
122
+ border-radius: var(--radius-lg);
220
123
  font-size: 12px;
221
124
  font-weight: 500;
222
125
  white-space: nowrap;
@@ -248,26 +151,13 @@
248
151
  .es-savestate.is-error {
249
152
  color: var(--es-danger);
250
153
  }
154
+ /* The autosave switch (the shell's Switch) with the pill as its label. */
251
155
  .es-savestate-toggle {
252
- appearance: none;
253
- -webkit-appearance: none;
254
- border: 0;
255
- background: none;
256
- padding: 0;
257
- margin: 0;
258
- font: inherit;
259
- cursor: pointer;
260
- border-radius: 8px;
261
- }
262
- .es-savestate-toggle:focus-visible {
263
- outline: none;
264
- box-shadow: 0 0 0 2px var(--es-accent-shadow);
265
- }
266
- .es-savestate-toggle .es-savestate {
267
- border: 1px solid transparent;
156
+ display: inline-flex;
157
+ align-items: center;
268
158
  }
269
- .es-savestate-toggle:hover .es-savestate {
270
- background: var(--es-hover);
159
+ .es-savestate-toggle label {
160
+ cursor: pointer;
271
161
  }
272
162
  /* Paused: boxed, so it reads as a switch turned off, not a passive label. */
273
163
  .es-savestate.is-off {
@@ -357,48 +247,19 @@
357
247
  }
358
248
 
359
249
  /* ── Resize grips ────────────────────────────────────────────────────────── */
360
- /* An absolute child of the panel row, pinned over the panel↔canvas seam from the
361
- same width vars the panels read. The 9px hit area is invisible at rest; a 2px
362
- bar on the seam tints to the accent on hover and focus. The host's panel row
363
- must be `position: relative`. */
364
- .es-resizer {
365
- position: absolute;
366
- top: 0;
367
- bottom: 0;
368
- width: 9px;
369
- z-index: 5;
370
- display: flex;
371
- cursor: col-resize;
372
- touch-action: none;
373
- }
374
- .es-resizer--left {
375
- left: var(--es-left-w, 288px);
376
- justify-content: flex-start;
377
- }
378
- .es-resizer--right {
379
- right: var(--es-right-w, 320px);
380
- justify-content: flex-end;
381
- }
382
- .es-resizer-bar {
383
- width: 2px;
384
- border-radius: 2px;
385
- background: transparent;
386
- transition: background 0.12s;
387
- }
388
- .es-resizer:hover .es-resizer-bar,
389
- .es-resizer:focus-visible .es-resizer-bar {
390
- background: var(--es-accent);
391
- }
392
- .es-resizer:focus {
393
- outline: none;
394
- }
395
- body.es-col-resizing,
396
- body.es-col-resizing * {
397
- cursor: col-resize !important;
398
- user-select: none !important;
399
- }
400
- body.es-col-resizing iframe {
401
- pointer-events: none !important;
250
+ /* The grip is the shell's `ResizeHandle` (UI-24): its hit area, its bar on
251
+ hover and focus, its cursor during a drag. Only WHERE it sits is the
252
+ editor's: an absolute child of the panel ROW, pinned over the panel↔canvas
253
+ seam from the same width vars the panels read, instead of the shell's
254
+ default edge of its parent. The host's panel row must be `position:
255
+ relative`. (Two classes, to outrank the shell's own edge utility.) */
256
+ .es-resizer.es-resizer--left {
257
+ left: calc(var(--es-left-w, 288px) - 4px);
258
+ right: auto;
259
+ }
260
+ .es-resizer.es-resizer--right {
261
+ right: calc(var(--es-right-w, 320px) - 4px);
262
+ left: auto;
402
263
  }
403
264
 
404
265
  /* ── Inspector head ──────────────────────────────────────────────────────── */
@@ -414,24 +275,16 @@ body.es-col-resizing iframe {
414
275
  min-width: 0;
415
276
  padding: 12px 16px 0;
416
277
  }
417
- .es-insp-up-btn {
418
- display: inline-flex;
419
- align-items: baseline;
420
- gap: 4px;
278
+ /* The link is the shell's Button (`link`) in its Tooltip's wrapper; only its
279
+ truncation is laid out here — a long name is cut inside the panel. */
280
+ span:has(> .es-insp-up-btn) {
421
281
  max-width: 100%;
422
282
  min-width: 0;
423
- padding: 0;
424
- border: 0;
425
- background: none;
426
- font: inherit;
427
- font-size: 12px;
428
- color: var(--es-accent);
429
- text-align: left;
430
- cursor: pointer;
431
283
  }
432
- .es-insp-up-btn:focus-visible {
433
- outline: 2px solid var(--es-accent);
434
- outline-offset: 2px;
284
+ .es-insp-up-btn {
285
+ max-width: 100%;
286
+ min-width: 0;
287
+ overflow: hidden;
435
288
  }
436
289
  .es-insp-up-arrow {
437
290
  flex: none;
@@ -470,7 +323,9 @@ body.es-col-resizing iframe {
470
323
  row 2 [Cancel]
471
324
  The actions box steps aside (`display: contents`) so its buttons can sit in
472
325
  different rows; every glyph button is locked to row 1 and takes the next
473
- free column, so a head with several tools keeps them side by side. */
326
+ free column, so a head with several tools keeps them side by side. Each
327
+ button sits in the shell Tooltip's wrapper, so the grid places the wrapper
328
+ that holds it (`:has(> …)`). */
474
329
  @container (width < 260px) {
475
330
  .es-insp-head:has(.es-insp-textbtn) {
476
331
  display: grid;
@@ -482,13 +337,13 @@ body.es-col-resizing iframe {
482
337
  }
483
338
  .es-insp-head:has(.es-insp-textbtn) > .es-insp-chip,
484
339
  .es-insp-head:has(.es-insp-textbtn) > .es-insp-titles,
485
- .es-insp-head:has(.es-insp-textbtn) .es-insp-iconbtn {
340
+ .es-insp-head:has(.es-insp-textbtn) .es-insp-actions > :has(> .es-insp-iconbtn) {
486
341
  grid-row: 1;
487
342
  }
488
343
  .es-insp-head:has(.es-insp-textbtn) > .es-insp-actions {
489
344
  display: contents;
490
345
  }
491
- .es-insp-head:has(.es-insp-textbtn) .es-insp-textbtn {
346
+ .es-insp-head:has(.es-insp-textbtn) .es-insp-actions > :has(> .es-insp-textbtn) {
492
347
  grid-row: 2;
493
348
  grid-column: 2;
494
349
  justify-self: start;
@@ -500,7 +355,7 @@ body.es-col-resizing iframe {
500
355
  .es-insp-chip {
501
356
  width: 26px;
502
357
  height: 26px;
503
- border-radius: 6px;
358
+ border-radius: var(--radius-md);
504
359
  display: flex;
505
360
  align-items: center;
506
361
  justify-content: center;
@@ -537,47 +392,12 @@ body.es-col-resizing iframe {
537
392
  gap: 2px;
538
393
  flex: none;
539
394
  }
540
- .es-insp-iconbtn {
541
- width: 28px;
542
- height: 28px;
543
- border: none;
544
- background: transparent;
545
- border-radius: 7px;
546
- cursor: pointer;
547
- color: var(--es-icon);
548
- display: flex;
549
- align-items: center;
550
- justify-content: center;
551
- padding: 0;
552
- transition: background 0.12s, color 0.12s;
553
- }
554
- .es-insp-iconbtn:hover {
555
- background: var(--es-hover);
556
- color: var(--es-text-strong);
557
- }
558
- .es-insp-iconbtn--danger:hover {
559
- background: var(--es-danger-tint);
560
- color: var(--es-danger);
561
- }
562
- .es-insp-iconbtn[aria-expanded="true"] {
563
- background: var(--es-accent-tint);
564
- color: var(--es-accent);
565
- }
566
-
567
395
  /* A side panel's head has two pieces besides its glyph buttons (MediaPanelHead,
568
396
  card [MERGE] One Media Panel Head, 2026-10-06; until then each editor's own
569
397
  sheet drew them, as `.sf-head-*` and `.cd-head-*`). A MODE's way out is a
570
- word ("Cancel", "Done") on the ghost button, drawn to the 28px of the head's
571
- icon buttons so the row keeps one height (its sides are the scale's 12; both
572
- editors drew 10, off the 2/4/8/12/16/32 scale); the hairline parts the
398
+ word ("Cancel", "Done") on the shell's small Button (`.es-insp-textbtn`
399
+ names it for the narrow layout above); the hairline parts the
573
400
  panel's own tools from the Hide panel button that ends every head. */
574
- .es-btn-ghost.es-insp-textbtn {
575
- height: 28px;
576
- padding: 0 12px;
577
- font-size: 12.5px;
578
- border-radius: 7px;
579
- white-space: nowrap;
580
- }
581
401
  .es-insp-divider {
582
402
  width: 1px;
583
403
  height: 16px;
@@ -588,59 +408,16 @@ body.es-col-resizing iframe {
588
408
 
589
409
  /* ── Edit notice — somebody else is in here, or a save was refused ───────── */
590
410
  /* A bar under the top bar, never a dialog (blueprint 2.5): the operator has to
591
- be able to read and copy from the screen it warns them about. No token of its
592
- own — the amber is the shell's amber tint (`--es-preview-tint`) eased into the
593
- panel white, edged with `--es-warning`, so a re-themed editor re-themes it. */
411
+ be able to read and copy from the screen it warns them about. */
412
+ /* The shell's Banner, with the shell's Buttons as its action (easel issue 199, step 4):
413
+ the wrapper only places it under the top bar. */
594
414
  .es-notice {
595
415
  box-sizing: border-box;
596
- display: flex;
597
- flex-wrap: wrap;
598
- align-items: center;
599
- gap: 8px 12px;
600
416
  padding: 8px 16px;
601
417
  font-family: var(--es-font);
602
- font-size: 13px;
603
- line-height: 1.45;
604
- color: var(--es-text-secondary);
605
- background: var(--es-app-bg);
606
- border-bottom: 1px solid var(--es-divider);
607
- }
608
- .es-notice.is-warning {
609
- color: var(--es-text);
610
- background: color-mix(in srgb, var(--es-preview-tint) 70%, var(--es-panel));
611
- border-bottom-color: color-mix(in srgb, var(--es-warning) 30%, var(--es-preview-tint));
612
- }
613
- .es-notice-text {
614
- flex: 1 1 320px;
615
- min-width: 0;
616
- }
617
- .es-notice-text strong {
618
- font-weight: 600;
619
- color: var(--es-text-strong);
620
418
  }
621
419
  .es-notice-actions {
622
420
  display: flex;
623
421
  flex-wrap: wrap;
624
422
  gap: 8px;
625
423
  }
626
- .es-notice-btn {
627
- appearance: none;
628
- -webkit-appearance: none;
629
- font: inherit;
630
- font-size: 12px;
631
- font-weight: 500;
632
- padding: 4px 12px;
633
- border: 1px solid var(--es-btn-border);
634
- border-radius: var(--es-field-radius);
635
- background: var(--es-raised);
636
- color: var(--es-btn-text);
637
- white-space: nowrap;
638
- cursor: pointer;
639
- }
640
- .es-notice-btn:hover {
641
- background: var(--es-hover);
642
- }
643
- .es-notice-btn:focus-visible {
644
- outline: 2px solid var(--es-accent);
645
- outline-offset: 1px;
646
- }