efficient-easel 0.0.0 → 0.96.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/LICENSE +10 -0
  2. package/README.md +723 -4
  3. package/dist/autosave/index.d.ts +101 -0
  4. package/dist/autosave/index.js +1 -0
  5. package/dist/blocks/index.d.ts +295 -0
  6. package/dist/blocks/index.js +0 -0
  7. package/dist/buckets-1vRlQceY.d.ts +13 -0
  8. package/dist/canvas/index.d.ts +194 -0
  9. package/dist/canvas/index.js +1 -0
  10. package/dist/chrome/chrome.css +570 -0
  11. package/dist/chrome/index.d.ts +219 -0
  12. package/dist/chrome/index.js +21 -0
  13. package/dist/chunk-2LUSK32B.js +1 -0
  14. package/dist/chunk-2OAZYSWJ.js +1 -0
  15. package/dist/chunk-67VZRQLG.js +13 -0
  16. package/dist/chunk-6JDII7N3.js +1 -0
  17. package/dist/chunk-6OPSJGAI.js +1 -0
  18. package/dist/chunk-6WY6ZXJG.js +1 -0
  19. package/dist/chunk-7A7O2JWQ.js +1 -0
  20. package/dist/chunk-BZ5ASXOL.js +1 -0
  21. package/dist/chunk-CERTFXCV.js +13 -0
  22. package/dist/chunk-EFTTSIMK.js +1 -0
  23. package/dist/chunk-FI23ALVJ.js +1 -0
  24. package/dist/chunk-FJE4IDBQ.js +1 -0
  25. package/dist/chunk-GGTPNGFJ.js +1 -0
  26. package/dist/chunk-H4B72XCJ.js +1 -0
  27. package/dist/chunk-H6DDIBQ4.js +1 -0
  28. package/dist/chunk-KDIVA6WD.js +1 -0
  29. package/dist/chunk-LPRQDDQO.js +1 -0
  30. package/dist/chunk-MCLBHENY.js +1 -0
  31. package/dist/chunk-MFIRYGHW.js +1 -0
  32. package/dist/chunk-NKB3KVAB.js +1 -0
  33. package/dist/chunk-O2NLQBFX.js +1 -0
  34. package/dist/chunk-OGZFDJ6G.js +1 -0
  35. package/dist/chunk-OWBO4WKA.js +0 -0
  36. package/dist/chunk-QB3BZIRA.js +5 -0
  37. package/dist/chunk-RE4LP46F.js +1 -0
  38. package/dist/chunk-STFMZJKU.js +14 -0
  39. package/dist/chunk-SUO5627P.js +1 -0
  40. package/dist/chunk-TDA4HH54.js +1 -0
  41. package/dist/chunk-WK3AFPXY.js +1 -0
  42. package/dist/chunk-YIMRTIBN.js +3 -0
  43. package/dist/chunk-YNZZYHA4.js +450 -0
  44. package/dist/chunk-ZAOM6PFH.js +1 -0
  45. package/dist/chunk-ZNCEOSXO.js +1 -0
  46. package/dist/copy/copy.css +282 -0
  47. package/dist/copy/index.d.ts +336 -0
  48. package/dist/copy/index.js +10 -0
  49. package/dist/data/index.d.ts +208 -0
  50. package/dist/data/index.js +1 -0
  51. package/dist/drag/drag.css +360 -0
  52. package/dist/drag/index.d.ts +379 -0
  53. package/dist/drag/index.js +18 -0
  54. package/dist/edit-lock/index.d.ts +354 -0
  55. package/dist/edit-lock/index.js +1 -0
  56. package/dist/editor/editor.css +58 -0
  57. package/dist/editor/index.d.ts +361 -0
  58. package/dist/editor/index.js +9 -0
  59. package/dist/fields/fields.css +207 -0
  60. package/dist/fields/index.d.ts +273 -0
  61. package/dist/fields/index.js +46 -0
  62. package/dist/gold/gold.css +196 -0
  63. package/dist/gold/index.d.ts +280 -0
  64. package/dist/gold/index.js +1 -0
  65. package/dist/icons/index.d.ts +474 -0
  66. package/dist/icons/index.js +1 -0
  67. package/dist/index.d.ts +6 -0
  68. package/dist/index.js +1 -0
  69. package/dist/markup/index.d.ts +452 -0
  70. package/dist/markup/index.js +1 -0
  71. package/dist/menu/index.d.ts +49 -0
  72. package/dist/menu/index.js +15 -0
  73. package/dist/menu/menu.css +254 -0
  74. package/dist/node-ops/index.d.ts +91 -0
  75. package/dist/node-ops/index.js +1 -0
  76. package/dist/panel/index.d.ts +1088 -0
  77. package/dist/panel/index.js +16 -0
  78. package/dist/panel/panel.css +1460 -0
  79. package/dist/pick/index.d.ts +153 -0
  80. package/dist/pick/index.js +14 -0
  81. package/dist/pick/pick.css +235 -0
  82. package/dist/plan-DtNw2VuI.d.ts +88 -0
  83. package/dist/rail/index.d.ts +145 -0
  84. package/dist/rail/index.js +1 -0
  85. package/dist/rail/rail.css +175 -0
  86. package/dist/render/index.d.ts +117 -0
  87. package/dist/render/index.js +5 -0
  88. package/dist/resolve-DpH8YQOA.d.ts +57 -0
  89. package/dist/shell/index.d.ts +159 -0
  90. package/dist/shell/index.js +1 -0
  91. package/dist/shell/shell.css +122 -0
  92. package/dist/strings/catalogue.d.ts +141 -0
  93. package/dist/strings/catalogue.js +1 -0
  94. package/dist/strings/index.d.ts +56 -0
  95. package/dist/strings/index.js +2 -0
  96. package/dist/style-clipboard/index.d.ts +184 -0
  97. package/dist/style-clipboard/index.js +1 -0
  98. package/dist/tree/index.d.ts +173 -0
  99. package/dist/tree/index.js +1 -0
  100. package/dist/tree/tree.css +618 -0
  101. package/dist/types-CEqUFH2j.d.ts +129 -0
  102. package/dist/types-CuNiSnX6.d.ts +204 -0
  103. package/dist/types-DNrn_w4V.d.ts +88 -0
  104. package/dist/types-DziKCwsN.d.ts +105 -0
  105. package/dist/types-_Vayx8ov.d.ts +153 -0
  106. package/dist/types-pGBXevBU.d.ts +131 -0
  107. package/dist/useNodeOps-6IAej9jy.d.ts +68 -0
  108. package/dist/watch-DmasQA61.d.ts +60 -0
  109. package/dist/wordFit-9B9nqyf3.d.ts +35 -0
  110. package/dist/words-B2YLaKEr.d.ts +30 -0
  111. package/dist/words-B87GIFWH.d.ts +86 -0
  112. package/dist/words-BK-npta-.d.ts +27 -0
  113. package/dist/words-CD3WADyb.d.ts +51 -0
  114. package/dist/words-CsRWXkY4.d.ts +129 -0
  115. package/dist/words-D9ChvbNC.d.ts +52 -0
  116. package/dist/words-DOhF-4D4.d.ts +35 -0
  117. package/dist/words-DagJdwIA.d.ts +78 -0
  118. package/dist/words-DfxEp_RH.d.ts +142 -0
  119. package/package.json +202 -3
