efficient-shell 5.6.0 → 6.4.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 (112) hide show
  1. package/README.md +189 -46
  2. package/THIRD_PARTY_NOTICES.md +212 -0
  3. package/dist/Browser-USPZWDYH.js +43 -0
  4. package/dist/Calculator-FP5HUWCW.js +29 -0
  5. package/dist/ConfirmDialog-3TIHKJRT.js +1 -0
  6. package/dist/{CurrencyConverter-YJ47A3L5.js → CurrencyConverter-XD5LYOCD.js} +7 -7
  7. package/dist/Documents-ADFEHEUW.js +52 -0
  8. package/dist/Files-BVUVRNC5.js +87 -0
  9. package/dist/Notepad-WJI5ODGS.js +25 -0
  10. package/dist/PdfViewer-226I3LSD.js +1 -0
  11. package/dist/PomodoroTimer-GEMQHIPJ.js +91 -0
  12. package/dist/Preview-HNSEYNZL.js +182 -0
  13. package/dist/Sidebar-PVHZPNIY.js +44 -0
  14. package/dist/{Spreadsheet-INXG5UMI.js → Spreadsheet-6Z5ACGTT.js} +14 -14
  15. package/dist/{Stock-CBQN2IJF.js → Stock-X3LIU4ZX.js} +4 -4
  16. package/dist/{Weather-2EVUTIXX.js → Weather-BLO354GM.js} +18 -18
  17. package/dist/{WorldClock-TZUF7MOB.js → WorldClock-BAH7QFRG.js} +7 -7
  18. package/dist/apps/index.js +1 -1
  19. package/dist/bin/ui-check.js +1 -1
  20. package/dist/brand.css +1 -1
  21. package/dist/chunk-2SSRFEOZ.js +91 -0
  22. package/dist/{chunk-TFETN2GI.js → chunk-4JZZLUGG.js} +1 -1
  23. package/dist/{chunk-GG7AE6OC.js → chunk-5K76RQML.js} +1 -1
  24. package/dist/chunk-5LUN632W.js +5 -0
  25. package/dist/chunk-5MDQNFOG.js +9 -0
  26. package/dist/chunk-7IQ3B77H.js +1 -0
  27. package/dist/{chunk-SKB6L53D.js → chunk-7PGHRG6J.js} +9 -9
  28. package/dist/chunk-CXJQ42XT.js +21 -0
  29. package/dist/chunk-DBXZS7OA.js +29 -0
  30. package/dist/{chunk-X4FJ54PP.js → chunk-FIEA5CGB.js} +1 -1
  31. package/dist/chunk-GJ37JCC2.js +1 -0
  32. package/dist/{chunk-KXAUDC3A.js → chunk-GWDVD3VQ.js} +27 -27
  33. package/dist/chunk-HQLAISFX.js +11 -0
  34. package/dist/chunk-I2H2CKID.js +3 -0
  35. package/dist/{chunk-MLYC5VQI.js → chunk-I534M65N.js} +2 -2
  36. package/dist/{chunk-R4XSSHDI.js → chunk-IHZL3GJU.js} +1 -1
  37. package/dist/chunk-K6DKX55N.js +1 -0
  38. package/dist/chunk-KNUR2UEG.js +1 -0
  39. package/dist/{chunk-JXAAZGLJ.js → chunk-MORDXCV6.js} +1 -1
  40. package/dist/chunk-PFKMIPZH.js +1028 -0
  41. package/dist/chunk-PJPK2ADX.js +1 -0
  42. package/dist/chunk-Q5O5UMJY.js +6 -0
  43. package/dist/chunk-QJHWLDEN.js +108 -0
  44. package/dist/chunk-R5VRLW5Z.js +1 -0
  45. package/dist/chunk-S3LGKIZ3.js +12 -0
  46. package/dist/chunk-SBF5A6XV.js +1 -0
  47. package/dist/chunk-SK5DL6JX.js +24 -0
  48. package/dist/{chunk-CLPJDJH4.js → chunk-UUYPUKP7.js} +1 -1
  49. package/dist/{chunk-AFYQTVZO.js → chunk-UVBK6YSR.js} +1 -1
  50. package/dist/chunk-VR6TR575.js +13 -0
  51. package/dist/{chunk-PKEQ7XOH.js → chunk-XO2AMGVU.js} +1 -1
  52. package/dist/{chunk-N3A3GWJI.js → chunk-YX2IMURA.js} +12 -12
  53. package/dist/chunk-ZEIKSMFR.js +10 -0
  54. package/dist/client-DLm3Fvbc.d.ts +65 -0
  55. package/dist/file-intake/index.js +1 -1
  56. package/dist/i18n/index.d.ts +1 -1
  57. package/dist/icons/efficient-looks.d.ts +1 -1
  58. package/dist/icons/efficient.d.ts +1 -1
  59. package/dist/icons/index.d.ts +12 -12
  60. package/dist/icons/index.js +1 -1
  61. package/dist/{index-BgM75MC6.d.ts → index-D5bms_uA.d.ts} +188 -76
  62. package/dist/index.d.ts +6 -4
  63. package/dist/index.js +639 -639
  64. package/dist/locales/zh-Hans.d.ts +1 -1
  65. package/dist/locales/zh-Hans.js +1 -1
  66. package/dist/map/index.js +22 -22
  67. package/dist/markdown/index.js +6 -6
  68. package/dist/markup/index.d.ts +460 -36
  69. package/dist/markup/index.js +1 -1
  70. package/dist/page-tabs/index.js +5 -5
  71. package/dist/pdf/index.js +1 -1
  72. package/dist/{registry-J-TU7zKx.d.ts → registry-Dw6QO2Ch.d.ts} +12 -9
  73. package/dist/{strings-BVB64Nck.d.ts → strings-fYzxZPn4.d.ts} +5 -0
  74. package/dist/telemetry/index.d.ts +39 -0
  75. package/dist/telemetry/index.js +1 -0
  76. package/dist/ui/index.d.ts +4 -3
  77. package/dist/ui/index.js +1 -1
  78. package/dist/ui.css +62 -21
  79. package/package.json +7 -21
  80. package/dist/Browser-6QB2QTO7.js +0 -43
  81. package/dist/Calculator-KOWRV2VK.js +0 -29
  82. package/dist/ConfirmDialog-32NLLK4K.js +0 -1
  83. package/dist/Documents-TGGI2QBV.js +0 -52
  84. package/dist/Files-KSDK3EO4.js +0 -87
  85. package/dist/Notepad-EKB6FJPR.js +0 -25
  86. package/dist/PdfViewer-KW5B44AX.js +0 -1
  87. package/dist/PomodoroTimer-RD5GRRA5.js +0 -91
  88. package/dist/Preview-AQVXKT2I.js +0 -182
  89. package/dist/Sidebar-VYXN5YIJ.js +0 -44
  90. package/dist/chunk-3GPU3JOW.js +0 -24
  91. package/dist/chunk-3HJTOINK.js +0 -1
  92. package/dist/chunk-4SWPNARR.js +0 -5
  93. package/dist/chunk-6LTDJKIL.js +0 -138
  94. package/dist/chunk-FROOMUJ7.js +0 -11
  95. package/dist/chunk-HRLPFUPB.js +0 -3
  96. package/dist/chunk-HZZNJHRH.js +0 -1
  97. package/dist/chunk-JEM73E4C.js +0 -21
  98. package/dist/chunk-L5XA5MLK.js +0 -108
  99. package/dist/chunk-OGXU7QFW.js +0 -12
  100. package/dist/chunk-RCHCT7AO.js +0 -6
  101. package/dist/chunk-RDQ7OXUA.js +0 -1026
  102. package/dist/chunk-RL626E7Q.js +0 -13
  103. package/dist/chunk-RMJZOKHY.js +0 -10
  104. package/dist/chunk-TNBCUZVE.js +0 -1
  105. package/dist/chunk-WRBXEZFN.js +0 -1
  106. package/dist/chunk-XWSWYMGM.js +0 -2
  107. package/dist/chunk-Z2353HNE.js +0 -9
  108. package/dist/chunk-ZUCYJAQH.js +0 -29
  109. package/dist/icons/heroicons.d.ts +0 -6
  110. package/dist/icons/heroicons.js +0 -1
  111. package/dist/icons/lucide.d.ts +0 -6
  112. package/dist/icons/lucide.js +0 -1
