efficient-easel 0.0.0 → 0.97.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.
- package/LICENSE +10 -0
- package/README.md +818 -4
- package/dist/autosave/index.d.ts +101 -0
- package/dist/autosave/index.js +1 -0
- package/dist/blocks/index.d.ts +306 -0
- package/dist/blocks/index.js +0 -0
- package/dist/buckets-1vRlQceY.d.ts +13 -0
- package/dist/canvas/index.d.ts +194 -0
- package/dist/canvas/index.js +1 -0
- package/dist/chrome/chrome.css +570 -0
- package/dist/chrome/index.d.ts +219 -0
- package/dist/chrome/index.js +21 -0
- package/dist/chunk-2LUSK32B.js +1 -0
- package/dist/chunk-2OAZYSWJ.js +1 -0
- package/dist/chunk-67VZRQLG.js +13 -0
- package/dist/chunk-6OPSJGAI.js +1 -0
- package/dist/chunk-6PS6STGA.js +1 -0
- package/dist/chunk-6WY6ZXJG.js +1 -0
- package/dist/chunk-7A7O2JWQ.js +1 -0
- package/dist/chunk-BZ5ASXOL.js +1 -0
- package/dist/chunk-CERTFXCV.js +13 -0
- package/dist/chunk-D2BCHTQR.js +1 -0
- package/dist/chunk-FI23ALVJ.js +1 -0
- package/dist/chunk-GGTPNGFJ.js +1 -0
- package/dist/chunk-H4B72XCJ.js +1 -0
- package/dist/chunk-H6DDIBQ4.js +1 -0
- package/dist/chunk-KDIVA6WD.js +1 -0
- package/dist/chunk-KRXQM65X.js +1 -0
- package/dist/chunk-MCLBHENY.js +1 -0
- package/dist/chunk-MFIRYGHW.js +1 -0
- package/dist/chunk-NKB3KVAB.js +1 -0
- package/dist/chunk-O2NLQBFX.js +1 -0
- package/dist/chunk-OGZFDJ6G.js +1 -0
- package/dist/chunk-OWBO4WKA.js +0 -0
- package/dist/chunk-QB3BZIRA.js +5 -0
- package/dist/chunk-RE4LP46F.js +1 -0
- package/dist/chunk-STFMZJKU.js +14 -0
- package/dist/chunk-SUO5627P.js +1 -0
- package/dist/chunk-TDA4HH54.js +1 -0
- package/dist/chunk-WK3AFPXY.js +1 -0
- package/dist/chunk-YIMRTIBN.js +3 -0
- package/dist/chunk-YNZZYHA4.js +450 -0
- package/dist/chunk-YRLPFZW3.js +1 -0
- package/dist/chunk-ZAOM6PFH.js +1 -0
- package/dist/chunk-ZNCEOSXO.js +1 -0
- package/dist/copy/copy.css +282 -0
- package/dist/copy/index.d.ts +336 -0
- package/dist/copy/index.js +10 -0
- package/dist/data/index.d.ts +375 -0
- package/dist/data/index.js +1 -0
- package/dist/drag/drag.css +360 -0
- package/dist/drag/index.d.ts +379 -0
- package/dist/drag/index.js +18 -0
- package/dist/edit-lock/index.d.ts +354 -0
- package/dist/edit-lock/index.js +1 -0
- package/dist/editor/editor.css +58 -0
- package/dist/editor/index.d.ts +361 -0
- package/dist/editor/index.js +9 -0
- package/dist/fields/fields.css +207 -0
- package/dist/fields/index.d.ts +273 -0
- package/dist/fields/index.js +46 -0
- package/dist/gold/gold.css +196 -0
- package/dist/gold/index.d.ts +280 -0
- package/dist/gold/index.js +1 -0
- package/dist/icons/index.d.ts +474 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -0
- package/dist/markup/index.d.ts +452 -0
- package/dist/markup/index.js +1 -0
- package/dist/menu/index.d.ts +49 -0
- package/dist/menu/index.js +15 -0
- package/dist/menu/menu.css +254 -0
- package/dist/node-ops/index.d.ts +91 -0
- package/dist/node-ops/index.js +1 -0
- package/dist/panel/index.d.ts +1088 -0
- package/dist/panel/index.js +16 -0
- package/dist/panel/panel.css +1460 -0
- package/dist/pick/index.d.ts +153 -0
- package/dist/pick/index.js +14 -0
- package/dist/pick/pick.css +235 -0
- package/dist/plan-DChbUXdS.d.ts +88 -0
- package/dist/rail/index.d.ts +145 -0
- package/dist/rail/index.js +1 -0
- package/dist/rail/rail.css +175 -0
- package/dist/render/index.d.ts +117 -0
- package/dist/render/index.js +5 -0
- package/dist/resolve-D68pb3Bx.d.ts +57 -0
- package/dist/shell/index.d.ts +159 -0
- package/dist/shell/index.js +1 -0
- package/dist/shell/shell.css +122 -0
- package/dist/strings/catalogue.d.ts +141 -0
- package/dist/strings/catalogue.js +1 -0
- package/dist/strings/index.d.ts +56 -0
- package/dist/strings/index.js +2 -0
- package/dist/style-clipboard/index.d.ts +184 -0
- package/dist/style-clipboard/index.js +1 -0
- package/dist/tree/index.d.ts +173 -0
- package/dist/tree/index.js +1 -0
- package/dist/tree/tree.css +618 -0
- package/dist/types-41704JE4.d.ts +112 -0
- package/dist/types-CEqUFH2j.d.ts +129 -0
- package/dist/types-CuNiSnX6.d.ts +204 -0
- package/dist/types-DNrn_w4V.d.ts +88 -0
- package/dist/types-Dy6oR6Z2.d.ts +153 -0
- package/dist/types-pGBXevBU.d.ts +131 -0
- package/dist/useNodeOps-BF0fQsif.d.ts +68 -0
- package/dist/watch-DmasQA61.d.ts +60 -0
- package/dist/wordFit-9B9nqyf3.d.ts +35 -0
- package/dist/words-B2YLaKEr.d.ts +30 -0
- package/dist/words-B87GIFWH.d.ts +86 -0
- package/dist/words-BK-npta-.d.ts +27 -0
- package/dist/words-CD3WADyb.d.ts +51 -0
- package/dist/words-CsRWXkY4.d.ts +129 -0
- package/dist/words-D9ChvbNC.d.ts +52 -0
- package/dist/words-DOhF-4D4.d.ts +35 -0
- package/dist/words-DagJdwIA.d.ts +78 -0
- package/dist/words-DfxEp_RH.d.ts +142 -0
- package/package.json +202 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{AccentIcon,AccountIcon,AddIcon,AlignCenterIcon,AlignLeftIcon,AlignRightIcon,AlignSeparateIcon,AnchorIcon,AnimationIcon,BackgroundIcon,BellIcon,BlackAndWhiteIcon,BlockBadgeIcon,BlockBannerIcon,BlockBoxIcon,BlockButtonIcon,BlockColumnIcon,BlockCouponIcon,BlockDividerIcon,BlockFooterIcon,BlockHeadingIcon,BlockIncludedListIcon,BlockLayeredImagesIcon,BlockProductIcon,BlockSectionIcon,BlockSocialIcon,BlockSpacerIcon,BlockTextIcon,BlockVideoIcon,BlogPostIcon,BoldIcon,BreadcrumbIcon,ButtonArrowIcon,ButtonIcon,CarIcon,CartIcon,CaseIcon,CategoryIcon,ChatIcon,CheckIcon,ChevronIcon,ClearMarksIcon,ClockIcon,CloseIcon,ColumnGapIcon,ColumnWidthsIcon,ColumnsIcon,ContrastIcon,CopyIcon,CopyStyleIcon,CopyToPageIcon,CornerIcon,DesktopIcon,DiagramIcon,DirectionDownIcon,DirectionIcon,DirectionLeftIcon,DirectionRightIcon,DirectionUpIcon,DollarIcon,DragIcon,DropIcon,DuplicateIcon,EmphasisIcon,ExternalIcon,EyeIcon,EyeOffIcon,FinishIcon,FullWidthIcon,GapIcon,GlobeIcon,GridIcon,GroundIcon,HashIcon,HeadquartersIcon,HeightIcon,HoverIcon,ImageIcon,ItalicIcon,LayersIcon,LayoutIcon,LetterSpacingIcon,LineBetweenIcon,LineStyleIcon,LinkIcon,ListIcon,LocationIcon,LockIcon,LogoIcon,MediaIcon,MenuIcon,MetaIcon,MoveDownIcon,MoveUpIcon,NoteIcon,NudgeIcon,OpacityIcon,OwnTextIcon,PaddingBottomIcon,PaddingLeftIcon,PaddingRightIcon,PaddingTopIcon,PaddingXIcon,PaddingYIcon,PageIcon,PasteIcon,PasteStyleIcon,PhoneIcon,PlacementIcon,PlayIcon,PlusMinusIcon,ProductFieldIcon,ProductSizeIcon,QuietIcon,RadiusIcon,RedoIcon,RenameIcon,RevertIcon,RotateIcon,ScrollPointIcon,SearchIcon,SendIcon,SettingsIcon,ShadowIcon,SitemapIcon,SortIcon,SpecTableIcon,SquareIcon,StateIcon,StickyIcon,StrikeIcon,StylesIcon,TabletIcon,TabsIcon,TagIcon,TemplateIcon,TextColorIcon,TextLinesIcon,TextSizeIcon,TextSmallIcon,ThemeIcon,TitleIcon,TrashIcon,TriggerIcon,TypeIcon,UnderlineIcon,UndoIcon,VAlignIcon,WarningIcon,WeightIcon,WheelIcon,WidthIcon,WordsFromIcon,WrapIcon,iconMeanings}from"../chunk-YNZZYHA4.js";
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { EditorRail, EditorRailButton, RailTokens, railCssVars, railTokens } from './rail/index.js';
|
|
2
|
+
export { AddIcon, LayersIcon, MediaIcon, PageIcon as PagesIcon, IconProps as RailIconProps, SitemapIcon, StylesIcon } from './icons/index.js';
|
|
3
|
+
export { W as WordFit } from './wordFit-9B9nqyf3.js';
|
|
4
|
+
export { E as EditorRailButtonProps, a as EditorRailItem, b as EditorRailOrientation, c as EditorRailProps } from './types-DNrn_w4V.js';
|
|
5
|
+
export { ShellTokens, shellCssVars, shellTokens } from './shell/index.js';
|
|
6
|
+
import 'react';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./chunk-OWBO4WKA.js";export{shellCssVars,shellTokens}from"./chunk-FI23ALVJ.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";
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The editorial markup rule — ONE grammar, shared by every product that lets a
|
|
3
|
+
* human type formatted copy into a plain text box.
|
|
4
|
+
*
|
|
5
|
+
* WHO OWNS IT. This package does (efficient-easel; `efficient-editor` until 0.95.0, `editor-shell` until 0.66.0), since 2026-09-28. Until then the rule lived in
|
|
6
|
+
* `efficient-shell/markup`, a package Victor owns, and this package only imported
|
|
7
|
+
* it. Lewis decided to keep our own copy rather than wait on upstream for the
|
|
8
|
+
* rich text box: "Two copies is fine. We use our own independent one." This
|
|
9
|
+
* file started from efficient-shell 4.121.0 (the same file as the 4.101.0 and
|
|
10
|
+
* 4.120.0 the apps pin, bar one spelling) and grows here from now on. The two
|
|
11
|
+
* copies are expected to drift; nothing in either product reads both.
|
|
12
|
+
*
|
|
13
|
+
* FRAMEWORK-FREE, behind its own subpath (`efficient-easel/markup`): no React, no
|
|
14
|
+
* JSX, no DOM, no imports at all. The public storefront reads it, and the
|
|
15
|
+
* backend's banner renderer may one day port it, so it must stay a few hundred
|
|
16
|
+
* bytes of plain code that any bundler or resolver can load.
|
|
17
|
+
*
|
|
18
|
+
* WHAT IS SHARED IS THE RULE, NOT THE RENDERING. Each product renders the tree
|
|
19
|
+
* its own way, because it must: the web uses CSS classes bound to theme tokens,
|
|
20
|
+
* email must inline every style (mail clients drop classes). Two renderers, one
|
|
21
|
+
* grammar, one writer ({@link applyMark}) — so a toolbar button and a parser
|
|
22
|
+
* can never disagree about what a delimiter means.
|
|
23
|
+
*
|
|
24
|
+
* ## The delimiters
|
|
25
|
+
*
|
|
26
|
+
* Standard markdown, in every product:
|
|
27
|
+
*
|
|
28
|
+
* **phrase** bold
|
|
29
|
+
* _phrase_ italic
|
|
30
|
+
* ~~phrase~~ strikethrough
|
|
31
|
+
* ==phrase== highlight — the brand accent colour, applied to a selection
|
|
32
|
+
* ^^phrase^^ quiet — smaller and softer
|
|
33
|
+
*
|
|
34
|
+
* THE THREE NAMES a merchant sees ({@link MARK_NAMES}, owner-approved
|
|
35
|
+
* 2026-09-28) sit on these marks: Strong is `**`, Emphasis is `==`, Quiet is
|
|
36
|
+
* `^^`. The storefront once had separate word-style marks for Emphasis and
|
|
37
|
+
* Strong, `@@phrase@@` and `++phrase++`. Production holds none of them, and no
|
|
38
|
+
* button writes them any more, but they are still READ — as Emphasis and Strong
|
|
39
|
+
* — so a string somebody pasted never prints its delimiters.
|
|
40
|
+
*
|
|
41
|
+
* And, read by {@link parseMarkup} (the tree a rich text box and a renderer
|
|
42
|
+
* use), in every product:
|
|
43
|
+
*
|
|
44
|
+
* [words](href) a link — safe addresses only ({@link isSafeLinkHref})
|
|
45
|
+
* - words a bullet line, one level
|
|
46
|
+
* **a _b_ a** one run inside another, one level
|
|
47
|
+
*
|
|
48
|
+
* EVERY READING IS LINE BY LINE: a run never crosses a line break. The email
|
|
49
|
+
* always read this way; the editor's painter and the storefront used to read
|
|
50
|
+
* the whole string, so the same saved text drew differently in each.
|
|
51
|
+
*
|
|
52
|
+
* Plus a per-product LEGACY set ({@link STOREFRONT_MARKUP} /
|
|
53
|
+
* {@link CAMPAIGN_MARKUP}) that keeps already-published copy rendering exactly
|
|
54
|
+
* as it does today. Those legacy rules are the only part of this grammar that is
|
|
55
|
+
* meant to be deleted: once stored content has been converted, drop the rule
|
|
56
|
+
* from the product's rule list and nothing else changes.
|
|
57
|
+
*
|
|
58
|
+
* ## Two deliberate delimiter choices
|
|
59
|
+
*
|
|
60
|
+
* 1. ITALIC IS `_phrase_`, NOT `*phrase*`. In the storefront and the campaign
|
|
61
|
+
* designer a single asterisk already means the ACCENT colour, on 704 live
|
|
62
|
+
* instances. Standard markdown says italic. Both cannot be true, so the
|
|
63
|
+
* asterisk keeps its existing meaning and italic takes markdown's OTHER
|
|
64
|
+
* italic marker. Nothing published moves.
|
|
65
|
+
* 2. `_` NEVER FIRES INSIDE A WORD. Without that guard, a mail merge line
|
|
66
|
+
* holding `{{first_name}}` and `{{last_name}}` would italicise everything
|
|
67
|
+
* between the two underscores. The guard is CommonMark's own rule for `_`
|
|
68
|
+
* (an intraword underscore is literal) and it is checked with plain character
|
|
69
|
+
* tests rather than a lookbehind, because a lookbehind is a parse error on
|
|
70
|
+
* Safari below 16.4 and this code ships to a public storefront.
|
|
71
|
+
*
|
|
72
|
+
* ## One KNOWN divergence from the standard named above
|
|
73
|
+
*
|
|
74
|
+
* `__phrase__` IS ITALIC HERE. CommonMark reads a doubled underscore as STRONG;
|
|
75
|
+
* this grammar has a single `_` rule, so the outer pair matches, the inner
|
|
76
|
+
* underscores are ordinary characters, and `__all__` comes back as `_all_`.
|
|
77
|
+
*
|
|
78
|
+
* Left alone deliberately, and recorded rather than fixed. A census of stored
|
|
79
|
+
* copy found 14 rows in this shape and every one of them is the literal link
|
|
80
|
+
* key `__all__`, which no renderer is handed — so nothing published is being
|
|
81
|
+
* mis-drawn today. Adding a `__` rule to buy CommonMark parity would change how
|
|
82
|
+
* a stored `_` pair parses for the benefit of no existing string, and this
|
|
83
|
+
* module's promise is that the grammar grows while nothing published moves.
|
|
84
|
+
* Bold is `**phrase**` in every product; that is the form the toolbar writes
|
|
85
|
+
* and the form to reach for. Pinned by a test, so it stays a decision rather
|
|
86
|
+
* than becoming a discovery.
|
|
87
|
+
*/
|
|
88
|
+
/** A style a toolbar button can apply. The first six are inline marks; then
|
|
89
|
+
* the composer-only affordances (a link and the three line prefixes), which
|
|
90
|
+
* only a renderer that draws block content can honour; and `clear`, which
|
|
91
|
+
* writes no mark at all — it takes them off ({@link clearMarks}). */
|
|
92
|
+
type MarkStyle = 'bold' | 'italic' | 'strike' | 'code' | 'highlight' | 'quiet' | 'link' | 'bullet' | 'number' | 'quote' | 'clear';
|
|
93
|
+
/** What a parsed run MEANS. `accent` is the storefront/campaign brand colour —
|
|
94
|
+
* the legacy `*phrase*` — and `highlight` is its standard-markdown successor;
|
|
95
|
+
* both resolve to the same paint, which is what makes converting stored copy
|
|
96
|
+
* from one to the other invisible. */
|
|
97
|
+
type InlineKind = 'text' | 'bold' | 'italic' | 'strike' | 'code' | 'highlight' | 'accent' | 'quiet';
|
|
98
|
+
/**
|
|
99
|
+
* The words a press WRITES when nothing is selected — `**bold text**`, left
|
|
100
|
+
* selected so the next keystroke replaces it. English unless the caller of
|
|
101
|
+
* {@link applyMark} hands in its own (0.95.0: a host in another language).
|
|
102
|
+
* They are words put in the box, not rules: nothing here changes what parses.
|
|
103
|
+
*/
|
|
104
|
+
type MarkPlaceholders = Record<'bold' | 'italic' | 'strike' | 'code' | 'highlight' | 'quiet' | 'link', string>;
|
|
105
|
+
declare const MARK_PLACEHOLDERS: MarkPlaceholders;
|
|
106
|
+
interface WrapResult {
|
|
107
|
+
text: string;
|
|
108
|
+
/** Where the caret should land — inside the new marks, or after them. */
|
|
109
|
+
selectionStart: number;
|
|
110
|
+
selectionEnd: number;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Apply a style to `text[start:end]`, returning the new text and where the
|
|
114
|
+
* selection should sit. Toggling is deliberate: pressing Bold on already-bold
|
|
115
|
+
* text unwraps it, so the shortcut behaves like every other editor.
|
|
116
|
+
*
|
|
117
|
+
* Unwrapping only ever recognises the style's OWN fence. An italic button that
|
|
118
|
+
* also unwrapped `*phrase*` would silently strip an accent run in the storefront
|
|
119
|
+
* and the campaign designer, where the asterisk means colour, not slant.
|
|
120
|
+
*/
|
|
121
|
+
declare function applyMark(text: string, start: number, end: number, style: MarkStyle, rules?: readonly InlineRule[], placeholders?: MarkPlaceholders): WrapResult;
|
|
122
|
+
/**
|
|
123
|
+
* True when `text[start:end]` is ALREADY wrapped in `style`'s own fence — the
|
|
124
|
+
* very test {@link applyMark} uses to decide that a press unwraps. So a button
|
|
125
|
+
* drawn as pressed is exactly a button whose press takes the mark off; the two
|
|
126
|
+
* cannot disagree. False for a style with no fence (a link, a line prefix).
|
|
127
|
+
*/
|
|
128
|
+
declare function markedAt(text: string, start: number, end: number, style: MarkStyle): boolean;
|
|
129
|
+
/** A link as it sits in authored text: where it starts and ends, its words and
|
|
130
|
+
* its address. */
|
|
131
|
+
interface LinkSpan {
|
|
132
|
+
start: number;
|
|
133
|
+
end: number;
|
|
134
|
+
label: string;
|
|
135
|
+
href: string;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* The link the caret or selection sits in — `[words](href)` covering all of
|
|
139
|
+
* `start..end` — or null. What the link button opens on: inside a link it edits
|
|
140
|
+
* that link, anywhere else it makes a new one.
|
|
141
|
+
*
|
|
142
|
+
* Read with the parser's own rule ({@link linkAt}), so a span this returns is
|
|
143
|
+
* exactly one the renderers draw as a link — an unsafe address is not a link
|
|
144
|
+
* and is not offered for editing as one.
|
|
145
|
+
*/
|
|
146
|
+
declare function findLink(text: string, start: number, end: number): LinkSpan | null;
|
|
147
|
+
/**
|
|
148
|
+
* Make `text[start:end]` a link to `href`, or re-point the link it sits in.
|
|
149
|
+
* The caret lands after the link, selecting nothing: the link is finished.
|
|
150
|
+
*
|
|
151
|
+
* The words are the selection, or — with nothing selected — the address
|
|
152
|
+
* itself, so a merchant who pastes an address and presses Apply gets a link
|
|
153
|
+
* that reads as that address rather than a placeholder to type over.
|
|
154
|
+
*
|
|
155
|
+
* ⚠️ The address is the CALLER'S to check ({@link isSafeLinkHref}): one that
|
|
156
|
+
* fails would be written as plain text the renderers never draw as a link.
|
|
157
|
+
*/
|
|
158
|
+
declare function writeLink(text: string, start: number, end: number, href: string): WrapResult;
|
|
159
|
+
/** Take a link off, keeping its words — selected, so a second press of the
|
|
160
|
+
* link button puts it straight back. */
|
|
161
|
+
declare function removeLink(text: string, link: LinkSpan): WrapResult;
|
|
162
|
+
/**
|
|
163
|
+
* One toolbar button.
|
|
164
|
+
*
|
|
165
|
+
* - `label` is a text glyph, never a word, for a host that draws type (the
|
|
166
|
+
* message composer). The copy box draws the icon set's glyph instead.
|
|
167
|
+
* - `name` is what the button is CALLED: its accessible name, and the first
|
|
168
|
+
* word of its tooltip. Approved 2026-10-02: no word is drawn ON a button,
|
|
169
|
+
* so a name lives here and in `title` and nowhere a merchant reads it as
|
|
170
|
+
* part of the bar.
|
|
171
|
+
* - `title` is the tooltip: the name, what the mark writes, and the shortcut.
|
|
172
|
+
* - `shortcut` is the key the host combines with its platform modifier: a bare
|
|
173
|
+
* letter (`'b'`), or `'shift+'` and a letter.
|
|
174
|
+
*/
|
|
175
|
+
interface MarkupTool {
|
|
176
|
+
style: MarkStyle;
|
|
177
|
+
label: string;
|
|
178
|
+
title: string;
|
|
179
|
+
name?: string;
|
|
180
|
+
shortcut?: string;
|
|
181
|
+
}
|
|
182
|
+
/** Every button, in the order a writer reaches for them. Clear is last: it
|
|
183
|
+
* acts on every mark before it. */
|
|
184
|
+
declare const MARKUP_TOOLS: readonly MarkupTool[];
|
|
185
|
+
/**
|
|
186
|
+
* The names a merchant sees for the three styles that carry one, in both
|
|
187
|
+
* editors (owner-approved 2026-09-28). One word per idea: Strong IS bold
|
|
188
|
+
* (`**`, ⌘B) and Emphasis IS the highlight (`==`), so a toolbar that shows the
|
|
189
|
+
* names shows no separate B or ◆ beside them.
|
|
190
|
+
*/
|
|
191
|
+
declare const MARK_NAMES: {
|
|
192
|
+
readonly bold: "Strong";
|
|
193
|
+
readonly highlight: "Emphasis";
|
|
194
|
+
readonly quiet: "Quiet";
|
|
195
|
+
};
|
|
196
|
+
/** The tools a host shows, resolved to descriptors in {@link MARKUP_TOOLS}
|
|
197
|
+
* order. Keeps a host from hand-rebuilding a descriptor (and drifting on a
|
|
198
|
+
* tooltip) just to pick a subset. */
|
|
199
|
+
declare function markupTools(styles: readonly MarkStyle[]): MarkupTool[];
|
|
200
|
+
/**
|
|
201
|
+
* One delimited run. `open`/`close` are literal strings; a run's inner text may
|
|
202
|
+
* never contain the first character of `close`, which is what keeps this
|
|
203
|
+
* equivalent to the "asterisk, run of non-asterisks, asterisk" regexes the
|
|
204
|
+
* products used before — the same leftmost match, the same single close
|
|
205
|
+
* candidate, so today's stored copy parses exactly as it always has.
|
|
206
|
+
*/
|
|
207
|
+
interface InlineRule {
|
|
208
|
+
readonly open: string;
|
|
209
|
+
readonly close: string;
|
|
210
|
+
readonly kind: Exclude<InlineKind, 'text'>;
|
|
211
|
+
/** CommonMark's `_` rule: neither delimiter may touch a word character, so
|
|
212
|
+
* `snake_case` and `{{first_name}}` stay literal. */
|
|
213
|
+
readonly wordBoundary?: boolean;
|
|
214
|
+
/**
|
|
215
|
+
* Characters that may not FOLLOW the close. A run whose closing delimiter
|
|
216
|
+
* butts up against one of these does not fire, and both delimiters stay
|
|
217
|
+
* literal.
|
|
218
|
+
*
|
|
219
|
+
* Two legacy rules need it, for the same reason: a delimiter that is also
|
|
220
|
+
* ordinary punctuation will otherwise claim text that was never markup. `#`
|
|
221
|
+
* must not pair across rank markers ("Ranked #1 and #2"), and `[` must not
|
|
222
|
+
* claim the label of a markdown link ("[terms](https://…)"), where the URL
|
|
223
|
+
* would be stranded outside the run and printed to the reader.
|
|
224
|
+
*
|
|
225
|
+
* A character SET rather than a flag per case, so the next collision is one
|
|
226
|
+
* more rule field and no new branch in {@link tokenizeInline}.
|
|
227
|
+
*/
|
|
228
|
+
readonly notBefore?: string;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* The standard-markdown marks, shared by every product.
|
|
232
|
+
*
|
|
233
|
+
* `**` and `*` cannot collide even though they share a prefix: a `*` followed
|
|
234
|
+
* immediately by another `*` is an EMPTY accent run, which no rule matches, so
|
|
235
|
+
* at any one position exactly one of the two can fire. Order between them is
|
|
236
|
+
* therefore not observable — which is worth stating, because the obvious
|
|
237
|
+
* assumption ("longest delimiter must be listed first") would make a future
|
|
238
|
+
* reader think this array's order is load-bearing when the empty-run rule is
|
|
239
|
+
* what actually does the work.
|
|
240
|
+
*/
|
|
241
|
+
declare const STANDARD_MARKUP: readonly InlineRule[];
|
|
242
|
+
/**
|
|
243
|
+
* The buttons a COPY FIELD offers — a storefront section's copy box and a
|
|
244
|
+
* campaign block's copy box show exactly these, so a merchant meets one toolbar
|
|
245
|
+
* wherever they type.
|
|
246
|
+
*
|
|
247
|
+
* DERIVED, not listed. A copy field renders inline runs only — no lists, no
|
|
248
|
+
* block quote, no anchor — so the set it may advertise is precisely the tools
|
|
249
|
+
* whose output this grammar parses back. Writing that as a literal list was the
|
|
250
|
+
* bug's hiding place: the list and the grammar were two hand-maintained facts
|
|
251
|
+
* that agreed by luck, and nothing failed when a tool wrote a delimiter the
|
|
252
|
+
* parser read as something else. Now a style with no rule cannot appear here,
|
|
253
|
+
* whatever anyone types into the array.
|
|
254
|
+
*
|
|
255
|
+
* It resolves to bold, italic, strike, highlight and quiet. `code` is the one that
|
|
256
|
+
* surprises: it is an inline mark and it has a fence, but no rule reads a
|
|
257
|
+
* backtick, so a copy field would print `` `word` `` verbatim — and a button
|
|
258
|
+
* whose output the renderer draws as literal text is worse than no button.
|
|
259
|
+
*
|
|
260
|
+
* Declared here, below the rules, because it now depends on them.
|
|
261
|
+
*/
|
|
262
|
+
declare const COPY_FIELD_TOOLS: readonly MarkStyle[];
|
|
263
|
+
/**
|
|
264
|
+
* The storefront's rule set: standard markdown plus the ONE legacy run it has
|
|
265
|
+
* published — `*phrase*` in the theme accent. Delete that last entry once
|
|
266
|
+
* stored copy has been converted to `==phrase==`; both kinds already paint the
|
|
267
|
+
* same, so the deletion is invisible.
|
|
268
|
+
*/
|
|
269
|
+
declare const STOREFRONT_MARKUP: readonly InlineRule[];
|
|
270
|
+
/**
|
|
271
|
+
* The campaign designer's rule set: standard markdown plus its three legacy
|
|
272
|
+
* runs — `#phrase#` bold, `*phrase*` accent, `[phrase]` italic. The bold guard
|
|
273
|
+
* is the one the email parser already carried: a `#` followed by a digit does
|
|
274
|
+
* not close a run, so "Ranked #1 and #2" is literal while "Save #20%#" bolds.
|
|
275
|
+
*
|
|
276
|
+
* The italic guard exists because `[phrase]` collides with the one piece of
|
|
277
|
+
* markdown syntax every writer already knows. Without it, "Read [our
|
|
278
|
+
* terms](https://…) first" italicises the label AND prints the bare URL to the
|
|
279
|
+
* reader — the words come back wearing a mark nobody chose. It is not a
|
|
280
|
+
* toolbar problem: a merchant typing an ordinary link into a copy box hits it,
|
|
281
|
+
* which is why the guard is on the RULE and not on which buttons a field
|
|
282
|
+
* offers. `[phrase]` on its own is untouched, so every published line still
|
|
283
|
+
* reads exactly as it did.
|
|
284
|
+
*/
|
|
285
|
+
declare const CAMPAIGN_MARKUP: readonly InlineRule[];
|
|
286
|
+
/** A parsed run. `text` is the inner text with the delimiters removed, so a
|
|
287
|
+
* caller that only wants words ({@link stripInline}) joins the texts and a
|
|
288
|
+
* caller that paints ({@link InlineKind}) switches on the kind. */
|
|
289
|
+
interface InlineToken {
|
|
290
|
+
kind: InlineKind;
|
|
291
|
+
text: string;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Split authored text into its runs.
|
|
295
|
+
*
|
|
296
|
+
* The returned tokens ALTERNATE, starting and ending with a `text` token that
|
|
297
|
+
* may be empty — exactly the shape `String.split` with one capture group
|
|
298
|
+
* produced in the parsers this replaces. That contract is load-bearing, not
|
|
299
|
+
* cosmetic: the storefront wraps every segment (including the empty ones) in a
|
|
300
|
+
* `<span>`, so preserving the empty tokens is what keeps a published page's
|
|
301
|
+
* markup byte-for-byte identical.
|
|
302
|
+
*
|
|
303
|
+
* A lone delimiter with no partner stays literal — the products' copy contains
|
|
304
|
+
* real stray asterisks, and swallowing one would silently eat a word.
|
|
305
|
+
*/
|
|
306
|
+
declare function tokenizeInline(value: string, rules: readonly InlineRule[]): InlineToken[];
|
|
307
|
+
/**
|
|
308
|
+
* The plain words of an authored string — every paired run unwrapped to its
|
|
309
|
+
* inner text, every unpaired delimiter left alone.
|
|
310
|
+
*
|
|
311
|
+
* This is the ONLY correct way to fill an `alt`, an `aria-label` or a
|
|
312
|
+
* structured-data field from authored copy. Deleting a delimiter character by
|
|
313
|
+
* hand — "replace every asterisk with nothing" — breaks the moment the grammar
|
|
314
|
+
* grows a marker, and it disagrees with the renderer about a stray asterisk,
|
|
315
|
+
* which matters: search engines expect structured data to match the text a
|
|
316
|
+
* visitor actually sees.
|
|
317
|
+
*/
|
|
318
|
+
declare function stripInline(value: string, rules: readonly InlineRule[]): string;
|
|
319
|
+
/** Plain words. */
|
|
320
|
+
interface MarkupText {
|
|
321
|
+
kind: 'text';
|
|
322
|
+
text: string;
|
|
323
|
+
}
|
|
324
|
+
/** A delimited run. `open` and `close` are the delimiters that were actually
|
|
325
|
+
* saved — `#` for a legacy campaign bold, `**` for the standard one — so the
|
|
326
|
+
* tree writes back the same bytes it was read from. */
|
|
327
|
+
interface MarkupRun {
|
|
328
|
+
kind: InlineRule['kind'];
|
|
329
|
+
open: string;
|
|
330
|
+
close: string;
|
|
331
|
+
children: MarkupNode[];
|
|
332
|
+
}
|
|
333
|
+
/** `[words](href)`. Only made when {@link isSafeLinkHref} passes. */
|
|
334
|
+
interface MarkupLink {
|
|
335
|
+
kind: 'link';
|
|
336
|
+
href: string;
|
|
337
|
+
children: MarkupNode[];
|
|
338
|
+
}
|
|
339
|
+
type MarkupNode = MarkupText | MarkupRun | MarkupLink;
|
|
340
|
+
/** One line of the saved string. `bullet` means it was saved as `- words`;
|
|
341
|
+
* `children` hold what follows the `- `. */
|
|
342
|
+
interface MarkupLine {
|
|
343
|
+
bullet: boolean;
|
|
344
|
+
children: MarkupNode[];
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* True for an address a link may carry: `http`, `https`, `mailto` or `tel`, or
|
|
348
|
+
* one with no scheme at all (`/tyres`, `#top`, `terms.html`, `//cdn…`).
|
|
349
|
+
* Anything else — `javascript:`, `data:`, `file:` — is refused, and so is an
|
|
350
|
+
* address with any space or control character in it, because browsers drop
|
|
351
|
+
* those before they read the scheme (`java\tscript:` runs as `javascript:`).
|
|
352
|
+
*
|
|
353
|
+
* Renderers call this too, before they write an `href`, so the rule has one
|
|
354
|
+
* home.
|
|
355
|
+
*/
|
|
356
|
+
declare function isSafeLinkHref(href: string): boolean;
|
|
357
|
+
/**
|
|
358
|
+
* Read authored text into lines of nodes — the whole grammar, for a rich text
|
|
359
|
+
* box and for the renderers that draw links and bullets.
|
|
360
|
+
*
|
|
361
|
+
* - A run never crosses a line break.
|
|
362
|
+
* - A line that starts with `- ` and has words after it is a bullet. One level
|
|
363
|
+
* only: an indented ` - ` is plain text.
|
|
364
|
+
* - `[words](href)` is a link when {@link isSafeLinkHref} passes; otherwise the
|
|
365
|
+
* whole thing stays plain text.
|
|
366
|
+
* - A run's words are read once more, so `**bold _with italic_**` is italic
|
|
367
|
+
* inside bold. One level only: a third level stays as typed.
|
|
368
|
+
*
|
|
369
|
+
* Nothing is lost: {@link serializeMarkup} writes back the exact bytes.
|
|
370
|
+
*/
|
|
371
|
+
declare function parseMarkup(value: string, rules: readonly InlineRule[]): MarkupLine[];
|
|
372
|
+
/** Write lines back to authored text: the inverse of {@link parseMarkup}. */
|
|
373
|
+
declare function serializeMarkup(lines: readonly MarkupLine[]): string;
|
|
374
|
+
/**
|
|
375
|
+
* One run a character sits in. `id` says WHICH run: two characters with the
|
|
376
|
+
* same id are in one run, so `**a****b**` (two bold runs, back to back) is told
|
|
377
|
+
* apart from `**ab**` (one) and is written back as it was saved.
|
|
378
|
+
*
|
|
379
|
+
* `open` and `close` are the delimiters that were saved (`#` for a legacy
|
|
380
|
+
* campaign bold), or, for a run made in the editor, the toolbar's own fence.
|
|
381
|
+
* A link's `close` is `](href)`.
|
|
382
|
+
*/
|
|
383
|
+
interface MarkSpan {
|
|
384
|
+
id: number;
|
|
385
|
+
kind: InlineRule['kind'] | 'link';
|
|
386
|
+
open: string;
|
|
387
|
+
close: string;
|
|
388
|
+
href?: string;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* A NEW run, as the toolbar writes it: `**` for bold, `_` for italic, and so on
|
|
392
|
+
* ({@link applyMark}'s own fences), or a link to `href`. Null for a style that
|
|
393
|
+
* writes no run (a line prefix, clear). A paste filter or an editor making a
|
|
394
|
+
* run asks here, so no delimiter is spelled outside this module (E8).
|
|
395
|
+
*/
|
|
396
|
+
declare function spanFor(id: number, style: MarkStyle, href?: string): MarkSpan | null;
|
|
397
|
+
/** One character of the words — never a delimiter — with the runs it sits in,
|
|
398
|
+
* outside first, and its offset in the saved string. */
|
|
399
|
+
interface MarkedChar {
|
|
400
|
+
ch: string;
|
|
401
|
+
marks: readonly MarkSpan[];
|
|
402
|
+
at: number;
|
|
403
|
+
}
|
|
404
|
+
/** One line: its bullet flag (the `- ` is not a character) and its words. */
|
|
405
|
+
interface MarkedLine {
|
|
406
|
+
bullet: boolean;
|
|
407
|
+
chars: MarkedChar[];
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Read authored text as characters, each carrying its runs — what an editor
|
|
411
|
+
* that hides the delimiters edits, and what {@link clearMarks} splits runs on.
|
|
412
|
+
*
|
|
413
|
+
* Built on {@link parseMarkup}, so it is the same reading, character by
|
|
414
|
+
* character. A character is a code point (an emoji is one, two UTF-16 units
|
|
415
|
+
* long); `at` counts UTF-16 units, as a text box's selection does.
|
|
416
|
+
*
|
|
417
|
+
* {@link writeMarkedLines} writes it back byte for byte.
|
|
418
|
+
*/
|
|
419
|
+
declare function markedLines(value: string, rules: readonly InlineRule[]): MarkedLine[];
|
|
420
|
+
/**
|
|
421
|
+
* Write characters back to authored text: the inverse of {@link markedLines}.
|
|
422
|
+
* Returns where each character landed (`at[line][char]`), so a selection can be
|
|
423
|
+
* carried across the write.
|
|
424
|
+
*
|
|
425
|
+
* Runs are opened and closed as the characters ask: a character outside a run
|
|
426
|
+
* that its neighbours are in closes it, and the run's next character opens it
|
|
427
|
+
* again. That is how a run is SPLIT — clear the middle of `**abcd**` and it is
|
|
428
|
+
* written `**a**bc**d**`. Whether the result reads back as written is the
|
|
429
|
+
* caller's to check ({@link sameMarks}): `_` cannot close against a letter.
|
|
430
|
+
*/
|
|
431
|
+
declare function writeMarkedLines(lines: readonly MarkedLine[]): {
|
|
432
|
+
text: string;
|
|
433
|
+
at: number[][];
|
|
434
|
+
};
|
|
435
|
+
/** True when two readings hold the same words wearing the same marks — the
|
|
436
|
+
* kinds and addresses, not which run or which delimiter. */
|
|
437
|
+
declare function sameMarks(a: readonly MarkedLine[], b: readonly MarkedLine[]): boolean;
|
|
438
|
+
/**
|
|
439
|
+
* Take every mark off `text[start:end]`, keeping the words and any link (a
|
|
440
|
+
* link has its own Remove). A run the selection only partly covers is split,
|
|
441
|
+
* so the rest of it keeps its mark. Bullets stay: they are lines, not marks.
|
|
442
|
+
*
|
|
443
|
+
* With nothing selected, the runs the caret touches — the characters either
|
|
444
|
+
* side of it — come off whole.
|
|
445
|
+
*
|
|
446
|
+
* A split the grammar cannot hold comes off whole instead: the middle of
|
|
447
|
+
* `_word_` would leave an underscore against a letter, which reads as a
|
|
448
|
+
* literal underscore. And if even that would not read back, nothing changes.
|
|
449
|
+
*/
|
|
450
|
+
declare function clearMarks(text: string, start: number, end: number, rules?: readonly InlineRule[]): WrapResult;
|
|
451
|
+
|
|
452
|
+
export { CAMPAIGN_MARKUP, COPY_FIELD_TOOLS, type InlineKind, type InlineRule, type InlineToken, type LinkSpan, MARKUP_TOOLS, MARK_NAMES, MARK_PLACEHOLDERS, type MarkPlaceholders, type MarkSpan, type MarkStyle, type MarkedChar, type MarkedLine, type MarkupLine, type MarkupLink, type MarkupNode, type MarkupRun, type MarkupText, type MarkupTool, STANDARD_MARKUP, STOREFRONT_MARKUP, type WrapResult, applyMark, clearMarks, findLink, isSafeLinkHref, markedAt, markedLines, markupTools, parseMarkup, removeLink, sameMarks, serializeMarkup, spanFor, stripInline, tokenizeInline, writeLink, writeMarkedLines };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{CAMPAIGN_MARKUP,COPY_FIELD_TOOLS,MARKUP_TOOLS,MARK_NAMES,MARK_PLACEHOLDERS,STANDARD_MARKUP,STOREFRONT_MARKUP,applyMark,clearMarks,findLink,isSafeLinkHref,markedAt,markedLines,markupTools,parseMarkup,removeLink,sameMarks,serializeMarkup,spanFor,stripInline,tokenizeInline,writeLink,writeMarkedLines}from"../chunk-6OPSJGAI.js";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { a as EditorContextMenuProps, b as EditorContextMenuSkin } from '../types-pGBXevBU.js';
|
|
3
|
+
export { E as EditorContextMenuItem } from '../types-pGBXevBU.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The right-click menu box, shared by both editors (card 66400.d).
|
|
7
|
+
*
|
|
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.
|
|
15
|
+
*
|
|
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 EditorContextMenuSkin}, 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)).
|
|
25
|
+
*
|
|
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.
|
|
30
|
+
*/
|
|
31
|
+
declare function EditorContextMenu({ x, y, items, onClose, maxHeight, skin, portalTo, }: EditorContextMenuProps): react.ReactPortal | null;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The default skin — the storefront editor's approved look, in classes that
|
|
35
|
+
* `efficient-easel/menu.css` dresses.
|
|
36
|
+
*
|
|
37
|
+
* ⚠️ `es-editor` IS LOAD-BEARING AND IS NOT DECORATION. `shell.css` declares
|
|
38
|
+
* every `--es-*` on `.es-editor`, deliberately never on `:root`, so importing it
|
|
39
|
+
* themes an editor root and leaks nothing into the host page — the campaign
|
|
40
|
+
* designer renders INSIDE the admin DOM. The box is portalled to
|
|
41
|
+
* `document.body`, which is OUTSIDE that root, so without the class here every
|
|
42
|
+
* `var(--es-*)` in `menu.css` resolves to nothing: the background collapses to
|
|
43
|
+
* transparent and the border to `currentColor`. The result is markup that is
|
|
44
|
+
* present, answers every query a test can ask, and cannot be seen. Pinned by
|
|
45
|
+
* `tests/menu-layer.test.tsx` (f).
|
|
46
|
+
*/
|
|
47
|
+
declare const ES_MENU_SKIN: EditorContextMenuSkin;
|
|
48
|
+
|
|
49
|
+
export { ES_MENU_SKIN, EditorContextMenu, EditorContextMenuProps, EditorContextMenuSkin };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import{useRef as e,useState as n,useLayoutEffect as t,useEffect as l}from"react";import{createPortal as r}from"react-dom";import{jsxs as i,Fragment as a,jsx as o}from"react/jsx-runtime";var c={menu:"es-editor es-menu",menuSub:"es-editor es-menu",item:"es-menu-item",itemDanger:"es-menu-item is-danger",divider:"es-menu-divider",header:"es-menu-header",icon:"es-menu-icon",shortcut:"es-menu-shortcut",label:"es-menu-label",labelSub:"es-menu-label es-menu-label-sub",chevron:"es-menu-chevron",tick:"es-menu-tick"};function u({x:u,y:s,items:d,onClose:m,maxHeight:h,skin:b,portalTo:y}){const p=e(null),g=e(null),[f,w]=n(null),[v,x]=n({x:u,y:s}),[N,k]=n({x:u,y:s});if(N.x===u&&N.y===s||(k({x:u,y:s}),x({x:u,y:s})),t(()=>{const e=p.current;if(!e)return;const{width:n,height:t}=e.getBoundingClientRect(),l=window.innerWidth,r=window.innerHeight;let i=s;s+t>r-8&&(i=s-t>=8?s-t:Math.max(8,r-t-8));let a=u;u+n>l-8&&(a=u-n>=8?u-n:Math.max(8,l-n-8)),a===v.x&&i===v.y||x({x:a,y:i})},[u,s,d,v.x,v.y]),l(()=>{const e=document.activeElement;return p.current?.querySelector('[role="menuitem"]')?.focus(),()=>e?.focus?.()},[]),l(()=>{const e=e=>{const n=e.target;g.current?.contains(n)||p.current&&!p.current.contains(n)&&m()},n=e=>{"Escape"===e.key&&m()},t=setTimeout(()=>{window.addEventListener("mousedown",e),window.addEventListener("contextmenu",e),window.addEventListener("keydown",n)},0);return()=>{clearTimeout(t),window.removeEventListener("mousedown",e),window.removeEventListener("contextmenu",e),window.removeEventListener("keydown",n)}},[m]),0===d.length)return null;const C=b??c,E=e=>e.danger?C.itemDanger:C.item,S=({on:e})=>/* @__PURE__ */o("span",{className:C.tick,"aria-hidden":"true",children:e?"✓":""}),L=({of:e})=>null==e.icon?null:/* @__PURE__ */o("span",{className:C.icon,"aria-hidden":"true",children:e.icon}),T=({of:e})=>null==e.shortcut?null:/* @__PURE__ */o("span",{className:C.shortcut,"aria-hidden":"true",children:e.shortcut}),B=e=>null!=e.icon||null!=e.shortcut,M=(e,n,t)=>{if(!e.children?.length)return void w(null);const l=t.getBoundingClientRect(),r=p.current,i=r?parseFloat(getComputedStyle(r).minWidth):NaN,a=Number.isFinite(i)&&i>0?i:r?.getBoundingClientRect().width??0,o=l.right+a>window.innerWidth-8?l.left-a:l.right;w({i:n,x:Math.max(8,o),y:l.top})},F=f?d[f.i]:null,H=e=>({...C.surface,...e});return r(
|
|
2
|
+
/* @__PURE__ */i(a,{children:[
|
|
3
|
+
/* @__PURE__ */o("div",{ref:p,"data-es-menu":"",role:"menu","aria-label":d.find(e=>e.header)?.label,className:C.menu,style:H({left:v.x,top:v.y,maxHeight:h||void 0}),children:d.map((e,n)=>"—"===e.label?
|
|
4
|
+
/* @__PURE__ */o("div",{role:"separator",className:C.divider},n):e.header?/* @__PURE__ */o("div",{className:C.header,children:e.label},n):e.children?.length?/* @__PURE__ */i("button",{type:"button",role:"menuitem","aria-haspopup":"menu","aria-expanded":f?.i===n,onMouseEnter:t=>M(e,n,t.currentTarget),onFocus:t=>M(e,n,t.currentTarget),onClick:t=>M(e,n,t.currentTarget),className:E(e),style:C.itemStyle,children:[
|
|
5
|
+
/* @__PURE__ */o(L,{of:e}),
|
|
6
|
+
/* @__PURE__ */o("span",{className:C.label,children:e.label}),
|
|
7
|
+
/* @__PURE__ */o(T,{of:e}),
|
|
8
|
+
/* @__PURE__ */o("span",{"aria-hidden":"true",className:C.chevron,children:"›"})]},n):/* @__PURE__ */o("button",{type:"button",role:"menuitem",onMouseEnter:()=>w(null),onClick:()=>{e.onClick(),m()},className:E(e),style:C.itemStyle,children:B(e)?/* @__PURE__ */i(a,{children:[
|
|
9
|
+
/* @__PURE__ */o(L,{of:e}),
|
|
10
|
+
/* @__PURE__ */o("span",{className:C.label,children:e.label}),
|
|
11
|
+
/* @__PURE__ */o(T,{of:e})]}):e.label},n))}),F?.children?.length&&f?/* @__PURE__ */o("div",{ref:g,"data-es-menu":"",role:"menu","aria-label":F.label,className:C.menuSub??C.menu,style:H({left:f.x,top:f.y}),children:F.children.map((e,n)=>/* @__PURE__ */i("button",{type:"button",role:"menuitemradio","aria-checked":Boolean(e.checked),onClick:()=>{e.onClick(),m()},className:E(e),style:C.itemStyle,children:[
|
|
12
|
+
/* @__PURE__ */o(S,{on:Boolean(e.checked)}),
|
|
13
|
+
/* @__PURE__ */o(L,{of:e}),
|
|
14
|
+
/* @__PURE__ */o("span",{className:C.labelSub??C.label,children:e.label}),
|
|
15
|
+
/* @__PURE__ */o(T,{of:e})]},n))}):null]}),y??document.body)}export{c as ES_MENU_SKIN,u as EditorContextMenu};
|