efficient-easel 11.2.0 → 11.8.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 (121) hide show
  1. package/README.md +20 -41
  2. package/dist/{canvas-typing-DvoqQINU.d.ts → canvas-typing-CC0wA-ys.d.ts} +16 -105
  3. package/dist/chrome/chrome.css +5 -5
  4. package/dist/{chunk-66LWADFP.js → chunk-2XMHLXMA.js} +1 -1
  5. package/dist/chunk-33C6ALT5.js +1 -0
  6. package/dist/{chunk-XJCMEEBH.js → chunk-36LVP5WD.js} +7 -7
  7. package/dist/{chunk-SJPKSE5G.js → chunk-5IUS6PM6.js} +12 -12
  8. package/dist/{chunk-FPAVNX3Y.js → chunk-5XUMLKQF.js} +3 -3
  9. package/dist/{chunk-PPL7GB7D.js → chunk-62VPYC7F.js} +1 -1
  10. package/dist/chunk-643PVCEG.js +1 -0
  11. package/dist/{chunk-TWFM6GNK.js → chunk-6IT5H37Y.js} +1 -1
  12. package/dist/{chunk-AFSSKGTD.js → chunk-6TJ2CIMY.js} +1 -1
  13. package/dist/{chunk-4ACVXF5V.js → chunk-7BRKNQF7.js} +1 -1
  14. package/dist/chunk-A6PY6C3C.js +1 -0
  15. package/dist/{chunk-XWK7WZQS.js → chunk-ATF2T5CI.js} +1 -1
  16. package/dist/chunk-AYWN5NRH.js +1 -0
  17. package/dist/chunk-DE35IQ4T.js +57 -0
  18. package/dist/{chunk-PPHUWGLZ.js → chunk-EC2I7SIY.js} +1 -1
  19. package/dist/{chunk-DGAXQTDW.js → chunk-FHLYUIJJ.js} +1 -1
  20. package/dist/{chunk-65Z5FSAW.js → chunk-G3GWAP5R.js} +5 -5
  21. package/dist/{chunk-2FZJWH24.js → chunk-H4UMY7QF.js} +1 -1
  22. package/dist/chunk-H6IXH4A3.js +1 -0
  23. package/dist/{chunk-EXKKLTXH.js → chunk-HRUMNRYI.js} +1 -1
  24. package/dist/chunk-JHYKRGRA.js +5 -0
  25. package/dist/chunk-JRRYFSCY.js +5 -0
  26. package/dist/chunk-KEWQZYUF.js +7 -0
  27. package/dist/{chunk-IFS5P7CO.js → chunk-LZS7JZK5.js} +1 -1
  28. package/dist/{chunk-FGJSCWUN.js → chunk-NY7HHUGB.js} +2 -2
  29. package/dist/chunk-PS5BWS4I.js +2 -0
  30. package/dist/{chunk-47RGVCPH.js → chunk-QFVRMARS.js} +12 -12
  31. package/dist/chunk-TBF5CVPA.js +1 -0
  32. package/dist/{chunk-26GHWMNN.js → chunk-UBAYMWCX.js} +19 -19
  33. package/dist/chunk-UMDQ6CLQ.js +2 -0
  34. package/dist/{chunk-ZXGQT75B.js → chunk-URC5Y5G2.js} +1 -1
  35. package/dist/chunk-VVPZMWJE.js +1 -0
  36. package/dist/chunk-WRFD4EXD.js +1 -0
  37. package/dist/chunk-YHZSBL2N.js +1 -0
  38. package/dist/copy/copy.css +8 -108
  39. package/dist/copy/index.d.ts +39 -9
  40. package/dist/copy/index.js +18 -19
  41. package/dist/drag/drag.css +8 -8
  42. package/dist/drag/index.js +1 -1
  43. package/dist/easel/index.d.ts +3 -3
  44. package/dist/easel/index.js +1 -1
  45. package/dist/fields/fields.css +8 -8
  46. package/dist/fields/index.js +1 -1
  47. package/dist/gold/index.d.ts +3 -3
  48. package/dist/gold/index.js +1 -1
  49. package/dist/{host-Ud6rLpph.d.ts → host-GOlVK3LX.d.ts} +9 -0
  50. package/dist/icons/index.d.ts +1 -0
  51. package/dist/markup/index.d.ts +1 -629
  52. package/dist/markup/index.js +1 -1
  53. package/dist/menu/menu.css +1 -1
  54. package/dist/panel/index.js +1 -1
  55. package/dist/panel/panel.css +12 -12
  56. package/dist/pick/index.js +1 -1
  57. package/dist/pick/pick.css +7 -7
  58. package/dist/screen/components/AddBlockRow.js +1 -1
  59. package/dist/screen/components/AddSidePanel.js +1 -1
  60. package/dist/screen/components/MediaSidePanel.d.ts +1 -1
  61. package/dist/screen/components/MediaSidePanel.js +1 -1
  62. package/dist/screen/components/NodeContextMenu.d.ts +1 -1
  63. package/dist/screen/components/NodeContextMenu.js +1 -1
  64. package/dist/screen/components/PageEditGuardNotices.js +1 -1
  65. package/dist/screen/components/PanelToggleIcon.d.ts +6 -5
  66. package/dist/screen/components/PanelToggleIcon.js +1 -1
  67. package/dist/screen/components/SaveControls.d.ts +1 -1
  68. package/dist/screen/components/ScreenChrome.js +1 -1
  69. package/dist/screen/components/ScreenDocumentContext.d.ts +1 -1
  70. package/dist/screen/components/ScreenDocumentContext.js +1 -1
  71. package/dist/screen/components/ScreenMount.d.ts +1 -1
  72. package/dist/screen/components/ScreenMount.js +1 -1
  73. package/dist/screen/components/ScreenStoreProvider.d.ts +1 -1
  74. package/dist/screen/components/ScreenStoreProvider.js +1 -1
  75. package/dist/screen/components/SectionScreenShell.js +16 -16
  76. package/dist/screen/components/SidePanelHead.js +1 -1
  77. package/dist/screen/components/usePartBridge.d.ts +1 -1
  78. package/dist/screen/components/usePartBridge.js +1 -1
  79. package/dist/screen/index.d.ts +1 -1
  80. package/dist/screen/index.js +1 -1
  81. package/dist/screen/lib/document-addressing.d.ts +1 -1
  82. package/dist/screen/lib/host.d.ts +1 -1
  83. package/dist/screen/lib/key-scope.js +1 -1
  84. package/dist/screen/lib/media-panel.d.ts +1 -1
  85. package/dist/screen/lib/media-picker.d.ts +1 -1
  86. package/dist/screen/lib/puck/device-fields.js +1 -1
  87. package/dist/screen/lib/puck/slot-child-name.js +1 -1
  88. package/dist/screen/lib/puck/style-clipboard.d.ts +1 -1
  89. package/dist/screen/lib/puck/style-clipboard.js +1 -1
  90. package/dist/screen/lib/puck/use-section-ops.js +1 -1
  91. package/dist/screen/lib/screen-parts.d.ts +1 -1
  92. package/dist/screen/lib/screen-parts.js +1 -1
  93. package/dist/screen/screen.css +16 -16
  94. package/dist/screen/words.js +1 -1
  95. package/dist/shell/shell.css +10 -8
  96. package/dist/strings/catalogue.d.ts +2 -2
  97. package/dist/strings/catalogue.js +1 -1
  98. package/dist/strings/index.d.ts +2 -2
  99. package/dist/strings/index.js +1 -1
  100. package/dist/text-styles/index.d.ts +4 -231
  101. package/dist/text-styles/index.js +35 -35
  102. package/dist/text-styles/model.d.ts +259 -0
  103. package/dist/text-styles/model.js +1 -0
  104. package/dist/text-styles/text-styles.css +6 -6
  105. package/dist/{words-dsex49Jh.d.ts → words-CiuKuiy0.d.ts} +12 -6
  106. package/package.json +6 -1
  107. package/dist/chunk-546BBOPE.js +0 -1
  108. package/dist/chunk-AZOCY74V.js +0 -57
  109. package/dist/chunk-BHRC3ASL.js +0 -1
  110. package/dist/chunk-BPFPM3L4.js +0 -7
  111. package/dist/chunk-DD7A6RRL.js +0 -5
  112. package/dist/chunk-EQFBUNEB.js +0 -1
  113. package/dist/chunk-FAAZZXIS.js +0 -1
  114. package/dist/chunk-FFYXVAAL.js +0 -1
  115. package/dist/chunk-GCCBEY2W.js +0 -2
  116. package/dist/chunk-GG4FGBFR.js +0 -1
  117. package/dist/chunk-KQMML6JM.js +0 -4
  118. package/dist/chunk-Q6AM43CA.js +0 -1
  119. package/dist/chunk-U7KFFC7N.js +0 -5
  120. package/dist/chunk-VWXUMVCD.js +0 -1
  121. package/dist/chunk-ZAOM6PFH.js +0 -1
