efficient-shell 0.0.0-stage → 4.126.2

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/LICENSE +21 -0
  2. package/README.md +753 -2
  3. package/dist/Browser-DBRSIQ4J.js +478 -0
  4. package/dist/Browser-DBRSIQ4J.js.map +1 -0
  5. package/dist/Calculator-HCKFTSJR.js +188 -0
  6. package/dist/Calculator-HCKFTSJR.js.map +1 -0
  7. package/dist/ConfirmDialog-LJXMB45A.js +3 -0
  8. package/dist/ConfirmDialog-LJXMB45A.js.map +1 -0
  9. package/dist/CurrencyConverter-BKKORN6K.js +232 -0
  10. package/dist/CurrencyConverter-BKKORN6K.js.map +1 -0
  11. package/dist/Documents-5MD5SKPB.js +437 -0
  12. package/dist/Documents-5MD5SKPB.js.map +1 -0
  13. package/dist/Files-KY35DPWB.js +801 -0
  14. package/dist/Files-KY35DPWB.js.map +1 -0
  15. package/dist/Notepad-2BO5VEUG.js +406 -0
  16. package/dist/Notepad-2BO5VEUG.js.map +1 -0
  17. package/dist/PdfViewer-LSP3YNFC.js +4 -0
  18. package/dist/PdfViewer-LSP3YNFC.js.map +1 -0
  19. package/dist/PomodoroTimer-SVJ62RXM.js +629 -0
  20. package/dist/PomodoroTimer-SVJ62RXM.js.map +1 -0
  21. package/dist/Preview-N4UYYXG6.js +2955 -0
  22. package/dist/Preview-N4UYYXG6.js.map +1 -0
  23. package/dist/Sidebar-J76PV4QF.js +290 -0
  24. package/dist/Sidebar-J76PV4QF.js.map +1 -0
  25. package/dist/Spreadsheet-5EWZGMXN.js +515 -0
  26. package/dist/Spreadsheet-5EWZGMXN.js.map +1 -0
  27. package/dist/Stock-CDUS4VCZ.js +154 -0
  28. package/dist/Stock-CDUS4VCZ.js.map +1 -0
  29. package/dist/Weather-EEDBWBPQ.js +427 -0
  30. package/dist/Weather-EEDBWBPQ.js.map +1 -0
  31. package/dist/WorldClock-FFFULVTJ.js +198 -0
  32. package/dist/WorldClock-FFFULVTJ.js.map +1 -0
  33. package/dist/apps/index.d.ts +267 -0
  34. package/dist/apps/index.js +147 -0
  35. package/dist/apps/index.js.map +1 -0
  36. package/dist/brand.css +2 -0
  37. package/dist/chunk-2VM2FRZJ.js +215 -0
  38. package/dist/chunk-2VM2FRZJ.js.map +1 -0
  39. package/dist/chunk-32W6VY2J.js +549 -0
  40. package/dist/chunk-32W6VY2J.js.map +1 -0
  41. package/dist/chunk-3CUSBZWG.js +59 -0
  42. package/dist/chunk-3CUSBZWG.js.map +1 -0
  43. package/dist/chunk-3L463T5H.js +26 -0
  44. package/dist/chunk-3L463T5H.js.map +1 -0
  45. package/dist/chunk-4TVV2BIU.js +13334 -0
  46. package/dist/chunk-4TVV2BIU.js.map +1 -0
  47. package/dist/chunk-6XQHH7D2.js +105 -0
  48. package/dist/chunk-6XQHH7D2.js.map +1 -0
  49. package/dist/chunk-7CCHEEYC.js +94 -0
  50. package/dist/chunk-7CCHEEYC.js.map +1 -0
  51. package/dist/chunk-7KIOEKHS.js +617 -0
  52. package/dist/chunk-7KIOEKHS.js.map +1 -0
  53. package/dist/chunk-AXWC3332.js +1103 -0
  54. package/dist/chunk-AXWC3332.js.map +1 -0
  55. package/dist/chunk-BFBWLCO6.js +16 -0
  56. package/dist/chunk-BFBWLCO6.js.map +1 -0
  57. package/dist/chunk-C3NELUTX.js +79 -0
  58. package/dist/chunk-C3NELUTX.js.map +1 -0
  59. package/dist/chunk-CT3HVZRZ.js +26 -0
  60. package/dist/chunk-CT3HVZRZ.js.map +1 -0
  61. package/dist/chunk-D7PYW2QS.js +265 -0
  62. package/dist/chunk-D7PYW2QS.js.map +1 -0
  63. package/dist/chunk-DMCFOWOJ.js +153 -0
  64. package/dist/chunk-DMCFOWOJ.js.map +1 -0
  65. package/dist/chunk-FQB24B42.js +416 -0
  66. package/dist/chunk-FQB24B42.js.map +1 -0
  67. package/dist/chunk-GA35NEND.js +56 -0
  68. package/dist/chunk-GA35NEND.js.map +1 -0
  69. package/dist/chunk-HNH7FOYB.js +69 -0
  70. package/dist/chunk-HNH7FOYB.js.map +1 -0
  71. package/dist/chunk-HYZ5QMVO.js +462 -0
  72. package/dist/chunk-HYZ5QMVO.js.map +1 -0
  73. package/dist/chunk-I464GYJJ.js +17 -0
  74. package/dist/chunk-I464GYJJ.js.map +1 -0
  75. package/dist/chunk-JFQGPY7W.js +819 -0
  76. package/dist/chunk-JFQGPY7W.js.map +1 -0
  77. package/dist/chunk-MGGY74RY.js +14 -0
  78. package/dist/chunk-MGGY74RY.js.map +1 -0
  79. package/dist/chunk-N3CG5HG5.js +144 -0
  80. package/dist/chunk-N3CG5HG5.js.map +1 -0
  81. package/dist/chunk-W6XTG23K.js +3708 -0
  82. package/dist/chunk-W6XTG23K.js.map +1 -0
  83. package/dist/chunk-YG45DAUT.js +96 -0
  84. package/dist/chunk-YG45DAUT.js.map +1 -0
  85. package/dist/chunk-YMGXZABY.js +7 -0
  86. package/dist/chunk-YMGXZABY.js.map +1 -0
  87. package/dist/chunk-YNGPTOL6.js +46 -0
  88. package/dist/chunk-YNGPTOL6.js.map +1 -0
  89. package/dist/chunk-YNHLQ24V.js +400 -0
  90. package/dist/chunk-YNHLQ24V.js.map +1 -0
  91. package/dist/chunk-YNKK7NE3.js +171 -0
  92. package/dist/chunk-YNKK7NE3.js.map +1 -0
  93. package/dist/chunk-YPO6RDMX.js +20 -0
  94. package/dist/chunk-YPO6RDMX.js.map +1 -0
  95. package/dist/file-intake/index.d.ts +87 -0
  96. package/dist/file-intake/index.js +3 -0
  97. package/dist/file-intake/index.js.map +1 -0
  98. package/dist/index-dQia6NpB.d.ts +5904 -0
  99. package/dist/index.d.ts +2495 -0
  100. package/dist/index.js +7460 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/markdown/index.d.ts +118 -0
  103. package/dist/markdown/index.js +184 -0
  104. package/dist/markdown/index.js.map +1 -0
  105. package/dist/markup/index.d.ts +235 -0
  106. package/dist/markup/index.js +135 -0
  107. package/dist/markup/index.js.map +1 -0
  108. package/dist/page-tabs/index.d.ts +154 -0
  109. package/dist/page-tabs/index.js +706 -0
  110. package/dist/page-tabs/index.js.map +1 -0
  111. package/dist/shell.css +105 -0
  112. package/dist/sounds-NT4DEZGD.js +3 -0
  113. package/dist/sounds-NT4DEZGD.js.map +1 -0
  114. package/dist/styles.css +22 -0
  115. package/dist/themes.css +249 -0
  116. package/dist/types-BOinJXUf.d.ts +118 -0
  117. package/dist/ui/index.d.ts +4 -0
  118. package/dist/ui/index.js +15 -0
  119. package/dist/ui/index.js.map +1 -0
  120. package/dist/ui.css +1518 -0
  121. package/package.json +178 -4