@@ -1,31 +1,46 @@
1
+ /**
2
+ * MOVED HERE FROM efficient-easel (`src/markup/index.ts` at
3
+ * 6d31d5df9646afd0d826394292a84b9c002f7355) — Lewis, 2026-10-12, decision D1:
4
+ * this grammar is THE one grammar, and it lives in efficient-shell as
5
+ * `efficient-shell/markup`. It replaces the shell's older, smaller grammar
6
+ * (the 405-line `src/markup/index.ts` of efficient-shell 5.0.0). The file below
7
+ * is easel's byte for byte, header included, so its history reads as written
8
+ * (the "WHO OWNS IT" paragraph is easel's own history, now closed: the shell
9
+ * owns the rule again, and easel, the admin portal and the web builder import
10
+ * it from here).
11
+ *
12
+ * ONE logic change from easel's copy: the read-only `++phrase++` Strong rule
13
+ * opens only at a WORD START — not right after a letter or a digit — so
14
+ * `A++ rated tyres, A++ service` stays plain text, while `++20%++` is still
15
+ * Strong (Lewis, 2026-10-12). See {@link InlineRule.wordStart}.
16
+ *
17
+ * Still FRAMEWORK-FREE: no React, no DOM, no imports at all. `efficient-shell/markup`
18
+ * is its own tsup entry, and the one subpath whose `exports` entry carries a
19
+ * `default` condition beside `import`, because build scripts reach it through
20
+ * Node's CJS loader (`tsx`) as well as through bundlers.
21
+ */
1
22
  /**
2
23
  * The editorial markup rule — ONE grammar, shared by every product that lets a
3
24
  * human type formatted copy into a plain text box.
4
25
  *
5
- * Why this lives in efficient-shell, and why behind its own subpath:
6
- * the rule is consumed by the admin portal (agent messages + the campaign
7
- * designer) AND by the public storefront, which has ten dependencies in total
8
- * and cannot afford the shell's peer graph (a 3D viewer, a PDF renderer, a DXF
9
- * viewer, an xlsx parser, a Word converter). So this module is deliberately
10
- * FRAMEWORK-FREE: no React, no JSX, no DOM, no imports at all. `efficient-shell/markup`
11
- * therefore adds a few hundred bytes of first-party code to a bundle and nothing
12
- * else — see the subpath's own tsup entry.
13
- *
14
- * It is also the ONE subpath whose `exports` entry carries a `default`
15
- * condition alongside `import`, and that is deliberate rather than untidy. The
16
- * root and `./apps` are React component entries; a bundler resolves them and
17
- * nothing else has any business to. This module is consumed by build scripts
18
- * too — the storefront's media-manifest generator reaches it through its Puck
19
- * config — and those run under `tsx`, which resolves through Node's CJS
20
- * loader. With only `types` and `import` declared, that resolution fails with
21
- * ERR_PACKAGE_PATH_NOT_EXPORTED, which reads like a missing file rather than a
22
- * missing condition. A module this portable should answer to any resolver.
23
- *
24
- * WHAT IS SHARED IS THE RULE, NOT THE RENDERING. Each product renders the token
25
- * list its own way, because it must: the web uses CSS classes bound to theme
26
- * tokens, email must inline every style (mail clients drop classes). Two
27
- * renderers, one grammar, one writer ({@link applyMark}) — so a toolbar button
28
- * and a parser can never disagree about what a delimiter means.
26
+ * 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
27
+ * `efficient-shell/markup`, a package Victor owns, and this package only imported
28
+ * it. Lewis decided to keep our own copy rather than wait on upstream for the
29
+ * rich text box: "Two copies is fine. We use our own independent one." This
30
+ * file started from efficient-shell 4.121.0 (the same file as the 4.101.0 and
31
+ * 4.120.0 the apps pin, bar one spelling) and grows here from now on. The two
32
+ * copies are expected to drift; nothing in either product reads both.
33
+ *
34
+ * FRAMEWORK-FREE, behind its own subpath (`efficient-easel/markup`): no React, no
35
+ * JSX, no DOM, no imports at all. The public storefront reads it, and the
36
+ * backend's banner renderer may one day port it, so it must stay a few hundred
37
+ * bytes of plain code that any bundler or resolver can load.
38
+ *
39
+ * WHAT IS SHARED IS THE RULE, NOT THE RENDERING. Each product renders the tree
40
+ * its own way, because it must: the web uses CSS classes bound to theme tokens,
41
+ * email must inline every style (mail clients drop classes). Two renderers, one
42
+ * grammar, one writer ({@link applyMark}) — so a toolbar button and a parser
43
+ * can never disagree about what a delimiter means.
29
44
  *
30
45
  * ## The delimiters
31
46
  *
@@ -35,6 +50,55 @@
35
50
  * _phrase_ italic
36
51
  * ~~phrase~~ strikethrough
37
52
  * ==phrase== highlight — the brand accent colour, applied to a selection
53
+ * ^^phrase^^ quiet — smaller and softer
54
+ *
55
+ * THE THREE NAMES a merchant sees ({@link MARK_NAMES}, owner-approved
56
+ * 2026-09-28) sit on these marks: Strong is `**`, Emphasis is `==`, Quiet is
57
+ * `^^`. The storefront once had separate word-style marks for Emphasis and
58
+ * Strong, `@@phrase@@` and `++phrase++`. Production holds none of them, and no
59
+ * button writes them any more, but they are still READ — as Emphasis and Strong
60
+ * — so a string somebody pasted never prints its delimiters.
61
+ *
62
+ * And, read by {@link parseMarkup} (the tree a rich text box and a renderer
63
+ * use), in every product:
64
+ *
65
+ * [words](href) a link — safe addresses only ({@link isSafeLinkHref})
66
+ * - words a bullet line, one level
67
+ * 1. words a numbered line, one level: 1-3 digits, ". ", words
68
+ * **a _b_ a** one run inside another, up to four levels
69
+ *
70
+ * [words]{underline} underline
71
+ * [words]{sup} superscript
72
+ * [words]{sub} subscript
73
+ * [words]{color=name} a theme colour, by NAME ({@link isColorName})
74
+ * [words]{color=#rrggbb} any colour, six hex digits ({@link isColorValue})
75
+ *
76
+ * THE SPANS AND THE NUMBERED LINE (plan step E1, approved by GG 2026-10-06)
77
+ * are the final text box's marks. One shape for all four, so one reading rule:
78
+ * `[`, words, `]`, then EXACTLY one of those blocks. Anything else after the
79
+ * `]` — `{foo}`, `{color=#f00}`, a merge tag's `{{` — is not a span, and the
80
+ * whole thing stays as typed. Two marks on the same words nest
81
+ * (`[[x]{sup}]{color=accent}`); one block never holds two.
82
+ *
83
+ * Why these cannot clash with an old mark: no old rule closes on `]{` (a link
84
+ * needs `](`), and the campaign's legacy `[phrase]` italic steps back from a
85
+ * span block, the way it steps back from a link (`notBeforeBlock`). No inline
86
+ * rule opens on a digit. Production held neither shape when they were chosen:
87
+ * 0 of 24,233 text values contained `]{`, and none had a line starting with a
88
+ * number and `.` or `)`.
89
+ *
90
+ * Not chosen: `^x^` / `~x~` (one character from Quiet `^^` and strike `~~`,
91
+ * and `~20 min` is ordinary copy), `__x__` (pinned as italic below, and
92
+ * CommonMark's bold) and `++x++` (the old Strong).
93
+ *
94
+ * Only the tree ({@link parseMarkup}) reads them. The flat
95
+ * {@link tokenizeInline} — the old renderers' reading — keeps them as text, as
96
+ * it keeps links: an email or a page drawn before its renderer learns them
97
+ * prints the delimiters. No toolbar button writes them yet.
98
+ *
99
+ * EVERY READING IS LINE BY LINE: a run never crosses a line break. The email
100
+ * always read this way; the editor's painter and the storefront used to read
101
+ * the whole string, so the same saved text drew differently in each.
38
102
  *
39
103
  * Plus a per-product LEGACY set ({@link STOREFRONT_MARKUP} /
40
104
  * {@link CAMPAIGN_MARKUP}) that keeps already-published copy rendering exactly
@@ -72,15 +136,44 @@
72
136
  * and the form to reach for. Pinned by a test, so it stays a decision rather
73
137
  * than becoming a discovery.
74
138
  */