@@ -1,629 +1 @@
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
- * 1. words a numbered line, one level: 1-3 digits, ". ", words
47
- * **a _b_ a** one run inside another, up to four levels
48
- *
49
- * [words]{underline} underline
50
- * [words]{sup} superscript
51
- * [words]{sub} subscript
52
- * [words]{color=name} a theme colour, by NAME ({@link isColorName})
53
- * [words]{color=#rrggbb} any colour, six hex digits ({@link isColorValue})
54
- *
55
- * THE SPANS AND THE NUMBERED LINE (plan step E1, approved by GG 2026-10-06)
56
- * are the final text box's marks. One shape for all four, so one reading rule:
57
- * `[`, words, `]`, then EXACTLY one of those blocks. Anything else after the
58
- * `]` — `{foo}`, `{color=#f00}`, a merge tag's `{{` — is not a span, and the
59
- * whole thing stays as typed. Two marks on the same words nest
60
- * (`[[x]{sup}]{color=accent}`); one block never holds two.
61
- *
62
- * Why these cannot clash with an old mark: no old rule closes on `]{` (a link
63
- * needs `](`), and the campaign's legacy `[phrase]` italic steps back from a
64
- * span block, the way it steps back from a link (`notBeforeBlock`). No inline
65
- * rule opens on a digit. Production held neither shape when they were chosen:
66
- * 0 of 24,233 text values contained `]{`, and none had a line starting with a
67
- * number and `.` or `)`.
68
- *
69
- * Not chosen: `^x^` / `~x~` (one character from Quiet `^^` and strike `~~`,
70
- * and `~20 min` is ordinary copy), `__x__` (pinned as italic below, and
71
- * CommonMark's bold) and `++x++` (the old Strong).
72
- *
73
- * Only the tree ({@link parseMarkup}) reads them. The flat
74
- * {@link tokenizeInline} — the old renderers' reading — keeps them as text, as
75
- * it keeps links: an email or a page drawn before its renderer learns them
76
- * prints the delimiters. No toolbar button writes them yet.
77
- *
78
- * EVERY READING IS LINE BY LINE: a run never crosses a line break. The email
79
- * always read this way; the editor's painter and the storefront used to read
80
- * the whole string, so the same saved text drew differently in each.
81
- *
82
- * Plus a per-product LEGACY set ({@link STOREFRONT_MARKUP} /
83
- * {@link CAMPAIGN_MARKUP}) that keeps already-published copy rendering exactly
84
- * as it does today. Those legacy rules are the only part of this grammar that is
85
- * meant to be deleted: once stored content has been converted, drop the rule
86
- * from the product's rule list and nothing else changes.
87
- *
88
- * ## Two deliberate delimiter choices
89
- *
90
- * 1. ITALIC IS `_phrase_`, NOT `*phrase*`. In the storefront and the campaign
91
- * designer a single asterisk already means the ACCENT colour, on 704 live
92
- * instances. Standard markdown says italic. Both cannot be true, so the
93
- * asterisk keeps its existing meaning and italic takes markdown's OTHER
94
- * italic marker. Nothing published moves.
95
- * 2. `_` NEVER FIRES INSIDE A WORD. Without that guard, a mail merge line
96
- * holding `{{first_name}}` and `{{last_name}}` would italicise everything
97
- * between the two underscores. The guard is CommonMark's own rule for `_`
98
- * (an intraword underscore is literal) and it is checked with plain character
99
- * tests rather than a lookbehind, because a lookbehind is a parse error on
100
- * Safari below 16.4 and this code ships to a public storefront.
101
- *
102
- * ## One KNOWN divergence from the standard named above
103
- *
104
- * `__phrase__` IS ITALIC HERE. CommonMark reads a doubled underscore as STRONG;
105
- * this grammar has a single `_` rule, so the outer pair matches, the inner
106
- * underscores are ordinary characters, and `__all__` comes back as `_all_`.
107
- *
108
- * Left alone deliberately, and recorded rather than fixed. A census of stored
109
- * copy found 14 rows in this shape and every one of them is the literal link
110
- * key `__all__`, which no renderer is handed — so nothing published is being
111
- * mis-drawn today. Adding a `__` rule to buy CommonMark parity would change how
112
- * a stored `_` pair parses for the benefit of no existing string, and this
113
- * module's promise is that the grammar grows while nothing published moves.
114
- * Bold is `**phrase**` in every product; that is the form the toolbar writes
115
- * and the form to reach for. Pinned by a test, so it stays a decision rather
116
- * than becoming a discovery.
117
- */
118
- /** A style a toolbar button can apply. The first six are inline marks; then
119
- * the composer-only affordances (a link and the three line prefixes), which
120
- * only a renderer that draws block content can honour; `clear`, which
121
- * writes no mark at all — it takes them off ({@link clearMarks}); and the
122
- * three spans of the final text box (E1), which no button in
123
- * {@link MARKUP_TOOLS} offers yet. A colour carries a name, so it is written
124
- * by {@link applyColor}, not here. */
125
- type MarkStyle = 'bold' | 'italic' | 'strike' | 'code' | 'highlight' | 'quiet' | 'link' | 'bullet' | 'number' | 'quote' | 'clear' | 'underline' | 'sup' | 'sub';
126
- /** What a span means: `[words]{underline}`, `{sup}`, `{sub}` or
127
- * `{color=name}`. Only the tree reads these; {@link InlineKind} is the flat
128
- * reading's, which never makes one. */
129
- type SpanKind = 'underline' | 'sup' | 'sub' | 'color';
130
- /** What a parsed run MEANS. `accent` is the storefront/campaign brand colour —
131
- * the legacy `*phrase*` — and `highlight` is its standard-markdown successor;
132
- * both resolve to the same paint, which is what makes converting stored copy
133
- * from one to the other invisible. */
134
- type InlineKind = 'text' | 'bold' | 'italic' | 'strike' | 'code' | 'highlight' | 'accent' | 'quiet';
135
- /** A theme colour's name as a span saves it: lowercase letters, digits and
136
- * `-`, starting with a letter (`accent`, `brand-2`). A name is the theme's,
137
- * so changing the theme recolours the words. Never a hex — that is the other
138
- * kind of colour value ({@link isColorValue}). */
139
- declare function isColorName(name: string): boolean;
140
- /** A colour a span may carry: a theme NAME ({@link isColorName}), or "any
141
- * colour" as EXACTLY six hex digits after a `#` (`#0b5cff`). Either case
142
- * reads; the writers save lowercase ({@link colorValue}). Nothing else —
143
- * `#f00`, `rgb()`, a typo — is a colour, so it stays plain text.
144
- *
145
- * WIDENS E1 (GG 2026-10-06): the final design draws "Any color", and a
146
- * feature is never cut to fit. A hex does not follow the theme; a name does. */
147
- declare function isColorValue(value: string): boolean;
148
- /**
149
- * The words a press WRITES when nothing is selected — `**bold text**`, left
150
- * selected so the next keystroke replaces it. English unless the caller of
151
- * {@link applyMark} hands in its own (0.95.0: a host in another language).
152
- * They are words put in the box, not rules: nothing here changes what parses.
153
- */
154
- type MarkPlaceholders = Record<'bold' | 'italic' | 'strike' | 'code' | 'highlight' | 'quiet' | 'link', string> & Partial<Record<'underline' | 'sup' | 'sub' | 'color', string>>;
155
- declare const MARK_PLACEHOLDERS: MarkPlaceholders;
156
- interface WrapResult {
157
- text: string;
158
- /** Where the caret should land — inside the new marks, or after them. */
159
- selectionStart: number;
160
- selectionEnd: number;
161
- }
162
- /**
163
- * Apply a style to `text[start:end]`, returning the new text and where the
164
- * selection should sit. Toggling is deliberate: pressing Bold on already-bold
165
- * text unwraps it, so the shortcut behaves like every other editor.
166
- *
167
- * Unwrapping only ever recognises the style's OWN fence. An italic button that
168
- * also unwrapped `*phrase*` would silently strip an accent run in the storefront
169
- * and the campaign designer, where the asterisk means colour, not slant.
170
- */
171
- declare function applyMark(text: string, start: number, end: number, style: MarkStyle, rules?: readonly InlineRule[], placeholders?: MarkPlaceholders): WrapResult;
172
- /**
173
- * Colour `text[start:end]` with `name` — a theme colour name or a 6-digit hex
174
- * ({@link isColorValue}; a hex is saved lowercase) — writing
175
- * `[words]{color=name}` and leaving the words selected. On words that are
176
- * already exactly one colour span it changes the colour; `null` takes it
177
- * off. A value {@link isColorValue} refuses changes nothing — `#f00` or a typo
178
- * is never saved as a block the reader would print as text.
179
- *
180
- * Separate from {@link applyMark} because a colour carries a value, as a link
181
- * does ({@link writeLink}).
182
- */
183
- declare function applyColor(text: string, start: number, end: number, name: string | null, placeholders?: MarkPlaceholders): WrapResult;
184
- /**
185
- * True when `text[start:end]` is ALREADY wrapped in `style`'s own fence — the
186
- * very test {@link applyMark} uses to decide that a press unwraps. So a button
187
- * drawn as pressed is exactly a button whose press takes the mark off; the two
188
- * cannot disagree. False for a style with no fence (a link, a line prefix).
189
- */
190
- declare function markedAt(text: string, start: number, end: number, style: MarkStyle): boolean;
191
- /** A link as it sits in authored text: where it starts and ends, its words and
192
- * its address. */
193
- interface LinkSpan {
194
- start: number;
195
- end: number;
196
- label: string;
197
- href: string;
198
- }
199
- /**
200
- * The link the caret or selection sits in — `[words](href)` covering all of
201
- * `start..end` — or null. What the link button opens on: inside a link it edits
202
- * that link, anywhere else it makes a new one.
203
- *
204
- * Read with the parser's own rule ({@link linkAt}), so a span this returns is
205
- * exactly one the renderers draw as a link — an unsafe address is not a link
206
- * and is not offered for editing as one.
207
- */
208
- declare function findLink(text: string, start: number, end: number): LinkSpan | null;
209
- /**
210
- * Make `text[start:end]` a link to `href`, or re-point the link it sits in.
211
- * The caret lands after the link, selecting nothing: the link is finished.
212
- *
213
- * The words are the selection, or — with nothing selected — the address
214
- * itself, so a merchant who pastes an address and presses Apply gets a link
215
- * that reads as that address rather than a placeholder to type over.
216
- *
217
- * ⚠️ The address is the CALLER'S to check ({@link isSafeLinkHref}): one that
218
- * fails would be written as plain text the renderers never draw as a link.
219
- */
220
- declare function writeLink(text: string, start: number, end: number, href: string): WrapResult;
221
- /** Take a link off, keeping its words — selected, so a second press of the
222
- * link button puts it straight back. */
223
- declare function removeLink(text: string, link: LinkSpan): WrapResult;
224
- /**
225
- * One toolbar button.
226
- *
227
- * - `label` is a text glyph, never a word, for a host that draws type (the
228
- * message composer). The copy box draws the icon set's glyph instead.
229
- * - `name` is what the button is CALLED: its accessible name, and the first
230
- * word of its tooltip. Approved 2026-10-02: no word is drawn ON a button,
231
- * so a name lives here and in `title` and nowhere a merchant reads it as
232
- * part of the bar.
233
- * - `title` is the tooltip: the name, what the mark writes, and the shortcut.
234
- * - `shortcut` is the key the host combines with its platform modifier: a bare
235
- * letter (`'b'`), or `'shift+'` and a letter.
236
- */
237
- interface MarkupTool {
238
- style: MarkStyle;
239
- label: string;
240
- title: string;
241
- name?: string;
242
- shortcut?: string;
243
- }
244
- /** Every button, in the order a writer reaches for them. Clear is last: it
245
- * acts on every mark before it. */
246
- declare const MARKUP_TOOLS: readonly MarkupTool[];
247
- /**
248
- * The names a merchant sees for the three styles that carry one, in both
249
- * editors (owner-approved 2026-09-28). One word per idea: Strong IS bold
250
- * (`**`, ⌘B) and Emphasis IS the highlight (`==`), so a toolbar that shows the
251
- * names shows no separate B or ◆ beside them.
252
- */
253
- declare const MARK_NAMES: {
254
- readonly bold: "Strong";
255
- readonly highlight: "Emphasis";
256
- readonly quiet: "Quiet";
257
- };
258
- /** The tools a host shows, resolved to descriptors in {@link MARKUP_TOOLS}
259
- * order. Keeps a host from hand-rebuilding a descriptor (and drifting on a
260
- * tooltip) just to pick a subset. */
261
- declare function markupTools(styles: readonly MarkStyle[]): MarkupTool[];
262
- /**
263
- * One delimited run. `open`/`close` are literal strings; a run's inner text may
264
- * never contain the first character of `close`, which is what keeps this
265
- * equivalent to the "asterisk, run of non-asterisks, asterisk" regexes the
266
- * products used before — the same leftmost match, the same single close
267
- * candidate, so today's stored copy parses exactly as it always has.
268
- */
269
- interface InlineRule {
270
- readonly open: string;
271
- readonly close: string;
272
- readonly kind: Exclude<InlineKind, 'text'>;
273
- /** CommonMark's `_` rule: neither delimiter may touch a word character, so
274
- * `snake_case` and `{{first_name}}` stay literal. */
275
- readonly wordBoundary?: boolean;
276
- /**
277
- * Characters that may not FOLLOW the close. A run whose closing delimiter
278
- * butts up against one of these does not fire, and both delimiters stay
279
- * literal.
280
- *
281
- * Two legacy rules need it, for the same reason: a delimiter that is also
282
- * ordinary punctuation will otherwise claim text that was never markup. `#`
283
- * must not pair across rank markers ("Ranked #1 and #2"), and `[` must not
284
- * claim the label of a markdown link ("[terms](https://…)"), where the URL
285
- * would be stranded outside the run and printed to the reader.
286
- *
287
- * A character SET rather than a flag per case, so the next collision is one
288
- * more rule field and no new branch in {@link tokenizeInline}.
289
- */
290
- readonly notBefore?: string;
291
- /**
292
- * True when the run may not close right before a span block (`{sup}`,
293
- * `{color=accent}`, …). The campaign's legacy `[phrase]` needs it for the
294
- * reason it needs `notBefore: '('`: without it `[x]{sup}` would read as an
295
- * italic `x` followed by a printed `{sup}`. Only the four exact blocks count,
296
- * so `[x]{{first_name}}` and `[x]{foo}` read as they always did.
297
- */
298
- readonly notBeforeBlock?: boolean;
299
- }
300
- /**
301
- * The standard-markdown marks, shared by every product.
302
- *
303
- * `**` and `*` cannot collide even though they share a prefix: a `*` followed
304
- * immediately by another `*` is an EMPTY accent run, which no rule matches, so
305
- * at any one position exactly one of the two can fire. Order between them is
306
- * therefore not observable — which is worth stating, because the obvious
307
- * assumption ("longest delimiter must be listed first") would make a future
308
- * reader think this array's order is load-bearing when the empty-run rule is
309
- * what actually does the work.
310
- */
311
- declare const STANDARD_MARKUP: readonly InlineRule[];
312
- /**
313
- * Marks the copy box no longer OFFERS (E5a, 2026-10-06: Lewis dropped
314
- * Emphasis `==` and Quiet `^^` from the final design). They are still read,
315
- * drawn and written back byte for byte — saved copy wears them — but no bar
316
- * button writes them, and `CopyBox` draws none for them even when a host asks.
317
- * Declared before {@link COPY_FIELD_TOOLS} reads it at module load.
318
- */
319
- declare const RETIRED_MARKS: readonly MarkStyle[];
320
- /**
321
- * The buttons a COPY FIELD offers — a storefront section's copy box and a
322
- * campaign block's copy box show exactly these, so a merchant meets one toolbar
323
- * wherever they type.
324
- *
325
- * DERIVED, not listed. A copy field renders inline runs only — no lists, no
326
- * block quote, no anchor — so the set it may advertise is precisely the tools
327
- * whose output this grammar parses back. Writing that as a literal list was the
328
- * bug's hiding place: the list and the grammar were two hand-maintained facts
329
- * that agreed by luck, and nothing failed when a tool wrote a delimiter the
330
- * parser read as something else. Now a style with no rule cannot appear here,
331
- * whatever anyone types into the array.
332
- *
333
- * It resolves to bold, italic, strike, highlight and quiet. `code` is the one that
334
- * surprises: it is an inline mark and it has a fence, but no rule reads a
335
- * backtick, so a copy field would print `` `word` `` verbatim — and a button
336
- * whose output the renderer draws as literal text is worse than no button.
337
- *
338
- * Declared here, below the rules, because it now depends on them.
339
- */
340
- declare const COPY_FIELD_TOOLS: readonly MarkStyle[];
341
- /**
342
- * The storefront's rule set: standard markdown plus the ONE legacy run it has
343
- * published — `*phrase*` in the theme accent. Delete that last entry once
344
- * stored copy has been converted to `==phrase==`; both kinds already paint the
345
- * same, so the deletion is invisible.
346
- */
347
- declare const STOREFRONT_MARKUP: readonly InlineRule[];
348
- /**
349
- * The campaign designer's rule set: standard markdown plus its three legacy
350
- * runs — `#phrase#` bold, `*phrase*` accent, `[phrase]` italic. The bold guard
351
- * is the one the email parser already carried: a `#` followed by a digit does
352
- * not close a run, so "Ranked #1 and #2" is literal while "Save #20%#" bolds.
353
- *
354
- * The italic guard exists because `[phrase]` collides with the one piece of
355
- * markdown syntax every writer already knows. Without it, "Read [our
356
- * terms](https://…) first" italicises the label AND prints the bare URL to the
357
- * reader — the words come back wearing a mark nobody chose. It is not a
358
- * toolbar problem: a merchant typing an ordinary link into a copy box hits it,
359
- * which is why the guard is on the RULE and not on which buttons a field
360
- * offers. `[phrase]` on its own is untouched, so every published line still
361
- * reads exactly as it did.
362
- */
363
- declare const CAMPAIGN_MARKUP: readonly InlineRule[];
364
- /** A parsed run. `text` is the inner text with the delimiters removed, so a
365
- * caller that only wants words ({@link stripInline}) joins the texts and a
366
- * caller that paints ({@link InlineKind}) switches on the kind. */
367
- interface InlineToken {
368
- kind: InlineKind;
369
- text: string;
370
- }
371
- /**
372
- * Split authored text into its runs.
373
- *
374
- * The returned tokens ALTERNATE, starting and ending with a `text` token that
375
- * may be empty — exactly the shape `String.split` with one capture group
376
- * produced in the parsers this replaces. That contract is load-bearing, not
377
- * cosmetic: the storefront wraps every segment (including the empty ones) in a
378
- * `<span>`, so preserving the empty tokens is what keeps a published page's
379
- * markup byte-for-byte identical.
380
- *
381
- * A lone delimiter with no partner stays literal — the products' copy contains
382
- * real stray asterisks, and swallowing one would silently eat a word.
383
- */
384
- declare function tokenizeInline(value: string, rules: readonly InlineRule[]): InlineToken[];
385
- /**
386
- * The plain words of an authored string — every paired run unwrapped to its
387
- * inner text, every unpaired delimiter left alone.
388
- *
389
- * This is the ONLY correct way to fill an `alt`, an `aria-label` or a
390
- * structured-data field from authored copy. Deleting a delimiter character by
391
- * hand — "replace every asterisk with nothing" — breaks the moment the grammar
392
- * grows a marker, and it disagrees with the renderer about a stray asterisk,
393
- * which matters: search engines expect structured data to match the text a
394
- * visitor actually sees.
395
- */
396
- declare function stripInline(value: string, rules: readonly InlineRule[]): string;
397
- /** Plain words. */
398
- interface MarkupText {
399
- kind: 'text';
400
- text: string;
401
- }
402
- /** A delimited run. `open` and `close` are the delimiters that were actually
403
- * saved — `#` for a legacy campaign bold, `**` for the standard one, `[` and
404
- * `]{sup}` for a span — so the tree writes back the same bytes it was read
405
- * from. `color` is the theme colour's name, on a `color` span only. */
406
- interface MarkupRun {
407
- kind: InlineRule['kind'] | SpanKind;
408
- open: string;
409
- close: string;
410
- color?: string;
411
- children: MarkupNode[];
412
- }
413
- /** `[words](href)`. Only made when {@link isSafeLinkHref} passes. */
414
- interface MarkupLink {
415
- kind: 'link';
416
- href: string;
417
- children: MarkupNode[];
418
- }
419
- type MarkupNode = MarkupText | MarkupRun | MarkupLink;
420
- /** One line of the saved string. `bullet` means it was saved as `- words`;
421
- * `number` holds the digits of a line saved as `12. words`, as typed (`01`
422
- * stays `01`), and is null on every other line. `children` hold what follows
423
- * the prefix. A line built by hand may leave `number` out: it means null. */
424
- interface MarkupLine {
425
- bullet: boolean;
426
- number?: string | null;
427
- children: MarkupNode[];
428
- }
429
- /**
430
- * True for an address a link may carry: `http`, `https`, `mailto` or `tel`, or
431
- * one with no scheme at all (`/tyres`, `#top`, `terms.html`, `//cdn…`).
432
- * Anything else — `javascript:`, `data:`, `file:` — is refused, and so is an
433
- * address with any space or control character in it, because browsers drop
434
- * those before they read the scheme (`java\tscript:` runs as `javascript:`).
435
- *
436
- * Renderers call this too, before they write an `href`, so the rule has one
437
- * home.
438
- */
439
- declare function isSafeLinkHref(href: string): boolean;
440
- /**
441
- * Read authored text into lines of nodes — the whole grammar, for a rich text
442
- * box and for the renderers that draw links and bullets.
443
- *
444
- * - A run never crosses a line break.
445
- * - A line that starts with `- ` and has words after it is a bullet. One level
446
- * only: an indented ` - ` is plain text.
447
- * - A line that starts with 1-3 digits, `. ` and words is numbered, the digits
448
- * kept as typed. One level only, as bullets.
449
- * - `[words](href)` is a link when {@link isSafeLinkHref} passes; otherwise the
450
- * whole thing stays plain text.
451
- * - `[words]{underline}`, `{sup}`, `{sub}` and `{color=name}` are spans; any
452
- * other block stays plain text.
453
- * - A run's words are read again, so `**bold _with italic_**` is italic inside
454
- * bold. Four levels: a fifth stays as typed.
455
- *
456
- * Nothing is lost: {@link serializeMarkup} writes back the exact bytes.
457
- */
458
- declare function parseMarkup(value: string, rules: readonly InlineRule[]): MarkupLine[];
459
- /** Write lines back to authored text: the inverse of {@link parseMarkup}. */
460
- declare function serializeMarkup(lines: readonly MarkupLine[]): string;
461
- /**
462
- * One run a character sits in. `id` says WHICH run: two characters with the
463
- * same id are in one run, so `**a****b**` (two bold runs, back to back) is told
464
- * apart from `**ab**` (one) and is written back as it was saved.
465
- *
466
- * `open` and `close` are the delimiters that were saved (`#` for a legacy
467
- * campaign bold), or, for a run made in the editor, the toolbar's own fence.
468
- * A link's `close` is `](href)`, a span's `]{sup}` or `]{color=name}`.
469
- */
470
- interface MarkSpan {
471
- id: number;
472
- kind: InlineRule['kind'] | SpanKind | 'link';
473
- open: string;
474
- close: string;
475
- href?: string;
476
- color?: string;
477
- }
478
- /**
479
- * A NEW run, as the toolbar writes it: `**` for bold, `_` for italic, and so on
480
- * ({@link applyMark}'s own fences), a link to `value`, or a colour span of
481
- * `value` — a theme name or a 6-digit hex, saved lowercase ({@link isColorValue}). Null for a style that writes no run (a line
482
- * prefix, clear) and for a link or colour with no usable value. A paste filter
483
- * or an editor making a run asks here, so no delimiter is spelled outside this
484
- * module (E8).
485
- */
486
- declare function spanFor(id: number, style: MarkStyle | 'color', value?: string): MarkSpan | null;
487
- /** One character of the words — never a delimiter — with the runs it sits in,
488
- * outside first, and its offset in the saved string. */
489
- interface MarkedChar {
490
- ch: string;
491
- marks: readonly MarkSpan[];
492
- at: number;
493
- }
494
- /** One line: its bullet flag or its number (the `- ` and the `12. ` are not
495
- * characters) and its words. `number` left out means null, as on
496
- * {@link MarkupLine}. */
497
- interface MarkedLine {
498
- bullet: boolean;
499
- number?: string | null;
500
- chars: MarkedChar[];
501
- }
502
- /**
503
- * Read authored text as characters, each carrying its runs — what an editor
504
- * that hides the delimiters edits, and what {@link clearMarks} splits runs on.
505
- *
506
- * Built on {@link parseMarkup}, so it is the same reading, character by
507
- * character. A character is a code point (an emoji is one, two UTF-16 units
508
- * long); `at` counts UTF-16 units, as a text box's selection does.
509
- *
510
- * {@link writeMarkedLines} writes it back byte for byte.
511
- */
512
- declare function markedLines(value: string, rules: readonly InlineRule[]): MarkedLine[];
513
- /**
514
- * Write characters back to authored text: the inverse of {@link markedLines}.
515
- * Returns where each character landed (`at[line][char]`), so a selection can be
516
- * carried across the write.
517
- *
518
- * Runs are opened and closed as the characters ask: a character outside a run
519
- * that its neighbours are in closes it, and the run's next character opens it
520
- * again. That is how a run is SPLIT — clear the middle of `**abcd**` and it is
521
- * written `**a**bc**d**`. Whether the result reads back as written is the
522
- * caller's to check ({@link sameMarks}): `_` cannot close against a letter.
523
- */
524
- declare function writeMarkedLines(lines: readonly MarkedLine[]): {
525
- text: string;
526
- at: number[][];
527
- };
528
- /** True when two readings hold the same words wearing the same marks — the
529
- * kinds, addresses, colours and list numbers, not which run or which
530
- * delimiter. */
531
- declare function sameMarks(a: readonly MarkedLine[], b: readonly MarkedLine[]): boolean;
532
- /**
533
- * Take every mark off `text[start:end]`, keeping the words and any link (a
534
- * link has its own Remove). A run the selection only partly covers is split,
535
- * so the rest of it keeps its mark. Bullets and numbers stay: they are lines,
536
- * not marks.
537
- *
538
- * With nothing selected, the runs the caret touches — the characters either
539
- * side of it — come off whole.
540
- *
541
- * A split the grammar cannot hold comes off whole instead: the middle of
542
- * `_word_` would leave an underscore against a letter, which reads as a
543
- * literal underscore. And if even that would not read back, nothing changes.
544
- */
545
- declare function clearMarks(text: string, start: number, end: number, rules?: readonly InlineRule[]): WrapResult;
546
- /**
547
- * The final toolbar's Clear style (owner ruling, 2026-10-07): take EVERY mark
548
- * off the WORDS in `text[start:end]` but a link, keeping the words selected —
549
- * the bar's (bold, italic, underline, strikethrough, colour, superscript,
550
- * subscript) and the retired ones it no longer offers (Emphasis, Quiet, the
551
- * legacy accent), so the words come back plain (GG, 2026-10-07). A link is
552
- * content, not style; a line's bullet or number is a line, not a mark.
553
- *
554
- * A run the selection only partly covers is split, so the rest of it keeps
555
- * its mark; a split the grammar cannot hold (the middle of `_word_`) takes the
556
- * run off whole; and if even that would not read back, nothing changes.
557
- *
558
- * With nothing selected nothing changes: the button is disabled then, so a
559
- * press can never wipe a whole paragraph. Unlike {@link clearMarks} (the 0.94
560
- * bar's Clear marks), which with a caret takes off the runs it touches.
561
- */
562
- declare function clearStyle(text: string, start: number, end: number, rules?: readonly InlineRule[]): WrapResult;
563
- /**
564
- * Press `style` on the WORDS in `text[start:end]`, whichever runs they sit in —
565
- * what a view that hides the delimiters needs, where `applyMark` (which wraps
566
- * exactly the characters selected) would cross or strand a delimiter.
567
- *
568
- * - Every selected word already wears the mark: it comes off them. Part of a
569
- * run is split off; the rest keeps the mark.
570
- * - Otherwise the mark goes on, as ONE run over the words and any run of the
571
- * same mark they touch (`**half** off` → `**half off**`).
572
- * - Nothing is written that would not read back: a third level of nesting,
573
- * or `_` inside a word, leaves the text as it was.
574
- * - With nothing selected it is {@link applyMark}: a selected placeholder.
575
- *
576
- * Offsets are the saved string's, before and after.
577
- */
578
- declare function toggleMark(text: string, start: number, end: number, style: MarkStyle, rules?: readonly InlineRule[]): WrapResult;
579
- /**
580
- * The marks every word in `text[start:end]` wears — `bold`, `link`, a legacy
581
- * `accent` … — or, with nothing selected, the marks of the word just before the
582
- * caret on its line: what typing there would continue. What a toolbar draws
583
- * as pressed in a view without delimiters.
584
- */
585
- declare function marksIn(text: string, start: number, end: number, rules?: readonly InlineRule[]): Set<string>;
586
- /**
587
- * Link the WORDS in `text[start:end]` to `href`, nesting with any run they
588
- * sit in rather than crossing it: words inside a bold run become a link inside
589
- * it (`**Book [a fitting](/book)**`), a whole bold run goes inside the link.
590
- *
591
- * With nothing selected, inside a link it re-points that link; anywhere else
592
- * it writes the address as its own words ({@link writeLink}). The caret lands
593
- * after the link. ⚠️ The address is the caller's to check ({@link isSafeLinkHref}).
594
- */
595
- declare function linkOver(text: string, start: number, end: number, href: string, rules?: readonly InlineRule[]): WrapResult;
596
- /** Take off the link the caret or selection touches, keeping its words —
597
- * selected, so a second press of the link button puts it straight back. */
598
- declare function unlinkAt(text: string, start: number, end: number, rules?: readonly InlineRule[]): WrapResult;
599
- /**
600
- * Colour the WORDS in `text[start:end]` with `name` — a theme colour name or
601
- * a 6-digit hex, saved lowercase (`[words]{color=name}`), nesting with any run they sit in rather than
602
- * crossing it — the colour window's write, as {@link linkOver} is the link
603
- * window's. A colour the words already wear is replaced; `null` takes the
604
- * colour off them, splitting a run that only partly covers them.
605
- *
606
- * A value {@link isColorValue} refuses (`#f00`, a word with a space) changes
607
- * nothing: a block the reader would print as text is never written. With
608
- * nothing selected nothing changes either — the box keeps a pending colour
609
- * for the words typed next instead.
610
- */
611
- declare function colorOver(text: string, start: number, end: number, name: string | null, rules?: readonly InlineRule[]): WrapResult;
612
- /** The ONE colour every word in `text[start:end]` wears — or, with nothing
613
- * selected, the word before the caret on its line — or null. What the colour
614
- * window draws as chosen, and the colour under the A. */
615
- declare function colorIn(text: string, start: number, end: number, rules?: readonly InlineRule[]): string | null;
616
- /**
617
- * Make every line `text[start:end]` touches (the caret's line, with nothing
618
- * selected) a bulleted list, a numbered list, or — `null` — no list. The
619
- * words and their marks keep their bytes; only the line prefixes change. A
620
- * numbered list counts on from a numbered line just above it, else from 1.
621
- * An empty line becomes an empty list line (`- `), which the rich view draws
622
- * as a bullet with nothing after it yet. The selection keeps its words.
623
- */
624
- declare function setList(text: string, start: number, end: number, kind: 'bullet' | 'number' | null, rules?: readonly InlineRule[]): WrapResult;
625
- /** `bullet` or `number` when every line `text[start:end]` touches is that
626
- * kind of list line, else null — what the list buttons draw as pressed. */
627
- declare function listIn(text: string, start: number, end: number, rules?: readonly InlineRule[]): 'bullet' | 'number' | null;
628
-
629
- 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, RETIRED_MARKS, STANDARD_MARKUP, STOREFRONT_MARKUP, type SpanKind, type WrapResult, applyColor, applyMark, clearMarks, clearStyle, colorIn, colorOver, findLink, isColorName, isColorValue, isSafeLinkHref, linkOver, listIn, markedAt, markedLines, marksIn, markupTools, parseMarkup, removeLink, sameMarks, serializeMarkup, setList, spanFor, stripInline, toggleMark, tokenizeInline, unlinkAt, writeLink, writeMarkedLines };
1
+ export * from 'efficient-shell/markup';
@@ -1 +1 @@
1
- export{CAMPAIGN_MARKUP,COPY_FIELD_TOOLS,MARKUP_TOOLS,MARK_NAMES,MARK_PLACEHOLDERS,RETIRED_MARKS,STANDARD_MARKUP,STOREFRONT_MARKUP,applyColor,applyMark,clearMarks,clearStyle,colorIn,colorOver,findLink,isColorName,isColorValue,isSafeLinkHref,linkOver,listIn,markedAt,markedLines,marksIn,markupTools,parseMarkup,removeLink,sameMarks,serializeMarkup,setList,spanFor,stripInline,toggleMark,tokenizeInline,unlinkAt,writeLink,writeMarkedLines}from"../chunk-546BBOPE.js";
1
+ export*from"efficient-shell/markup";
@@ -43,7 +43,7 @@
43
43
  margin-left: auto;
44
44
  padding-left: 16px;
45
45
  color: var(--ink-muted);
46
- font-size: 12px;
46
+ font-size: var(--text-xs, 12px);
47
47
  font-variant-numeric: tabular-nums;
48
48
  }