@@ -0,0 +1,153 @@
1
+ import * as react from 'react';
2
+ import { P as PickWords } from '../words-B87GIFWH.js';
3
+
4
+ /** One colour the site's theme names — offered first, by name. */
5
+ type ThemeColour = {
6
+ name: string;
7
+ value: string;
8
+ };
9
+ type ColourPickerProps = {
10
+ /** The colour, `#rrggbb` — or `''` while the setting has none of its own. */
11
+ value: string;
12
+ /** Live, as the colour moves — the same `#rrggbb` from every way in. */
13
+ onChange: (next: string) => void;
14
+ /** The row's name: the swatch is "<label>", the hex box "<label> hex code". */
15
+ label: string;
16
+ /** The theme's colours, offered first in the window. */
17
+ theme?: readonly ThemeColour[];
18
+ /**
19
+ * A WORD the row prints in place of the hex. While `value` is `''` it names
20
+ * the empty state ("Not set" by default). Given WITH a colour, it names the
21
+ * state that colour is in — "Auto" for an ink that follows its fill, "Accent"
22
+ * for a button on the theme's colour: the swatch shows the colour the page
23
+ * really has, the row prints the word, and the hex is in the window. A
24
+ * caption that IS the value's hex is no word, and the row's hex box stays.
25
+ */
26
+ caption?: string;
27
+ /** The swatch's DOM id, for a row whose own `<label for>` points at it. */
28
+ id?: string;
29
+ readOnly?: boolean;
30
+ /**
31
+ * The picker's own words in the host's language:
32
+ * `useEditorStrings().pick.colour` (0.95.0). Left out, English. A prop, not
33
+ * the strings provider: the pickers import nothing outside their folder.
34
+ */
35
+ words?: PickWords['colour'];
36
+ };
37
+ /**
38
+ * ONE COLOUR ROW (blueprint step 4.6): a swatch and its hex, and — pressing
39
+ * the swatch — a small window under the row with the theme's colours, a
40
+ * saturation-and-brightness square, a hue strip and the hex again.
41
+ *
42
+ * ⚠️ NEVER THE SYSTEM COLOUR DIALOG. `<input type="color">` opens a different
43
+ * window on every browser and operating system, with none of the site's theme
44
+ * colours in it, and it is the one control in the panel a merchant cannot see
45
+ * the inside of before pressing it. Everything here is drawn by this package.
46
+ *
47
+ * THE WINDOW OPENS IN THE PANEL'S OWN FLOW, under the row, not floating over
48
+ * it: a settings panel scrolls, and a floating window is clipped by the edge it
49
+ * scrolls under — and it would sit outside the `--es-*` token root a panel
50
+ * provides (see the context menu, which has to carry that root itself).
51
+ *
52
+ * The hex box is always a box you can type into — pasting a brand colour
53
+ * costs no clicks — live on the keystroke that makes it a colour, and put back
54
+ * to what is stored on Enter, Escape or leaving it.
55
+ */
56
+ declare function ColourPicker({ value, onChange, label, theme, caption, id: swatchId, readOnly, words, }: ColourPickerProps): react.JSX.Element;
57
+
58
+ type ImageBoxProps = {
59
+ /** The file's URL, or `''` while the setting has none. */
60
+ value: string;
61
+ onChange: (url: string) => void;
62
+ /** The setting's name — "Background image", "Logo image". */
63
+ label: string;
64
+ /**
65
+ * How THIS APP opens its media library: resolves to the URL chosen, or
66
+ * `null` when the person closes it without choosing. The admin opens its
67
+ * media window, the shop asks the admin to, a test resolves a string — the
68
+ * box does not know or care. Absent: the box shows the picture but offers no
69
+ * way to change it.
70
+ */
71
+ openLibrary?: () => Promise<string | null>;
72
+ kind?: 'image' | 'video';
73
+ /** What the empty box says. Default "Choose an image" / "Choose a video". */
74
+ placeholder?: string;
75
+ readOnly?: boolean;
76
+ /**
77
+ * The box's own words in the host's language:
78
+ * `useEditorStrings().pick.image` (0.95.0). Left out, English. A prop, not
79
+ * the strings provider: the pickers import nothing outside their folder.
80
+ */
81
+ words?: PickWords['image'];
82
+ };
83
+ /**
84
+ * ONE IMAGE BOX (blueprint step 4.6): the picture, its file name under it,
85
+ * and Replace and Remove — or, empty, one press that opens the library.
86
+ *
87
+ * The library is the APP's. Every editor opens a different one — the admin's
88
+ * media window with its product and storefront tabs, the shop's ask to the
89
+ * admin, a future host's own — so the box is handed `openLibrary` and opens
90
+ * nothing itself, never a native file dialog.
91
+ *
92
+ * The file name sits UNDER the picture, not on it: a small crop of a photo is
93
+ * often not enough to tell two similar shots apart, and a caption laid over the
94
+ * picture covers the part it was meant to identify.
95
+ */
96
+ declare function ImageBox({ value, onChange, label, openLibrary, kind, placeholder, readOnly, words, }: ImageBoxProps): react.JSX.Element;
97
+
98
+ /**
99
+ * The colour maths the picker runs on — pure, no React, no DOM.
100
+ *
101
+ * A colour is stored as `#rrggbb`, lower case, everywhere a document holds one;
102
+ * the picker WORKS in HSV (hue, saturation, value), because that is what a
103
+ * square and a hue strip are a picture of. These turn one into the other.
104
+ *
105
+ * ⚠️ HSV FROM A HEX LOSES THE HUE OF A GREY. `#808080` has no hue, so converting
106
+ * it back gives hue 0 — red — and a merchant who drags the square down to grey
107
+ * would find the strip jumped to red. The picker therefore keeps its own HSV
108
+ * while it is open and only reads one back from the stored hex when the value
109
+ * changes from OUTSIDE (see `ColourPicker`).
110
+ */
111
+ type Hsv = {
112
+ h: number;
113
+ s: number;
114
+ v: number;
115
+ };
116
+ /** The most a hex box takes: `#` and six digits. */
117
+ declare const HEX_MAX_LENGTH = 7;
118
+ /**
119
+ * What a typed string is as a colour — `#rrggbb` — or `null` while it is not
120
+ * one yet. Accepts three or six digits, with or without `#`, any case.
121
+ */
122
+ declare function typedHex(text: string): string | null;
123
+ /** A stored hex as a person reads it: upper case. */
124
+ declare const hexReadout: (hex: string) => string;
125
+ /** `#rrggbb` → HSV (h 0–360, s and v 0–1). A string that is not a colour reads as black. */
126
+ declare function hexToHsv(hex: string): Hsv;
127
+ /** HSV → `#rrggbb`. Out-of-range input is clamped, never thrown. */
128
+ declare function hsvToHex({ h, s, v }: Hsv): string;
129
+ /**
130
+ * A point in the saturation/brightness square → s and v. Saturation runs left
131
+ * to right, brightness bottom to top; a point outside the square is held to
132
+ * its edge, so a drag that leaves it keeps working.
133
+ */
134
+ declare function svFromPoint(x: number, y: number, box: {
135
+ left: number;
136
+ top: number;
137
+ width: number;
138
+ height: number;
139
+ }): {
140
+ s: number;
141
+ v: number;
142
+ };
143
+ /** A point along the hue strip → a hue, 0–360, held to the strip's ends. */
144
+ declare function hueFromPoint(x: number, box: {
145
+ left: number;
146
+ width: number;
147
+ }): number;
148
+ /** The last segment of a media URL, decoded and without its query — what a
149
+ * merchant recognises a file by. An inline `data:` or `blob:` URL has no file
150
+ * name, so it reads as none rather than as its own bytes. */
151
+ declare function mediaName(url: string): string;
152
+
153
+ export { ColourPicker, type ColourPickerProps, HEX_MAX_LENGTH, type Hsv, ImageBox, type ImageBoxProps, PickWords, type ThemeColour, hexReadout, hexToHsv, hsvToHex, hueFromPoint, mediaName, svFromPoint, typedHex };
@@ -0,0 +1,14 @@
1
+ import{DEFAULT_PICK_WORDS as e}from"../chunk-ZAOM6PFH.js";import{useId as a,useState as t,useRef as n,useEffect as l}from"react";import{jsxs as r,jsx as i}from"react/jsx-runtime";var s=/^(?:[0-9a-f]{3}|[0-9a-f]{6})$/i,o=7;function c(e){const a=e.trim().replace(/^#/,"");if(!s.test(a))return null;return`#${(3===a.length?[...a].map(e=>e+e).join(""):a).toLowerCase()}`}var u=e=>e.toUpperCase(),h=(e,a,t)=>Math.min(t,Math.max(a,e));function m(e){const a=c(e)??"#000000",t=parseInt(a.slice(1,3),16)/255,n=parseInt(a.slice(3,5),16)/255,l=parseInt(a.slice(5,7),16)/255,r=Math.max(t,n,l),i=r-Math.min(t,n,l);let s=0;return 0!==i&&(s=r===t?(n-l)/i%6*60:r===n?60*((l-t)/i+2):60*((t-n)/i+4)),{h:(s+360)%360,s:0===r?0:i/r,v:r}}function d({h:e,s:a,v:t}){const n=(e%360+360)%360,l=h(a,0,1),r=h(t,0,1),i=r*l,s=i*(1-Math.abs(n/60%2-1)),o=r-i,[c,u,m]=n<60?[i,s,0]:n<120?[s,i,0]:n<180?[0,i,s]:n<240?[0,s,i]:n<300?[s,0,i]:[i,0,s],d=e=>Math.round(255*(e+o)).toString(16).padStart(2,"0");return`#${d(c)}${d(u)}${d(m)}`}function p(e,a,t){return{s:t.width>0?h((e-t.left)/t.width,0,1):0,v:t.height>0?1-h((a-t.top)/t.height,0,1):0}}function v(e,a){return a.width>0?360*h((e-a.left)/a.width,0,1):0}function g(e){if(/^(data|blob):/i.test(e))return"";const a=(e.split(/[?#]/)[0]??"").split("/").filter(Boolean).pop()??"";try{return decodeURIComponent(a)}catch{return a}}function b({value:s,onChange:o,label:h,theme:g,caption:b,id:f,readOnly:y,words:w=e.colour}){const N=a(),[x,C]=t(!1),M=n(null),D=n(null),[A,I]=t(()=>m(s||"#000000")),$=n(s);l(()=>{s!==$.current&&($.current=s,s&&I(m(s)))},[s]);const L=e=>{y||e===s||($.current=e,o(e))},O=e=>{I(m(e)),L(e)},E=e=>{I(e),L(d(e))};l(()=>{if(!x)return;const e=M.current?.ownerDocument;if(!e)return;const a=e=>{M.current&&!M.current.contains(e.target)&&C(!1)};return e.addEventListener("click",a,!0),()=>e.removeEventListener("click",a,!0)},[x]);const K=e=>({onPointerDown:a=>{y||(a.currentTarget.setPointerCapture?.(a.pointerId),e(a))},onPointerMove:a=>{1&a.buttons&&e(a)}}),P=K(e=>E({...A,...p(e.clientX,e.clientY,e.currentTarget.getBoundingClientRect())})),T=K(e=>E({...A,h:v(e.clientX,e.currentTarget.getBoundingClientRect())})),B=e=>`${Math.round(100*e)}%`,R=void 0===b||s&&c(b)===c(s)?null:b,S=R??(s?u(s):w.notSet);/* @__PURE__ */
2
+ return r("div",{ref:M,className:"es-pick-colour",children:[
3
+ /* @__PURE__ */r("div",{className:"es-pick-colour-row",children:[
4
+ /* @__PURE__ */i("button",{ref:D,id:f,type:"button",className:s?"es-pick-swatch":"es-pick-swatch is-unset",style:s?{background:s}:void 0,"aria-label":w.swatch({label:h,shown:S}),"aria-expanded":x,"aria-controls":x?N:void 0,disabled:y,onClick:()=>C(e=>!e)}),null===R?/* @__PURE__ */i(k,{value:s,onChange:O,name:w.hex({label:h}),readOnly:y}):/* @__PURE__ */i("span",{className:"es-pick-caption",children:R})]}),x?/* @__PURE__ */r("div",{id:N,role:"dialog","aria-label":w.dialog({label:h}),className:"es-pick-window",onKeyDown:e=>{"Escape"===e.key&&(e.preventDefault(),e.stopPropagation(),C(!1),D.current?.focus())},children:[g&&g.length>0?/* @__PURE__ */i("div",{role:"group","aria-label":w.theme,className:"es-pick-theme",children:g.map(e=>/* @__PURE__ */i("button",{type:"button",className:"es-pick-theme-swatch",style:{background:e.value},title:w.themeSwatchTitle({name:e.name,hex:u(e.value)}),"aria-label":w.themeSwatch({name:e.name,hex:u(e.value)}),"aria-pressed":c(e.value)===s,onClick:()=>O(c(e.value)??e.value)},`${e.name}:${e.value}`))}):null,
5
+ /* @__PURE__ */i("div",{className:"es-pick-sv",role:"slider",tabIndex:0,"aria-label":w.square,"aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":Math.round(100*A.v),"aria-valuetext":w.squareValue({saturation:B(A.s),brightness:B(A.v)}),style:{backgroundColor:d({h:A.h,s:1,v:1})},onKeyDown:e=>{const a=e.shiftKey?.1:.01,t={ArrowLeft:[-a,0],ArrowRight:[a,0],ArrowDown:[0,-a],ArrowUp:[0,a]}[e.key];t&&(e.preventDefault(),E({...A,s:Math.min(1,Math.max(0,A.s+t[0])),v:Math.min(1,Math.max(0,A.v+t[1]))}))},...P,children:/* @__PURE__ */i("span",{className:"es-pick-sv-thumb",style:{left:B(A.s),top:B(1-A.v)}})}),
6
+ /* @__PURE__ */i("div",{className:"es-pick-hue",role:"slider",tabIndex:0,"aria-label":w.hue,"aria-valuemin":0,"aria-valuemax":359,"aria-valuenow":Math.round(A.h),onKeyDown:e=>{const a=e.shiftKey?10:1,t="ArrowLeft"===e.key||"ArrowDown"===e.key?-a:"ArrowRight"===e.key||"ArrowUp"===e.key?a:null,n="Home"===e.key?0:"End"===e.key?359:null===t?null:Math.min(359,Math.max(0,A.h+t));null!==n&&(e.preventDefault(),E({...A,h:n}))},...T,children:/* @__PURE__ */i("span",{className:"es-pick-hue-thumb",style:{left:A.h/360*100+"%"}})}),
7
+ /* @__PURE__ */i(k,{value:s,onChange:O,name:w.hex({label:h}),readOnly:y})]}):null]})}function k({value:e,onChange:a,name:n,readOnly:l}){const[r,s]=t(null);/* @__PURE__ */
8
+ return i("input",{type:"text",className:"es-pick-hex",value:r??(e?u(e):""),"aria-label":n,maxLength:7,spellCheck:!1,autoComplete:"off",readOnly:l,onChange:t=>{s(t.target.value);const n=c(t.target.value);null!==n&&n!==e&&a(n)},onFocus:e=>e.currentTarget.select(),onKeyDown:e=>{"Enter"!==e.key&&"Escape"!==e.key||(e.preventDefault(),e.stopPropagation(),s(null),e.currentTarget.blur())},onBlur:()=>s(null)})}function f({value:a,onChange:n,label:l,openLibrary:s,kind:o="image",placeholder:c,readOnly:u,words:h=e.image}){const[m,d]=t(!1),[p,v]=t(!1),b=Boolean(s)&&!u,k=async()=>{if(s&&!m&&!u){d(!0),v(!1);try{const e=await s();e&&n(e)}catch{v(!0)}finally{d(!1)}}},f=m?/* @__PURE__ */i("p",{className:"es-pick-image-status",role:"status",children:h.choosing}):p?/* @__PURE__ */i("p",{className:"es-pick-image-status is-error",role:"alert",children:h.failed}):null;if(!a)/* @__PURE__ */
9
+ return r("div",{className:"es-pick-image",children:[
10
+ /* @__PURE__ */r("button",{type:"button",className:"es-pick-image-slot",onClick:k,disabled:!b||m,"aria-label":h.empty({label:l,kind:o,placeholder:c}),children:[
11
+ /* @__PURE__ */i("span",{className:"es-pick-image-slot-name",children:c??("video"===o?h.chooseVideo:h.chooseImage)}),b?/* @__PURE__ */i("span",{className:"es-pick-image-slot-hint",children:h.hint}):null]}),f]});const y=g(a),w=y?h.named({label:l,name:y}):l;/* @__PURE__ */
12
+ return r("div",{className:"es-pick-image",children:[
13
+ /* @__PURE__ */r("figure",{className:"es-pick-image-figure",children:[
14
+ /* @__PURE__ */i("div",{className:"es-pick-image-frame",children:"video"===o?/* @__PURE__ */i("video",{src:a,muted:!0,playsInline:!0,preload:"metadata","aria-label":w}):/* @__PURE__ */i("img",{src:a,alt:w})}),y?/* @__PURE__ */i("figcaption",{className:"es-pick-image-name",title:y,children:y}):null]}),b||!u?/* @__PURE__ */r("div",{className:"es-pick-image-actions",children:[b?/* @__PURE__ */i("button",{type:"button",className:"es-pick-image-btn",onClick:k,disabled:m,"aria-label":h.replaceNamed({label:l}),children:h.replace}):null,u?null:/* @__PURE__ */i("button",{type:"button",className:"es-pick-image-btn is-bare",onClick:()=>n(""),disabled:m,"aria-label":h.removeNamed({label:l}),children:h.remove})]}):null,f]})}export{b as ColourPicker,o as HEX_MAX_LENGTH,f as ImageBox,u as hexReadout,m as hexToHsv,d as hsvToHex,v as hueFromPoint,g as mediaName,p as svFromPoint,c as typedHex};
@@ -0,0 +1,235 @@
1
+ /* efficient-easel/pick — the colour row and its window, and the image box
2
+ (blueprint step 4.6). Drawn on the panel's own tokens, so both editors'
3
+ panels get the same picker without a line of their own CSS. */
4
+
5
+ /* ── The colour row ─────────────────────────────────────────────────────── */
6
+ .es-pick-colour {
7
+ /* The hex box and the word that stands in for it ("Auto", "Not set") are
8
+ ONE width. A row is right-aligned as a unit, so a word narrower than a hex
9
+ pushes its swatch out of line down a column of colour rows. */
10
+ --es-pick-readout-w: 80px;
11
+ display: flex;
12
+ flex-direction: column;
13
+ gap: 10px;
14
+ }
15
+ .es-pick-colour-row {
16
+ display: flex;
17
+ align-items: center;
18
+ gap: 8px;
19
+ }
20
+ /* 26 × 26, no border, 7px corner — the look the owner settled for every
21
+ colour row (2026-09-04). A hairline ring inside, so a white swatch on a
22
+ white panel still has an edge. */
23
+ .es-pick-swatch {
24
+ flex: none;
25
+ width: 26px;
26
+ height: 26px;
27
+ padding: 0;
28
+ border: 0;
29
+ border-radius: 7px;
30
+ box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.1);
31
+ cursor: pointer;
32
+ }
33
+ .es-pick-swatch.is-unset {
34
+ background:
35
+ linear-gradient(135deg, transparent 46%, var(--es-danger) 46%, var(--es-danger) 54%, transparent 54%),
36
+ var(--es-panel);
37
+ box-shadow: inset 0 0 0 1px var(--es-input-border);
38
+ }
39
+ .es-pick-swatch:focus-visible,
40
+ .es-pick-theme-swatch:focus-visible,
41
+ .es-pick-sv:focus-visible,
42
+ .es-pick-hue:focus-visible {
43
+ outline: 2px solid var(--es-accent);
44
+ outline-offset: 2px;
45
+ }
46
+ .es-pick-swatch:disabled {
47
+ cursor: default;
48
+ }
49
+ .es-pick-hex {
50
+ box-sizing: border-box;
51
+ width: var(--es-pick-readout-w);
52
+ min-width: 0;
53
+ padding: 4px 6px;
54
+ border: 1px solid transparent;
55
+ border-radius: 6px;
56
+ background: transparent;
57
+ color: var(--es-text);
58
+ font: 500 12px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace;
59
+ letter-spacing: 0.02em;
60
+ }
61
+ .es-pick-hex:hover {
62
+ border-color: var(--es-input-border);
63
+ }
64
+ .es-pick-hex:focus {
65
+ outline: none;
66
+ border-color: var(--es-accent);
67
+ background: var(--es-panel);
68
+ }
69
+ .es-pick-caption {
70
+ box-sizing: border-box;
71
+ width: var(--es-pick-readout-w);
72
+ padding: 0 7px;
73
+ font-size: 12px;
74
+ color: var(--es-muted);
75
+ }
76
+
77
+ /* ── The window under the row ───────────────────────────────────────────── */
78
+ .es-pick-window {
79
+ display: flex;
80
+ flex-direction: column;
81
+ gap: 10px;
82
+ padding: 10px;
83
+ border: 1px solid var(--es-divider);
84
+ border-radius: 10px;
85
+ background: var(--es-panel);
86
+ }
87
+ .es-pick-theme {
88
+ display: flex;
89
+ flex-wrap: wrap;
90
+ gap: 6px;
91
+ }
92
+ .es-pick-theme-swatch {
93
+ width: 22px;
94
+ height: 22px;
95
+ padding: 0;
96
+ border: 0;
97
+ border-radius: 6px;
98
+ box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.1);
99
+ cursor: pointer;
100
+ }
101
+ .es-pick-theme-swatch[aria-pressed='true'] {
102
+ box-shadow:
103
+ inset 0 0 0 1px rgb(0 0 0 / 0.1),
104
+ 0 0 0 2px var(--es-panel),
105
+ 0 0 0 4px var(--es-accent);
106
+ }
107
+ /* The square: the hue at full strength, washed to white left-to-right and to
108
+ black top-to-bottom — which is what saturation and brightness ARE. */
109
+ .es-pick-sv {
110
+ position: relative;
111
+ height: 128px;
112
+ border-radius: 6px;
113
+ background-image:
114
+ linear-gradient(to top, #000, transparent),
115
+ linear-gradient(to right, #fff, transparent);
116
+ cursor: crosshair;
117
+ touch-action: none;
118
+ }
119
+ .es-pick-sv-thumb,
120
+ .es-pick-hue-thumb {
121
+ position: absolute;
122
+ width: 12px;
123
+ height: 12px;
124
+ border-radius: 50%;
125
+ border: 2px solid #fff;
126
+ box-shadow: 0 0 0 1px rgb(0 0 0 / 0.35);
127
+ transform: translate(-50%, -50%);
128
+ pointer-events: none;
129
+ }
130
+ .es-pick-hue {
131
+ position: relative;
132
+ height: 12px;
133
+ border-radius: 6px;
134
+ background: linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00);
135
+ cursor: pointer;
136
+ touch-action: none;
137
+ }
138
+ .es-pick-hue-thumb {
139
+ top: 50%;
140
+ }
141
+
142
+ /* ── The image box ──────────────────────────────────────────────────────── */
143
+ .es-pick-image {
144
+ display: flex;
145
+ flex-direction: column;
146
+ gap: 8px;
147
+ }
148
+ .es-pick-image-slot {
149
+ display: flex;
150
+ flex-direction: column;
151
+ align-items: center;
152
+ justify-content: center;
153
+ gap: 4px;
154
+ min-height: 96px;
155
+ padding: 12px;
156
+ border: 1.5px dashed var(--es-input-border);
157
+ border-radius: 8px;
158
+ background: var(--es-app-bg);
159
+ color: var(--es-text);
160
+ font: inherit;
161
+ cursor: pointer;
162
+ }
163
+ .es-pick-image-slot:hover:not(:disabled),
164
+ .es-pick-image-slot:focus-visible {
165
+ border-color: var(--es-accent);
166
+ background: var(--es-accent-tint);
167
+ }
168
+ .es-pick-image-slot:disabled {
169
+ cursor: default;
170
+ opacity: 0.6;
171
+ }
172
+ .es-pick-image-slot-name {
173
+ font-size: 12.5px;
174
+ font-weight: 600;
175
+ }
176
+ .es-pick-image-slot-hint {
177
+ font-size: 11.5px;
178
+ color: var(--es-muted);
179
+ }
180
+ .es-pick-image-figure {
181
+ margin: 0;
182
+ display: flex;
183
+ flex-direction: column;
184
+ gap: 6px;
185
+ }
186
+ .es-pick-image-frame {
187
+ height: 96px;
188
+ overflow: hidden;
189
+ border-radius: 8px;
190
+ background: var(--es-app-bg);
191
+ }
192
+ .es-pick-image-frame img,
193
+ .es-pick-image-frame video {
194
+ display: block;
195
+ width: 100%;
196
+ height: 100%;
197
+ object-fit: cover;
198
+ }
199
+ .es-pick-image-name {
200
+ overflow: hidden;
201
+ font-size: 11.5px;
202
+ color: var(--es-muted);
203
+ text-overflow: ellipsis;
204
+ white-space: nowrap;
205
+ }
206
+ .es-pick-image-actions {
207
+ display: flex;
208
+ gap: 8px;
209
+ }
210
+ .es-pick-image-btn {
211
+ padding: 5px 10px;
212
+ border: 1px solid var(--es-input-border);
213
+ border-radius: 6px;
214
+ background: var(--es-panel);
215
+ color: var(--es-text);
216
+ font: 500 12px/1.3 var(--es-font);
217
+ cursor: pointer;
218
+ }
219
+ .es-pick-image-btn.is-bare {
220
+ border-color: transparent;
221
+ background: transparent;
222
+ color: var(--es-muted);
223
+ }
224
+ .es-pick-image-btn:hover:not(:disabled) {
225
+ border-color: var(--es-accent);
226
+ color: var(--es-text);
227
+ }
228
+ .es-pick-image-status {
229
+ margin: 0;
230
+ font-size: 11.5px;
231
+ color: var(--es-muted);
232
+ }
233
+ .es-pick-image-status.is-error {
234
+ color: var(--es-danger);
235
+ }
@@ -0,0 +1,88 @@
1
+ import { P as Place, D as Data, a as DataConfig, N as Node } from './types-DziKCwsN.js';
2
+
3
+ /**
4
+ * Every node operation the two editors offer, as ONE pure step: a document in,
5
+ * the next document and what to select out.
6
+ *
7
+ * Why one step. On Puck, the editors built these out of several dispatches —
8
+ * the storefront's delete was three (remove, reset the inspector, clear the
9
+ * selection), its cross-slot move three undo steps, its paste two — and Puck's
10
+ * selection is a POSITION, so after a delete it quietly pointed at whatever slid
11
+ * into the gap. Here each operation is computed whole, on `efficient-easel/data`,
12
+ * and the host commits it in one write (see ./useNodeOps). One action, one undo.
13
+ *
14
+ * Selection is by ID (or a slot, for the campaign designer's columns), never by
15
+ * position: `select` is what to select afterwards, `undefined` means "leave it".
16
+ */
17
+
18
+ /** What is selected: a node by id, a slot (a column) as a level of its own, or nothing. */
19
+ type NodeSelection = {
20
+ kind: 'node';
21
+ id: string;
22
+ } | {
23
+ kind: 'slot';
24
+ parentId: string;
25
+ slot: string;
26
+ } | null;
27
+ interface Planned {
28
+ data: Data;
29
+ /** What to select afterwards; `undefined` leaves the selection as it is. */
30
+ select: NodeSelection | undefined;
31
+ }
32
+ /** After a delete: select nothing (both editors' safe result today) or a neighbour. */
33
+ type AfterRemove = 'none' | 'neighbour';
34
+ /**
35
+ * Delete `id`. `'neighbour'` selects the next block in the same list, else the
36
+ * previous one, else the parent — never "whatever now sits at the old index".
37
+ */
38
+ declare function planRemove(data: Data, config: DataConfig, id: string, after?: AfterRemove): Planned;
39
+ /** Copy `id` to the place just after it, and select the copy. */
40
+ declare function planDuplicate(data: Data, config: DataConfig, id: string, options?: {
41
+ strip?: (node: Node) => Node;
42
+ id?: string;
43
+ }): Planned;
44
+ /** Move `id` to `to` (same list or another slot) in ONE step, and select it there. */
45
+ declare function planMove(data: Data, config: DataConfig, id: string, to: Place): Planned;
46
+ /**
47
+ * The range a node may be stepped within its own list — `[min, max]` indexes.
48
+ * The default is the whole list; the campaign designer passes the body band
49
+ * between its pinned Header and Footer.
50
+ */
51
+ type StepBounds = (place: Place, length: number) => {
52
+ min: number;
53
+ max: number;
54
+ };
55
+ /** Can `id` step one place up (-1) or down (+1) inside its bounds? */
56
+ declare function canStep(data: Data, config: DataConfig, id: string, dir: -1 | 1, bounds?: StepBounds): boolean;
57
+ /** Step `id` one place up or down; `null` when it cannot move that way. */
58
+ declare function planStep(data: Data, config: DataConfig, id: string, dir: -1 | 1, bounds?: StepBounds): Planned | null;
59
+ /**
60
+ * Put a copy of `node` (a clipboard's) at `at`, every node in it with a fresh
61
+ * id — so a clipboard pastes any number of times — and select it.
62
+ */
63
+ declare function planPaste(data: Data, config: DataConfig, node: Node, at: Place, options?: {
64
+ strip?: (node: Node) => Node;
65
+ }): Planned;
66
+ /** Change props on `id` (hide, rename, anything) in one step; selection unchanged. */
67
+ declare function planSetProps(data: Data, config: DataConfig, id: string, patch: Record<string, unknown>): Planned;
68
+ /**
69
+ * The level above a selection — what "Go up one level" selects.
70
+ *
71
+ * - a node in a slot: its parent, or — when `slotIsLevel` says that slot is a
72
+ * level of its own (the campaign designer's columns, once a section has more
73
+ * than one) — the slot;
74
+ * - a slot: the node that owns it;
75
+ * - a top-level node, or nothing selected: `null` (there is nothing above).
76
+ */
77
+ declare function parentStep(data: Data, config: DataConfig, selection: NodeSelection, slotIsLevel?: (parent: Node, slot: string) => boolean): NodeSelection;
78
+ /** A node by its place: the zone key `"<parentId>:<slot>"` and its index. */
79
+ interface ItemSelector {
80
+ zone?: string;
81
+ index: number;
82
+ }
83
+ /** A node selection as Puck's `itemSelector`; `null` for nothing or a slot. */
84
+ declare function toItemSelector(data: Data, config: DataConfig, selection: NodeSelection): ItemSelector | null;
85
+ /** Puck's `itemSelector` as a node selection; `null` when it points at nothing. */
86
+ declare function fromItemSelector(data: Data, config: DataConfig, selector: ItemSelector | null | undefined): NodeSelection;
87
+
88
+ export { type AfterRemove as A, type ItemSelector as I, type NodeSelection as N, type Planned as P, type StepBounds as S, planDuplicate as a, planMove as b, canStep as c, planPaste as d, planRemove as e, fromItemSelector as f, planSetProps as g, planStep as h, parentStep as p, toItemSelector as t };
@@ -0,0 +1,145 @@
1
+ import * as react from 'react';
2
+ import { c as EditorRailProps, E as EditorRailButtonProps } from '../types-DNrn_w4V.js';
3
+ export { a as EditorRailItem, b as EditorRailOrientation } from '../types-DNrn_w4V.js';
4
+ export { AddIcon, LayersIcon, MediaIcon, PageIcon as PagesIcon, IconProps as RailIconProps, SitemapIcon, StylesIcon } from '../icons/index.js';
5
+ export { W as WordFit } from '../wordFit-9B9nqyf3.js';
6
+
7
+ /**
8
+ * The shared icon-rail — one slim strip of icon buttons that every EFFICIENT
9
+ * editor uses to switch panels (Pages / Layers / Styles / Media / Sitemap) and
10
+ * to quick-add ("+"). By default it is the vertical strip down the left; pass
11
+ * `orientation="row"` and the same buttons lay into a top bar instead. It is a
12
+ * **pure presentational switcher**: give it `items[]`, it draws them; which one
13
+ * is `active` and what each does is the host's business.
14
+ *
15
+ * No `'use client'` here on purpose — the rail has no hooks or state, so it is a
16
+ * plain module a Next-16 server component can import and even statically render.
17
+ * It's interactive only through the `onSelect` props the host passes, and the
18
+ * host owns its client boundary (both editors' chrome are already client
19
+ * components), so baking a directive into this leaf would only narrow where it
20
+ * can be imported.
21
+ *
22
+ * A `toolbar` (not a `tablist`), because the rail mixes view *toggles* with
23
+ * plain *actions* like the quick-add — a toolbar of buttons models that
24
+ * honestly, where a tablist would force every button to pretend to be a tab.
25
+ * That is true in both directions, so `orientation` moves `aria-orientation`
26
+ * and leaves the role alone: what the rail IS did not change, only which way it
27
+ * runs — and a toolbar's arrow keys follow its orientation, so a row that kept
28
+ * saying `vertical` would describe the wrong keys.
29
+ *
30
+ * `orientation` defaults to `'column'`, and that path is byte-identical to what
31
+ * this component rendered before the prop existed: same class list, same
32
+ * attributes, same order. `'row'` adds ONE modifier class beside `es-rail` —
33
+ * never instead of it, since every button rule and token in rail.css hangs off
34
+ * the base class — and rail.css does the rest.
35
+ *
36
+ * `wordFit` (a row only) puts every button's word in the markup beside its
37
+ * glyph and hands the words' measured widths to the stylesheet, which shows
38
+ * all of them, only the active one, or none — by the width of the rail's
39
+ * column, the settings tabs' three states. Still no hooks: the measuring is
40
+ * `TopBarRail`'s, and the choosing is CSS's.
41
+ *
42
+ * ITS ONE WORD is the toolbar's name, `ariaLabel` (./words). The rail reads no
43
+ * strings provider — that would be a hook — so a host in another language
44
+ * passes `ariaLabel`; `TopBarRail` does it from the provider.
45
+ */
46
+ declare function EditorRail({ items, ariaLabel, className, orientation, wordFit, }: EditorRailProps): react.JSX.Element;
47
+
48
+ /**
49
+ * A single rail button: an icon in a 34×34 square that hovers, highlights when
50
+ * active, and dims when disabled. Look only — the caller owns the click.
51
+ *
52
+ * `aria-pressed` is emitted **only** when `active` is a real boolean, so a view
53
+ * toggle announces its pressed state while an action button (the quick-add "+",
54
+ * whose item omits `active`) stays a plain button rather than a stuck toggle.
55
+ */
56
+ declare function EditorRailButton({ label, worded, icon, active, disabled, onSelect, className, }: EditorRailButtonProps): react.JSX.Element;
57
+
58
+ /**
59
+ * Rail design tokens — the ONE source both apps' rails converge on.
60
+ *
61
+ * Every value below is taken verbatim from the storefront editor's rail
62
+ * (`efficient-shop` → `storefront-editor.css`, the `.sf-rail` / `.sf-rail-btn`
63
+ * rules), which is the visual reference. The admin portal's hand-rolled rail had
64
+ * drifted (button 36 vs 34, icon 20 vs 18, radius 8 vs 9, a Tailwind accent
65
+ * instead of a CSS var); these tokens end that drift.
66
+ *
67
+ * Exposed BOTH ways, per the same value:
68
+ * - `railTokens` — this JS object, for anything that needs the numbers/colours
69
+ * in code (inline styles, a theme bridge, a Storybook control);
70
+ * - `rail.css` — the stylesheet, which declares the identical values as CSS
71
+ * custom properties on `.es-rail` (names listed in `railCssVars`).
72
+ *
73
+ * Numeric fields are pixel magnitudes (unitless) so they compose in code; the
74
+ * CSS applies the `px`.
75
+ */
76
+ declare const railTokens: {
77
+ /** Rail column width. `--sf-rail-w: 46px`. */
78
+ readonly railWidth: 46;
79
+ /** Vertical padding inside the rail (top === bottom). */
80
+ readonly railPaddingY: 8;
81
+ /** Gap between buttons. */
82
+ readonly railGap: 4;
83
+ /** Rail card corner radius. `--sf-panel-radius: 12px`. */
84
+ readonly railRadius: 12;
85
+ /** Button hit-area (square). */
86
+ readonly buttonSize: 34;
87
+ /** Button corner radius. */
88
+ readonly buttonRadius: 9;
89
+ /** Icon glyph size. */
90
+ readonly iconSize: 18;
91
+ /** A worded row button (`TopBarRail`): its side padding, the gap between the
92
+ * icon and its word, and the word's type. */
93
+ readonly wordPaddingX: 8;
94
+ readonly wordGap: 6;
95
+ readonly wordFontSize: 12;
96
+ readonly wordFontWeight: 500;
97
+ /** Hover/active colour transition. */
98
+ readonly transition: "background 0.12s, color 0.12s";
99
+ /** Disabled-button opacity. */
100
+ readonly disabledOpacity: 0.35;
101
+ readonly color: {
102
+ /** Rail card background. `--sf-panel`. */
103
+ readonly background: "#ffffff";
104
+ /** Rail card shadow. `--sf-panel-shadow`. */
105
+ readonly shadow: "0 1px 2px rgba(0, 0, 0, 0.05), 0 10px 30px rgba(0, 0, 0, 0.06)";
106
+ /** Idle icon colour. `--sf-muted` (gray-500). */
107
+ readonly idle: "#6b7280";
108
+ /** Hover background. `--sf-hover` (gray-100). */
109
+ readonly hoverBg: "#f3f4f6";
110
+ /** Hover icon colour. `--sf-text` (gray-900). */
111
+ readonly hoverFg: "#111827";
112
+ /** Active icon colour. `--sf-accent` (blue-600). */
113
+ readonly accent: "#2563eb";
114
+ /** Active background. `--sf-accent-tint` (blue-50). */
115
+ readonly accentTint: "#eff6ff";
116
+ };
117
+ };
118
+ type RailTokens = typeof railTokens;
119
+ /**
120
+ * The CSS custom-property name behind each token. `rail.css` sets these on
121
+ * `.es-rail`; override any of them on (or above) the rail element to re-theme it
122
+ * — e.g. `style={{ ['--es-rail-accent']: brand }}` — without shipping new CSS.
123
+ */
124
+ declare const railCssVars: {
125
+ readonly railWidth: "--es-rail-w";
126
+ readonly railPaddingY: "--es-rail-pad-y";
127
+ readonly railGap: "--es-rail-gap";
128
+ readonly railRadius: "--es-rail-radius";
129
+ readonly buttonSize: "--es-rail-btn-size";
130
+ readonly buttonRadius: "--es-rail-btn-radius";
131
+ readonly iconSize: "--es-rail-icon-size";
132
+ readonly wordPaddingX: "--es-rail-word-pad-x";
133
+ readonly wordGap: "--es-rail-word-gap";
134
+ readonly wordFontSize: "--es-rail-word-size";
135
+ readonly wordFontWeight: "--es-rail-word-weight";
136
+ readonly background: "--es-rail-bg";
137
+ readonly shadow: "--es-rail-shadow";
138
+ readonly idle: "--es-rail-fg";
139
+ readonly hoverBg: "--es-rail-hover-bg";
140
+ readonly hoverFg: "--es-rail-hover-fg";
141
+ readonly accent: "--es-rail-accent";
142
+ readonly accentTint: "--es-rail-accent-tint";
143
+ };
144
+
145
+ export { EditorRail, EditorRailButton, EditorRailButtonProps, EditorRailProps, type RailTokens, railCssVars, railTokens };
@@ -0,0 +1 @@
1
+ import"../chunk-OWBO4WKA.js";export{EditorRail,EditorRailButton,railCssVars,railTokens}from"../chunk-YIMRTIBN.js";import"../chunk-2OAZYSWJ.js";import"../chunk-BZ5ASXOL.js";export{AddIcon,LayersIcon,MediaIcon,PageIcon as PagesIcon,SitemapIcon,StylesIcon}from"../chunk-YNZZYHA4.js";