75
- /** A style a toolbar button can apply. The first five are inline marks; the
76
- * last four are the composer-only affordances (a link and the three line
77
- * prefixes), which only a renderer that draws block content can honour. */
78
- type MarkStyle = 'bold' | 'italic' | 'strike' | 'code' | 'highlight' | 'link' | 'bullet' | 'number' | 'quote';
139
+ /** A style a toolbar button can apply. The first six are inline marks; then
140
+ * the composer-only affordances (a link and the three line prefixes), which
141
+ * only a renderer that draws block content can honour; `clear`, which
142
+ * writes no mark at all — it takes them off ({@link clearMarks}); and the
143
+ * three spans of the final text box (E1), which no button in
144
+ * {@link MARKUP_TOOLS} offers yet. A colour carries a name, so it is written
145
+ * by {@link applyColor}, not here. */
146
+ type MarkStyle = 'bold' | 'italic' | 'strike' | 'code' | 'highlight' | 'quiet' | 'link' | 'bullet' | 'number' | 'quote' | 'clear' | 'underline' | 'sup' | 'sub';
147
+ /** What a span means: `[words]{underline}`, `{sup}`, `{sub}` or
148
+ * `{color=name}`. Only the tree reads these; {@link InlineKind} is the flat
149
+ * reading's, which never makes one. */
150
+ type SpanKind = 'underline' | 'sup' | 'sub' | 'color';
79
151
  /** What a parsed run MEANS. `accent` is the storefront/campaign brand colour —
80
152
  * the legacy `*phrase*` — and `highlight` is its standard-markdown successor;
81
153
  * both resolve to the same paint, which is what makes converting stored copy
82
154
  * from one to the other invisible. */