@@ -0,0 +1,135 @@
1
+ // src/markup/index.ts
2
+ var INLINE = {
3
+ bold: { fence: "**", placeholder: "bold text" },
4
+ italic: { fence: "_", placeholder: "italic text" },
5
+ strike: { fence: "~~", placeholder: "struck out" },
6
+ code: { fence: "`", placeholder: "code" },
7
+ highlight: { fence: "==", placeholder: "highlighted text" }
8
+ };
9
+ var LINE_PREFIX = {
10
+ bullet: () => "- ",
11
+ number: (i) => `${i + 1}. `,
12
+ quote: () => "> "
13
+ };
14
+ function applyMark(text, start, end, style) {
15
+ const selected = text.slice(start, end);
16
+ if (style === "link") {
17
+ const label = selected || "link text";
18
+ const snippet = `[${label}](https://)`;
19
+ return {
20
+ text: text.slice(0, start) + snippet + text.slice(end),
21
+ // Land the caret on the URL — the label is usually already right.
22
+ // Offset spans "[", the label, "](" — three characters plus the label.
23
+ selectionStart: start + label.length + 3,
24
+ selectionEnd: start + snippet.length - 1
25
+ };
26
+ }
27
+ const prefix = LINE_PREFIX[style];
28
+ if (prefix) {
29
+ const from = text.lastIndexOf("\n", Math.max(start - 1, 0)) + 1;
30
+ const toIdx = text.indexOf("\n", end);
31
+ const to = toIdx === -1 ? text.length : toIdx;
32
+ const block = text.slice(from, to);
33
+ const lines = block.split("\n");
34
+ const marked = lines.every((l, i) => l.startsWith(prefix(i)));
35
+ const next = lines.map((l, i) => marked ? l.slice(prefix(i).length) : prefix(i) + l).join("\n");
36
+ return {
37
+ text: text.slice(0, from) + next + text.slice(to),
38
+ selectionStart: from,
39
+ selectionEnd: from + next.length
40
+ };
41
+ }
42
+ const { fence, placeholder } = INLINE[style];
43
+ const before = text.slice(Math.max(0, start - fence.length), start);
44
+ const after = text.slice(end, end + fence.length);
45
+ if (before === fence && after === fence) {
46
+ return {
47
+ text: text.slice(0, start - fence.length) + selected + text.slice(end + fence.length),
48
+ selectionStart: start - fence.length,
49
+ selectionEnd: start - fence.length + selected.length
50
+ };
51
+ }
52
+ const inner = selected || placeholder;
53
+ return {
54
+ text: text.slice(0, start) + fence + inner + fence + text.slice(end),
55
+ selectionStart: start + fence.length,
56
+ selectionEnd: start + fence.length + inner.length
57
+ };
58
+ }
59
+ var MARKUP_TOOLS = [
60
+ { style: "bold", label: "B", title: "Bold (\u2318B)", shortcut: "b" },
61
+ { style: "italic", label: "I", title: "Italic (\u2318I)", shortcut: "i" },
62
+ { style: "strike", label: "S", title: "Strikethrough" },
63
+ { style: "highlight", label: "\u25C6", title: "Highlight in the brand color" },
64
+ { style: "code", label: "\u2039\u203A", title: "Code" },
65
+ { style: "link", label: "\u{1F517}", title: "Link (\u2318K)", shortcut: "k" },
66
+ { style: "bullet", label: "\u2022", title: "Bulleted list" },
67
+ { style: "number", label: "1.", title: "Numbered list" },
68
+ { style: "quote", label: "\u275D", title: "Quote" }
69
+ ];
70
+ function markupTools(styles) {
71
+ return MARKUP_TOOLS.filter((t) => styles.includes(t.style));
72
+ }
73
+ var STANDARD_MARKUP = [
74
+ { open: "**", close: "**", kind: "bold" },
75
+ { open: "==", close: "==", kind: "highlight" },
76
+ { open: "~~", close: "~~", kind: "strike" },
77
+ { open: "_", close: "_", kind: "italic", wordBoundary: true }
78
+ ];
79
+ var COPY_FIELD_TOOLS = MARKUP_TOOLS.map((tool) => tool.style).filter((style) => STANDARD_MARKUP.some((rule) => rule.kind === style));
80
+ var STOREFRONT_MARKUP = [
81
+ ...STANDARD_MARKUP,
82
+ { open: "*", close: "*", kind: "accent" }
83
+ ];
84
+ var CAMPAIGN_MARKUP = [
85
+ ...STANDARD_MARKUP,
86
+ { open: "*", close: "*", kind: "accent" },
87
+ { open: "#", close: "#", kind: "bold", notBefore: "0123456789" },
88
+ { open: "[", close: "]", kind: "italic", notBefore: "(" }
89
+ ];
90
+ function wordish(ch) {
91
+ return !/\s/.test(ch) && !/[!-\/:-@[-`{-~]/.test(ch);
92
+ }
93
+ function tokenizeInline(value, rules) {
94
+ const out = [];
95
+ let buf = "";
96
+ let i = 0;
97
+ while (i < value.length) {
98
+ const hit = matchAt(value, i, rules);
99
+ if (hit) {
100
+ out.push({ kind: "text", text: buf });
101
+ buf = "";
102
+ out.push({ kind: hit.rule.kind, text: hit.inner });
103
+ i = hit.end;
104
+ continue;
105
+ }
106
+ buf += value[i];
107
+ i += 1;
108
+ }
109
+ out.push({ kind: "text", text: buf });
110
+ return out;
111
+ }
112
+ function matchAt(value, i, rules) {
113
+ for (const rule of rules) {
114
+ if (!value.startsWith(rule.open, i)) continue;
115
+ if (rule.wordBoundary && i > 0 && wordish(value[i - 1])) continue;
116
+ const from = i + rule.open.length;
117
+ const closeAt = value.indexOf(rule.close, from);
118
+ if (closeAt < from + 1) continue;
119
+ const inner = value.slice(from, closeAt);
120
+ if (inner.includes(rule.close[0])) continue;
121
+ const end = closeAt + rule.close.length;
122
+ const next = value[end];
123
+ if (rule.notBefore && next !== void 0 && rule.notBefore.includes(next)) continue;
124
+ if (rule.wordBoundary && next !== void 0 && wordish(next)) continue;
125
+ return { rule, inner, end };
126
+ }
127
+ return null;
128
+ }
129
+ function stripInline(value, rules) {
130
+ return tokenizeInline(value, rules).map((t) => t.text).join("");
131
+ }
132
+
133
+ export { CAMPAIGN_MARKUP, COPY_FIELD_TOOLS, MARKUP_TOOLS, STANDARD_MARKUP, STOREFRONT_MARKUP, applyMark, markupTools, stripInline, tokenizeInline };
134
+ //# sourceMappingURL=index.js.map
135
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/markup/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * The editorial markup rule — ONE grammar, shared by every product that lets a\n * human type formatted copy into a plain text box.\n *\n * Why this lives in react-os-shell, and why behind its own subpath:\n * the rule is consumed by the admin portal (agent messages + the campaign\n * designer) AND by the public storefront, which has ten dependencies in total\n * and cannot afford the shell's peer graph (a 3D viewer, a PDF renderer, a DXF\n * viewer, an xlsx parser, a Word converter). So this module is deliberately\n * FRAMEWORK-FREE: no React, no JSX, no DOM, no imports at all. `react-os-shell/markup`\n * therefore adds a few hundred bytes of first-party code to a bundle and nothing\n * else — see the subpath's own tsup entry.\n *\n * It is also the ONE subpath whose `exports` entry carries a `default`\n * condition alongside `import`, and that is deliberate rather than untidy. The\n * root and `./apps` are React component entries; a bundler resolves them and\n * nothing else has any business to. This module is consumed by build scripts\n * too — the storefront's media-manifest generator reaches it through its Puck\n * config — and those run under `tsx`, which resolves through Node's CJS\n * loader. With only `types` and `import` declared, that resolution fails with\n * ERR_PACKAGE_PATH_NOT_EXPORTED, which reads like a missing file rather than a\n * missing condition. A module this portable should answer to any resolver.\n *\n * WHAT IS SHARED IS THE RULE, NOT THE RENDERING. Each product renders the token\n * list its own way, because it must: the web uses CSS classes bound to theme\n * tokens, email must inline every style (mail clients drop classes). Two\n * renderers, one grammar, one writer ({@link applyMark}) — so a toolbar button\n * and a parser can never disagree about what a delimiter means.\n *\n * ## The delimiters\n *\n * Standard markdown, in every product:\n *\n * **phrase** bold\n * _phrase_ italic\n * ~~phrase~~ strikethrough\n * ==phrase== highlight — the brand accent colour, applied to a selection\n *\n * Plus a per-product LEGACY set ({@link STOREFRONT_MARKUP} /\n * {@link CAMPAIGN_MARKUP}) that keeps already-published copy rendering exactly\n * as it does today. Those legacy rules are the only part of this grammar that is\n * meant to be deleted: once stored content has been converted, drop the rule\n * from the product's rule list and nothing else changes.\n *\n * ## Two deliberate delimiter choices\n *\n * 1. ITALIC IS `_phrase_`, NOT `*phrase*`. In the storefront and the campaign\n * designer a single asterisk already means the ACCENT colour, on 704 live\n * instances. Standard markdown says italic. Both cannot be true, so the\n * asterisk keeps its existing meaning and italic takes markdown's OTHER\n * italic marker. Nothing published moves.\n * 2. `_` NEVER FIRES INSIDE A WORD. Without that guard, a mail merge line\n * holding `{{first_name}}` and `{{last_name}}` would italicise everything\n * between the two underscores. The guard is CommonMark's own rule for `_`\n * (an intraword underscore is literal) and it is checked with plain character\n * tests rather than a lookbehind, because a lookbehind is a parse error on\n * Safari below 16.4 and this code ships to a public storefront.\n *\n * ## One KNOWN divergence from the standard named above\n *\n * `__phrase__` IS ITALIC HERE. CommonMark reads a doubled underscore as STRONG;\n * this grammar has a single `_` rule, so the outer pair matches, the inner\n * underscores are ordinary characters, and `__all__` comes back as `_all_`.\n *\n * Left alone deliberately, and recorded rather than fixed. A census of stored\n * copy found 14 rows in this shape and every one of them is the literal link\n * key `__all__`, which no renderer is handed — so nothing published is being\n * mis-drawn today. Adding a `__` rule to buy CommonMark parity would change how\n * a stored `_` pair parses for the benefit of no existing string, and this\n * module's promise is that the grammar grows while nothing published moves.\n * Bold is `**phrase**` in every product; that is the form the toolbar writes\n * and the form to reach for. Pinned by a test, so it stays a decision rather\n * than becoming a discovery.\n */\n\n/** A style a toolbar button can apply. The first five are inline marks; the\n * last four are the composer-only affordances (a link and the three line\n * prefixes), which only a renderer that draws block content can honour. */\nexport type MarkStyle =\n | 'bold' | 'italic' | 'strike' | 'code' | 'highlight'\n | 'link' | 'bullet' | 'number' | 'quote';\n\n/** What a parsed run MEANS. `accent` is the storefront/campaign brand colour —\n * the legacy `*phrase*` — and `highlight` is its standard-markdown successor;\n * both resolve to the same paint, which is what makes converting stored copy\n * from one to the other invisible. */\nexport type InlineKind =\n | 'text' | 'bold' | 'italic' | 'strike' | 'code' | 'highlight' | 'accent';\n\n// ── Writing: the toolbar's half of the rule ────────────────────────────────\n\nconst INLINE: Record<string, { fence: string; placeholder: string }> = {\n bold: { fence: '**', placeholder: 'bold text' },\n italic: { fence: '_', placeholder: 'italic text' },\n strike: { fence: '~~', placeholder: 'struck out' },\n code: { fence: '`', placeholder: 'code' },\n highlight: { fence: '==', placeholder: 'highlighted text' },\n};\n\nconst LINE_PREFIX: Record<string, (i: number) => string> = {\n bullet: () => '- ',\n number: (i) => `${i + 1}. `,\n quote: () => '> ',\n};\n\nexport interface WrapResult {\n text: string;\n /** Where the caret should land — inside the new marks, or after them. */\n selectionStart: number;\n selectionEnd: number;\n}\n\n/**\n * Apply a style to `text[start:end]`, returning the new text and where the\n * selection should sit. Toggling is deliberate: pressing Bold on already-bold\n * text unwraps it, so the shortcut behaves like every other editor.\n *\n * Unwrapping only ever recognises the style's OWN fence. An italic button that\n * also unwrapped `*phrase*` would silently strip an accent run in the storefront\n * and the campaign designer, where the asterisk means colour, not slant.\n */\nexport function applyMark(\n text: string, start: number, end: number, style: MarkStyle,\n): WrapResult {\n const selected = text.slice(start, end);\n\n if (style === 'link') {\n const label = selected || 'link text';\n const snippet = `[${label}](https://)`;\n return {\n text: text.slice(0, start) + snippet + text.slice(end),\n // Land the caret on the URL — the label is usually already right.\n // Offset spans \"[\", the label, \"](\" — three characters plus the label.\n selectionStart: start + label.length + 3,\n selectionEnd: start + snippet.length - 1,\n };\n }\n\n const prefix = LINE_PREFIX[style];\n if (prefix) {\n // Line styles apply to every line the selection touches.\n const from = text.lastIndexOf('\\n', Math.max(start - 1, 0)) + 1;\n const toIdx = text.indexOf('\\n', end);\n const to = toIdx === -1 ? text.length : toIdx;\n const block = text.slice(from, to);\n const lines = block.split('\\n');\n const marked = lines.every((l, i) => l.startsWith(prefix(i)));\n const next = lines\n .map((l, i) => (marked ? l.slice(prefix(i).length) : prefix(i) + l))\n .join('\\n');\n return {\n text: text.slice(0, from) + next + text.slice(to),\n selectionStart: from,\n selectionEnd: from + next.length,\n };\n }\n\n const { fence, placeholder } = INLINE[style];\n const before = text.slice(Math.max(0, start - fence.length), start);\n const after = text.slice(end, end + fence.length);\n if (before === fence && after === fence) { // already marked → unwrap\n return {\n text: text.slice(0, start - fence.length) + selected + text.slice(end + fence.length),\n selectionStart: start - fence.length,\n selectionEnd: start - fence.length + selected.length,\n };\n }\n const inner = selected || placeholder;\n return {\n text: text.slice(0, start) + fence + inner + fence + text.slice(end),\n selectionStart: start + fence.length,\n selectionEnd: start + fence.length + inner.length,\n };\n}\n\n/** One toolbar button. `label` is a text glyph for hosts that draw type\n * (the message composer); hosts with an icon set map `style` to their own.\n * `shortcut` is the bare key the host combines with its platform modifier. */\nexport interface MarkupTool {\n style: MarkStyle;\n label: string;\n title: string;\n shortcut?: string;\n}\n\n/** Every button, in the order a writer reaches for them. */\nexport const MARKUP_TOOLS: readonly MarkupTool[] = [\n { style: 'bold', label: 'B', title: 'Bold (⌘B)', shortcut: 'b' },\n { style: 'italic', label: 'I', title: 'Italic (⌘I)', shortcut: 'i' },\n { style: 'strike', label: 'S', title: 'Strikethrough' },\n { style: 'highlight', label: '◆', title: 'Highlight in the brand color' },\n { style: 'code', label: '‹›', title: 'Code' },\n { style: 'link', label: '🔗', title: 'Link (⌘K)', shortcut: 'k' },\n { style: 'bullet', label: '•', title: 'Bulleted list' },\n { style: 'number', label: '1.', title: 'Numbered list' },\n { style: 'quote', label: '❝', title: 'Quote' },\n];\n\n/** The tools a host shows, resolved to descriptors in {@link MARKUP_TOOLS}\n * order. Keeps a host from hand-rebuilding a descriptor (and drifting on a\n * tooltip) just to pick a subset. */\nexport function markupTools(styles: readonly MarkStyle[]): MarkupTool[] {\n return MARKUP_TOOLS.filter((t) => styles.includes(t.style));\n}\n\n// ── Reading: the parser's half of the rule ─────────────────────────────────\n\n/**\n * One delimited run. `open`/`close` are literal strings; a run's inner text may\n * never contain the first character of `close`, which is what keeps this\n * equivalent to the \"asterisk, run of non-asterisks, asterisk\" regexes the\n * products used before — the same leftmost match, the same single close\n * candidate, so today's stored copy parses exactly as it always has.\n */\nexport interface InlineRule {\n readonly open: string;\n readonly close: string;\n readonly kind: Exclude<InlineKind, 'text'>;\n /** CommonMark's `_` rule: neither delimiter may touch a word character, so\n * `snake_case` and `{{first_name}}` stay literal. */\n readonly wordBoundary?: boolean;\n /**\n * Characters that may not FOLLOW the close. A run whose closing delimiter\n * butts up against one of these does not fire, and both delimiters stay\n * literal.\n *\n * Two legacy rules need it, for the same reason: a delimiter that is also\n * ordinary punctuation will otherwise claim text that was never markup. `#`\n * must not pair across rank markers (\"Ranked #1 and #2\"), and `[` must not\n * claim the label of a markdown link (\"[terms](https://…)\"), where the URL\n * would be stranded outside the run and printed to the reader.\n *\n * A character SET rather than a flag per case, so the next collision is one\n * more rule field and no new branch in {@link tokenizeInline}.\n */\n readonly notBefore?: string;\n}\n\n/**\n * The standard-markdown marks, shared by every product.\n *\n * `**` and `*` cannot collide even though they share a prefix: a `*` followed\n * immediately by another `*` is an EMPTY accent run, which no rule matches, so\n * at any one position exactly one of the two can fire. Order between them is\n * therefore not observable — which is worth stating, because the obvious\n * assumption (\"longest delimiter must be listed first\") would make a future\n * reader think this array's order is load-bearing when the empty-run rule is\n * what actually does the work.\n */\nexport const STANDARD_MARKUP: readonly InlineRule[] = [\n { open: '**', close: '**', kind: 'bold' },\n { open: '==', close: '==', kind: 'highlight' },\n { open: '~~', close: '~~', kind: 'strike' },\n { open: '_', close: '_', kind: 'italic', wordBoundary: true },\n];\n\n/**\n * The buttons a COPY FIELD offers — a storefront section's copy box and a\n * campaign block's copy box show exactly these, so a merchant meets one toolbar\n * wherever they type.\n *\n * DERIVED, not listed. A copy field renders inline runs only — no lists, no\n * block quote, no anchor — so the set it may advertise is precisely the tools\n * whose output this grammar parses back. Writing that as a literal list was the\n * bug's hiding place: the list and the grammar were two hand-maintained facts\n * that agreed by luck, and nothing failed when a tool wrote a delimiter the\n * parser read as something else. Now a style with no rule cannot appear here,\n * whatever anyone types into the array.\n *\n * It resolves to bold, italic, strike and highlight. `code` is the one that\n * surprises: it is an inline mark and it has a fence, but no rule reads a\n * backtick, so a copy field would print `` `word` `` verbatim — and a button\n * whose output the renderer draws as literal text is worse than no button.\n *\n * Declared here, below the rules, because it now depends on them.\n */\nexport const COPY_FIELD_TOOLS: readonly MarkStyle[] = MARKUP_TOOLS\n .map((tool) => tool.style)\n .filter((style) => STANDARD_MARKUP.some((rule) => rule.kind === style));\n\n/**\n * The storefront's rule set: standard markdown plus the ONE legacy run it has\n * published — `*phrase*` in the theme accent. Delete that last entry once\n * stored copy has been converted to `==phrase==`; both kinds already paint the\n * same, so the deletion is invisible.\n */\nexport const STOREFRONT_MARKUP: readonly InlineRule[] = [\n ...STANDARD_MARKUP,\n { open: '*', close: '*', kind: 'accent' },\n];\n\n/**\n * The campaign designer's rule set: standard markdown plus its three legacy\n * runs — `#phrase#` bold, `*phrase*` accent, `[phrase]` italic. The bold guard\n * is the one the email parser already carried: a `#` followed by a digit does\n * not close a run, so \"Ranked #1 and #2\" is literal while \"Save #20%#\" bolds.\n *\n * The italic guard exists because `[phrase]` collides with the one piece of\n * markdown syntax every writer already knows. Without it, \"Read [our\n * terms](https://…) first\" italicises the label AND prints the bare URL to the\n * reader — the words come back wearing a mark nobody chose. It is not a\n * toolbar problem: a merchant typing an ordinary link into a copy box hits it,\n * which is why the guard is on the RULE and not on which buttons a field\n * offers. `[phrase]` on its own is untouched, so every published line still\n * reads exactly as it did.\n */\nexport const CAMPAIGN_MARKUP: readonly InlineRule[] = [\n ...STANDARD_MARKUP,\n { open: '*', close: '*', kind: 'accent' },\n { open: '#', close: '#', kind: 'bold', notBefore: '0123456789' },\n { open: '[', close: ']', kind: 'italic', notBefore: '(' },\n];\n\n/** A parsed run. `text` is the inner text with the delimiters removed, so a\n * caller that only wants words ({@link stripInline}) joins the texts and a\n * caller that paints ({@link InlineKind}) switches on the kind. */\nexport interface InlineToken {\n kind: InlineKind;\n text: string;\n}\n\n/** True for a character that a `_` may not touch. Whitespace and ASCII\n * punctuation are safe neighbours; everything else — letters, digits, and any\n * non-ASCII script — counts as part of a word, so `汉_字_` stays literal too.\n * Deliberately conservative: a character wrongly called word-ish only ever\n * leaves an underscore rendering as it does today. */\nfunction wordish(ch: string): boolean {\n return !/\\s/.test(ch) && !/[!-\\/:-@[-`{-~]/.test(ch);\n}\n\n/**\n * Split authored text into its runs.\n *\n * The returned tokens ALTERNATE, starting and ending with a `text` token that\n * may be empty — exactly the shape `String.split` with one capture group\n * produced in the parsers this replaces. That contract is load-bearing, not\n * cosmetic: the storefront wraps every segment (including the empty ones) in a\n * `<span>`, so preserving the empty tokens is what keeps a published page's\n * markup byte-for-byte identical.\n *\n * A lone delimiter with no partner stays literal — the products' copy contains\n * real stray asterisks, and swallowing one would silently eat a word.\n */\nexport function tokenizeInline(\n value: string, rules: readonly InlineRule[],\n): InlineToken[] {\n const out: InlineToken[] = [];\n let buf = '';\n let i = 0;\n\n while (i < value.length) {\n const hit = matchAt(value, i, rules);\n if (hit) {\n out.push({ kind: 'text', text: buf });\n buf = '';\n out.push({ kind: hit.rule.kind, text: hit.inner });\n i = hit.end;\n continue;\n }\n buf += value[i];\n i += 1;\n }\n out.push({ kind: 'text', text: buf });\n return out;\n}\n\n/** The first rule that opens at `i` AND finds its close, or null. */\nfunction matchAt(value: string, i: number, rules: readonly InlineRule[]) {\n for (const rule of rules) {\n if (!value.startsWith(rule.open, i)) continue;\n if (rule.wordBoundary && i > 0 && wordish(value[i - 1])) continue;\n\n const from = i + rule.open.length;\n const closeAt = value.indexOf(rule.close, from);\n if (closeAt < from + 1) continue; // no close, or an empty run\n\n // The inner text may not contain the close's leading character: one close\n // candidate per opening, which is what the regexes this replaces did.\n const inner = value.slice(from, closeAt);\n if (inner.includes(rule.close[0])) continue;\n\n const end = closeAt + rule.close.length;\n const next = value[end];\n if (rule.notBefore && next !== undefined && rule.notBefore.includes(next)) continue;\n if (rule.wordBoundary && next !== undefined && wordish(next)) continue;\n\n return { rule, inner, end };\n }\n return null;\n}\n\n/**\n * The plain words of an authored string — every paired run unwrapped to its\n * inner text, every unpaired delimiter left alone.\n *\n * This is the ONLY correct way to fill an `alt`, an `aria-label` or a\n * structured-data field from authored copy. Deleting a delimiter character by\n * hand — \"replace every asterisk with nothing\" — breaks the moment the grammar\n * grows a marker, and it disagrees with the renderer about a stray asterisk,\n * which matters: search engines expect structured data to match the text a\n * visitor actually sees.\n */\nexport function stripInline(value: string, rules: readonly InlineRule[]): string {\n return tokenizeInline(value, rules).map((t) => t.text).join('');\n}\n"]}
@@ -0,0 +1,154 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { Location } from 'react-router-dom';
4
+
5
+ /**
6
+ * The page-tabs state, as plain functions over plain data.
7
+ *
8
+ * Kept free of React and of the router so the rules that decide what a user
9
+ * sees — which tab a click lands on, which one goes when there are too many,
10
+ * what survives a reload — are tested directly rather than through a rendered
11
+ * strip. `PageTabsProvider` holds a `TabsState` and calls these; nothing else
12
+ * mutates one.
13
+ *
14
+ * A tab is an OPEN PAGE, identified by `key` (by default the page's pathname,
15
+ * so `/orders?status=open` and `/orders?status=done` are the same tab showing
16
+ * different filters, while `/orders/41` and `/orders/42` are two tabs — the
17
+ * two windows the desktop used to give them). `path` is the full address the
18
+ * tab was last at, which is where clicking it goes back to.
19
+ */
20
+ interface PageTab {
21
+ /** Identity. Two addresses with the same key are the same tab. */
22
+ key: string;
23
+ /** Where the tab was last: pathname + search. Never the fragment — see
24
+ * `pathOf` in PageTabsProvider. */
25
+ path: string;
26
+ /** What the strip shows. The route's name until the page supplies its own
27
+ * (`usePageTabTitle`), after which navigation within the tab keeps it. */
28
+ title: string;
29
+ /** Who set `title`: the route table, or the page itself. */
30
+ titleFrom: 'route' | 'page';
31
+ /** Pinned tabs sit first and cannot be closed — a home page. */
32
+ pinned: boolean;
33
+ /** Recency, for choosing which tab to close when there are too many. A
34
+ * counter rather than a clock, so it is deterministic under test. */
35
+ lastActive: number;
36
+ }
37
+ declare const DEFAULT_MAX_TABS = 12;
38
+
39
+ /** What the consumer's route table says about an address. */
40
+ interface PageTabRoute {
41
+ /** The tab's name until the page names it (`usePageTabTitle`). */
42
+ title: string;
43
+ /** Identity; defaults to the pathname. Give two addresses the same key to
44
+ * make them one tab. */
45
+ key?: string;
46
+ /** Drawn before the title in the strip. Never persisted — re-resolved from
47
+ * the address, so a restored tab gets today's icon. */
48
+ icon?: ReactNode;
49
+ }
50
+ /** The parts of a location a route decision may depend on. */
51
+ type TabLocation = Pick<Location, 'pathname' | 'search' | 'hash'>;
52
+ /** Return null for an address that is never a tab: a redirect on its way
53
+ * somewhere, a page that must not stay open behind others. */
54
+ type ResolvePageTab = (location: TabLocation) => PageTabRoute | null;
55
+ interface CloseTabOptions {
56
+ /** Go here instead of the neighbouring tab (a form that just created its
57
+ * record goes to the record). */
58
+ to?: string;
59
+ /** Skip the unsaved-changes question — the page knows its work is saved. */
60
+ force?: boolean;
61
+ }
62
+
63
+ interface PageTabsProviderProps {
64
+ children: ReactNode;
65
+ /** The route table: whether an address is a tab, and what it is called. */
66
+ resolve: ResolvePageTab;
67
+ /** Addresses that are always open, first in the strip, and cannot be
68
+ * closed — the portal's home page. */
69
+ pinned?: string[];
70
+ /** localStorage key for the open tabs. Scope it to the user. Omit to keep
71
+ * tabs for the life of the page only. */
72
+ storageKey?: string;
73
+ /** Past this many, opening a page closes the least recently used tab that
74
+ * has no unsaved changes. */
75
+ maxTabs?: number;
76
+ /** Off, there is no strip and no keeping-alive: the outlet renders the
77
+ * current page alone. For phones, where a strip of tabs has no room. */
78
+ enabled?: boolean;
79
+ /** Ask before discarding unsaved changes. Defaults to the kit's `confirm`. */
80
+ confirmDiscard?: (tabs: PageTab[]) => Promise<boolean>;
81
+ }
82
+ /** A path back into the parts `resolve` reads. Relative to nothing: every
83
+ * stored path starts with `/`. */
84
+ declare function parseTabPath(path: string): TabLocation;
85
+ declare function PageTabsProvider({ children, resolve, pinned, storageKey, maxTabs, enabled, confirmDiscard, }: PageTabsProviderProps): react_jsx_runtime.JSX.Element;
86
+
87
+ /** True when hidden tabs are paused (`<Activity>`), false when they are only
88
+ * hidden. Exported for the consumer that wants to know which it got. */
89
+ declare const PAGE_TABS_PAUSE_HIDDEN: boolean;
90
+ interface PageTabsOutletProps {
91
+ /** Render the page at an address — usually `<Routes location={location}>`
92
+ * with the app's content routes. Called once per open tab. */
93
+ children: (location: Location) => ReactNode;
94
+ /** Classes for each tab's frame. The frame is the page's scroll container,
95
+ * so it wants a height and `overflow-y-auto`. */
96
+ frameClassName?: string;
97
+ }
98
+ declare function PageTabsOutlet({ children, frameClassName }: PageTabsOutletProps): react_jsx_runtime.JSX.Element;
99
+
100
+ interface PageTabBarProps {
101
+ /** Replaces the strip's own classes (height, background, padding). */
102
+ className?: string;
103
+ /** Replaces the active tab's colours — its background should be the
104
+ * page's, so the tab and the page read as one surface. */
105
+ activeClassName?: string;
106
+ /** Controls at the right end of the strip. They do not scroll with the
107
+ * tabs. */
108
+ end?: ReactNode;
109
+ }
110
+ declare function PageTabBar({ className, activeClassName, end }: PageTabBarProps): react_jsx_runtime.JSX.Element | null;
111
+
112
+ interface PageTabsApi {
113
+ enabled: boolean;
114
+ tabs: PageTab[];
115
+ activeKey: string | null;
116
+ activate: (key: string) => void;
117
+ close: (key: string, options?: CloseTabOptions) => Promise<boolean>;
118
+ closeOthers: (key: string) => Promise<void>;
119
+ closeRight: (key: string) => Promise<void>;
120
+ closeAll: () => Promise<void>;
121
+ }
122
+ /** The strip, for code outside any one page (a keyboard shortcut, a menu). */
123
+ declare function usePageTabs(): PageTabsApi;
124
+ interface PageTabHandle {
125
+ /** This page's tab, or null outside one. */
126
+ key: string | null;
127
+ /**
128
+ * Close this page's tab. Unsaved changes are asked about unless `force`.
129
+ * Outside a tab it only navigates to `to`, if given. Resolves true when the
130
+ * tab closed.
131
+ */
132
+ close: (options?: CloseTabOptions) => Promise<boolean>;
133
+ }
134
+ /** The tab this page is in. */
135
+ declare function usePageTab(): PageTabHandle;
136
+ /**
137
+ * Name this page's tab — a record's number once it has loaded, say. Empty
138
+ * strings are ignored, so a page can pass its title before the data arrives.
139
+ * The name sticks: moving within the tab (a filter, a hash) keeps it.
140
+ */
141
+ declare function usePageTabTitle(title: string | null | undefined): void;
142
+ /**
143
+ * Mark this page's tab as holding unsaved changes. Closing it then asks
144
+ * first, and the browser tab asks before a reload.
145
+ *
146
+ * The flag survives the tab being hidden. A hidden tab's effects are cleaned
147
+ * up (see PageTabsOutlet), and clearing the flag in that clean-up would let a
148
+ * hidden form close silently; so the clean-up only clears it when the page
149
+ * went away while its tab was on screen (the tab moved to another page). A
150
+ * closed tab's flag goes with the tab.
151
+ */
152
+ declare function usePageTabDirty(dirty: boolean): void;
153
+
154
+ export { type CloseTabOptions, DEFAULT_MAX_TABS, PAGE_TABS_PAUSE_HIDDEN, type PageTab, PageTabBar, type PageTabBarProps, type PageTabHandle, type PageTabRoute, type PageTabsApi, PageTabsOutlet, type PageTabsOutletProps, PageTabsProvider, type PageTabsProviderProps, type ResolvePageTab, type TabLocation, parseTabPath, usePageTab, usePageTabDirty, usePageTabTitle, usePageTabs };