efficient-easel 0.106.0 → 1.0.1

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 (170) hide show
  1. package/README.md +70 -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-4SWZAYKI.js +5 -0
  8. package/dist/chunk-5545A3PC.js +4 -0
  9. package/dist/chunk-7SQF6LZL.js +1 -0
  10. package/dist/chunk-BXMXTO36.js +1 -0
  11. package/dist/chunk-EA2HTX37.js +5 -0
  12. package/dist/{chunk-VINYARYJ.js → chunk-FER7FCAG.js} +1 -1
  13. package/dist/chunk-FPAVNX3Y.js +6 -0
  14. package/dist/{chunk-LY4K2ECT.js → chunk-GXFAQ7JN.js} +1 -1
  15. package/dist/chunk-HMJS344F.js +1 -0
  16. package/dist/{chunk-JWMNPRTT.js → chunk-I64QM6DU.js} +1 -1
  17. package/dist/chunk-JLTZQ4US.js +1 -0
  18. package/dist/chunk-K56YDHYQ.js +1 -0
  19. package/dist/chunk-LOMEH6DG.js +1 -0
  20. package/dist/{chunk-FS5BLARH.js → chunk-MAEXP3KE.js} +1 -1
  21. package/dist/chunk-MJM6SFD3.js +3 -0
  22. package/dist/chunk-NLRW4BGG.js +8 -0
  23. package/dist/chunk-O5F46JWM.js +64 -0
  24. package/dist/chunk-ONRYLR2D.js +2 -0
  25. package/dist/{chunk-WTMHT7BI.js → chunk-ORR3Q4WV.js} +1 -1
  26. package/dist/chunk-P2VEMZH5.js +1 -0
  27. package/dist/{chunk-MHKBLPQ2.js → chunk-PT6V53OO.js} +1 -1
  28. package/dist/{chunk-6WH7FA2M.js → chunk-PYCLHOSA.js} +2 -2
  29. package/dist/chunk-Q27BB5ZC.js +14 -0
  30. package/dist/chunk-QFMJBBKM.js +5 -0
  31. package/dist/chunk-QLD57GBB.js +6 -0
  32. package/dist/chunk-QR4DLZ3P.js +5 -0
  33. package/dist/chunk-QRKKQGUO.js +4 -0
  34. package/dist/chunk-QTPJMJOJ.js +1 -0
  35. package/dist/chunk-RG7PFKCA.js +7 -0
  36. package/dist/chunk-TJ7YIRWQ.js +21 -0
  37. package/dist/{chunk-3P6AMTOS.js → chunk-VPFNZ7TB.js} +1 -1
  38. package/dist/chunk-W3XX2CNW.js +16 -0
  39. package/dist/chunk-WDXPWSP7.js +24 -0
  40. package/dist/chunk-X6DQWK2C.js +1 -0
  41. package/dist/chunk-XWR5YVA7.js +9 -0
  42. package/dist/chunk-ZNNATBDC.js +1 -0
  43. package/dist/copy/copy.css +66 -177
  44. package/dist/copy/index.d.ts +0 -2
  45. package/dist/copy/index.js +31 -32
  46. package/dist/drag/drag.css +31 -11
  47. package/dist/drag/index.js +1 -1
  48. package/dist/easel/index.js +1 -1
  49. package/dist/fields/fields.css +35 -304
  50. package/dist/fields/index.d.ts +87 -95
  51. package/dist/fields/index.js +1 -1
  52. package/dist/gold/gold.css +3 -3
  53. package/dist/gold/index.d.ts +6 -5
  54. package/dist/gold/index.js +1 -1
  55. package/dist/index.d.ts +3 -3
  56. package/dist/index.js +1 -1
  57. package/dist/menu/index.d.ts +49 -93
  58. package/dist/menu/index.js +1 -1
  59. package/dist/menu/menu.css +37 -246
  60. package/dist/node-ops/index.d.ts +1 -1
  61. package/dist/panel/index.d.ts +46 -72
  62. package/dist/panel/index.js +17 -18
  63. package/dist/panel/panel.css +143 -372
  64. package/dist/pick/index.d.ts +52 -33
  65. package/dist/pick/index.js +7 -7
  66. package/dist/pick/pick.css +19 -150
  67. package/dist/rail/index.d.ts +48 -90
  68. package/dist/rail/index.js +1 -1
  69. package/dist/rail/rail.css +21 -108
  70. package/dist/screen/components/AddBlockRow.d.ts +8 -2
  71. package/dist/screen/components/AddBlockRow.js +1 -1
  72. package/dist/screen/components/AddChildRow.d.ts +4 -3
  73. package/dist/screen/components/AddChildRow.js +1 -1
  74. package/dist/screen/components/AddSidePanel.js +1 -1
  75. package/dist/screen/components/MediaSidePanel.d.ts +1 -1
  76. package/dist/screen/components/MediaSidePanel.js +1 -1
  77. package/dist/screen/components/NodeContextMenu.d.ts +12 -8
  78. package/dist/screen/components/NodeContextMenu.js +1 -1
  79. package/dist/screen/components/PageEditGuardNotices.js +1 -1
  80. package/dist/screen/components/ScreenChrome.js +1 -1
  81. package/dist/screen/components/ScreenDocumentContext.d.ts +1 -1
  82. package/dist/screen/components/ScreenDocumentContext.js +1 -1
  83. package/dist/screen/components/ScreenMount.d.ts +7 -7
  84. package/dist/screen/components/ScreenMount.js +1 -1
  85. package/dist/screen/components/ScreenStoreProvider.d.ts +7 -7
  86. package/dist/screen/components/ScreenStoreProvider.js +1 -1
  87. package/dist/screen/components/SectionScreenShell.js +16 -16
  88. package/dist/screen/components/SidePanelHead.d.ts +4 -2
  89. package/dist/screen/components/SidePanelHead.js +1 -1
  90. package/dist/screen/components/UiIcon.d.ts +10 -26
  91. package/dist/screen/components/UiIcon.js +2 -1
  92. package/dist/screen/components/panel-widths.js +1 -1
  93. package/dist/screen/components/usePartBridge.d.ts +7 -7
  94. package/dist/screen/components/usePartBridge.js +1 -1
  95. package/dist/screen/index.d.ts +7 -7
  96. package/dist/screen/index.js +1 -1
  97. package/dist/screen/lib/admin-bridge.d.ts +1 -1
  98. package/dist/screen/lib/admin-bridge.js +1 -1
  99. package/dist/screen/lib/chrome-theme.d.ts +20 -12
  100. package/dist/screen/lib/chrome-theme.js +1 -1
  101. package/dist/screen/lib/document-addressing.d.ts +1 -1
  102. package/dist/screen/lib/host-chrome.d.ts +1 -1
  103. package/dist/screen/lib/host.d.ts +1 -1
  104. package/dist/screen/lib/key-scope.js +1 -1
  105. package/dist/screen/lib/links/link-filters.d.ts +1 -1
  106. package/dist/screen/lib/media-panel.d.ts +1 -1
  107. package/dist/screen/lib/media-picker.d.ts +1 -1
  108. package/dist/screen/lib/page-lock.d.ts +1 -1
  109. package/dist/screen/lib/puck/style-clipboard.d.ts +7 -7
  110. package/dist/screen/lib/puck/use-section-ops.d.ts +16 -0
  111. package/dist/screen/lib/puck/use-section-ops.js +1 -1
  112. package/dist/screen/lib/screen-parts.d.ts +14 -8
  113. package/dist/screen/lib/screen-parts.js +1 -1
  114. package/dist/screen/lib/section-templates.d.ts +1 -1
  115. package/dist/screen/lib/session-context.d.ts +1 -1
  116. package/dist/screen/lib/session.d.ts +1 -1
  117. package/dist/screen/screen.css +33 -206
  118. package/dist/shell/index.d.ts +134 -118
  119. package/dist/shell/index.js +1 -1
  120. package/dist/shell/shell.css +130 -88
  121. package/dist/strings/catalogue.d.ts +5 -5
  122. package/dist/strings/catalogue.js +1 -1
  123. package/dist/strings/index.d.ts +5 -5
  124. package/dist/strings/index.js +1 -1
  125. package/dist/style-clipboard/index.d.ts +1 -1
  126. package/dist/text-styles/index.d.ts +12 -3
  127. package/dist/text-styles/index.js +33 -38
  128. package/dist/text-styles/text-styles.css +39 -71
  129. package/dist/tree/index.d.ts +49 -44
  130. package/dist/tree/index.js +1 -1
  131. package/dist/tree/tree.css +31 -653
  132. package/dist/{types-DJDBtdB2.d.ts → types-BqGnUVX5.d.ts} +32 -32
  133. package/dist/types-BsyPh6EW.d.ts +99 -0
  134. package/dist/{types-DWS-s9z-.d.ts → types-CsgMwHHa.d.ts} +14 -11
  135. package/dist/{types-CqY85foL.d.ts → types-Dt1CFR1S.d.ts} +4 -2
  136. package/dist/{wordFit-D4S36PvB.d.ts → wordFit-7XpmhcaW.d.ts} +3 -5
  137. package/dist/words-Cl9xMUYY.d.ts +41 -0
  138. package/dist/{words-D_Lnw1Gr.d.ts → words-CvFnwGSr.d.ts} +1 -1
  139. package/package.json +5 -1
  140. package/dist/chunk-2UX75JDP.js +0 -1
  141. package/dist/chunk-37OCQYNX.js +0 -65
  142. package/dist/chunk-3EDSDPIP.js +0 -1
  143. package/dist/chunk-3QVB4AG2.js +0 -3
  144. package/dist/chunk-3WSPDAK2.js +0 -1
  145. package/dist/chunk-5MZ3RNQQ.js +0 -13
  146. package/dist/chunk-5ZBBXZPV.js +0 -6
  147. package/dist/chunk-BZ5ASXOL.js +0 -1
  148. package/dist/chunk-C6BJB5AC.js +0 -2
  149. package/dist/chunk-DX4XELD6.js +0 -18
  150. package/dist/chunk-EOJNJCMJ.js +0 -16
  151. package/dist/chunk-EWVJSRRY.js +0 -6
  152. package/dist/chunk-H24N5TMZ.js +0 -11
  153. package/dist/chunk-I6T4TZ5Q.js +0 -8
  154. package/dist/chunk-KTVH2ZRL.js +0 -15
  155. package/dist/chunk-M7N2V4TG.js +0 -2
  156. package/dist/chunk-MS4QRBJ6.js +0 -5
  157. package/dist/chunk-ND55LTCA.js +0 -5
  158. package/dist/chunk-OWBO4WKA.js +0 -0
  159. package/dist/chunk-P6O363EE.js +0 -6
  160. package/dist/chunk-PBSEUZD6.js +0 -1
  161. package/dist/chunk-RW4XBUUW.js +0 -1
  162. package/dist/chunk-UEVTQBM7.js +0 -10
  163. package/dist/chunk-VCQVNPTG.js +0 -22
  164. package/dist/chunk-WZ7TGAAT.js +0 -3
  165. package/dist/chunk-YC4TB3CJ.js +0 -25
  166. package/dist/chunk-Z6SOLDWV.js +0 -1
  167. package/dist/chunk-ZFDCDJNZ.js +0 -16
  168. package/dist/chunk-ZNS7727Y.js +0 -12
  169. package/dist/types-Djdq6Xkd.d.ts +0 -138
  170. package/dist/words-DgHcY5qB.d.ts +0 -33