83
- type InlineKind = 'text' | 'bold' | 'italic' | 'strike' | 'code' | 'highlight' | 'accent';
155
+ type InlineKind = 'text' | 'bold' | 'italic' | 'strike' | 'code' | 'highlight' | 'accent' | 'quiet';
156
+ /** A theme colour's name as a span saves it: lowercase letters, digits and
157
+ * `-`, starting with a letter (`accent`, `brand-2`). A name is the theme's,
158
+ * so changing the theme recolours the words. Never a hex — that is the other
159
+ * kind of colour value ({@link isColorValue}). */
160
+ declare function isColorName(name: string): boolean;
161
+ /** A colour a span may carry: a theme NAME ({@link isColorName}), or "any
162
+ * colour" as EXACTLY six hex digits after a `#` (`#0b5cff`). Either case
163
+ * reads; the writers save lowercase ({@link colorValue}). Nothing else —
164
+ * `#f00`, `rgb()`, a typo — is a colour, so it stays plain text.
165
+ *
166
+ * WIDENS E1 (GG 2026-10-06): the final design draws "Any color", and a
167
+ * feature is never cut to fit. A hex does not follow the theme; a name does. */
168
+ declare function isColorValue(value: string): boolean;
169
+ /**
170
+ * The words a press WRITES when nothing is selected — `**bold text**`, left
171
+ * selected so the next keystroke replaces it. English unless the caller of
172
+ * {@link applyMark} hands in its own (0.95.0: a host in another language).
173
+ * They are words put in the box, not rules: nothing here changes what parses.
174
+ */
175
+ type MarkPlaceholders = Record<'bold' | 'italic' | 'strike' | 'code' | 'highlight' | 'quiet' | 'link', string> & Partial<Record<'underline' | 'sup' | 'sub' | 'color', string>>;
176
+ declare const MARK_PLACEHOLDERS: MarkPlaceholders;
84
177
  interface WrapResult {
85
178
  text: string;
86
179
  /** Where the caret should land — inside the new marks, or after them. */
@@ -96,18 +189,93 @@ interface WrapResult {
96
189
  * also unwrapped `*phrase*` would silently strip an accent run in the storefront
97
190
  * and the campaign designer, where the asterisk means colour, not slant.
98
191
  */
99
- declare function applyMark(text: string, start: number, end: number, style: MarkStyle): WrapResult;
100
- /** One toolbar button. `label` is a text glyph for hosts that draw type
101
- * (the message composer); hosts with an icon set map `style` to their own.
102
- * `shortcut` is the bare key the host combines with its platform modifier. */
192
+ declare function applyMark(text: string, start: number, end: number, style: MarkStyle, rules?: readonly InlineRule[], placeholders?: MarkPlaceholders): WrapResult;
193
+ /**
194
+ * Colour `text[start:end]` with `name` — a theme colour name or a 6-digit hex
195
+ * ({@link isColorValue}; a hex is saved lowercase) — writing
196
+ * `[words]{color=name}` and leaving the words selected. On words that are
197
+ * already exactly one colour span it changes the colour; `null` takes it
198
+ * off. A value {@link isColorValue} refuses changes nothing — `#f00` or a typo
199
+ * is never saved as a block the reader would print as text.
200
+ *
201
+ * Separate from {@link applyMark} because a colour carries a value, as a link
202
+ * does ({@link writeLink}).
203
+ */
204
+ declare function applyColor(text: string, start: number, end: number, name: string | null, placeholders?: MarkPlaceholders): WrapResult;
205
+ /**
206
+ * True when `text[start:end]` is ALREADY wrapped in `style`'s own fence — the
207
+ * very test {@link applyMark} uses to decide that a press unwraps. So a button
208
+ * drawn as pressed is exactly a button whose press takes the mark off; the two
209
+ * cannot disagree. False for a style with no fence (a link, a line prefix).
210
+ */
211
+ declare function markedAt(text: string, start: number, end: number, style: MarkStyle): boolean;
212
+ /** A link as it sits in authored text: where it starts and ends, its words and
213
+ * its address. */
214
+ interface LinkSpan {
215
+ start: number;
216
+ end: number;
217
+ label: string;
218
+ href: string;
219
+ }
220
+ /**
221
+ * The link the caret or selection sits in — `[words](href)` covering all of
222
+ * `start..end` — or null. What the link button opens on: inside a link it edits
223
+ * that link, anywhere else it makes a new one.
224
+ *
225
+ * Read with the parser's own rule ({@link linkAt}), so a span this returns is
226
+ * exactly one the renderers draw as a link — an unsafe address is not a link
227
+ * and is not offered for editing as one.
228
+ */
229
+ declare function findLink(text: string, start: number, end: number): LinkSpan | null;
230
+ /**
231
+ * Make `text[start:end]` a link to `href`, or re-point the link it sits in.
232
+ * The caret lands after the link, selecting nothing: the link is finished.
233
+ *
234
+ * The words are the selection, or — with nothing selected — the address
235
+ * itself, so a merchant who pastes an address and presses Apply gets a link
236
+ * that reads as that address rather than a placeholder to type over.
237
+ *
238
+ * ⚠️ The address is the CALLER'S to check ({@link isSafeLinkHref}): one that
239
+ * fails would be written as plain text the renderers never draw as a link.
240
+ */
241
+ declare function writeLink(text: string, start: number, end: number, href: string): WrapResult;
242
+ /** Take a link off, keeping its words — selected, so a second press of the
243
+ * link button puts it straight back. */
244
+ declare function removeLink(text: string, link: LinkSpan): WrapResult;
245
+ /**
246
+ * One toolbar button.
247
+ *
248
+ * - `label` is a text glyph, never a word, for a host that draws type (the
249
+ * message composer). The copy box draws the icon set's glyph instead.
250
+ * - `name` is what the button is CALLED: its accessible name, and the first
251
+ * word of its tooltip. Approved 2026-10-02: no word is drawn ON a button,
252
+ * so a name lives here and in `title` and nowhere a merchant reads it as
253
+ * part of the bar.
254
+ * - `title` is the tooltip: the name, what the mark writes, and the shortcut.
255
+ * - `shortcut` is the key the host combines with its platform modifier: a bare
256
+ * letter (`'b'`), or `'shift+'` and a letter.
257
+ */
103
258
  interface MarkupTool {
104
259
  style: MarkStyle;
105
260
  label: string;
106
261
  title: string;
262
+ name?: string;
107
263
  shortcut?: string;
108
264
  }
109
- /** Every button, in the order a writer reaches for them. */
265
+ /** Every button, in the order a writer reaches for them. Clear is last: it
266
+ * acts on every mark before it. */
110
267
  declare const MARKUP_TOOLS: readonly MarkupTool[];
268
+ /**
269
+ * The names a merchant sees for the three styles that carry one, in both
270
+ * editors (owner-approved 2026-09-28). One word per idea: Strong IS bold
271
+ * (`**`, ⌘B) and Emphasis IS the highlight (`==`), so a toolbar that shows the
272
+ * names shows no separate B or ◆ beside them.
273
+ */
274
+ declare const MARK_NAMES: {
275
+ readonly bold: "Strong";
276
+ readonly highlight: "Emphasis";
277
+ readonly quiet: "Quiet";
278
+ };
111
279
  /** The tools a host shows, resolved to descriptors in {@link MARKUP_TOOLS}
112
280
  * order. Keeps a host from hand-rebuilding a descriptor (and drifting on a
113
281
  * tooltip) just to pick a subset. */
@@ -141,6 +309,23 @@ interface InlineRule {
141
309
  * more rule field and no new branch in {@link tokenizeInline}.
142
310
  */
143
311
  readonly notBefore?: string;
312
+ /**
313
+ * True when the run may not close right before a span block (`{sup}`,
314
+ * `{color=accent}`, …). The campaign's legacy `[phrase]` needs it for the
315
+ * reason it needs `notBefore: '('`: without it `[x]{sup}` would read as an
316
+ * italic `x` followed by a printed `{sup}`. Only the four exact blocks count,
317
+ * so `[x]{{first_name}}` and `[x]{foo}` read as they always did.
318
+ */
319
+ readonly notBeforeBlock?: boolean;
320
+ /**
321
+ * True when the run may only OPEN at a word start: the open may not follow a
322
+ * letter or a digit (any script). The read-only `++` Strong needs it, because
323
+ * `++` is ordinary copy after a word — "A++ rated tyres, A++ service" would
324
+ * otherwise read as one Strong run from the first `A++` to the second. Only
325
+ * the open is checked, so "Save ++20%++ today" is still Strong. Lewis,
326
+ * 2026-10-12 (D1).
327
+ */
328
+ readonly wordStart?: boolean;
144
329
  }
145
330
  /**
146
331
  * The standard-markdown marks, shared by every product.
@@ -154,6 +339,14 @@ interface InlineRule {
154
339
  * what actually does the work.
155
340
  */
156
341
  declare const STANDARD_MARKUP: readonly InlineRule[];
342
+ /**
343
+ * Marks the copy box no longer OFFERS (E5a, 2026-10-06: Lewis dropped
344
+ * Emphasis `==` and Quiet `^^` from the final design). They are still read,
345
+ * drawn and written back byte for byte — saved copy wears them — but no bar
346
+ * button writes them, and `CopyBox` draws none for them even when a host asks.
347
+ * Declared before {@link COPY_FIELD_TOOLS} reads it at module load.
348
+ */
349
+ declare const RETIRED_MARKS: readonly MarkStyle[];
157
350
  /**
158
351
  * The buttons a COPY FIELD offers — a storefront section's copy box and a
159
352
  * campaign block's copy box show exactly these, so a merchant meets one toolbar
@@ -167,7 +360,7 @@ declare const STANDARD_MARKUP: readonly InlineRule[];
167
360
  * parser read as something else. Now a style with no rule cannot appear here,
168
361
  * whatever anyone types into the array.
169
362
  *
170
- * It resolves to bold, italic, strike and highlight. `code` is the one that
363
+ * It resolves to bold, italic, strike, highlight and quiet. `code` is the one that
171
364
  * surprises: it is an inline mark and it has a fence, but no rule reads a
172
365
  * backtick, so a copy field would print `` `word` `` verbatim — and a button
173
366
  * whose output the renderer draws as literal text is worse than no button.
@@ -231,5 +424,236 @@ declare function tokenizeInline(value: string, rules: readonly InlineRule[]): In
231
424
  * visitor actually sees.
232
425
  */
233
426
  declare function stripInline(value: string, rules: readonly InlineRule[]): string;
427
+ /** Plain words. */
428
+ interface MarkupText {
429
+ kind: 'text';
430
+ text: string;
431
+ }
432
+ /** A delimited run. `open` and `close` are the delimiters that were actually
433
+ * saved — `#` for a legacy campaign bold, `**` for the standard one, `[` and
434
+ * `]{sup}` for a span — so the tree writes back the same bytes it was read
435
+ * from. `color` is the theme colour's name, on a `color` span only. */
436
+ interface MarkupRun {
437
+ kind: InlineRule['kind'] | SpanKind;
438
+ open: string;
439
+ close: string;
440
+ color?: string;
441
+ children: MarkupNode[];
442
+ }
443
+ /** `[words](href)`. Only made when {@link isSafeLinkHref} passes. */
444
+ interface MarkupLink {
445
+ kind: 'link';
446
+ href: string;
447
+ children: MarkupNode[];
448
+ }
449
+ type MarkupNode = MarkupText | MarkupRun | MarkupLink;
450
+ /** One line of the saved string. `bullet` means it was saved as `- words`;
451
+ * `number` holds the digits of a line saved as `12. words`, as typed (`01`
452
+ * stays `01`), and is null on every other line. `children` hold what follows
453
+ * the prefix. A line built by hand may leave `number` out: it means null. */
454
+ interface MarkupLine {
455
+ bullet: boolean;
456
+ number?: string | null;
457
+ children: MarkupNode[];
458
+ }
459
+ /**
460
+ * True for an address a link may carry: `http`, `https`, `mailto` or `tel`, or
461
+ * one with no scheme at all (`/tyres`, `#top`, `terms.html`, `//cdn…`).
462
+ * Anything else — `javascript:`, `data:`, `file:` — is refused, and so is an
463
+ * address with any space or control character in it, because browsers drop
464
+ * those before they read the scheme (`java\tscript:` runs as `javascript:`).
465
+ *
466
+ * Renderers call this too, before they write an `href`, so the rule has one
467
+ * home.
468
+ */
469
+ declare function isSafeLinkHref(href: string): boolean;
470
+ /**
471
+ * Read authored text into lines of nodes — the whole grammar, for a rich text
472
+ * box and for the renderers that draw links and bullets.
473
+ *
474
+ * - A run never crosses a line break.
475
+ * - A line that starts with `- ` and has words after it is a bullet. One level
476
+ * only: an indented ` - ` is plain text.
477
+ * - A line that starts with 1-3 digits, `. ` and words is numbered, the digits
478
+ * kept as typed. One level only, as bullets.
479
+ * - `[words](href)` is a link when {@link isSafeLinkHref} passes; otherwise the
480
+ * whole thing stays plain text.
481
+ * - `[words]{underline}`, `{sup}`, `{sub}` and `{color=name}` are spans; any
482
+ * other block stays plain text.
483
+ * - A run's words are read again, so `**bold _with italic_**` is italic inside
484
+ * bold. Four levels: a fifth stays as typed.
485
+ *
486
+ * Nothing is lost: {@link serializeMarkup} writes back the exact bytes.
487
+ */
488
+ declare function parseMarkup(value: string, rules: readonly InlineRule[]): MarkupLine[];
489
+ /** Write lines back to authored text: the inverse of {@link parseMarkup}. */
490
+ declare function serializeMarkup(lines: readonly MarkupLine[]): string;
491
+ /**
492
+ * One run a character sits in. `id` says WHICH run: two characters with the
493
+ * same id are in one run, so `**a****b**` (two bold runs, back to back) is told
494
+ * apart from `**ab**` (one) and is written back as it was saved.
495
+ *
496
+ * `open` and `close` are the delimiters that were saved (`#` for a legacy
497
+ * campaign bold), or, for a run made in the editor, the toolbar's own fence.
498
+ * A link's `close` is `](href)`, a span's `]{sup}` or `]{color=name}`.
499
+ */
500
+ interface MarkSpan {
501
+ id: number;
502
+ kind: InlineRule['kind'] | SpanKind | 'link';
503
+ open: string;
504
+ close: string;
505
+ href?: string;
506
+ color?: string;
507
+ }
508
+ /**
509
+ * A NEW run, as the toolbar writes it: `**` for bold, `_` for italic, and so on
510
+ * ({@link applyMark}'s own fences), a link to `value`, or a colour span of
511
+ * `value` — a theme name or a 6-digit hex, saved lowercase ({@link isColorValue}). Null for a style that writes no run (a line
512
+ * prefix, clear) and for a link or colour with no usable value. A paste filter
513
+ * or an editor making a run asks here, so no delimiter is spelled outside this
514
+ * module (E8).
515
+ */
516
+ declare function spanFor(id: number, style: MarkStyle | 'color', value?: string): MarkSpan | null;
517
+ /** One character of the words — never a delimiter — with the runs it sits in,
518
+ * outside first, and its offset in the saved string. */
519
+ interface MarkedChar {
520
+ ch: string;
521
+ marks: readonly MarkSpan[];
522
+ at: number;
523
+ }
524
+ /** One line: its bullet flag or its number (the `- ` and the `12. ` are not
525
+ * characters) and its words. `number` left out means null, as on
526
+ * {@link MarkupLine}. */
527
+ interface MarkedLine {
528
+ bullet: boolean;
529
+ number?: string | null;
530
+ chars: MarkedChar[];
531
+ }
532
+ /**
533
+ * Read authored text as characters, each carrying its runs — what an editor
534
+ * that hides the delimiters edits, and what {@link clearMarks} splits runs on.
535
+ *
536
+ * Built on {@link parseMarkup}, so it is the same reading, character by
537
+ * character. A character is a code point (an emoji is one, two UTF-16 units
538
+ * long); `at` counts UTF-16 units, as a text box's selection does.
539
+ *
540
+ * {@link writeMarkedLines} writes it back byte for byte.
541
+ */
542
+ declare function markedLines(value: string, rules: readonly InlineRule[]): MarkedLine[];
543
+ /**
544
+ * Write characters back to authored text: the inverse of {@link markedLines}.
545
+ * Returns where each character landed (`at[line][char]`), so a selection can be
546
+ * carried across the write.
547
+ *
548
+ * Runs are opened and closed as the characters ask: a character outside a run
549
+ * that its neighbours are in closes it, and the run's next character opens it
550
+ * again. That is how a run is SPLIT — clear the middle of `**abcd**` and it is
551
+ * written `**a**bc**d**`. Whether the result reads back as written is the
552
+ * caller's to check ({@link sameMarks}): `_` cannot close against a letter.
553
+ */
554
+ declare function writeMarkedLines(lines: readonly MarkedLine[]): {
555
+ text: string;
556
+ at: number[][];
557
+ };
558
+ /** True when two readings hold the same words wearing the same marks — the
559
+ * kinds, addresses, colours and list numbers, not which run or which
560
+ * delimiter. */
561
+ declare function sameMarks(a: readonly MarkedLine[], b: readonly MarkedLine[]): boolean;
562
+ /**
563
+ * Take every mark off `text[start:end]`, keeping the words and any link (a
564
+ * link has its own Remove). A run the selection only partly covers is split,
565
+ * so the rest of it keeps its mark. Bullets and numbers stay: they are lines,
566
+ * not marks.
567
+ *
568
+ * With nothing selected, the runs the caret touches — the characters either
569
+ * side of it — come off whole.
570
+ *
571
+ * A split the grammar cannot hold comes off whole instead: the middle of
572
+ * `_word_` would leave an underscore against a letter, which reads as a
573
+ * literal underscore. And if even that would not read back, nothing changes.
574
+ */
575
+ declare function clearMarks(text: string, start: number, end: number, rules?: readonly InlineRule[]): WrapResult;
576
+ /**
577
+ * The final toolbar's Clear style (owner ruling, 2026-10-07): take EVERY mark
578
+ * off the WORDS in `text[start:end]` but a link, keeping the words selected —
579
+ * the bar's (bold, italic, underline, strikethrough, colour, superscript,
580
+ * subscript) and the retired ones it no longer offers (Emphasis, Quiet, the
581
+ * legacy accent), so the words come back plain (GG, 2026-10-07). A link is
582
+ * content, not style; a line's bullet or number is a line, not a mark.
583
+ *
584
+ * A run the selection only partly covers is split, so the rest of it keeps
585
+ * its mark; a split the grammar cannot hold (the middle of `_word_`) takes the
586
+ * run off whole; and if even that would not read back, nothing changes.
587
+ *
588
+ * With nothing selected nothing changes: the button is disabled then, so a
589
+ * press can never wipe a whole paragraph. Unlike {@link clearMarks} (the 0.94
590
+ * bar's Clear marks), which with a caret takes off the runs it touches.
591
+ */
592
+ declare function clearStyle(text: string, start: number, end: number, rules?: readonly InlineRule[]): WrapResult;
593
+ /**
594
+ * Press `style` on the WORDS in `text[start:end]`, whichever runs they sit in —
595
+ * what a view that hides the delimiters needs, where `applyMark` (which wraps
596
+ * exactly the characters selected) would cross or strand a delimiter.
597
+ *
598
+ * - Every selected word already wears the mark: it comes off them. Part of a
599
+ * run is split off; the rest keeps the mark.
600
+ * - Otherwise the mark goes on, as ONE run over the words and any run of the
601
+ * same mark they touch (`**half** off` → `**half off**`).
602
+ * - Nothing is written that would not read back: a third level of nesting,
603
+ * or `_` inside a word, leaves the text as it was.
604
+ * - With nothing selected it is {@link applyMark}: a selected placeholder.
605
+ *
606
+ * Offsets are the saved string's, before and after.
607
+ */
608
+ declare function toggleMark(text: string, start: number, end: number, style: MarkStyle, rules?: readonly InlineRule[]): WrapResult;
609
+ /**
610
+ * The marks every word in `text[start:end]` wears — `bold`, `link`, a legacy
611
+ * `accent` … — or, with nothing selected, the marks of the word just before the
612
+ * caret on its line: what typing there would continue. What a toolbar draws
613
+ * as pressed in a view without delimiters.
614
+ */
615
+ declare function marksIn(text: string, start: number, end: number, rules?: readonly InlineRule[]): Set<string>;
616
+ /**
617
+ * Link the WORDS in `text[start:end]` to `href`, nesting with any run they
618
+ * sit in rather than crossing it: words inside a bold run become a link inside
619
+ * it (`**Book [a fitting](/book)**`), a whole bold run goes inside the link.
620
+ *
621
+ * With nothing selected, inside a link it re-points that link; anywhere else
622
+ * it writes the address as its own words ({@link writeLink}). The caret lands
623
+ * after the link. ⚠️ The address is the caller's to check ({@link isSafeLinkHref}).
624
+ */
625
+ declare function linkOver(text: string, start: number, end: number, href: string, rules?: readonly InlineRule[]): WrapResult;
626
+ /** Take off the link the caret or selection touches, keeping its words —
627
+ * selected, so a second press of the link button puts it straight back. */
628
+ declare function unlinkAt(text: string, start: number, end: number, rules?: readonly InlineRule[]): WrapResult;
629
+ /**
630
+ * Colour the WORDS in `text[start:end]` with `name` — a theme colour name or
631
+ * a 6-digit hex, saved lowercase (`[words]{color=name}`), nesting with any run they sit in rather than
632
+ * crossing it — the colour window's write, as {@link linkOver} is the link
633
+ * window's. A colour the words already wear is replaced; `null` takes the
634
+ * colour off them, splitting a run that only partly covers them.
635
+ *
636
+ * A value {@link isColorValue} refuses (`#f00`, a word with a space) changes
637
+ * nothing: a block the reader would print as text is never written. With
638
+ * nothing selected nothing changes either — the box keeps a pending colour
639
+ * for the words typed next instead.
640
+ */
641
+ declare function colorOver(text: string, start: number, end: number, name: string | null, rules?: readonly InlineRule[]): WrapResult;
642
+ /** The ONE colour every word in `text[start:end]` wears — or, with nothing
643
+ * selected, the word before the caret on its line — or null. What the colour
644
+ * window draws as chosen, and the colour under the A. */
645
+ declare function colorIn(text: string, start: number, end: number, rules?: readonly InlineRule[]): string | null;
646
+ /**
647
+ * Make every line `text[start:end]` touches (the caret's line, with nothing
648
+ * selected) a bulleted list, a numbered list, or — `null` — no list. The
649
+ * words and their marks keep their bytes; only the line prefixes change. A
650
+ * numbered list counts on from a numbered line just above it, else from 1.
651
+ * An empty line becomes an empty list line (`- `), which the rich view draws
652
+ * as a bullet with nothing after it yet. The selection keeps its words.
653
+ */
654
+ declare function setList(text: string, start: number, end: number, kind: 'bullet' | 'number' | null, rules?: readonly InlineRule[]): WrapResult;
655
+ /** `bullet` or `number` when every line `text[start:end]` touches is that
656
+ * kind of list line, else null — what the list buttons draw as pressed. */
657
+ declare function listIn(text: string, start: number, end: number, rules?: readonly InlineRule[]): 'bullet' | 'number' | null;
234
658
 
235
- export { CAMPAIGN_MARKUP, COPY_FIELD_TOOLS, type InlineKind, type InlineRule, type InlineToken, MARKUP_TOOLS, type MarkStyle, type MarkupTool, STANDARD_MARKUP, STOREFRONT_MARKUP, type WrapResult, applyMark, markupTools, stripInline, tokenizeInline };
659
+ 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 +1 @@
1
- var e={bold:{fence:"**",placeholder:"bold text"},italic:{fence:"_",placeholder:"italic text"},strike:{fence:"~~",placeholder:"struck out"},code:{fence:"`",placeholder:"code"},highlight:{fence:"==",placeholder:"highlighted text"}},t={bullet:()=>"- ",number:e=>`${e+1}. `,quote:()=>"> "};function l(l,n,i,o){const c=l.slice(n,i);if("link"===o){const e=c||"link text",t=`[${e}](https://)`;return{text:l.slice(0,n)+t+l.slice(i),selectionStart:n+e.length+3,selectionEnd:n+t.length-1}}const s=t[o];if(s){const e=l.lastIndexOf("\n",Math.max(n-1,0))+1,t=l.indexOf("\n",i),o=-1===t?l.length:t,c=l.slice(e,o).split("\n"),r=c.every((e,t)=>e.startsWith(s(t))),d=c.map((e,t)=>r?e.slice(s(t).length):s(t)+e).join("\n");return{text:l.slice(0,e)+d+l.slice(o),selectionStart:e,selectionEnd:e+d.length}}const{fence:r,placeholder:d}=e[o],h=l.slice(Math.max(0,n-r.length),n),u=l.slice(i,i+r.length);if(h===r&&u===r)return{text:l.slice(0,n-r.length)+c+l.slice(i+r.length),selectionStart:n-r.length,selectionEnd:n-r.length+c.length};const a=c||d;return{text:l.slice(0,n)+r+a+r+l.slice(i),selectionStart:n+r.length,selectionEnd:n+r.length+a.length}}var n=[{style:"bold",label:"B",title:"Bold (⌘B)",shortcut:"b"},{style:"italic",label:"I",title:"Italic (⌘I)",shortcut:"i"},{style:"strike",label:"S",title:"Strikethrough"},{style:"highlight",label:"◆",title:"Highlight in the brand color"},{style:"code",label:"‹›",title:"Code"},{style:"link",label:"🔗",title:"Link (⌘K)",shortcut:"k"},{style:"bullet",label:"•",title:"Bulleted list"},{style:"number",label:"1.",title:"Numbered list"},{style:"quote",label:"❝",title:"Quote"}];function i(e){return n.filter(t=>e.includes(t.style))}var o=[{open:"**",close:"**",kind:"bold"},{open:"==",close:"==",kind:"highlight"},{open:"~~",close:"~~",kind:"strike"},{open:"_",close:"_",kind:"italic",wordBoundary:!0}],c=n.map(e=>e.style).filter(e=>o.some(t=>t.kind===e)),s=[...o,{open:"*",close:"*",kind:"accent"}],r=[...o,{open:"*",close:"*",kind:"accent"},{open:"#",close:"#",kind:"bold",notBefore:"0123456789"},{open:"[",close:"]",kind:"italic",notBefore:"("}];function d(e){return!/\s/.test(e)&&!/[!-\/:-@[-`{-~]/.test(e)}function h(e,t){const l=[];let n="",i=0;for(;i<e.length;){const o=u(e,i,t);o?(l.push({kind:"text",text:n}),n="",l.push({kind:o.rule.kind,text:o.inner}),i=o.end):(n+=e[i],i+=1)}return l.push({kind:"text",text:n}),l}function u(e,t,l){for(const n of l){if(!e.startsWith(n.open,t))continue;if(n.wordBoundary&&t>0&&d(e[t-1]))continue;const l=t+n.open.length,i=e.indexOf(n.close,l);if(i<l+1)continue;const o=e.slice(l,i);if(o.includes(n.close[0]))continue;const c=i+n.close.length,s=e[c];if((!n.notBefore||void 0===s||!n.notBefore.includes(s))&&(!n.wordBoundary||void 0===s||!d(s)))return{rule:n,inner:o,end:c}}return null}function a(e,t){return h(e,t).map(e=>e.text).join("")}export{r as CAMPAIGN_MARKUP,c as COPY_FIELD_TOOLS,n as MARKUP_TOOLS,o as STANDARD_MARKUP,s as STOREFRONT_MARKUP,l as applyMark,i as markupTools,a as stripInline,h as tokenizeInline};
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-PJPK2ADX.js";