49
49
  /* On the row the pointer or the keys are on, whose ground the shell tints,
@@ -1,4 +1,4 @@
1
- export{DESIGN_GROUPS,SETTINGS_BUCKETS,SettingsGroup}from"../chunk-RC25EGHD.js";import{DEFAULT_PANEL_WORDS as e}from"../chunk-KFV7N2DJ.js";export{SettingsField,SettingsSegmented,SettingsSwitch}from"../chunk-XJCMEEBH.js";import{withTip as t}from"../chunk-P2VEMZH5.js";import{Tabs as s,CountBadge as i,Tooltip as n}from"efficient-shell/ui";export{BorderInput as SettingsBorder,CornersInput as SettingsCorners,AlignmentGrid as SettingsPlacement,SidesInput as SettingsSides,SidesEachToggle as SettingsSidesEach,borderSides,borderStartEach,sidesStartEach}from"efficient-shell/ui";import{jsx as a,jsxs as l,Fragment as r}from"react/jsx-runtime";import{RevertIcon as d}from"efficient-shell/icons/efficient";function c(e){const t=e[0];if(!t?.htmlId||!t.htmlId.endsWith(`-tab-${t.id}`))return;const s=t.htmlId.slice(0,-`-tab-${t.id}`.length);return e.every(e=>e.htmlId===`${s}-tab-${e.id}`)?s:void 0}function o({items:t,activeId:n,onSelect:d,ariaLabel:o=e.settings,idPrefix:u,className:m}){
1
+ export{DESIGN_GROUPS,SETTINGS_BUCKETS,SettingsGroup}from"../chunk-RC25EGHD.js";import{DEFAULT_PANEL_WORDS as e}from"../chunk-KFV7N2DJ.js";export{SettingsField,SettingsSegmented,SettingsSwitch}from"../chunk-36LVP5WD.js";import{withTip as t}from"../chunk-P2VEMZH5.js";import{Tabs as s,CountBadge as i,Tooltip as n}from"efficient-shell/ui";export{BorderInput as SettingsBorder,CornersInput as SettingsCorners,AlignmentGrid as SettingsPlacement,SidesInput as SettingsSides,SidesEachToggle as SettingsSidesEach,borderSides,borderStartEach,sidesStartEach}from"efficient-shell/ui";import{jsx as a,jsxs as l,Fragment as r}from"react/jsx-runtime";import{RevertIcon as d}from"efficient-shell/icons/efficient";function c(e){const t=e[0];if(!t?.htmlId||!t.htmlId.endsWith(`-tab-${t.id}`))return;const s=t.htmlId.slice(0,-`-tab-${t.id}`.length);return e.every(e=>e.htmlId===`${s}-tab-${e.id}`)?s:void 0}function o({items:t,activeId:n,onSelect:d,ariaLabel:o=e.settings,idPrefix:u,className:m}){
2
2
  /* @__PURE__ */
3
3
  return a("div",{className:"es-tabs-fit",children:/* @__PURE__ */a(s,{variant:"pill","aria-label":o,idPrefix:u??c(t),className:m?`es-tabs ${m}`:"es-tabs",value:n,onChange:d,items:t.map(e=>{const t="number"==typeof e.badge&&e.badge>0?/* @__PURE__ */a(i,{count:e.badge,tone:"accent"}):null;return e.icon?{id:e.id,tooltip:e.label,label:/* @__PURE__ */l(r,{children:[
4
4
  /* @__PURE__ */l("span",{className:"es-tab-tip",children:[