@@ -1 +1 @@
1
- export{FieldText,Inspector,InspectorField,LinkPicker,ListField,bucketsOf,copyItem,insertAt,labelOf,moveTo,planFields,removeAt}from"../chunk-VCQVNPTG.js";export{FieldSelect,FieldSlider,optionFor}from"../chunk-EOJNJCMJ.js";import"../chunk-ZFDCDJNZ.js";import"../chunk-3EDSDPIP.js";export{useResolvedFields}from"../chunk-YRLPFZW3.js";import"../chunk-KTVH2ZRL.js";import"../chunk-ZNS7727Y.js";import"../chunk-QTLAVDJE.js";export{FieldNumber,clampNumber,parseTyped,useNumberEntry}from"../chunk-EWVJSRRY.js";import"../chunk-FPOKPNO4.js";import"../chunk-P6O363EE.js";import"../chunk-MFIRYGHW.js";import"../chunk-BZ5ASXOL.js";import"../chunk-67GNT6PF.js";import"../chunk-L5KW4HG5.js";import"../chunk-KRXQM65X.js";import"../chunk-NKB3KVAB.js";
1
+ export{FieldText,Inspector,InspectorField,LinkPicker,ListField,bucketsOf,copyItem,insertAt,labelOf,moveTo,planFields,removeAt}from"../chunk-WDXPWSP7.js";export{FieldSelect,FieldSlider,optionFor}from"../chunk-NLRW4BGG.js";import"../chunk-XWR5YVA7.js";import"../chunk-Q27BB5ZC.js";import"../chunk-K56YDHYQ.js";import"../chunk-QTLAVDJE.js";export{useResolvedFields}from"../chunk-YRLPFZW3.js";import"../chunk-EA2HTX37.js";export{FieldNumber}from"../chunk-7SQF6LZL.js";import"../chunk-QLD57GBB.js";import"../chunk-FPOKPNO4.js";import"../chunk-MFIRYGHW.js";import"../chunk-67GNT6PF.js";import"../chunk-P2VEMZH5.js";import"../chunk-L5KW4HG5.js";import"../chunk-KRXQM65X.js";import"../chunk-NKB3KVAB.js";
@@ -20,9 +20,9 @@
20
20
  .es-gold {
21
21
  /* — tokens (see tokens.ts / goldCssVars) — */
22
22
  --es-gold-accent: var(--gold, #c9a461);
23
- --es-gold-quiet: var(--es-muted, #6b7280);
24
- --es-gold-caret: var(--es-text, #111827);
25
- --es-gold-placeholder: var(--es-field-placeholder, #9ca3af);
23
+ --es-gold-quiet: var(--es-muted, var(--ink-faint));
24
+ --es-gold-caret: var(--es-text, var(--ink-strongest));
25
+ --es-gold-placeholder: var(--es-field-placeholder, var(--ink-faintest));
26
26
  --es-gold-delim-opacity: 0.35;
27
27
  --es-gold-delim-resting-opacity: 0.25;
28
28
  --es-gold-bold-shadow: 0 0 0.4px currentColor;
@@ -200,8 +200,9 @@ declare function goldSegments(value: string, rules: readonly InlineRule[]): Gold
200
200
  * storefront's light-theme value (`efficient-storefront/app/globals.css` → `--gold:
201
201
  * #c9a461`), so the mark in the box matches the mark on the page;
202
202
  * - the caret and the placeholder defer to the shell layer's `--es-text` /
203
- * `--es-field-placeholder` (see `shell/tokens.ts`) with the same values as
204
- * their fallbacks;
203
+ * `--es-field-placeholder` (see `shell/tokens.ts`), falling back to the
204
+ * efficient-shell tokens those resolve to (UI-24), for a box drawn outside
205
+ * an `.es-editor` root;
205
206
  * - the FOCUSED dim is the rail's existing disabled opacity (0.35), not a new
206
207
  * number. Its RESTING sibling (0.25) is the one value in this file picked by
207
208
  * eye rather than borrowed: nothing else in the product paints a mark that
@@ -225,12 +226,12 @@ declare const goldTokens: {
225
226
  readonly accent: "var(--gold, #c9a461)";
226
227
  /** Quiet — `^^phrase^^`. Colour only: the page draws it smaller, but a
227
228
  * smaller glyph down here would pull the layer off the caret. */
228
- readonly quiet: "var(--es-muted, #6b7280)";
229
+ readonly quiet: "var(--es-muted, var(--ink-faint))";
229
230
  /** The caret. Set explicitly because the box's own text is transparent, and
230
231
  * `caret-color: auto` would make the caret transparent with it. */
231
- readonly caret: "var(--es-text, #111827)";
232
+ readonly caret: "var(--es-text, var(--ink-strongest))";
232
233
  /** The hint shown while the value is empty. */
233
- readonly placeholder: "var(--es-field-placeholder, #9ca3af)";
234
+ readonly placeholder: "var(--es-field-placeholder, var(--ink-faintest))";
234
235
  /** How far the asterisks are dimmed WHILE THE BOX HAS FOCUS — the moment they
235
236
  * are what the merchant is aiming at, so this is the louder of the pair.
236
237
  * Out of focus they drop to {@link goldTokens.delimiterRestingOpacity}. */
@@ -1 +1 @@
1
- export{GoldLayer,GoldTextInput,goldSegments}from"../chunk-2FZJWH24.js";import"../chunk-546BBOPE.js";var e={accent:"var(--gold, #c9a461)",quiet:"var(--es-muted, #6b7280)",caret:"var(--es-text, #111827)",placeholder:"var(--es-field-placeholder, #9ca3af)",delimiterOpacity:"0.35",delimiterRestingOpacity:"0.25",boldShadow:"0 0 0.4px currentColor",italicDecoration:"underline dotted",strikeDecoration:"line-through",pad:"0"},t={accent:"--es-gold-accent",quiet:"--es-gold-quiet",caret:"--es-gold-caret",placeholder:"--es-gold-placeholder",delimiterOpacity:"--es-gold-delim-opacity",delimiterRestingOpacity:"--es-gold-delim-resting-opacity",boldShadow:"--es-gold-bold-shadow",italicDecoration:"--es-gold-italic-decoration",strikeDecoration:"--es-gold-strike-decoration",pad:"--es-gold-pad"};export{t as goldCssVars,e as goldTokens};
1
+ export{GoldLayer,GoldTextInput,goldSegments}from"../chunk-2FZJWH24.js";import"../chunk-546BBOPE.js";var e={accent:"var(--gold, #c9a461)",quiet:"var(--es-muted, var(--ink-faint))",caret:"var(--es-text, var(--ink-strongest))",placeholder:"var(--es-field-placeholder, var(--ink-faintest))",delimiterOpacity:"0.35",delimiterRestingOpacity:"0.25",boldShadow:"0 0 0.4px currentColor",italicDecoration:"underline dotted",strikeDecoration:"line-through",pad:"0"},t={accent:"--es-gold-accent",quiet:"--es-gold-quiet",caret:"--es-gold-caret",placeholder:"--es-gold-placeholder",delimiterOpacity:"--es-gold-delim-opacity",delimiterRestingOpacity:"--es-gold-delim-resting-opacity",boldShadow:"--es-gold-bold-shadow",italicDecoration:"--es-gold-italic-decoration",strikeDecoration:"--es-gold-strike-decoration",pad:"--es-gold-pad"};export{t as goldCssVars,e as goldTokens};
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { EaselRail, EaselRailButton, RailTokens, railCssVars, railTokens } from './rail/index.js';
2
2
  export { AddIcon, LayersIcon, MediaIcon, PageIcon as PagesIcon, IconProps as RailIconProps, SitemapIcon, StylesIcon } from './icons/index.js';
3
- export { W as WordFit } from './wordFit-D4S36PvB.js';
4
- export { E as EaselRailButtonProps, a as EaselRailItem, b as EaselRailOrientation, c as EaselRailProps } from './types-DWS-s9z-.js';
5
- export { SPACING_SCALE, ShellTokens, shellCssVars, shellTokens } from './shell/index.js';
3
+ export { W as WordFit } from './wordFit-7XpmhcaW.js';
4
+ export { E as EaselRailButtonProps, a as EaselRailItem, b as EaselRailOrientation, c as EaselRailProps } from './types-CsgMwHHa.js';
5
+ export { SPACING_SCALE, ShellTokens, shellCssVars, shellDarkTokens, shellTokens } from './shell/index.js';
6
6
  import 'react';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export{SPACING_SCALE,shellCssVars,shellTokens}from"./chunk-2UX75JDP.js";import"./chunk-OWBO4WKA.js";export{EaselRail,EaselRailButton,railCssVars,railTokens}from"./chunk-WZ7TGAAT.js";import"./chunk-2OAZYSWJ.js";import"./chunk-BZ5ASXOL.js";export{AddIcon,LayersIcon,MediaIcon,PageIcon as PagesIcon,SitemapIcon,StylesIcon}from"./chunk-L5KW4HG5.js";
1
+ export{railCssVars,railTokens}from"./chunk-X6DQWK2C.js";export{SPACING_SCALE,shellCssVars,shellDarkTokens,shellTokens}from"./chunk-HMJS344F.js";export{EaselRail,EaselRailButton}from"./chunk-MJM6SFD3.js";import"./chunk-2OAZYSWJ.js";export{AddIcon,LayersIcon,MediaIcon,PageIcon as PagesIcon,SitemapIcon,StylesIcon}from"./chunk-L5KW4HG5.js";
@@ -1,104 +1,60 @@
1
1
  import * as react from 'react';
2
- import { a as EaselContextMenuProps, b as EaselContextMenuSkin } from '../types-Djdq6Xkd.js';
3
- export { E as EaselContextMenuItem } from '../types-Djdq6Xkd.js';
2
+ import { a as EaselContextMenuProps, E as EaselContextMenuItem } from '../types-BsyPh6EW.js';
3
+ export { b as EaselContextMenuSkin } from '../types-BsyPh6EW.js';
4
4
 
5
5
  /**
6
- * The right-click menu box, shared by both editors (card 66400.d).
6
+ * The editors' right-click menu: the shell's `ContextMenu` with an
7
+ * {@link EaselMenuItems} list in it (harness UI-16, UI-24; efficient-easel#199).
7
8
  *
8
- * MOVED HERE, NOT WRITTEN HERE. This is `efficient-admin-portal`'s
9
- * `src/components/RowContextMenu.tsx` — portal placement, viewport edge-flip,
10
- * a one-level submenu with ticks, focus move-in and restore, Escape and
11
- * outside-click — which had been in front of real users on three surfaces
12
- * (price sheets, the goods-issue dialog, the campaign canvas) before the
13
- * storefront editor needed the same affordance. The alternative was those same
14
- * 230 lines written a second time.
9
+ * Up to 0.106.0 this drew a box of its own — moved here from the admin
10
+ * portal's `RowContextMenu` (card 66400.d) with a skin, an edge flip, a
11
+ * one-level flyout and its own focus and Escape handling. All of that is the
12
+ * shell's now (`ContextMenu`, `PopupSubmenu`, efficient-shell 4.163.0), so
13
+ * this name is kept for the hosts that import it and draws nothing itself:
14
+ * placement inside the owning window, the `menu` role, arrow keys, Escape
15
+ * through the window's interceptor and focus in and back are the shell's.
15
16
  *
16
- * ONE THING CHANGED IN THE MOVE, and it is the reason the move is worth doing:
17
- * the box no longer knows what it looks like. The admin version hard-coded ONE
18
- * editor's surface — `glassStyle()` from efficient-shell, plus that portal's
19
- * Tailwind palette — into the only box both editors were ever going to share.
20
- * Here the look is a {@link EaselContextMenuSkin}, the default is the
21
- * storefront's approved one, and the admin passes its own strings in. So this
22
- * module imports nothing but React (`tests/menu-leaf-safety.test.ts`), and the
23
- * admin's rendered markup is byte-identical to what shipped
24
- * (`tests/menu-layer.test.tsx` (e)).
17
+ * `skin`, `maxHeight` and `portalTo` are accepted and ignored (see their
18
+ * notes): the menu wears the shell's look and is portalled by the shell.
25
19
  *
26
- * A CLIENT LEAF, like the gold layer and unlike the rail: it portals, it
27
- * measures and it moves focus, so it has hooks and touches the DOM. A Next-16
28
- * server component may import the module and must render it inside the host's
29
- * client boundary.
20
+ * A CLIENT component: it portals, measures and moves focus.
30
21
  */
31
- declare function EaselContextMenu({ x, y, items, onClose, maxHeight, skin, portalTo, }: EaselContextMenuProps): react.ReactPortal | null;
22
+ declare function EaselContextMenu({ x, y, items, onClose, owner, portalTo }: EaselContextMenuProps): react.JSX.Element | null;
32
23
 
33
24
  /**
34
- * WHERE THE MENU BOX LIVES — the decision, and the one check behind it
35
- * (second host, 2026-10-07).
36
- *
37
- * The box is portalled to `<body>`. A host's axe-core run flags that under
38
- * `region` (a best-practice rule, moderate): a `role="menu"` hanging off the
39
- * body is page content outside every landmark. Two answers were on the table.
40
- *
41
- * A. Wrap the box in something a landmark check lets through — a
42
- * `role="region"`, a `role="dialog"`. REFUSED.
43
- * A menu is neither. A region is a landmark, and one that appears in the
44
- * page's landmark list each time a menu opens is noise for the very
45
- * people landmarks are for; a dialog is announced as one and is expected
46
- * to hold the focus like one. Changing what a thing IS so a checker
47
- * stops reading it is the wrong way round.
48
- *
49
- * B. Portal the box INTO the editor's root, which sits inside whatever
50
- * landmark the host put its editor in. TAKEN,
51
- * as the host's choice (`portalTo`), with this check.
52
- *
53
- * WHY IT IS NOT THE DEFAULT. The box is `position: fixed` and placed at the
54
- * pointer in the VIEWPORT's coordinates. An ancestor that is transformed,
55
- * filtered or contained becomes the containing block of a fixed box inside it:
56
- * the box is then placed against that ancestor and clipped by it — the menu
57
- * opens somewhere the pointer is not, or is cut off. `<body>` is the one place
58
- * that can never happen, the box does not know where a host's root is, and
59
- * every surface that draws it today ships on the body. So the body stays the
60
- * default, and a host that wants the box in its landmarks says where:
61
- *
62
- * <EaselContextMenu portalTo={menuPortalTarget(rootRef.current)} … />
63
- *
64
- * `menuPortalTarget` hands the root back only when a fixed box in it is still
65
- * placed against the viewport, and `null` — the body — otherwise. So a root
66
- * that is scaled on one screen and not on another gets the right answer on
67
- * both, with no rule of the host's own.
68
- *
69
- * Two things the host still owns: the root's stacking (the box is `z-index:
70
- * 9999` INSIDE the root's stacking context, so an overlay the host draws above
71
- * its editor is above the menu too), and its tokens (the default skin names
72
- * `es-editor` on the box, which re-declares the token defaults there — a
73
- * re-themed root passes a skin without it; see `ES_MENU_SKIN`).
74
- *
75
- * Pure DOM, no React, nothing read at import.
76
- */
77
- /**
78
- * The element to hand `EaselContextMenu` as `portalTo` so the box lives inside
79
- * the editor: `root` itself when a fixed box in it is still placed against the
80
- * viewport, `null` (the body, the default) when the root or anything around it
81
- * would place and clip the box instead — or when there is no root yet.
82
- *
83
- * Call it when the menu opens, not once: whether a root is transformed can
84
- * change with the screen (a host that scales its editor to fit).
85
- */
86
- declare function menuPortalTarget(root: Element | null | undefined): HTMLElement | null;
87
-
88
- /**
89
- * The default skin — the storefront editor's approved look, in classes that
90
- * `efficient-easel/menu.css` dresses.
91
- *
92
- * ⚠️ `es-editor` IS LOAD-BEARING AND IS NOT DECORATION. `shell.css` declares
93
- * every `--es-*` on `.es-editor`, deliberately never on `:root`, so importing it
94
- * themes an editor root and leaks nothing into the host page — the campaign
95
- * designer renders INSIDE the admin DOM. The box is portalled to
96
- * `document.body`, which is OUTSIDE that root, so without the class here every
97
- * `var(--es-*)` in `menu.css` resolves to nothing: the background collapses to
98
- * transparent and the border to `currentColor`. The result is markup that is
99
- * present, answers every query a test can ask, and cannot be seen. Pinned by
100
- * `tests/menu-layer.test.tsx` (f).
25
+ * The editors' item list, drawn with the shell's menu pieces (harness UI-16,
26
+ * UI-24; efficient-easel#199 step 4).
27
+ *
28
+ * What a menu OFFERS stays a list of {@link EaselContextMenuItem}s — the node
29
+ * menu, the Layers rows, the canvas block and the list field build theirs from
30
+ * registries, and so do the hosts — and this turns one into the shell's
31
+ * `PopupMenuItem` / `PopupSubmenu` / `PopupMenuDivider` / `PopupMenuLabel`.
32
+ * Render it inside the shell's `ContextMenu` (or `EaselContextMenu`, which
33
+ * does that for you):
34
+ *
35
+ * <ContextMenu {...menu.props}>
36
+ * <EaselMenuItems items={items} onClose={menu.close} />
37
+ * </ContextMenu>
38
+ *
39
+ * `'—'` a `PopupMenuDivider`;
40
+ * `header` a `PopupMenuLabel` (the first names the menu: `menuLabel`);
41
+ * `children` a `PopupSubmenu` — the flyout the shell now has, which is
42
+ * what retires UI-16's one exception (the email canvas block
43
+ * menu's layer ladder). One level, as before: a child's own
44
+ * `children` are not drawn;
45
+ * otherwise a `PopupMenuItem`, `danger` kept.
46
+ *
47
+ * A root item closes the menu after it acts (a `PopupMenuItem` at the root
48
+ * leaves that to its caller); one in a submenu closes it through the shell.
49
+ * `icon` and `shortcut` are drawn only when given, in the `es-menu-*` columns
50
+ * `menu.css` lays out — no Tailwind class is written here (the hosts' Tailwind
51
+ * does not scan this package); a flyout's tick is the shell's (`checked`).
101
52
  */
102
- declare const ES_MENU_SKIN: EaselContextMenuSkin;
53
+ declare function EaselMenuItems({ items, onClose }: {
54
+ items: readonly EaselContextMenuItem[];
55
+ onClose: () => void;
56
+ }): react.JSX.Element;
57
+ /** The menu's accessible name: its first caption, as it has always been. */
58
+ declare function menuLabel(items: readonly EaselContextMenuItem[]): string | undefined;
103
59
 
104
- export { ES_MENU_SKIN, EaselContextMenu, EaselContextMenuProps, EaselContextMenuSkin, menuPortalTarget };
60
+ export { EaselContextMenu, EaselContextMenuItem, EaselContextMenuProps, EaselMenuItems, menuLabel };
@@ -1 +1 @@
1
- export{menuPortalTarget}from"../chunk-RW4XBUUW.js";export{ES_MENU_SKIN,EaselContextMenu}from"../chunk-KTVH2ZRL.js";
1
+ export{EaselContextMenu}from"../chunk-BXMXTO36.js";export{EaselMenuItems,menuLabel}from"../chunk-EA2HTX37.js";
@@ -1,265 +1,56 @@
1
1
  /*
2
- * efficient-easel/menu.css — the right-click menu box (card 66400.d).
2
+ * efficient-easel/menu.css — the columns of an editor menu row.
3
3
  *
4
- * THE VALUES ARE NOT OURS. They are the storefront editor's shipped
5
- * `.sf-blockmenu` / `.sf-blockmenu-item` — `efficient-storefront/components/storefront-editor.css`,
6
- * read at line 1165 — which is the look card 66400.d names for the menu Lewis
7
- * approved on 2026-08-31. Each `--sf-*` there is already an alias of the
8
- * matching `--es-*` here (card 66024), so the look is expressed with no new
9
- * value except the three noted below. Change one only to track a deliberate
10
- * change THERE. Pinned by `tests/menu-box-css.test.ts`.
4
+ * The box and the rows are efficient-shell's `ContextMenu` and `PopupMenu`
5
+ * pieces since 1.0.0 (efficient-easel#199, harness UI-16 and UI-24), dressed
6
+ * by `efficient-shell/ui.css`. A row from `EaselMenuItems` may carry three
7
+ * things the shell's row has no slot for, and this sheet lays them out with
8
+ * the shell's tokens and nothing else:
11
9
  *
12
- * ⚠️ THREE VALUES ARE UNDER A LIVE QUESTION, and each is behind ONE var so
13
- * settling it is a three-line change. The drawing Lewis approved that same day
14
- * (`mockups/editor-add-and-context-v1.html`, its `.ctx` rule) is a FLOATING
15
- * menu, where `.sf-blockmenu` is an in-flow picker, and it holds different
16
- * numbers for exactly the three things that difference would move:
10
+ * .es-menu-icon the glyph before the label;
11
+ * .es-menu-label the label, which takes the room between them;
12
+ * .es-menu-shortcut the keyboard shortcut, right-aligned and quieter.
17
13
  *
18
- * .sf-blockmenu (shipped) .ctx (the drawing)
19
- * radius 8px 9px
20
- * shadow 0 6px 18px rgba(0,0,0,.08) 0 10px 28px rgba(0,0,0,.16)
21
- * min-width 220px (the admin box's own) 186px
14
+ * A flyout's "you are here" tick is the shell's (`PopupMenuItem` `checked`,
15
+ * efficient-shell 4.165.0), not a column of this sheet's.
22
16
  *
23
- * This sheet ships `.sf-blockmenu`'s, per the card's own constraint. The
24
- * question is with the fork that owns the design.
25
- *
26
- * LIGHT only — neither editing surface has a dark mode (owner, 2026-08-06), so
27
- * there is no dark counterpart block here. Do not add one.
28
- *
29
- * Import once from a client entry / global stylesheet:
17
+ * Import once, after the shell's sheet:
18
+ * import 'efficient-shell/ui.css';
30
19
  * import 'efficient-easel/menu.css';
31
20
  */
32
-
33
- /* ── this sheet's own vars ──────────────────────────────────────────────────
34
- ON `.es-editor`, the chrome root, following `rail.css` (which owns
35
- `--es-rail-*`) and `panel.css` (`--es-row-gap`): the sheet that owns the
36
- rules owns its tokens, and `shell.css` stays a verbatim mirror of the
37
- storefront editor's `--sf-*` block. None of these is a shell token.
38
-
39
- ⚠️ The box itself names `es-editor` (see `skin.ts`) — it is portalled to
40
- `document.body`, outside the editor tree, so this is also where it picks these
41
- up. Read that note before moving them.
42
-
43
- ⛔ CUSTOM PROPERTIES ONLY IN THIS BLOCK. Never a paint property.
44
-
45
- `.es-editor` is not this package's own element — it is the class each editor
46
- puts on ITS OWN chrome root, and the campaign designer puts it on a root that
47
- lives INSIDE the admin portal's DOM (card 66024 made that the mechanism by
48
- which the designer is themed; `CampaignDesigner.tsx` adds the class and
49
- `campaignBlocks.css` aliases its `--cd-*` onto these values). A custom
50
- property declared here is inert until something reads it. A `background`, a
51
- `padding`, a `font` — anything that PAINTS — is not: it applies to that root
52
- the moment this sheet is imported, in every editor that imports it, and in
53
- the admin portal's own document.
54
-
55
- Audited on 2026-09-02 across every sheet in this package that opens an
56
- `.es-editor` block, and the honest number is zero:
57
-
58
- shell.css 67 declarations, 67 custom, 0 paint
59
- panel.css 11 declarations, 11 custom, 0 paint
60
- menu.css 7 declarations, 7 custom, 0 paint
61
-
62
- ⚠️ Nothing enforces this — it is a rule with no test behind it. If you are
63
- adding a declaration here, you are the check. */
64
- .es-editor {
65
- /* HOW FAR THE BOX FLOATS OFF THE PAGE BEHIND IT.
66
- The drawing's value. `.sf-blockmenu` — the shop's shipped Add-block picker
67
- — carries `0 6px 18px rgba(0,0,0,.08)`, and this is deliberately heavier:
68
- that picker sits IN FLOW inside a panel, while this box FLOATS over
69
- content, and a floating box has to separate itself from whatever is under
70
- it. The drawing's own header cites `.sf-blockmenu`, so its author had that
71
- value open and moved off it on purpose.
72
- This is the CONTEXT half of the split described on `--es-menu-hover-bg`:
73
- it follows the situation, not the neighbour. */
74
- --es-menu-shadow: 0 10px 28px rgba(0, 0, 0, 0.16);
75
-
76
- /* HOW SOFT ITS CORNERS ARE. The drawing's 9px, one more than the in-flow
77
- picker's 8 — the same floating-vs-in-flow distinction. */
78
- --es-menu-radius: 9px;
79
-
80
- /* THE NARROWEST THE BOX MAY GET — a floor, not a width.
81
- A menu that shrank to its longest label would change width with its
82
- contents, so a merchant's pointer would land on a different row depending
83
- on which label happened to be longest. The drawing's 186px. It is also the
84
- width the flyout measures against when it decides to flip left. */
85
- --es-menu-min-w: 186px;
86
-
87
- /* WHAT A ROW DOES UNDER THE POINTER — the one thing a merchant watches move.
88
- ⚠️ THIS ONE DOES NOT FOLLOW THE DRAWING, AND THAT IS DELIBERATE. The
89
- drawing's `.ctx` washes the row GREY and leaves the text alone. This washes
90
- the accent tint and turns the text blue, which is `.sf-blockmenu` — the Add
91
- menu, one keystroke away in the same editor.
92
-
93
- Owner ruling, 2026-09-02, recorded as a dated amendment in the
94
- `owner-design-rulings` skill ("the right-click menu hovers BLUE, not the
95
- drawing's grey"): it was put to him rather than decided, because two menus
96
- in one editor highlighting differently is a rule-zero failure. His word was
97
- blue.
98
-
99
- ⛔ DO NOT "RESTORE" THE DRAWING'S GREY. The split from the three values
100
- above is not an inconsistency, it is the rule:
101
-
102
- hover interaction grammar -> follows the NEIGHBOUR
103
- shadow / radius / context -> follows the SITUATION
104
- min-width (this box floats; the Add
105
- menu sits in flow)
106
-
107
- Still behind a var, because that is what made asking him cost nothing. */
108
- --es-menu-hover-bg: var(--es-accent-tint);
109
- --es-menu-hover-fg: var(--es-accent);
110
-
111
- /* A ROW THAT DESTROYS SOMETHING.
112
- ⚠️ The drawing is the ONLY reference for this one — `.sf-blockmenu` has no
113
- danger row at all, so nothing is being overridden here. Its `#b91c1c` is
114
- exactly `--es-danger-strong`, so it is read from the token layer rather
115
- than copied as a literal. */
116
- --es-menu-danger-fg: var(--es-danger-strong);
117
-
118
- /* THE SHORTCUT COLUMN'S INK — quieter than a label, because a shortcut is a
119
- reminder and never the thing being read; but it IS read, so it is a grey
120
- that can be: `--es-muted`, 4.8:1 on the box at 11px.
121
- ⚠️ It was the drawing's own `#b6bcc4` until 2026-10-07 — lighter than
122
- `--es-disabled`, 1.9:1 on white, the lowest contrast of any text in the
123
- package. A second host's axe-core run failed it (WCAG AA, 4.5:1) and the
124
- host put `--es-muted` here itself; this is that, at the source. Still
125
- behind ONE var, so the design can pick another grey that can be read.
126
- Held by ratio in tests/contrast-small-text.test.ts. */
127
- --es-menu-shortcut-fg: var(--es-muted);
128
- }
129
-
130
-
131
- /* ── the box ────────────────────────────────────────────────────────────────
132
- FIXED and lifted on purpose: it hangs OVER the panel it was opened from
133
- rather than inside it, so a left panel dragged to its 220px minimum — which
134
- clips its own overflow — cannot cut it off. Measured in Chromium at both
135
- panel widths; see `tests/fixtures/menu-clipping.html`. */
136
- .es-menu {
137
- position: fixed;
138
- z-index: 9999;
139
- box-sizing: border-box;
140
- display: flex;
141
- flex-direction: column;
142
- gap: 2px;
143
- min-width: var(--es-menu-min-w);
144
- /* A box that could grow past the viewport would be unreachable on its far
145
- side whatever the edge-flip did. */
146
- max-width: calc(100vw - 16px);
147
- padding: 4px;
148
- border: 1px solid var(--es-line);
149
- border-radius: var(--es-menu-radius);
150
- background: var(--es-panel);
151
- box-shadow: var(--es-menu-shadow);
152
- font-family: var(--es-font);
153
- /* For `maxHeight`. The flyout is portalled separately BECAUSE of this: a CSS
154
- overflow that is not `visible` on one axis clips the other too, so an
155
- in-flow flyout would be cut off at this box's edge. */
156
- overflow-y: auto;
157
- overscroll-behavior: contain;
158
- }
159
-
160
- /* ── a row ──────────────────────────────────────────────────────────────── */
161
- .es-menu-item {
162
- display: flex;
21
+ .es-menu-icon {
22
+ display: inline-flex;
23
+ flex: none;
163
24
  align-items: center;
164
- gap: 8px;
165
- width: 100%;
166
- padding: 8px;
167
- border: none;
168
- border-radius: 6px;
169
- background: transparent;
170
- color: var(--es-text);
171
- font: inherit;
172
- font-size: 12.5px;
173
- text-align: left;
174
- cursor: pointer;
175
- }
176
-
177
- /* The one thing a merchant watches move — see `--es-menu-hover-*` above for
178
- which reference this follows and why it is behind a var. */
179
- .es-menu-item:hover {
180
- background: var(--es-menu-hover-bg);
181
- color: var(--es-menu-hover-fg);
182
- }
183
-
184
- .es-menu-item:focus-visible {
185
- outline: 2px solid var(--es-accent-ring);
186
- outline-offset: -2px;
187
- }
188
-
189
- .es-menu-item.is-danger {
190
- color: var(--es-menu-danger-fg);
25
+ justify-content: center;
26
+ width: 16px;
27
+ height: 16px;
28
+ color: var(--ink-muted);
191
29
  }
192
- /* The drawing gives a danger row no hover of its own: it takes the same grey as
193
- every other row and KEEPS its red, rather than washing red as well. Stated as
194
- its own rule because `.es-menu-item:hover` would otherwise repaint the text. */
195
- .es-menu-item.is-danger:hover {
196
- background: var(--es-menu-hover-bg);
197
- color: var(--es-menu-danger-fg);
30
+ .es-menu-icon svg {
31
+ width: 14px;
32
+ height: 14px;
198
33
  }
199
-
200
- /* ── the parts of a row ─────────────────────────────────────────────────── */
201
34
  .es-menu-label {
202
- flex: 1;
203
- }
204
- /* A state name in a flyout can be long; a row in the box never is. */
205
- .es-menu-label-sub {
35
+ flex: 1 1 auto;
36
+ min-width: 0;
206
37
  overflow: hidden;
207
38
  text-overflow: ellipsis;
208
39
  white-space: nowrap;
209
40
  }
210
-
211
- .es-menu-chevron {
212
- color: var(--es-icon);
213
- }
214
-
215
- /* The drawing's `.ci` — a fixed 16px column so labels line up whether or not a
216
- row has a glyph, exactly as the tick column does in a flyout. */
217
- .es-menu-icon {
218
- flex: none;
219
- width: 16px;
220
- text-align: center;
221
- color: var(--es-muted);
222
- font-size: 11px;
223
- }
224
-
225
- /* The drawing's `.k`. `margin-left: auto` is what right-aligns it, so it works
226
- whether or not the row also carries a chevron. */
227
41
  .es-menu-shortcut {
42
+ flex: none;
228
43
  margin-left: auto;
229
- color: var(--es-menu-shortcut-fg);
230
- font-size: 11px;
231
- }
232
- /* On the row that is pointed at the ground is tinted, and the grey that passes
233
- on white does not pass on the tint (4.4:1). So the shortcut takes the ROW's
234
- ink there — the accent on a plain row (4.7:1), the red on one that destroys
235
- (5.9:1): one ink per row, and no new colour. */
236
- .es-menu-item:hover .es-menu-shortcut {
44
+ padding-left: 16px;
45
+ color: var(--ink-muted);
46
+ font-size: 12px;
47
+ font-variant-numeric: tabular-nums;
48
+ }
49
+ /* On the row the pointer or the keys are on, whose ground the shell tints,
50
+ the shortcut takes the row's own ink: one ink per row, and no new colour. */
51
+ [data-context-menu] button:hover .es-menu-shortcut,
52
+ [data-context-menu] button:focus-visible .es-menu-shortcut,
53
+ [data-popup-submenu-panel] button:hover .es-menu-shortcut,
54
+ [data-popup-submenu-panel] button:focus-visible .es-menu-shortcut {
237
55
  color: inherit;
238
56
  }
239
-
240
- /* Present on every row of a flyout that uses one, so the labels line up whether
241
- or not the row is the state you are already on. */
242
- .es-menu-tick {
243
- flex: none;
244
- width: 12px;
245
- color: var(--es-accent);
246
- }
247
-
248
- /* ── a caption, and a separator ─────────────────────────────────────────── */
249
- .es-menu-header {
250
- padding: 8px 8px 4px;
251
- color: var(--es-muted);
252
- font-size: 10px;
253
- font-weight: 500;
254
- letter-spacing: 0.06em;
255
- text-transform: uppercase;
256
- }
257
-
258
- /* AIR, NOT A LINE (card 66350). The owner has asked for divider hairlines to
259
- go, more than once, and `tests/panel-no-divider-lines.test.ts` sweeps every
260
- stylesheet this package ships for a one-sided border — this one included. A
261
- `'—'` item still separates; it does it with a gap. With the box's own 1px
262
- `gap` either side, that is 7px between groups against 1px between rows. */
263
- .es-menu-divider {
264
- height: 5px;
265
- }
@@ -1,7 +1,7 @@
1
1
  export { A as AfterRemove, I as ItemSelector, N as NodeSelection, P as Planned, S as StepBounds, c as canStep, f as fromItemSelector, p as parentStep, a as planDuplicate, b as planMove, d as planPaste, e as planRemove, g as planSetProps, h as planStep, t as toItemSelector } from '../plan-DChbUXdS.js';
2
2
  export { C as ClipboardEntry, N as NodeClipboard, a as NodeOps, b as NodeOpsHost, c as createNodeClipboard, u as useNodeOps } from '../useNodeOps-BF0fQsif.js';
3
3
  import { ReactNode } from 'react';
4
- import { E as EaselContextMenuItem } from '../types-Djdq6Xkd.js';
4
+ import { E as EaselContextMenuItem } from '../types-BsyPh6EW.js';
5
5
  import { N as NodeMenuWords } from '../words-DMG4dYq2.js';
6
6
  import '../types-41704JE4.js';
7
7