@bettercms-ai/kit 2.0.0 → 2.0.1

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 (120) hide show
  1. package/dist/adapters/lossy.d.ts +12 -4
  2. package/dist/adapters/lossy.js +19 -8
  3. package/dist/adapters/lossy.js.map +1 -1
  4. package/dist/adapters/prod-types.d.ts +46 -14
  5. package/dist/adapters/prod-types.js +37 -35
  6. package/dist/adapters/prod-types.js.map +1 -1
  7. package/dist/adapters/schema.d.ts +39 -0
  8. package/dist/adapters/schema.js +281 -82
  9. package/dist/adapters/schema.js.map +1 -1
  10. package/dist/context.d.ts +39 -0
  11. package/dist/context.js +40 -1
  12. package/dist/context.js.map +1 -1
  13. package/dist/controls/form-picker-register.d.ts +7 -1
  14. package/dist/controls/form-picker-register.js +1 -5
  15. package/dist/controls/form-picker-register.js.map +1 -1
  16. package/dist/controls/icon.d.ts +1 -1
  17. package/dist/controls/icon.js +1 -1
  18. package/dist/controls/icon.js.map +1 -1
  19. package/dist/controls/link.d.ts +11 -4
  20. package/dist/controls/link.js +120 -202
  21. package/dist/controls/link.js.map +1 -1
  22. package/dist/controls/location.js +3 -8
  23. package/dist/controls/location.js.map +1 -1
  24. package/dist/controls/media.d.ts +0 -14
  25. package/dist/controls/media.js +52 -95
  26. package/dist/controls/media.js.map +1 -1
  27. package/dist/controls/reference-services.d.ts +23 -2
  28. package/dist/controls/reference.d.ts +2 -2
  29. package/dist/controls/reference.js +79 -28
  30. package/dist/controls/reference.js.map +1 -1
  31. package/dist/controls/rich-text/article-shell.d.ts +145 -0
  32. package/dist/controls/rich-text/article-shell.js +514 -0
  33. package/dist/controls/rich-text/article-shell.js.map +1 -0
  34. package/dist/controls/rich-text/author.d.ts +25 -0
  35. package/dist/controls/rich-text/author.js +107 -0
  36. package/dist/controls/rich-text/author.js.map +1 -0
  37. package/dist/controls/rich-text/blocks.d.ts +19 -2
  38. package/dist/controls/rich-text/blocks.js +275 -638
  39. package/dist/controls/rich-text/blocks.js.map +1 -1
  40. package/dist/controls/rich-text/button.d.ts +23 -0
  41. package/dist/controls/rich-text/button.js +81 -0
  42. package/dist/controls/rich-text/button.js.map +1 -0
  43. package/dist/controls/rich-text/catalog.d.ts +2 -5
  44. package/dist/controls/rich-text/catalog.js +121 -7
  45. package/dist/controls/rich-text/catalog.js.map +1 -1
  46. package/dist/controls/rich-text/checklist.d.ts +24 -0
  47. package/dist/controls/rich-text/checklist.js +115 -0
  48. package/dist/controls/rich-text/checklist.js.map +1 -0
  49. package/dist/controls/rich-text/codec.d.ts +15 -1
  50. package/dist/controls/rich-text/codec.js +11 -1
  51. package/dist/controls/rich-text/codec.js.map +1 -1
  52. package/dist/controls/rich-text/columns.d.ts +41 -0
  53. package/dist/controls/rich-text/columns.js +181 -0
  54. package/dist/controls/rich-text/columns.js.map +1 -0
  55. package/dist/controls/rich-text/control.js +1 -0
  56. package/dist/controls/rich-text/control.js.map +1 -1
  57. package/dist/controls/rich-text/dom.d.ts +84 -4
  58. package/dist/controls/rich-text/dom.js +171 -10
  59. package/dist/controls/rich-text/dom.js.map +1 -1
  60. package/dist/controls/rich-text/editor.d.ts +4 -2
  61. package/dist/controls/rich-text/editor.js +199 -26
  62. package/dist/controls/rich-text/editor.js.map +1 -1
  63. package/dist/controls/rich-text/embed.d.ts +28 -0
  64. package/dist/controls/rich-text/embed.js +210 -0
  65. package/dist/controls/rich-text/embed.js.map +1 -0
  66. package/dist/controls/rich-text/footnote.d.ts +41 -0
  67. package/dist/controls/rich-text/footnote.js +185 -0
  68. package/dist/controls/rich-text/footnote.js.map +1 -0
  69. package/dist/controls/rich-text/gallery.d.ts +28 -0
  70. package/dist/controls/rich-text/gallery.js +167 -0
  71. package/dist/controls/rich-text/gallery.js.map +1 -0
  72. package/dist/controls/rich-text/link-form.d.ts +13 -1
  73. package/dist/controls/rich-text/link-form.js +41 -7
  74. package/dist/controls/rich-text/link-form.js.map +1 -1
  75. package/dist/controls/rich-text/ops.js +6 -0
  76. package/dist/controls/rich-text/ops.js.map +1 -1
  77. package/dist/controls/rich-text/related.d.ts +49 -0
  78. package/dist/controls/rich-text/related.js +233 -0
  79. package/dist/controls/rich-text/related.js.map +1 -0
  80. package/dist/controls/rich-text/toc.d.ts +47 -0
  81. package/dist/controls/rich-text/toc.js +160 -0
  82. package/dist/controls/rich-text/toc.js.map +1 -0
  83. package/dist/controls/slot-register.d.ts +6 -1
  84. package/dist/controls/slot-register.js +1 -4
  85. package/dist/controls/slot-register.js.map +1 -1
  86. package/dist/core/core.d.ts +11 -3
  87. package/dist/core/core.js +24 -6
  88. package/dist/core/core.js.map +1 -1
  89. package/dist/core/facets.d.ts +16 -0
  90. package/dist/core/facets.js +17 -1
  91. package/dist/core/facets.js.map +1 -1
  92. package/dist/core/index.js +2 -2
  93. package/dist/index.js +16 -5
  94. package/dist/index.js.map +1 -1
  95. package/dist/ledgers/exclusions.v1.js +141 -0
  96. package/dist/ledgers/exclusions.v1.js.map +1 -0
  97. package/dist/library/coverage.d.ts +13 -1
  98. package/dist/library/coverage.js +12 -1
  99. package/dist/library/coverage.js.map +1 -1
  100. package/dist/library/kit-catalog.json +156 -153
  101. package/dist/schema/types.d.ts +17 -0
  102. package/dist/schema/types.js.map +1 -1
  103. package/dist/styles.css +1 -1
  104. package/dist/testing/coverage-extras.d.ts +8 -0
  105. package/dist/testing/coverage-extras.js +44 -0
  106. package/dist/testing/coverage-extras.js.map +1 -0
  107. package/dist/testing/exclusions.d.ts +30 -0
  108. package/dist/testing/exclusions.js +13 -0
  109. package/dist/testing/exclusions.js.map +1 -0
  110. package/dist/ui/autocomplete.d.ts +29 -1
  111. package/dist/ui/autocomplete.js +102 -47
  112. package/dist/ui/autocomplete.js.map +1 -1
  113. package/dist/ui/button.js +3 -5
  114. package/dist/ui/button.js.map +1 -1
  115. package/dist/ui/index.js +2 -2
  116. package/dist/ui/overlay.d.ts +4 -1
  117. package/dist/ui/overlay.js +2 -1
  118. package/dist/ui/overlay.js.map +1 -1
  119. package/dist/ui/toast.js +1 -1
  120. package/package.json +8 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button.js","names":[],"sources":["../../../src/controls/rich-text/button.tsx"],"sourcesContent":["/**\n * RICH TEXT · Button (kit-richtext-ui-spec.md §9): V2's call-to-action link, stored as the PT `link` markDef with\n * `button: true`, written in the editor as `<p><a data-button=\"true\" href>label</a></p>`.\n *\n * The look is the editor's (editor.tsx, from V2 rich-text-editor.tsx:58 + :1386); this module is the behaviour:\n * insert V2 ops.ts:162-167: the empty line becomes `<a data-button href=\"/\">Button</a>`, with a line after it;\n * the editor then opens the button's popover (V2 rich-text-editor.tsx:627)\n * edit V2 rich-text-editor.tsx:865-873 + 1680-1716: a click on a button (or the link shortcut on one) opens the\n * link popover with V2's \"Button label\" field above the link form\n * save V2 saveLink :1-10: the address goes on the anchor, the label becomes its text (\"Button\" when empty)\n * remove V2 onDetach: a button alone on its line takes the line with it; otherwise only the anchor goes\n */\nimport * as React from \"react\";\nimport { Input } from \"../../ui/input\";\nimport { takeEmptyBlock, ensureLineAfter } from \"./ops\";\nimport { LinkPopoverBody } from \"../link\";\nimport { applyTextLink, fromLinkValue, textLinkOf, toLinkValue, type TextLink } from \"./link-form\";\n\n/** Catalog insert: the empty line becomes the button, a writable line follows. Returns the anchor to open. */\nexport function insertButton(root: HTMLElement): HTMLAnchorElement {\n const t = takeEmptyBlock(root);\n t.innerHTML = `<a data-button=\"true\" href=\"/\">Button</a>`;\n ensureLineAfter(t);\n return t.querySelector(\"a\")!;\n}\n\n/** The body's own button a click landed on (never one inside a rich block). */\nexport function buttonAt(root: HTMLElement, target: EventTarget | null): HTMLAnchorElement | null {\n const el = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;\n const a = el?.closest<HTMLAnchorElement>(\"a[data-button]\") ?? null;\n return a && root.contains(a) && !a.closest(\"[data-rt-isolate]\") ? a : null;\n}\n\n/** What the popover opens with for a button. */\nexport function buttonInitial(a: HTMLAnchorElement): { link: TextLink; label: string } {\n return { link: textLinkOf(a), label: a.textContent ?? \"\" };\n}\n\n/** Applies a saved popover to the button: address, new tab, rel, and the label as its text (V2: \"Button\" when empty). */\nexport function applyButton(a: HTMLAnchorElement, link: TextLink, label: string) {\n applyTextLink(a, link);\n a.setAttribute(\"data-button\", \"true\");\n a.textContent = label.trim() || \"Button\";\n}\n\n/** Removes a button: with its line when it is alone on a top-level line of the body (V2), else only the anchor. */\nexport function removeButton(root: HTMLElement, a: HTMLAnchorElement) {\n const p = a.closest(\"p\");\n if (p && p.parentNode === root && p.textContent === a.textContent) p.remove();\n else a.remove();\n}\n\n/** The popover body for a button: V2's \"Button label\" above V2's link panel (RTE:1688-1697). */\nexport function ButtonLinkPanel({\n initial,\n label: initialLabel,\n allowNewTab,\n onSave,\n onCancel,\n onDetach,\n}: {\n initial: TextLink;\n label: string;\n allowNewTab: boolean;\n onSave: (link: TextLink, label: string) => void;\n onCancel: () => void;\n onDetach?: () => void;\n}) {\n const [label, setLabel] = React.useState(initialLabel);\n return (\n <div className=\"flex flex-col gap-2\">\n <label className=\"flex flex-col gap-1\">\n <span className=\"text-kit-xs font-medium text-kit-fg-2\">Button label</span>\n <Input size=\"sm\" value={label} autoFocus onChange={(e) => setLabel(e.target.value)} placeholder=\"Shop the collection\" />\n </label>\n <LinkPopoverBody\n initial={toLinkValue(initial)}\n hideNewTab={!allowNewTab}\n onSave={(v) => onSave(fromLinkValue(v, allowNewTab), label)}\n onCancel={onCancel}\n onDetach={onDetach}\n dirty={label !== initialLabel}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,aAAa,MAAsC;CACjE,MAAM,IAAI,eAAe,IAAI;CAC7B,EAAE,YAAY;CACd,gBAAgB,CAAC;CACjB,OAAO,EAAE,cAAc,GAAG;AAC5B;;AAGA,SAAgB,SAAS,MAAmB,QAAsD;CAEhG,MAAM,KADK,kBAAkB,UAAU,SAAS,kBAAkB,OAAO,OAAO,gBAAgB,KAAA,EAClF,QAA2B,gBAAgB,KAAK;CAC9D,OAAO,KAAK,KAAK,SAAS,CAAC,KAAK,CAAC,EAAE,QAAQ,mBAAmB,IAAI,IAAI;AACxE;;AAGA,SAAgB,cAAc,GAAyD;CACrF,OAAO;EAAE,MAAM,WAAW,CAAC;EAAG,OAAO,EAAE,eAAe;CAAG;AAC3D;;AAGA,SAAgB,YAAY,GAAsB,MAAgB,OAAe;CAC/E,cAAc,GAAG,IAAI;CACrB,EAAE,aAAa,eAAe,MAAM;CACpC,EAAE,cAAc,MAAM,KAAK,KAAK;AAClC;;AAGA,SAAgB,aAAa,MAAmB,GAAsB;CACpE,MAAM,IAAI,EAAE,QAAQ,GAAG;CACvB,IAAI,KAAK,EAAE,eAAe,QAAQ,EAAE,gBAAgB,EAAE,aAAa,EAAE,OAAO;MACvE,EAAE,OAAO;AAChB;;AAGA,SAAgB,gBAAgB,EAC9B,SACA,OAAO,cACP,aACA,QACA,UACA,YAQC;CACD,MAAM,CAAC,OAAO,YAAY,MAAM,SAAS,YAAY;CACrD,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,qBAAC,SAAD;GAAO,WAAU;GAAjB,UAAA,CACE,oBAAC,QAAD;IAAM,WAAU;IAAwC,UAAA;GAAkB,CAAA,GAC1E,oBAAC,OAAD;IAAO,MAAK;IAAK,OAAO;IAAO,WAAA;IAAU,WAAW,MAAM,SAAS,EAAE,OAAO,KAAK;IAAG,aAAY;GAAuB,CAAA,CAClH;EACP,CAAA,GAAA,oBAAC,iBAAD;GACE,SAAS,YAAY,OAAO;GAC5B,YAAY,CAAC;GACb,SAAS,MAAM,OAAO,cAAc,GAAG,WAAW,GAAG,KAAK;GAChD;GACA;GACV,OAAO,UAAU;EAClB,CAAA,CACE;;AAET"}
@@ -10,10 +10,7 @@
10
10
  *
11
11
  * Only the blocks the field accepts are offered (`features`, from the host's codec: each block is offered only where
12
12
  * the field's stored shape has a place for it). Not in this catalogue, and why:
13
- * Footnote, Gallery, Embed, Columns, Checklist, Button, Table of contents, Related posts, Author bio
14
- * production's rich text has no stored block for them (a checklist renders as a bulleted list; production's embed
15
- * frames the raw link, which providers refuse, so Video is the one media link offered). A stored embed (an
16
- * imported `<iframe>`, whose address is already the provider's frame) opens and is edited as V2's Embed block.
13
+ * (none: every V2 block has a stored form now, plan RT0–RT8; each is offered only where the field's codec declares it)
17
14
  *
18
15
  * Components come from the host (KitServices.useInsertableComponents: the project's components, narrowed to the ones
19
16
  * the field's config accepts), handed in through CatalogComponentsProvider. Without that service none is offered.
@@ -23,7 +20,7 @@
23
20
  import * as React from "react";
24
21
  import type { RichTextFeature } from "./codec";
25
22
  import type { KitInsertableComponent } from "../component-services";
26
- export type BasicId = "p" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "quote" | "pullquote" | "callout" | "toggle" | "takeaways" | "ul" | "ol" | "code" | "hr" | "image" | "video" | "file" | "table";
23
+ export type BasicId = "p" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "quote" | "pullquote" | "callout" | "toggle" | "takeaways" | "ul" | "ol" | "code" | "hr" | "image" | "video" | "file" | "table" | "task" | "button" | "gallery" | "embed" | "toc" | "footnote" | "columns" | "related" | "author";
27
24
  export interface CatalogItem {
28
25
  key: string;
29
26
  /** a basic block (absent on a component, the More components row and the way back) */
@@ -2,7 +2,7 @@ import { cn } from "../../lib/cn.js";
2
2
  import { PopoverContent } from "../../ui/overlay.js";
3
3
  import * as React from "react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
- import { IconArrowLeft, IconBlockquote, IconChevronRight, IconComponents, IconFileText, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconInfoCircle, IconLayoutNavbarCollapse, IconList, IconListDetails, IconListNumbers, IconPhoto, IconPilcrow, IconQuote, IconSearch, IconSeparatorHorizontal, IconTable, IconTerminal2, IconVideo } from "@tabler/icons-react";
5
+ import { IconArrowLeft, IconBlockquote, IconChevronRight, IconClick, IconCode, IconColumns2, IconComponents, IconFileText, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconInfoCircle, IconLayoutGrid, IconLayoutNavbarCollapse, IconList, IconListCheck, IconListDetails, IconListNumbers, IconListTree, IconNews, IconPhoto, IconPilcrow, IconQuote, IconSearch, IconSeparatorHorizontal, IconSuperscript, IconTable, IconTerminal2, IconUserCircle, IconVideo } from "@tabler/icons-react";
6
6
  //#region src/controls/rich-text/catalog.tsx
7
7
  /**
8
8
  * RICH TEXT · the block catalogue shared by the toolbar Insert menu, the gutter "+" and the "/" slash menu (V2 HEAD
@@ -16,10 +16,7 @@ import { IconArrowLeft, IconBlockquote, IconChevronRight, IconComponents, IconFi
16
16
  *
17
17
  * Only the blocks the field accepts are offered (`features`, from the host's codec: each block is offered only where
18
18
  * the field's stored shape has a place for it). Not in this catalogue, and why:
19
- * Footnote, Gallery, Embed, Columns, Checklist, Button, Table of contents, Related posts, Author bio
20
- * production's rich text has no stored block for them (a checklist renders as a bulleted list; production's embed
21
- * frames the raw link, which providers refuse, so Video is the one media link offered). A stored embed (an
22
- * imported `<iframe>`, whose address is already the provider's frame) opens and is edited as V2's Embed block.
19
+ * (none: every V2 block has a stored form now, plan RT0–RT8; each is offered only where the field's codec declares it)
23
20
  *
24
21
  * Components come from the host (KitServices.useInsertableComponents: the project's components, narrowed to the ones
25
22
  * the field's config accepts), handed in through CatalogComponentsProvider. Without that service none is offered.
@@ -154,6 +151,18 @@ var TEXT = rows([
154
151
  "snippet",
155
152
  "pre"
156
153
  ]
154
+ ],
155
+ [
156
+ "footnote",
157
+ "Footnote",
158
+ "A numbered note at the end",
159
+ i(IconSuperscript),
160
+ [
161
+ "fn",
162
+ "reference",
163
+ "citation",
164
+ "source"
165
+ ]
157
166
  ]
158
167
  ]);
159
168
  var MEDIA = rows([
@@ -168,6 +177,18 @@ var MEDIA = rows([
168
177
  "photo"
169
178
  ]
170
179
  ],
180
+ [
181
+ "gallery",
182
+ "Gallery",
183
+ "Several images as a grid or carousel",
184
+ i(IconLayoutGrid),
185
+ [
186
+ "images",
187
+ "photos",
188
+ "carousel",
189
+ "slideshow"
190
+ ]
191
+ ],
171
192
  [
172
193
  "video",
173
194
  "Video",
@@ -182,6 +203,28 @@ var MEDIA = rows([
182
203
  "movie"
183
204
  ]
184
205
  ],
206
+ [
207
+ "embed",
208
+ "Embed",
209
+ "YouTube, X, CodePen, Figma, maps or audio",
210
+ i(IconCode),
211
+ [
212
+ "yt",
213
+ "youtube",
214
+ "vimeo",
215
+ "x",
216
+ "tweet",
217
+ "twitter",
218
+ "codepen",
219
+ "figma",
220
+ "map",
221
+ "maps",
222
+ "spotify",
223
+ "podcast",
224
+ "audio",
225
+ "iframe"
226
+ ]
227
+ ],
185
228
  [
186
229
  "file",
187
230
  "File",
@@ -194,6 +237,42 @@ var MEDIA = rows([
194
237
  ]
195
238
  ]
196
239
  ]);
240
+ /** V2 catalog.tsx ARTICLE: article blocks written in the body itself (the rest land with plan RT6–RT8). */
241
+ var ARTICLE = rows([
242
+ [
243
+ "related",
244
+ "Related posts",
245
+ "More stories, picked or automatic",
246
+ i(IconNews),
247
+ [
248
+ "related stories",
249
+ "read next",
250
+ "more stories"
251
+ ]
252
+ ],
253
+ [
254
+ "toc",
255
+ "Table of contents",
256
+ "Built from the headings",
257
+ i(IconListTree),
258
+ [
259
+ "contents",
260
+ "outline",
261
+ "index"
262
+ ]
263
+ ],
264
+ [
265
+ "author",
266
+ "Author bio",
267
+ "Photo, name and short bio",
268
+ i(IconUserCircle),
269
+ [
270
+ "bio",
271
+ "byline",
272
+ "about the author"
273
+ ]
274
+ ]
275
+ ]);
197
276
  var DATA = rows([[
198
277
  "table",
199
278
  "Table",
@@ -225,6 +304,27 @@ var SEARCH_ONLY = rows([
225
304
  "Heading 6",
226
305
  "Eyebrow heading",
227
306
  i(IconH6)
307
+ ],
308
+ [
309
+ "task",
310
+ "Checklist",
311
+ "To-do list with checkboxes",
312
+ i(IconListCheck),
313
+ ["todo", "checkbox"]
314
+ ],
315
+ [
316
+ "columns",
317
+ "Columns",
318
+ "Side by side text",
319
+ i(IconColumns2),
320
+ ["layout"]
321
+ ],
322
+ [
323
+ "button",
324
+ "Button",
325
+ "A call to action link",
326
+ i(IconClick),
327
+ ["link button"]
228
328
  ]
229
329
  ]);
230
330
  /** The feature a block needs (Text needs none). */
@@ -248,7 +348,16 @@ var NEEDS = {
248
348
  toggle: "toggle",
249
349
  video: "video",
250
350
  file: "file",
251
- table: "table"
351
+ table: "table",
352
+ task: "checklist",
353
+ button: "button",
354
+ gallery: "gallery",
355
+ embed: "embed",
356
+ toc: "toc",
357
+ footnote: "footnote",
358
+ columns: "columns",
359
+ related: "related",
360
+ author: "author"
252
361
  };
253
362
  var MORE = {
254
363
  key: "more",
@@ -355,6 +464,7 @@ function useCatalog(query, allows, folder = false, own) {
355
464
  ...keep(TEXT),
356
465
  ...keep(MEDIA),
357
466
  ...keep(DATA),
467
+ ...keep(ARTICLE),
358
468
  ...keep(SEARCH_ONLY),
359
469
  ...comps
360
470
  ], recent);
@@ -376,6 +486,10 @@ function useCatalog(query, allows, folder = false, own) {
376
486
  title: "Data",
377
487
  items: keep(DATA)
378
488
  },
489
+ {
490
+ title: "Article",
491
+ items: keep(ARTICLE)
492
+ },
379
493
  {
380
494
  title: null,
381
495
  items: lib && (comps.length || lib.loading || lib.error) ? [{
@@ -472,7 +586,7 @@ function CatalogList({ sections, flat, active, onHover, onPick, empty, emptyText
472
586
  }, it.key);
473
587
  })]
474
588
  }, s.title ?? `s${si}`)),
475
- empty ? /* @__PURE__ */ jsx("div", {
589
+ empty && !noMatch ? /* @__PURE__ */ jsx("div", {
476
590
  className: "px-2 py-6 text-center text-kit-sm text-kit-fg-3",
477
591
  children: emptyText
478
592
  }) : null
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.js","names":[],"sources":["../../../src/controls/rich-text/catalog.tsx"],"sourcesContent":["/**\n * RICH TEXT · the block catalogue shared by the toolbar Insert menu, the gutter \"+\" and the \"/\" slash menu (V2 HEAD\n * controls/rich-text/catalog.tsx, CPO D55).\n *\n * browse Text · Media · Data, grouped, then one quiet \"More components N\" row that opens the components the\n * field may embed (V2 HEAD catalog.tsx MORE), with its own search and a way back (\"All blocks\")\n * search every block and every embeddable component in one list: recently used first among the strong matches,\n * then by match (label, aliases, then description)\n * no match \"No match.\"\n *\n * Only the blocks the field accepts are offered (`features`, from the host's codec: each block is offered only where\n * the field's stored shape has a place for it). Not in this catalogue, and why:\n * Footnote, Gallery, Embed, Columns, Checklist, Button, Table of contents, Related posts, Author bio\n * production's rich text has no stored block for them (a checklist renders as a bulleted list; production's embed\n * frames the raw link, which providers refuse, so Video is the one media link offered). A stored embed (an\n * imported `<iframe>`, whose address is already the provider's frame) opens and is edited as V2's Embed block.\n *\n * Components come from the host (KitServices.useInsertableComponents: the project's components, narrowed to the ones\n * the field's config accepts), handed in through CatalogComponentsProvider. Without that service none is offered.\n * Deviation from V2: the library has no inline / page-section placement, so every component sits in one\n * \"Components\" group (V2: \"Article\" and \"Page sections\"), and no live preview flyout is drawn beside a row.\n */\nimport * as React from \"react\";\nimport {\n IconArrowLeft, IconBlockquote, IconChevronRight, IconComponents, IconFileText, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconInfoCircle, IconLayoutNavbarCollapse, IconList, IconListDetails, IconListNumbers, IconPhoto,\n IconPilcrow, IconQuote, IconSearch, IconSeparatorHorizontal, IconTable, IconTerminal2, IconVideo,\n} from \"@tabler/icons-react\";\nimport { cn } from \"../../lib/cn\";\nimport { PopoverContent } from \"../../ui/overlay\";\nimport type { RichTextFeature } from \"./codec\";\nimport type { KitInsertableComponent } from \"../component-services\";\n\nexport type BasicId =\n | \"p\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"quote\" | \"pullquote\" | \"callout\" | \"toggle\" | \"takeaways\" | \"ul\" | \"ol\" | \"code\" | \"hr\"\n | \"image\" | \"video\" | \"file\" | \"table\";\n\nexport interface CatalogItem {\n key: string;\n /** a basic block (absent on a component, the More components row and the way back) */\n id?: BasicId;\n /** component: embeds `component`; folder: opens More components; back: returns to every block */\n kind?: \"component\" | \"folder\" | \"back\";\n component?: KitInsertableComponent;\n /** the folder row's component count */\n count?: number;\n label: string;\n description: string;\n icon: React.ReactNode;\n /** extra words that find it */\n aliases?: string[];\n}\n\nconst i = (I: React.ComponentType<{ stroke?: number }>) => <I stroke={1.5} />;\ntype Row = [BasicId, string, string, React.ReactNode, string[]?];\nconst rows = (list: Row[]): CatalogItem[] => list.map(([id, label, description, icon, aliases]) => ({ key: id, id, label, description, icon, aliases }));\n\nconst TEXT = rows([\n [\"p\", \"Text\", \"Plain body text\", i(IconPilcrow), [\"paragraph\", \"body\"]],\n [\"h2\", \"Heading 2\", \"Section heading\", i(IconH2), [\"title\", \"subtitle\"]],\n [\"h3\", \"Heading 3\", \"Subsection heading\", i(IconH3)],\n [\"h4\", \"Heading 4\", \"Minor heading\", i(IconH4)],\n [\"quote\", \"Quote\", \"A quoted passage\", i(IconBlockquote), [\"blockquote\"]],\n [\"pullquote\", \"Pull quote\", \"A highlighted line with who said it\", i(IconQuote), [\"pq\", \"testimonial\", \"attribution\"]],\n [\"ul\", \"List\", \"Bulleted list\", i(IconList), [\"bullets\", \"bulleted list\", \"unordered\"]],\n [\"ol\", \"Numbered list\", \"Ordered steps\", i(IconListNumbers), [\"ordered\", \"steps\"]],\n [\"toggle\", \"Toggle\", \"Collapsible title and body\", i(IconLayoutNavbarCollapse), [\"accordion\", \"details\", \"collapse\"]],\n [\"callout\", \"Callout\", \"A highlighted note with a tone\", i(IconInfoCircle), [\"note\", \"tip\", \"warning\", \"aside\"]],\n [\"takeaways\", \"Key takeaways\", \"The story in a few points\", i(IconListDetails), [\"summary\", \"tldr\", \"tl;dr\", \"highlights\"]],\n [\"hr\", \"Divider\", \"A line between parts\", i(IconSeparatorHorizontal), [\"line\", \"separator\", \"rule\"]],\n [\"code\", \"Code\", \"Monospaced snippet\", i(IconTerminal2), [\"code block\", \"snippet\", \"pre\"]],\n]);\nconst MEDIA = rows([\n [\"image\", \"Image\", \"Image with alt text and caption\", i(IconPhoto), [\"img\", \"picture\", \"photo\"]],\n [\"video\", \"Video\", \"A YouTube, Vimeo or video file link\", i(IconVideo), [\"mp4\", \"youtube\", \"yt\", \"vimeo\", \"clip\", \"movie\"]],\n [\"file\", \"File\", \"A downloadable PDF or file\", i(IconFileText), [\"pdf\", \"download\", \"attachment\"]],\n]);\nconst DATA = rows([[\"table\", \"Table\", \"Rows and columns\", i(IconTable), [\"grid\", \"data\", \"comparison\", \"spreadsheet\"]]]);\n/** Found by search, not listed: rare in a body (H1 is the page title). */\nconst SEARCH_ONLY = rows([\n [\"h1\", \"Heading 1\", \"Largest heading\", i(IconH1)],\n [\"h5\", \"Heading 5\", \"Smallest heading\", i(IconH5)],\n [\"h6\", \"Heading 6\", \"Eyebrow heading\", i(IconH6)],\n]);\n\n/** The feature a block needs (Text needs none). */\nconst NEEDS: Record<BasicId, RichTextFeature | null> = {\n p: null, h1: \"h1\", h2: \"h2\", h3: \"h3\", h4: \"h4\", h5: \"h5\", h6: \"h6\", quote: \"blockquote\", ul: \"ul\", ol: \"ol\", code: \"pre\", hr: \"hr\", image: \"image\",\n pullquote: \"pullquote\", takeaways: \"takeaways\", callout: \"callout\", toggle: \"toggle\", video: \"video\", file: \"file\", table: \"table\",\n};\nconst MORE: CatalogItem = { key: \"more\", kind: \"folder\", label: \"More blocks\", description: \"Shared across your site\", icon: i(IconComponents) };\nconst BACK: CatalogItem = { key: \"back\", kind: \"back\", label: \"All blocks\", description: \"\", icon: i(IconArrowLeft) };\nconst componentRow = (c: KitInsertableComponent): CatalogItem => ({\n key: `c:${c.id}`,\n kind: \"component\",\n component: c,\n label: c.name,\n description: c.description || \"Shared across your site\",\n icon: i(IconComponents),\n});\n\n/** The components this field may embed, as the host answered (KitServices.useInsertableComponents). */\nexport interface CatalogComponents {\n components: KitInsertableComponent[];\n loading?: boolean;\n error?: string;\n}\nconst ComponentsContext = React.createContext<CatalogComponents | null>(null);\nexport const CatalogComponentsProvider = ComponentsContext.Provider;\n/** The components this field may embed (a component card's Swap component). */\nexport const useCatalogComponents = () => React.useContext(ComponentsContext);\n\nexport const blockAllowed = (id: BasicId, allows: (f: RichTextFeature) => boolean) => {\n const need = NEEDS[id];\n return !need || allows(need);\n};\n\n/* ── Recently used (per person, CPO D55): this browser's storage; a browser that keeps none just has no recents ── */\nconst RECENTS_KEY = \"kit-rt-insert-recents\";\nlet recents: string[] = (() => {\n try {\n const v = JSON.parse(globalThis.localStorage?.getItem(RECENTS_KEY) ?? \"[]\") as unknown;\n return Array.isArray(v) ? v.filter((x): x is string => typeof x === \"string\") : [];\n } catch {\n return [];\n }\n})();\nconst recentSubs = new Set<() => void>();\n/** Remember a pick so search ranks it first next time. */\nexport function noteRecent(it: CatalogItem) {\n recents = [it.key, ...recents.filter((k) => k !== it.key)].slice(0, 12);\n try {\n globalThis.localStorage?.setItem(RECENTS_KEY, JSON.stringify(recents));\n } catch {\n /* storage is off: recents last for this session */\n }\n recentSubs.forEach((s) => s());\n}\nfunction useRecents() {\n return React.useSyncExternalStore(\n (cb) => {\n recentSubs.add(cb);\n return () => recentSubs.delete(cb);\n },\n () => recents,\n () => recents,\n );\n}\n\nexport interface CatalogSection {\n title: string | null;\n items: CatalogItem[];\n}\n\n/** 5 exact (label, id or alias) · 4 label or alias starts with it · 3 a word of the label starts with it · 2 inside the\n * label · 1 the description. */\nconst score = (q: string, it: CatalogItem) => {\n const l = it.label.toLowerCase();\n const al = it.aliases ?? [];\n if (l === q || it.id === q || al.includes(q)) return 5;\n if (l.startsWith(q) || al.some((a) => a.startsWith(q))) return 4;\n if (l.split(/[\\s-]+/).some((w) => w.startsWith(q))) return 3;\n if (l.includes(q)) return 2;\n return it.description.toLowerCase().includes(q) ? 1 : 0;\n};\n/** Ranked by recently used among the strong matches, then by match, then catalogue order. */\nconst rank = (q: string, list: CatalogItem[], recent: string[]) =>\n list\n .map((it, i) => {\n const s = score(q, it);\n const r = recent.indexOf(it.key);\n return { it, s, strong: s >= 3 ? 1 : 0, r: s >= 3 && r >= 0 ? r : 999, i };\n })\n .filter((x) => x.s > 0)\n .sort((a, b) => b.strong - a.strong || a.r - b.r || b.s - a.s || a.i - b.i)\n .map((x) => x.it);\n\n/** `folder`: the More components view (its way back first, then the components, searched by `query`). */\nexport function useCatalog(query: string, allows: (f: RichTextFeature) => boolean, folder = false, own?: CatalogComponents | null) {\n const recent = useRecents();\n const provided = React.useContext(ComponentsContext);\n // the editor reads its own list (it renders the provider, so cannot read it)\n const lib = own === undefined ? provided : own;\n return React.useMemo(() => {\n const q = query.trim().toLowerCase().replace(/^\\//, \"\");\n const keep = (list: CatalogItem[]) => list.filter((it) => !it.id || blockAllowed(it.id, allows));\n const comps = (lib?.components ?? []).map(componentRow);\n let sections: CatalogSection[];\n let noMatch = false;\n let emptyText = \"No match.\";\n if (folder) {\n const found = q ? rank(q, comps, recent) : comps;\n noMatch = !!q && !!comps.length && !found.length;\n if (!comps.length) emptyText = lib?.error ?? (lib?.loading ? \"Loading blocks…\" : \"This field takes no blocks.\");\n sections = [{ title: null, items: [BACK] }, { title: \"Blocks\", items: found }];\n } else if (q) {\n const found = rank(q, [...keep(TEXT), ...keep(MEDIA), ...keep(DATA), ...keep(SEARCH_ONLY), ...comps], recent);\n noMatch = !found.length;\n sections = [{ title: null, items: found }];\n } else\n sections = [\n { title: \"Text\", items: keep(TEXT) },\n { title: \"Media\", items: keep(MEDIA) },\n { title: \"Data\", items: keep(DATA) },\n // offered while the list loads or failed too: opening it says which\n { title: null, items: lib && (comps.length || lib.loading || lib.error) ? [{ ...MORE, ...(comps.length ? { count: comps.length } : {}) }] : [] },\n ];\n sections = sections.filter((s) => s.items.length);\n const flat = sections.flatMap((s) => s.items);\n const empty = !flat.some((it) => it.kind !== \"back\");\n return { sections, flat, empty, noMatch, emptyText };\n }, [query, allows, recent, lib, folder]);\n}\n\n/* ── The list ───────────────────────────────────────────────────────────\n * Browsing: compact one-line rows (icon · name), grouped. Search results add the one-line description. */\nexport function CatalogList({\n sections,\n flat,\n active,\n onHover,\n onPick,\n empty,\n emptyText = \"No match.\",\n noMatch,\n detail,\n listId,\n className,\n}: {\n sections: CatalogSection[];\n flat: CatalogItem[];\n active: number;\n onHover: (i: number) => void;\n onPick: (it: CatalogItem) => void;\n empty: boolean;\n /** what an empty list says (\"No match.\", or why More components has none) */\n emptyText?: string;\n noMatch?: boolean;\n /** show each row's description (search results, More components) */\n detail?: boolean;\n listId: string;\n className?: string;\n}) {\n const box = React.useRef<HTMLDivElement>(null);\n React.useEffect(() => {\n // scroll only the list (scrollIntoView would also scroll the page behind a floating menu)\n const b = box.current;\n const row = b?.querySelector<HTMLElement>(`[data-idx=\"${active}\"]`);\n if (!b || !row) return;\n if (row.offsetTop < b.scrollTop) b.scrollTop = row.offsetTop - 6;\n else if (row.offsetTop + row.offsetHeight > b.scrollTop + b.clientHeight) b.scrollTop = row.offsetTop + row.offsetHeight - b.clientHeight + 6;\n }, [active]);\n return (\n <div ref={box} id={listId} role=\"listbox\" aria-label=\"Blocks\" className={cn(\"relative max-h-[320px] overflow-y-auto p-1.5\", className)}>\n {noMatch ? <div className=\"px-2 pb-1 pt-1.5 text-kit-sm text-kit-fg-3\">No match.</div> : null}\n {sections.map((s, si) => (\n <div\n key={s.title ?? `s${si}`}\n role=\"group\"\n aria-label={s.title ?? undefined}\n className={cn(!s.title && si > 0 && s.items[0]?.kind === \"folder\" && \"mt-1.5 border-t border-kit-line-subtle pt-1.5\")}\n >\n {s.title ? <div className=\"px-2 pb-0.5 pt-2 text-kit-2xs font-medium text-kit-fg-3\">{s.title}</div> : null}\n {s.items.map((it) => {\n const idx = flat.indexOf(it);\n const on = idx === active;\n const desc = detail && it.kind !== \"back\" && it.kind !== \"folder\" ? it.description : \"\";\n return (\n <button\n key={it.key}\n type=\"button\"\n role=\"option\"\n aria-selected={on}\n aria-description={!desc && it.description ? it.description : undefined}\n data-idx={idx}\n id={`${listId}-${idx}`}\n onMouseDown={(e) => e.preventDefault()}\n onMouseMove={() => !on && onHover(idx)}\n onClick={() => onPick(it)}\n className={cn(\n \"flex w-full items-center gap-2 rounded-kit-md px-2 text-left transition-colors motion-reduce:transition-none\",\n desc ? \"py-1.5\" : \"h-8\",\n it.kind === \"back\" ? \"text-kit-fg-2\" : \"text-kit-fg\",\n on && \"bg-kit-hover text-kit-fg\",\n )}\n >\n <span className={cn(\"flex shrink-0 items-center justify-center text-kit-fg-2 [&_svg]:size-4\", desc ? \"size-7 rounded-kit-md border border-kit-line bg-kit-control-bg\" : \"size-5\")}>\n {it.icon}\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-kit-sm\">{it.label}</span>\n {desc ? <span className=\"block truncate text-kit-xs text-kit-fg-3\">{desc}</span> : null}\n </span>\n {it.count != null ? <span className=\"text-kit-2xs tabular-nums text-kit-fg-3\">{it.count}</span> : null}\n {it.kind === \"folder\" ? <IconChevronRight className=\"size-3.5 shrink-0 text-kit-fg-3\" stroke={1.5} aria-hidden /> : null}\n </button>\n );\n })}\n </div>\n ))}\n {empty ? <div className=\"px-2 py-6 text-center text-kit-sm text-kit-fg-3\">{emptyText}</div> : null}\n </div>\n );\n}\n\nconst Footer = () => (\n <div data-hint=\"shortcuts\" className=\"flex h-7 shrink-0 items-center border-t border-kit-line px-3 text-kit-2xs text-kit-fg-3\">\n <span>\n <kbd className=\"font-kit-sans\">↵</kbd> select · type to search\n </span>\n </div>\n);\n\n/** Shared keyboard model: ↑↓ wrap, Home/End, Enter / Tab, → into More components. Returns true when handled. */\nexport function catalogKey(\n e: React.KeyboardEvent | KeyboardEvent,\n flat: CatalogItem[],\n active: number,\n setActive: (i: number) => void,\n pick: (it: CatalogItem) => void,\n homeEnd = true,\n) {\n const n = flat.length;\n if (!n) return false;\n if (e.key === \"ArrowDown\") return setActive((active + 1) % n), true;\n if (e.key === \"ArrowUp\") return setActive((active - 1 + n) % n), true;\n if (homeEnd && e.key === \"Home\") return setActive(0), true;\n if (homeEnd && e.key === \"End\") return setActive(n - 1), true;\n if (e.key === \"ArrowRight\" && flat[active]?.kind === \"folder\") return pick(flat[active]), true;\n if (e.key === \"Enter\" || e.key === \"Tab\") {\n const it = flat[active];\n if (it) pick(it);\n return true;\n }\n return false;\n}\n\n/* ── Insert menu (popover content for the toolbar \"Insert\" and the gutter \"+\") ─ */\nexport function InsertMenuContent({\n onInsert,\n allows,\n keepFocusOnClose,\n align = \"start\",\n side = \"bottom\",\n}: {\n onInsert: (it: CatalogItem) => void;\n allows: (f: RichTextFeature) => boolean;\n /** after an insert the caret stays in the editor: the menu gives no focus back to its trigger */\n keepFocusOnClose?: () => boolean;\n align?: \"start\" | \"center\" | \"end\";\n side?: \"top\" | \"bottom\" | \"left\" | \"right\";\n}) {\n const [q, setQ] = React.useState(\"\");\n const [active, setActive] = React.useState(0);\n const input = React.useRef<HTMLInputElement>(null);\n const listId = React.useId();\n const [folder, setFolder] = React.useState(false);\n const { sections, flat, empty, emptyText, noMatch } = useCatalog(q, allows, folder);\n // in More components the first row is the way back: the first component starts active\n React.useEffect(() => setActive(folder && flat.length > 1 ? 1 : 0), [q, folder]); // eslint-disable-line react-hooks/exhaustive-deps\n const leave = () => {\n setFolder(false);\n setQ(\"\");\n input.current?.focus();\n };\n const pick = (it: CatalogItem) => {\n if (it.kind === \"folder\") {\n setFolder(true);\n setQ(\"\");\n input.current?.focus();\n } else if (it.kind === \"back\") leave();\n else onInsert(it);\n };\n return (\n <PopoverContent\n side={side}\n align={align}\n className=\"flex max-h-[420px] w-[320px] flex-col overflow-hidden p-0\"\n initialFocus={input}\n finalFocus={keepFocusOnClose ? () => !keepFocusOnClose() : undefined}\n // Esc in More components steps back to every block, wherever focus sits in the menu (the list scrolls, so the\n // browser can focus it): caught before the popover's own Esc, which would close it\n onKeyDownCapture={(e) => {\n if (!folder || e.key !== \"Escape\") return;\n e.preventDefault();\n e.stopPropagation();\n leave();\n }}\n >\n <div className=\"flex h-10 shrink-0 items-center gap-2 border-b border-kit-line px-3\">\n <IconSearch className=\"size-4 shrink-0 text-kit-fg-3\" stroke={1.5} aria-hidden />\n <input\n ref={input}\n value={q}\n onChange={(e) => setQ(e.target.value)}\n onKeyDown={(e) => {\n if (folder && e.key === \"Backspace\" && !q) {\n // ⌫ in an empty search steps back to every block (Esc: the popup's capture above)\n e.preventDefault();\n e.stopPropagation();\n leave();\n } else if (catalogKey(e, flat, active, setActive, pick, !q)) e.preventDefault();\n }}\n role=\"combobox\"\n aria-expanded\n aria-controls={listId}\n aria-activedescendant={flat.length ? `${listId}-${active}` : undefined}\n aria-label={folder ? \"Search blocks\" : \"Search or pick a block\"}\n placeholder={folder ? \"Search blocks\" : \"Search or pick a block\"}\n className=\"h-full min-w-0 flex-1 bg-transparent text-kit-sm text-kit-fg outline-none placeholder:text-kit-fg-3 focus-visible:shadow-none\"\n />\n </div>\n <CatalogList\n sections={sections}\n flat={flat}\n active={active}\n onHover={setActive}\n onPick={pick}\n empty={empty}\n emptyText={emptyText}\n noMatch={noMatch}\n detail={!!q || folder}\n listId={listId}\n className=\"min-h-0 flex-1\"\n />\n <Footer />\n </PopoverContent>\n );\n}\n\n/* ── Slash menu panel (positioned by the editor; never takes focus) ── */\nexport function SlashMenuPanel({\n query,\n folder = false,\n active,\n setActive,\n onPick,\n allows,\n maxHeight,\n}: {\n /** the room the editor found for the menu (collision-aware); the list scrolls inside it */\n maxHeight?: number;\n query: string;\n /** the More components view (the editor keeps it with the rest of the slash state) */\n folder?: boolean;\n active: number;\n setActive: (i: number) => void;\n onPick: (it: CatalogItem) => void;\n allows: (f: RichTextFeature) => boolean;\n}) {\n const listId = React.useId();\n const { sections, flat, empty, emptyText, noMatch } = useCatalog(query, allows, folder);\n return (\n <div\n data-kit-scope=\"\"\n className=\"glass-strong flex w-[320px] flex-col overflow-hidden rounded-kit-xl border border-kit-line\"\n style={maxHeight ? { maxHeight } : undefined}\n onMouseDown={(e) => e.preventDefault()}\n >\n <div className=\"flex h-8 shrink-0 items-center gap-2 border-b border-kit-line px-3 text-kit-sm\">\n <span className={cn(\"flex-1 truncate\", query ? \"text-kit-fg\" : \"text-kit-fg-3\")}>{query ? `${folder ? \"More blocks · \" : \"\"}/${query}` : folder ? \"More blocks\" : \"Type to search\"}</span>\n <kbd data-hint=\"shortcuts\" className=\"font-kit-sans text-kit-2xs text-kit-fg-3\">\n esc\n </kbd>\n </div>\n <CatalogList\n sections={sections}\n flat={flat}\n active={active}\n onHover={setActive}\n onPick={onPick}\n empty={empty}\n emptyText={emptyText}\n noMatch={noMatch}\n detail={!!query || folder}\n listId={listId}\n className=\"max-h-[300px] min-h-0 flex-1\"\n />\n <Footer />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"catalog.js","names":[],"sources":["../../../src/controls/rich-text/catalog.tsx"],"sourcesContent":["/**\n * RICH TEXT · the block catalogue shared by the toolbar Insert menu, the gutter \"+\" and the \"/\" slash menu (V2 HEAD\n * controls/rich-text/catalog.tsx, CPO D55).\n *\n * browse Text · Media · Data, grouped, then one quiet \"More components N\" row that opens the components the\n * field may embed (V2 HEAD catalog.tsx MORE), with its own search and a way back (\"All blocks\")\n * search every block and every embeddable component in one list: recently used first among the strong matches,\n * then by match (label, aliases, then description)\n * no match \"No match.\"\n *\n * Only the blocks the field accepts are offered (`features`, from the host's codec: each block is offered only where\n * the field's stored shape has a place for it). Not in this catalogue, and why:\n * (none: every V2 block has a stored form now, plan RT0–RT8; each is offered only where the field's codec declares it)\n *\n * Components come from the host (KitServices.useInsertableComponents: the project's components, narrowed to the ones\n * the field's config accepts), handed in through CatalogComponentsProvider. Without that service none is offered.\n * Deviation from V2: the library has no inline / page-section placement, so every component sits in one\n * \"Components\" group (V2: \"Article\" and \"Page sections\"), and no live preview flyout is drawn beside a row.\n */\nimport * as React from \"react\";\nimport {\n IconClick, IconCode, IconColumns2, IconNews, IconSuperscript, IconUserCircle, IconLayoutGrid, IconListCheck, IconListTree,\n IconArrowLeft, IconBlockquote, IconChevronRight, IconComponents, IconFileText, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconInfoCircle, IconLayoutNavbarCollapse, IconList, IconListDetails, IconListNumbers, IconPhoto,\n IconPilcrow, IconQuote, IconSearch, IconSeparatorHorizontal, IconTable, IconTerminal2, IconVideo,\n} from \"@tabler/icons-react\";\nimport { cn } from \"../../lib/cn\";\nimport { PopoverContent } from \"../../ui/overlay\";\nimport type { RichTextFeature } from \"./codec\";\nimport type { KitInsertableComponent } from \"../component-services\";\n\nexport type BasicId =\n | \"p\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"quote\" | \"pullquote\" | \"callout\" | \"toggle\" | \"takeaways\" | \"ul\" | \"ol\" | \"code\" | \"hr\"\n | \"image\" | \"video\" | \"file\" | \"table\" | \"task\" | \"button\" | \"gallery\" | \"embed\" | \"toc\" | \"footnote\" | \"columns\" | \"related\" | \"author\";\n\nexport interface CatalogItem {\n key: string;\n /** a basic block (absent on a component, the More components row and the way back) */\n id?: BasicId;\n /** component: embeds `component`; folder: opens More components; back: returns to every block */\n kind?: \"component\" | \"folder\" | \"back\";\n component?: KitInsertableComponent;\n /** the folder row's component count */\n count?: number;\n label: string;\n description: string;\n icon: React.ReactNode;\n /** extra words that find it */\n aliases?: string[];\n}\n\nconst i = (I: React.ComponentType<{ stroke?: number }>) => <I stroke={1.5} />;\ntype Row = [BasicId, string, string, React.ReactNode, string[]?];\nconst rows = (list: Row[]): CatalogItem[] => list.map(([id, label, description, icon, aliases]) => ({ key: id, id, label, description, icon, aliases }));\n\nconst TEXT = rows([\n [\"p\", \"Text\", \"Plain body text\", i(IconPilcrow), [\"paragraph\", \"body\"]],\n [\"h2\", \"Heading 2\", \"Section heading\", i(IconH2), [\"title\", \"subtitle\"]],\n [\"h3\", \"Heading 3\", \"Subsection heading\", i(IconH3)],\n [\"h4\", \"Heading 4\", \"Minor heading\", i(IconH4)],\n [\"quote\", \"Quote\", \"A quoted passage\", i(IconBlockquote), [\"blockquote\"]],\n [\"pullquote\", \"Pull quote\", \"A highlighted line with who said it\", i(IconQuote), [\"pq\", \"testimonial\", \"attribution\"]],\n [\"ul\", \"List\", \"Bulleted list\", i(IconList), [\"bullets\", \"bulleted list\", \"unordered\"]],\n [\"ol\", \"Numbered list\", \"Ordered steps\", i(IconListNumbers), [\"ordered\", \"steps\"]],\n [\"toggle\", \"Toggle\", \"Collapsible title and body\", i(IconLayoutNavbarCollapse), [\"accordion\", \"details\", \"collapse\"]],\n [\"callout\", \"Callout\", \"A highlighted note with a tone\", i(IconInfoCircle), [\"note\", \"tip\", \"warning\", \"aside\"]],\n [\"takeaways\", \"Key takeaways\", \"The story in a few points\", i(IconListDetails), [\"summary\", \"tldr\", \"tl;dr\", \"highlights\"]],\n [\"hr\", \"Divider\", \"A line between parts\", i(IconSeparatorHorizontal), [\"line\", \"separator\", \"rule\"]],\n [\"code\", \"Code\", \"Monospaced snippet\", i(IconTerminal2), [\"code block\", \"snippet\", \"pre\"]],\n [\"footnote\", \"Footnote\", \"A numbered note at the end\", i(IconSuperscript), [\"fn\", \"reference\", \"citation\", \"source\"]],\n]);\nconst MEDIA = rows([\n [\"image\", \"Image\", \"Image with alt text and caption\", i(IconPhoto), [\"img\", \"picture\", \"photo\"]],\n [\"gallery\", \"Gallery\", \"Several images as a grid or carousel\", i(IconLayoutGrid), [\"images\", \"photos\", \"carousel\", \"slideshow\"]],\n [\"video\", \"Video\", \"A YouTube, Vimeo or video file link\", i(IconVideo), [\"mp4\", \"youtube\", \"yt\", \"vimeo\", \"clip\", \"movie\"]],\n [\"embed\", \"Embed\", \"YouTube, X, CodePen, Figma, maps or audio\", i(IconCode), [\"yt\", \"youtube\", \"vimeo\", \"x\", \"tweet\", \"twitter\", \"codepen\", \"figma\", \"map\", \"maps\", \"spotify\", \"podcast\", \"audio\", \"iframe\"]],\n [\"file\", \"File\", \"A downloadable PDF or file\", i(IconFileText), [\"pdf\", \"download\", \"attachment\"]],\n]);\n/** V2 catalog.tsx ARTICLE: article blocks written in the body itself (the rest land with plan RT6–RT8). */\nconst ARTICLE = rows([\n [\"related\", \"Related posts\", \"More stories, picked or automatic\", i(IconNews), [\"related stories\", \"read next\", \"more stories\"]],\n [\"toc\", \"Table of contents\", \"Built from the headings\", i(IconListTree), [\"contents\", \"outline\", \"index\"]],\n [\"author\", \"Author bio\", \"Photo, name and short bio\", i(IconUserCircle), [\"bio\", \"byline\", \"about the author\"]],\n]);\nconst DATA = rows([[\"table\", \"Table\", \"Rows and columns\", i(IconTable), [\"grid\", \"data\", \"comparison\", \"spreadsheet\"]]]);\n/** Found by search, not listed: rare in a body (H1 is the page title). */\nconst SEARCH_ONLY = rows([\n [\"h1\", \"Heading 1\", \"Largest heading\", i(IconH1)],\n [\"h5\", \"Heading 5\", \"Smallest heading\", i(IconH5)],\n [\"h6\", \"Heading 6\", \"Eyebrow heading\", i(IconH6)],\n // V2 catalog.tsx:89, :91 (rare in a story, so found by search)\n [\"task\", \"Checklist\", \"To-do list with checkboxes\", i(IconListCheck), [\"todo\", \"checkbox\"]],\n [\"columns\", \"Columns\", \"Side by side text\", i(IconColumns2), [\"layout\"]],\n [\"button\", \"Button\", \"A call to action link\", i(IconClick), [\"link button\"]],\n]);\n\n/** The feature a block needs (Text needs none). */\nconst NEEDS: Record<BasicId, RichTextFeature | null> = {\n p: null, h1: \"h1\", h2: \"h2\", h3: \"h3\", h4: \"h4\", h5: \"h5\", h6: \"h6\", quote: \"blockquote\", ul: \"ul\", ol: \"ol\", code: \"pre\", hr: \"hr\", image: \"image\",\n pullquote: \"pullquote\", takeaways: \"takeaways\", callout: \"callout\", toggle: \"toggle\", video: \"video\", file: \"file\", table: \"table\",\n task: \"checklist\", button: \"button\", gallery: \"gallery\", embed: \"embed\", toc: \"toc\",\n footnote: \"footnote\", columns: \"columns\", related: \"related\", author: \"author\",\n};\nconst MORE: CatalogItem = { key: \"more\", kind: \"folder\", label: \"More blocks\", description: \"Shared across your site\", icon: i(IconComponents) };\nconst BACK: CatalogItem = { key: \"back\", kind: \"back\", label: \"All blocks\", description: \"\", icon: i(IconArrowLeft) };\nconst componentRow = (c: KitInsertableComponent): CatalogItem => ({\n key: `c:${c.id}`,\n kind: \"component\",\n component: c,\n label: c.name,\n description: c.description || \"Shared across your site\",\n icon: i(IconComponents),\n});\n\n/** The components this field may embed, as the host answered (KitServices.useInsertableComponents). */\nexport interface CatalogComponents {\n components: KitInsertableComponent[];\n loading?: boolean;\n error?: string;\n}\nconst ComponentsContext = React.createContext<CatalogComponents | null>(null);\nexport const CatalogComponentsProvider = ComponentsContext.Provider;\n/** The components this field may embed (a component card's Swap component). */\nexport const useCatalogComponents = () => React.useContext(ComponentsContext);\n\nexport const blockAllowed = (id: BasicId, allows: (f: RichTextFeature) => boolean) => {\n const need = NEEDS[id];\n return !need || allows(need);\n};\n\n/* ── Recently used (per person, CPO D55): this browser's storage; a browser that keeps none just has no recents ── */\nconst RECENTS_KEY = \"kit-rt-insert-recents\";\nlet recents: string[] = (() => {\n try {\n const v = JSON.parse(globalThis.localStorage?.getItem(RECENTS_KEY) ?? \"[]\") as unknown;\n return Array.isArray(v) ? v.filter((x): x is string => typeof x === \"string\") : [];\n } catch {\n return [];\n }\n})();\nconst recentSubs = new Set<() => void>();\n/** Remember a pick so search ranks it first next time. */\nexport function noteRecent(it: CatalogItem) {\n recents = [it.key, ...recents.filter((k) => k !== it.key)].slice(0, 12);\n try {\n globalThis.localStorage?.setItem(RECENTS_KEY, JSON.stringify(recents));\n } catch {\n /* storage is off: recents last for this session */\n }\n recentSubs.forEach((s) => s());\n}\nfunction useRecents() {\n return React.useSyncExternalStore(\n (cb) => {\n recentSubs.add(cb);\n return () => recentSubs.delete(cb);\n },\n () => recents,\n () => recents,\n );\n}\n\nexport interface CatalogSection {\n title: string | null;\n items: CatalogItem[];\n}\n\n/** 5 exact (label, id or alias) · 4 label or alias starts with it · 3 a word of the label starts with it · 2 inside the\n * label · 1 the description. */\nconst score = (q: string, it: CatalogItem) => {\n const l = it.label.toLowerCase();\n const al = it.aliases ?? [];\n if (l === q || it.id === q || al.includes(q)) return 5;\n if (l.startsWith(q) || al.some((a) => a.startsWith(q))) return 4;\n if (l.split(/[\\s-]+/).some((w) => w.startsWith(q))) return 3;\n if (l.includes(q)) return 2;\n return it.description.toLowerCase().includes(q) ? 1 : 0;\n};\n/** Ranked by recently used among the strong matches, then by match, then catalogue order. */\nconst rank = (q: string, list: CatalogItem[], recent: string[]) =>\n list\n .map((it, i) => {\n const s = score(q, it);\n const r = recent.indexOf(it.key);\n return { it, s, strong: s >= 3 ? 1 : 0, r: s >= 3 && r >= 0 ? r : 999, i };\n })\n .filter((x) => x.s > 0)\n .sort((a, b) => b.strong - a.strong || a.r - b.r || b.s - a.s || a.i - b.i)\n .map((x) => x.it);\n\n/** `folder`: the More components view (its way back first, then the components, searched by `query`). */\nexport function useCatalog(query: string, allows: (f: RichTextFeature) => boolean, folder = false, own?: CatalogComponents | null) {\n const recent = useRecents();\n const provided = React.useContext(ComponentsContext);\n // the editor reads its own list (it renders the provider, so cannot read it)\n const lib = own === undefined ? provided : own;\n return React.useMemo(() => {\n const q = query.trim().toLowerCase().replace(/^\\//, \"\");\n const keep = (list: CatalogItem[]) => list.filter((it) => !it.id || blockAllowed(it.id, allows));\n const comps = (lib?.components ?? []).map(componentRow);\n let sections: CatalogSection[];\n let noMatch = false;\n let emptyText = \"No match.\";\n if (folder) {\n const found = q ? rank(q, comps, recent) : comps;\n noMatch = !!q && !!comps.length && !found.length;\n if (!comps.length) emptyText = lib?.error ?? (lib?.loading ? \"Loading blocks…\" : \"This field takes no blocks.\");\n sections = [{ title: null, items: [BACK] }, { title: \"Blocks\", items: found }];\n } else if (q) {\n const found = rank(q, [...keep(TEXT), ...keep(MEDIA), ...keep(DATA), ...keep(ARTICLE), ...keep(SEARCH_ONLY), ...comps], recent);\n noMatch = !found.length;\n sections = [{ title: null, items: found }];\n } else\n sections = [\n { title: \"Text\", items: keep(TEXT) },\n { title: \"Media\", items: keep(MEDIA) },\n { title: \"Data\", items: keep(DATA) },\n { title: \"Article\", items: keep(ARTICLE) },\n // offered while the list loads or failed too: opening it says which\n { title: null, items: lib && (comps.length || lib.loading || lib.error) ? [{ ...MORE, ...(comps.length ? { count: comps.length } : {}) }] : [] },\n ];\n sections = sections.filter((s) => s.items.length);\n const flat = sections.flatMap((s) => s.items);\n const empty = !flat.some((it) => it.kind !== \"back\");\n return { sections, flat, empty, noMatch, emptyText };\n }, [query, allows, recent, lib, folder]);\n}\n\n/* ── The list ───────────────────────────────────────────────────────────\n * Browsing: compact one-line rows (icon · name), grouped. Search results add the one-line description. */\nexport function CatalogList({\n sections,\n flat,\n active,\n onHover,\n onPick,\n empty,\n emptyText = \"No match.\",\n noMatch,\n detail,\n listId,\n className,\n}: {\n sections: CatalogSection[];\n flat: CatalogItem[];\n active: number;\n onHover: (i: number) => void;\n onPick: (it: CatalogItem) => void;\n empty: boolean;\n /** what an empty list says (\"No match.\", or why More components has none) */\n emptyText?: string;\n noMatch?: boolean;\n /** show each row's description (search results, More components) */\n detail?: boolean;\n listId: string;\n className?: string;\n}) {\n const box = React.useRef<HTMLDivElement>(null);\n React.useEffect(() => {\n // scroll only the list (scrollIntoView would also scroll the page behind a floating menu)\n const b = box.current;\n const row = b?.querySelector<HTMLElement>(`[data-idx=\"${active}\"]`);\n if (!b || !row) return;\n if (row.offsetTop < b.scrollTop) b.scrollTop = row.offsetTop - 6;\n else if (row.offsetTop + row.offsetHeight > b.scrollTop + b.clientHeight) b.scrollTop = row.offsetTop + row.offsetHeight - b.clientHeight + 6;\n }, [active]);\n return (\n <div ref={box} id={listId} role=\"listbox\" aria-label=\"Blocks\" className={cn(\"relative max-h-[320px] overflow-y-auto p-1.5\", className)}>\n {noMatch ? <div className=\"px-2 pb-1 pt-1.5 text-kit-sm text-kit-fg-3\">No match.</div> : null}\n {sections.map((s, si) => (\n <div\n key={s.title ?? `s${si}`}\n role=\"group\"\n aria-label={s.title ?? undefined}\n className={cn(!s.title && si > 0 && s.items[0]?.kind === \"folder\" && \"mt-1.5 border-t border-kit-line-subtle pt-1.5\")}\n >\n {s.title ? <div className=\"px-2 pb-0.5 pt-2 text-kit-2xs font-medium text-kit-fg-3\">{s.title}</div> : null}\n {s.items.map((it) => {\n const idx = flat.indexOf(it);\n const on = idx === active;\n const desc = detail && it.kind !== \"back\" && it.kind !== \"folder\" ? it.description : \"\";\n return (\n <button\n key={it.key}\n type=\"button\"\n role=\"option\"\n aria-selected={on}\n aria-description={!desc && it.description ? it.description : undefined}\n data-idx={idx}\n id={`${listId}-${idx}`}\n onMouseDown={(e) => e.preventDefault()}\n onMouseMove={() => !on && onHover(idx)}\n onClick={() => onPick(it)}\n className={cn(\n \"flex w-full items-center gap-2 rounded-kit-md px-2 text-left transition-colors motion-reduce:transition-none\",\n desc ? \"py-1.5\" : \"h-8\",\n it.kind === \"back\" ? \"text-kit-fg-2\" : \"text-kit-fg\",\n on && \"bg-kit-hover text-kit-fg\",\n )}\n >\n <span className={cn(\"flex shrink-0 items-center justify-center text-kit-fg-2 [&_svg]:size-4\", desc ? \"size-7 rounded-kit-md border border-kit-line bg-kit-control-bg\" : \"size-5\")}>\n {it.icon}\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-kit-sm\">{it.label}</span>\n {desc ? <span className=\"block truncate text-kit-xs text-kit-fg-3\">{desc}</span> : null}\n </span>\n {it.count != null ? <span className=\"text-kit-2xs tabular-nums text-kit-fg-3\">{it.count}</span> : null}\n {it.kind === \"folder\" ? <IconChevronRight className=\"size-3.5 shrink-0 text-kit-fg-3\" stroke={1.5} aria-hidden /> : null}\n </button>\n );\n })}\n </div>\n ))}\n {/* a search with no result says \"No match.\" once, in the line above (V2 catalog.tsx:263) */}\n {empty && !noMatch ? <div className=\"px-2 py-6 text-center text-kit-sm text-kit-fg-3\">{emptyText}</div> : null}\n </div>\n );\n}\n\nconst Footer = () => (\n <div data-hint=\"shortcuts\" className=\"flex h-7 shrink-0 items-center border-t border-kit-line px-3 text-kit-2xs text-kit-fg-3\">\n <span>\n <kbd className=\"font-kit-sans\">↵</kbd> select · type to search\n </span>\n </div>\n);\n\n/** Shared keyboard model: ↑↓ wrap, Home/End, Enter / Tab, → into More components. Returns true when handled. */\nexport function catalogKey(\n e: React.KeyboardEvent | KeyboardEvent,\n flat: CatalogItem[],\n active: number,\n setActive: (i: number) => void,\n pick: (it: CatalogItem) => void,\n homeEnd = true,\n) {\n const n = flat.length;\n if (!n) return false;\n if (e.key === \"ArrowDown\") return setActive((active + 1) % n), true;\n if (e.key === \"ArrowUp\") return setActive((active - 1 + n) % n), true;\n if (homeEnd && e.key === \"Home\") return setActive(0), true;\n if (homeEnd && e.key === \"End\") return setActive(n - 1), true;\n if (e.key === \"ArrowRight\" && flat[active]?.kind === \"folder\") return pick(flat[active]), true;\n if (e.key === \"Enter\" || e.key === \"Tab\") {\n const it = flat[active];\n if (it) pick(it);\n return true;\n }\n return false;\n}\n\n/* ── Insert menu (popover content for the toolbar \"Insert\" and the gutter \"+\") ─ */\nexport function InsertMenuContent({\n onInsert,\n allows,\n keepFocusOnClose,\n align = \"start\",\n side = \"bottom\",\n}: {\n onInsert: (it: CatalogItem) => void;\n allows: (f: RichTextFeature) => boolean;\n /** after an insert the caret stays in the editor: the menu gives no focus back to its trigger */\n keepFocusOnClose?: () => boolean;\n align?: \"start\" | \"center\" | \"end\";\n side?: \"top\" | \"bottom\" | \"left\" | \"right\";\n}) {\n const [q, setQ] = React.useState(\"\");\n const [active, setActive] = React.useState(0);\n const input = React.useRef<HTMLInputElement>(null);\n const listId = React.useId();\n const [folder, setFolder] = React.useState(false);\n const { sections, flat, empty, emptyText, noMatch } = useCatalog(q, allows, folder);\n // in More components the first row is the way back: the first component starts active\n React.useEffect(() => setActive(folder && flat.length > 1 ? 1 : 0), [q, folder]); // eslint-disable-line react-hooks/exhaustive-deps\n const leave = () => {\n setFolder(false);\n setQ(\"\");\n input.current?.focus();\n };\n const pick = (it: CatalogItem) => {\n if (it.kind === \"folder\") {\n setFolder(true);\n setQ(\"\");\n input.current?.focus();\n } else if (it.kind === \"back\") leave();\n else onInsert(it);\n };\n return (\n <PopoverContent\n side={side}\n align={align}\n className=\"flex max-h-[420px] w-[320px] flex-col overflow-hidden p-0\"\n initialFocus={input}\n finalFocus={keepFocusOnClose ? () => !keepFocusOnClose() : undefined}\n // Esc in More components steps back to every block, wherever focus sits in the menu (the list scrolls, so the\n // browser can focus it): caught before the popover's own Esc, which would close it\n onKeyDownCapture={(e) => {\n if (!folder || e.key !== \"Escape\") return;\n e.preventDefault();\n e.stopPropagation();\n leave();\n }}\n >\n <div className=\"flex h-10 shrink-0 items-center gap-2 border-b border-kit-line px-3\">\n <IconSearch className=\"size-4 shrink-0 text-kit-fg-3\" stroke={1.5} aria-hidden />\n <input\n ref={input}\n value={q}\n onChange={(e) => setQ(e.target.value)}\n onKeyDown={(e) => {\n if (folder && e.key === \"Backspace\" && !q) {\n // ⌫ in an empty search steps back to every block (Esc: the popup's capture above)\n e.preventDefault();\n e.stopPropagation();\n leave();\n } else if (catalogKey(e, flat, active, setActive, pick, !q)) e.preventDefault();\n }}\n role=\"combobox\"\n aria-expanded\n aria-controls={listId}\n aria-activedescendant={flat.length ? `${listId}-${active}` : undefined}\n aria-label={folder ? \"Search blocks\" : \"Search or pick a block\"}\n placeholder={folder ? \"Search blocks\" : \"Search or pick a block\"}\n className=\"h-full min-w-0 flex-1 bg-transparent text-kit-sm text-kit-fg outline-none placeholder:text-kit-fg-3 focus-visible:shadow-none\"\n />\n </div>\n <CatalogList\n sections={sections}\n flat={flat}\n active={active}\n onHover={setActive}\n onPick={pick}\n empty={empty}\n emptyText={emptyText}\n noMatch={noMatch}\n detail={!!q || folder}\n listId={listId}\n className=\"min-h-0 flex-1\"\n />\n <Footer />\n </PopoverContent>\n );\n}\n\n/* ── Slash menu panel (positioned by the editor; never takes focus) ── */\nexport function SlashMenuPanel({\n query,\n folder = false,\n active,\n setActive,\n onPick,\n allows,\n maxHeight,\n}: {\n /** the room the editor found for the menu (collision-aware); the list scrolls inside it */\n maxHeight?: number;\n query: string;\n /** the More components view (the editor keeps it with the rest of the slash state) */\n folder?: boolean;\n active: number;\n setActive: (i: number) => void;\n onPick: (it: CatalogItem) => void;\n allows: (f: RichTextFeature) => boolean;\n}) {\n const listId = React.useId();\n const { sections, flat, empty, emptyText, noMatch } = useCatalog(query, allows, folder);\n return (\n <div\n data-kit-scope=\"\"\n className=\"glass-strong flex w-[320px] flex-col overflow-hidden rounded-kit-xl border border-kit-line\"\n style={maxHeight ? { maxHeight } : undefined}\n onMouseDown={(e) => e.preventDefault()}\n >\n <div className=\"flex h-8 shrink-0 items-center gap-2 border-b border-kit-line px-3 text-kit-sm\">\n <span className={cn(\"flex-1 truncate\", query ? \"text-kit-fg\" : \"text-kit-fg-3\")}>{query ? `${folder ? \"More blocks · \" : \"\"}/${query}` : folder ? \"More blocks\" : \"Type to search\"}</span>\n <kbd data-hint=\"shortcuts\" className=\"font-kit-sans text-kit-2xs text-kit-fg-3\">\n esc\n </kbd>\n </div>\n <CatalogList\n sections={sections}\n flat={flat}\n active={active}\n onHover={setActive}\n onPick={onPick}\n empty={empty}\n emptyText={emptyText}\n noMatch={noMatch}\n detail={!!query || folder}\n listId={listId}\n className=\"max-h-[300px] min-h-0 flex-1\"\n />\n <Footer />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,IAAM,KAAK,MAAgD,oBAAC,GAAD,EAAG,QAAQ,IAAM,CAAA;AAE5E,IAAM,QAAQ,SAA+B,KAAK,KAAK,CAAC,IAAI,OAAO,aAAa,MAAM,cAAc;CAAE,KAAK;CAAI;CAAI;CAAO;CAAa;CAAM;AAAQ,EAAE;AAEvJ,IAAM,OAAO,KAAK;CAChB;EAAC;EAAK;EAAQ;EAAmB,EAAE,WAAW;EAAG,CAAC,aAAa,MAAM;CAAC;CACtE;EAAC;EAAM;EAAa;EAAmB,EAAE,MAAM;EAAG,CAAC,SAAS,UAAU;CAAC;CACvE;EAAC;EAAM;EAAa;EAAsB,EAAE,MAAM;CAAC;CACnD;EAAC;EAAM;EAAa;EAAiB,EAAE,MAAM;CAAC;CAC9C;EAAC;EAAS;EAAS;EAAoB,EAAE,cAAc;EAAG,CAAC,YAAY;CAAC;CACxE;EAAC;EAAa;EAAc;EAAuC,EAAE,SAAS;EAAG;GAAC;GAAM;GAAe;EAAa;CAAC;CACrH;EAAC;EAAM;EAAQ;EAAiB,EAAE,QAAQ;EAAG;GAAC;GAAW;GAAiB;EAAW;CAAC;CACtF;EAAC;EAAM;EAAiB;EAAiB,EAAE,eAAe;EAAG,CAAC,WAAW,OAAO;CAAC;CACjF;EAAC;EAAU;EAAU;EAA8B,EAAE,wBAAwB;EAAG;GAAC;GAAa;GAAW;EAAU;CAAC;CACpH;EAAC;EAAW;EAAW;EAAkC,EAAE,cAAc;EAAG;GAAC;GAAQ;GAAO;GAAW;EAAO;CAAC;CAC/G;EAAC;EAAa;EAAiB;EAA6B,EAAE,eAAe;EAAG;GAAC;GAAW;GAAQ;GAAS;EAAY;CAAC;CAC1H;EAAC;EAAM;EAAW;EAAwB,EAAE,uBAAuB;EAAG;GAAC;GAAQ;GAAa;EAAM;CAAC;CACnG;EAAC;EAAQ;EAAQ;EAAsB,EAAE,aAAa;EAAG;GAAC;GAAc;GAAW;EAAK;CAAC;CACzF;EAAC;EAAY;EAAY;EAA8B,EAAE,eAAe;EAAG;GAAC;GAAM;GAAa;GAAY;EAAQ;CAAC;AACtH,CAAC;AACD,IAAM,QAAQ,KAAK;CACjB;EAAC;EAAS;EAAS;EAAmC,EAAE,SAAS;EAAG;GAAC;GAAO;GAAW;EAAO;CAAC;CAC/F;EAAC;EAAW;EAAW;EAAwC,EAAE,cAAc;EAAG;GAAC;GAAU;GAAU;GAAY;EAAW;CAAC;CAC/H;EAAC;EAAS;EAAS;EAAuC,EAAE,SAAS;EAAG;GAAC;GAAO;GAAW;GAAM;GAAS;GAAQ;EAAO;CAAC;CAC1H;EAAC;EAAS;EAAS;EAA6C,EAAE,QAAQ;EAAG;GAAC;GAAM;GAAW;GAAS;GAAK;GAAS;GAAW;GAAW;GAAS;GAAO;GAAQ;GAAW;GAAW;GAAS;EAAQ;CAAC;CAC5M;EAAC;EAAQ;EAAQ;EAA8B,EAAE,YAAY;EAAG;GAAC;GAAO;GAAY;EAAY;CAAC;AACnG,CAAC;;AAED,IAAM,UAAU,KAAK;CACnB;EAAC;EAAW;EAAiB;EAAqC,EAAE,QAAQ;EAAG;GAAC;GAAmB;GAAa;EAAc;CAAC;CAC/H;EAAC;EAAO;EAAqB;EAA2B,EAAE,YAAY;EAAG;GAAC;GAAY;GAAW;EAAO;CAAC;CACzG;EAAC;EAAU;EAAc;EAA6B,EAAE,cAAc;EAAG;GAAC;GAAO;GAAU;EAAkB;CAAC;AAChH,CAAC;AACD,IAAM,OAAO,KAAK,CAAC;CAAC;CAAS;CAAS;CAAoB,EAAE,SAAS;CAAG;EAAC;EAAQ;EAAQ;EAAc;CAAa;AAAC,CAAC,CAAC;;AAEvH,IAAM,cAAc,KAAK;CACvB;EAAC;EAAM;EAAa;EAAmB,EAAE,MAAM;CAAC;CAChD;EAAC;EAAM;EAAa;EAAoB,EAAE,MAAM;CAAC;CACjD;EAAC;EAAM;EAAa;EAAmB,EAAE,MAAM;CAAC;CAEhD;EAAC;EAAQ;EAAa;EAA8B,EAAE,aAAa;EAAG,CAAC,QAAQ,UAAU;CAAC;CAC1F;EAAC;EAAW;EAAW;EAAqB,EAAE,YAAY;EAAG,CAAC,QAAQ;CAAC;CACvE;EAAC;EAAU;EAAU;EAAyB,EAAE,SAAS;EAAG,CAAC,aAAa;CAAC;AAC7E,CAAC;;AAGD,IAAM,QAAiD;CACrD,GAAG;CAAM,IAAI;CAAM,IAAI;CAAM,IAAI;CAAM,IAAI;CAAM,IAAI;CAAM,IAAI;CAAM,OAAO;CAAc,IAAI;CAAM,IAAI;CAAM,MAAM;CAAO,IAAI;CAAM,OAAO;CAC5I,WAAW;CAAa,WAAW;CAAa,SAAS;CAAW,QAAQ;CAAU,OAAO;CAAS,MAAM;CAAQ,OAAO;CAC3H,MAAM;CAAa,QAAQ;CAAU,SAAS;CAAW,OAAO;CAAS,KAAK;CAC9E,UAAU;CAAY,SAAS;CAAW,SAAS;CAAW,QAAQ;AACxE;AACA,IAAM,OAAoB;CAAE,KAAK;CAAQ,MAAM;CAAU,OAAO;CAAe,aAAa;CAA2B,MAAM,EAAE,cAAc;AAAE;AAC/I,IAAM,OAAoB;CAAE,KAAK;CAAQ,MAAM;CAAQ,OAAO;CAAc,aAAa;CAAI,MAAM,EAAE,aAAa;AAAE;AACpH,IAAM,gBAAgB,OAA4C;CAChE,KAAK,KAAK,EAAE;CACZ,MAAM;CACN,WAAW;CACX,OAAO,EAAE;CACT,aAAa,EAAE,eAAe;CAC9B,MAAM,EAAE,cAAc;AACxB;AAQA,IAAM,oBAAoB,MAAM,cAAwC,IAAI;AAC5E,IAAa,4BAA4B,kBAAkB;;AAE3D,IAAa,6BAA6B,MAAM,WAAW,iBAAiB;AAE5E,IAAa,gBAAgB,IAAa,WAA4C;CACpF,MAAM,OAAO,MAAM;CACnB,OAAO,CAAC,QAAQ,OAAO,IAAI;AAC7B;AAGA,IAAM,cAAc;AACpB,IAAI,iBAA2B;CAC7B,IAAI;EACF,MAAM,IAAI,KAAK,MAAM,WAAW,cAAc,QAAQ,WAAW,KAAK,IAAI;EAC1E,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,QAAQ,MAAmB,OAAO,MAAM,QAAQ,IAAI,CAAC;CACnF,QAAQ;EACN,OAAO,CAAC;CACV;AACF,EAAA,CAAG;AACH,IAAM,6BAAa,IAAI,IAAgB;;AAEvC,SAAgB,WAAW,IAAiB;CAC1C,UAAU,CAAC,GAAG,KAAK,GAAG,QAAQ,QAAQ,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,EAAE;CACtE,IAAI;EACF,WAAW,cAAc,QAAQ,aAAa,KAAK,UAAU,OAAO,CAAC;CACvE,QAAQ,CAER;CACA,WAAW,SAAS,MAAM,EAAE,CAAC;AAC/B;AACA,SAAS,aAAa;CACpB,OAAO,MAAM,sBACV,OAAO;EACN,WAAW,IAAI,EAAE;EACjB,aAAa,WAAW,OAAO,EAAE;CACnC,SACM,eACA,OACR;AACF;;;AASA,IAAM,SAAS,GAAW,OAAoB;CAC5C,MAAM,IAAI,GAAG,MAAM,YAAY;CAC/B,MAAM,KAAK,GAAG,WAAW,CAAC;CAC1B,IAAI,MAAM,KAAK,GAAG,OAAO,KAAK,GAAG,SAAS,CAAC,GAAG,OAAO;CACrD,IAAI,EAAE,WAAW,CAAC,KAAK,GAAG,MAAM,MAAM,EAAE,WAAW,CAAC,CAAC,GAAG,OAAO;CAC/D,IAAI,EAAE,MAAM,QAAQ,CAAC,CAAC,MAAM,MAAM,EAAE,WAAW,CAAC,CAAC,GAAG,OAAO;CAC3D,IAAI,EAAE,SAAS,CAAC,GAAG,OAAO;CAC1B,OAAO,GAAG,YAAY,YAAY,CAAC,CAAC,SAAS,CAAC,IAAI,IAAI;AACxD;;AAEA,IAAM,QAAQ,GAAW,MAAqB,WAC5C,KACG,KAAK,IAAI,MAAM;CACd,MAAM,IAAI,MAAM,GAAG,EAAE;CACrB,MAAM,IAAI,OAAO,QAAQ,GAAG,GAAG;CAC/B,OAAO;EAAE;EAAI;EAAG,QAAQ,KAAK,IAAI,IAAI;EAAG,GAAG,KAAK,KAAK,KAAK,IAAI,IAAI;EAAK;CAAE;AAC3E,CAAC,CAAC,CACD,QAAQ,MAAM,EAAE,IAAI,CAAC,CAAC,CACtB,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAC1E,KAAK,MAAM,EAAE,EAAE;;AAGpB,SAAgB,WAAW,OAAe,QAAyC,SAAS,OAAO,KAAgC;CACjI,MAAM,SAAS,WAAW;CAC1B,MAAM,WAAW,MAAM,WAAW,iBAAiB;CAEnD,MAAM,MAAM,QAAQ,KAAA,IAAY,WAAW;CAC3C,OAAO,MAAM,cAAc;EACzB,MAAM,IAAI,MAAM,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,QAAQ,OAAO,EAAE;EACtD,MAAM,QAAQ,SAAwB,KAAK,QAAQ,OAAO,CAAC,GAAG,MAAM,aAAa,GAAG,IAAI,MAAM,CAAC;EAC/F,MAAM,SAAS,KAAK,cAAc,CAAC,EAAA,CAAG,IAAI,YAAY;EACtD,IAAI;EACJ,IAAI,UAAU;EACd,IAAI,YAAY;EAChB,IAAI,QAAQ;GACV,MAAM,QAAQ,IAAI,KAAK,GAAG,OAAO,MAAM,IAAI;GAC3C,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,UAAU,CAAC,MAAM;GAC1C,IAAI,CAAC,MAAM,QAAQ,YAAY,KAAK,UAAU,KAAK,UAAU,oBAAoB;GACjF,WAAW,CAAC;IAAE,OAAO;IAAM,OAAO,CAAC,IAAI;GAAE,GAAG;IAAE,OAAO;IAAU,OAAO;GAAM,CAAC;EAC/E,OAAO,IAAI,GAAG;GACZ,MAAM,QAAQ,KAAK,GAAG;IAAC,GAAG,KAAK,IAAI;IAAG,GAAG,KAAK,KAAK;IAAG,GAAG,KAAK,IAAI;IAAG,GAAG,KAAK,OAAO;IAAG,GAAG,KAAK,WAAW;IAAG,GAAG;GAAK,GAAG,MAAM;GAC9H,UAAU,CAAC,MAAM;GACjB,WAAW,CAAC;IAAE,OAAO;IAAM,OAAO;GAAM,CAAC;EAC3C,OACE,WAAW;GACT;IAAE,OAAO;IAAQ,OAAO,KAAK,IAAI;GAAE;GACnC;IAAE,OAAO;IAAS,OAAO,KAAK,KAAK;GAAE;GACrC;IAAE,OAAO;IAAQ,OAAO,KAAK,IAAI;GAAE;GACnC;IAAE,OAAO;IAAW,OAAO,KAAK,OAAO;GAAE;GAEzC;IAAE,OAAO;IAAM,OAAO,QAAQ,MAAM,UAAU,IAAI,WAAW,IAAI,SAAS,CAAC;KAAE,GAAG;KAAM,GAAI,MAAM,SAAS,EAAE,OAAO,MAAM,OAAO,IAAI,CAAC;IAAG,CAAC,IAAI,CAAC;GAAE;EACjJ;EACF,WAAW,SAAS,QAAQ,MAAM,EAAE,MAAM,MAAM;EAChD,MAAM,OAAO,SAAS,SAAS,MAAM,EAAE,KAAK;EAC5C,MAAM,QAAQ,CAAC,KAAK,MAAM,OAAO,GAAG,SAAS,MAAM;EACnD,OAAO;GAAE;GAAU;GAAM;GAAO;GAAS;EAAU;CACrD,GAAG;EAAC;EAAO;EAAQ;EAAQ;EAAK;CAAM,CAAC;AACzC;AAIA,SAAgB,YAAY,EAC1B,UACA,MACA,QACA,SACA,QACA,OACA,YAAY,aACZ,SACA,QACA,QACA,aAeC;CACD,MAAM,MAAM,MAAM,OAAuB,IAAI;CAC7C,MAAM,gBAAgB;EAEpB,MAAM,IAAI,IAAI;EACd,MAAM,MAAM,GAAG,cAA2B,cAAc,OAAO,GAAG;EAClE,IAAI,CAAC,KAAK,CAAC,KAAK;EAChB,IAAI,IAAI,YAAY,EAAE,WAAW,EAAE,YAAY,IAAI,YAAY;OAC1D,IAAI,IAAI,YAAY,IAAI,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,IAAI,YAAY,IAAI,eAAe,EAAE,eAAe;CAC9I,GAAG,CAAC,MAAM,CAAC;CACX,OACE,qBAAC,OAAD;EAAK,KAAK;EAAK,IAAI;EAAQ,MAAK;EAAU,cAAW;EAAS,WAAW,GAAG,gDAAgD,SAAS;EAArI,UAAA;GACG,UAAU,oBAAC,OAAD;IAAK,WAAU;IAA6C,UAAA;GAAc,CAAA,IAAI;GACxF,SAAS,KAAK,GAAG,OAChB,qBAAC,OAAD;IAEE,MAAK;IACL,cAAY,EAAE,SAAS,KAAA;IACvB,WAAW,GAAG,CAAC,EAAE,SAAS,KAAK,KAAK,EAAE,MAAM,EAAE,EAAE,SAAS,YAAY,+CAA+C;IAJtH,UAAA,CAMG,EAAE,QAAQ,oBAAC,OAAD;KAAK,WAAU;KAA2D,UAAA,EAAE;IAAW,CAAA,IAAI,MACrG,EAAE,MAAM,KAAK,OAAO;KACnB,MAAM,MAAM,KAAK,QAAQ,EAAE;KAC3B,MAAM,KAAK,QAAQ;KACnB,MAAM,OAAO,UAAU,GAAG,SAAS,UAAU,GAAG,SAAS,WAAW,GAAG,cAAc;KACrF,OACE,qBAAC,UAAD;MAEE,MAAK;MACL,MAAK;MACL,iBAAe;MACf,oBAAkB,CAAC,QAAQ,GAAG,cAAc,GAAG,cAAc,KAAA;MAC7D,YAAU;MACV,IAAI,GAAG,OAAO,GAAG;MACjB,cAAc,MAAM,EAAE,eAAe;MACrC,mBAAmB,CAAC,MAAM,QAAQ,GAAG;MACrC,eAAe,OAAO,EAAE;MACxB,WAAW,GACT,gHACA,OAAO,WAAW,OAClB,GAAG,SAAS,SAAS,kBAAkB,eACvC,MAAM,0BACR;MAhBF,UAAA;OAkBE,oBAAC,QAAD;QAAM,WAAW,GAAG,0EAA0E,OAAO,mEAAmE,QAAQ;QAC7K,UAAA,GAAG;OACA,CAAA;OACN,qBAAC,QAAD;QAAM,WAAU;QAAhB,UAAA,CACE,oBAAC,QAAD;SAAM,WAAU;SAA8B,UAAA,GAAG;QAAY,CAAA,GAC5D,OAAO,oBAAC,QAAD;SAAM,WAAU;SAA4C,UAAA;QAAW,CAAA,IAAI,IAC/E;;OACL,GAAG,SAAS,OAAO,oBAAC,QAAD;QAAM,WAAU;QAA2C,UAAA,GAAG;OAAY,CAAA,IAAI;OACjG,GAAG,SAAS,WAAW,oBAAC,kBAAD;QAAkB,WAAU;QAAkC,QAAQ;QAAK,eAAA;OAAa,CAAA,IAAI;MAC9G;KA1BD,GAAA,GAAG,GA0BF;IAEZ,CAAC,CACE;GAzCE,GAAA,EAAE,SAAS,IAAI,IAyCjB,CACN;GAEA,SAAS,CAAC,UAAU,oBAAC,OAAD;IAAK,WAAU;IAAmD,UAAA;GAAe,CAAA,IAAI;EACvG;;AAET;AAEA,IAAM,eACJ,oBAAC,OAAD;CAAK,aAAU;CAAY,WAAU;CACnC,UAAA,qBAAC,QAAD,EAAA,UAAA,CACE,oBAAC,OAAD;EAAK,WAAU;EAAgB,UAAA;CAAM,CAAA,GAAC,0BAClC,EAAA,CAAA;AACH,CAAA;;AAIP,SAAgB,WACd,GACA,MACA,QACA,WACA,MACA,UAAU,MACV;CACA,MAAM,IAAI,KAAK;CACf,IAAI,CAAC,GAAG,OAAO;CACf,IAAI,EAAE,QAAQ,aAAa,OAAO,WAAW,SAAS,KAAK,CAAC,GAAG;CAC/D,IAAI,EAAE,QAAQ,WAAW,OAAO,WAAW,SAAS,IAAI,KAAK,CAAC,GAAG;CACjE,IAAI,WAAW,EAAE,QAAQ,QAAQ,OAAO,UAAU,CAAC,GAAG;CACtD,IAAI,WAAW,EAAE,QAAQ,OAAO,OAAO,UAAU,IAAI,CAAC,GAAG;CACzD,IAAI,EAAE,QAAQ,gBAAgB,KAAK,OAAO,EAAE,SAAS,UAAU,OAAO,KAAK,KAAK,OAAO,GAAG;CAC1F,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,OAAO;EACxC,MAAM,KAAK,KAAK;EAChB,IAAI,IAAI,KAAK,EAAE;EACf,OAAO;CACT;CACA,OAAO;AACT;AAGA,SAAgB,kBAAkB,EAChC,UACA,QACA,kBACA,QAAQ,SACR,OAAO,YAQN;CACD,MAAM,CAAC,GAAG,QAAQ,MAAM,SAAS,EAAE;CACnC,MAAM,CAAC,QAAQ,aAAa,MAAM,SAAS,CAAC;CAC5C,MAAM,QAAQ,MAAM,OAAyB,IAAI;CACjD,MAAM,SAAS,MAAM,MAAM;CAC3B,MAAM,CAAC,QAAQ,aAAa,MAAM,SAAS,KAAK;CAChD,MAAM,EAAE,UAAU,MAAM,OAAO,WAAW,YAAY,WAAW,GAAG,QAAQ,MAAM;CAElF,MAAM,gBAAgB,UAAU,UAAU,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC;CAC/E,MAAM,cAAc;EAClB,UAAU,KAAK;EACf,KAAK,EAAE;EACP,MAAM,SAAS,MAAM;CACvB;CACA,MAAM,QAAQ,OAAoB;EAChC,IAAI,GAAG,SAAS,UAAU;GACxB,UAAU,IAAI;GACd,KAAK,EAAE;GACP,MAAM,SAAS,MAAM;EACvB,OAAO,IAAI,GAAG,SAAS,QAAQ,MAAM;OAChC,SAAS,EAAE;CAClB;CACA,OACE,qBAAC,gBAAD;EACQ;EACC;EACP,WAAU;EACV,cAAc;EACd,YAAY,yBAAyB,CAAC,iBAAiB,IAAI,KAAA;EAG3D,mBAAmB,MAAM;GACvB,IAAI,CAAC,UAAU,EAAE,QAAQ,UAAU;GACnC,EAAE,eAAe;GACjB,EAAE,gBAAgB;GAClB,MAAM;EACR;EAbF,UAAA;GAeE,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,YAAD;KAAY,WAAU;KAAgC,QAAQ;KAAK,eAAA;IAAa,CAAA,GAChF,oBAAC,SAAD;KACE,KAAK;KACL,OAAO;KACP,WAAW,MAAM,KAAK,EAAE,OAAO,KAAK;KACpC,YAAY,MAAM;MAChB,IAAI,UAAU,EAAE,QAAQ,eAAe,CAAC,GAAG;OAEzC,EAAE,eAAe;OACjB,EAAE,gBAAgB;OAClB,MAAM;MACR,OAAO,IAAI,WAAW,GAAG,MAAM,QAAQ,WAAW,MAAM,CAAC,CAAC,GAAG,EAAE,eAAe;KAChF;KACA,MAAK;KACL,iBAAA;KACA,iBAAe;KACf,yBAAuB,KAAK,SAAS,GAAG,OAAO,GAAG,WAAW,KAAA;KAC7D,cAAY,SAAS,kBAAkB;KACvC,aAAa,SAAS,kBAAkB;KACxC,WAAU;IACX,CAAA,CACE;;GACL,oBAAC,aAAD;IACY;IACJ;IACE;IACR,SAAS;IACT,QAAQ;IACD;IACI;IACF;IACT,QAAQ,CAAC,CAAC,KAAK;IACP;IACR,WAAU;GACX,CAAA;GACD,oBAAC,QAAD,CAAS,CAAA;EACK;;AAEpB;AAGA,SAAgB,eAAe,EAC7B,OACA,SAAS,OACT,QACA,WACA,QACA,QACA,aAWC;CACD,MAAM,SAAS,MAAM,MAAM;CAC3B,MAAM,EAAE,UAAU,MAAM,OAAO,WAAW,YAAY,WAAW,OAAO,QAAQ,MAAM;CACtF,OACE,qBAAC,OAAD;EACE,kBAAe;EACf,WAAU;EACV,OAAO,YAAY,EAAE,UAAU,IAAI,KAAA;EACnC,cAAc,MAAM,EAAE,eAAe;EAJvC,UAAA;GAME,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,QAAD;KAAM,WAAW,GAAG,mBAAmB,QAAQ,gBAAgB,eAAe;KAAI,UAAA,QAAQ,GAAG,SAAS,mBAAmB,GAAG,GAAG,UAAU,SAAS,gBAAgB;IAAuB,CAAA,GACzL,oBAAC,OAAD;KAAK,aAAU;KAAY,WAAU;KAA2C,UAAA;IAE3E,CAAA,CACF;;GACL,oBAAC,aAAD;IACY;IACJ;IACE;IACR,SAAS;IACD;IACD;IACI;IACF;IACT,QAAQ,CAAC,CAAC,SAAS;IACX;IACR,WAAU;GACX,CAAA;GACD,oBAAC,QAAD,CAAS,CAAA;EACN;;AAET"}
@@ -0,0 +1,24 @@
1
+ /** The box is the item's ::before, 16px at the start of its 28px indent (pl-7); V2 hit-tests 22px. */
2
+ export declare const TASK_HIT_PX = 22;
3
+ /** The task item a node sits in, inside `root` and outside any rich block. */
4
+ export declare function taskItemOf(root: HTMLElement, node: Node | null): HTMLLIElement | null;
5
+ /** Ticks or unticks an item; keeps the editor-only a11y state in step. */
6
+ export declare function setTaskChecked(li: HTMLLIElement, checked: boolean): void;
7
+ /** Catalog insert: the empty line becomes `<ul data-task><li data-checked="false">`, caret inside. */
8
+ export declare function insertChecklist(root: HTMLElement): HTMLLIElement;
9
+ /** "[] " / "[ ] " typed at the start of a paragraph turns it into a task item. Returns true when it converted. */
10
+ export declare function checklistShortcut(root: HTMLElement, data: string | null): boolean;
11
+ /**
12
+ * A press on an item's box toggles it (mousedown, so the caret does not move). Returns true when it ticked; the
13
+ * editor then prevents the default and emits.
14
+ */
15
+ export declare function toggleTaskAtPointer(root: HTMLElement, target: EventTarget | null, clientX: number): boolean;
16
+ /** Space with the caret at the very start of an item ticks it (a leading space is never text). Returns true when it did. */
17
+ export declare function toggleTaskFromKey(root: HTMLElement, key: string): boolean;
18
+ /**
19
+ * After each input: an empty item is unchecked (V2), and every item carries role="checkbox" + aria-checked. Call it
20
+ * from the editor's input handler and after load.
21
+ */
22
+ export declare function syncTaskA11y(root: HTMLElement): void;
23
+ /** Takes the editor-only a11y attributes off a clone before it is stored. */
24
+ export declare function stripTaskA11y(clone: HTMLElement): void;
@@ -0,0 +1,115 @@
1
+ import { placeCaret, textBeforeCaret, topBlock } from "./dom.js";
2
+ import { takeEmptyBlock } from "./ops.js";
3
+ //#region src/controls/rich-text/checklist.tsx
4
+ /**
5
+ * RICH TEXT · Checklist (kit-richtext-ui-spec.md §8): V2's task list, a native list stored as PT `listItem: "check"`
6
+ * with `checked`, written in the editor as `<ul data-task="true"><li data-checked="true|false">`.
7
+ *
8
+ * The look is the editor's (editor.tsx, from V2 rich-text-editor.tsx:54-55); this module is the behaviour:
9
+ * insert V2 ops.ts:147-155: the empty line becomes a one-item unchecked task list, caret inside
10
+ * shortcut V2 ops.ts:224-240: "[] " or "[ ] " at the start of a paragraph
11
+ * tick V2 rich-text-editor.tsx:1314-1321: a press within 22px of the item's left edge toggles it
12
+ * new item V2 rich-text-editor.tsx:821: an empty item is unchecked (Enter after a ticked item starts unticked)
13
+ * a11y signed deviation (e1, 2026-10-24; behaviour only, pixels V2-exact): each item is role="checkbox" with
14
+ * aria-checked, and Space with the caret at the item's start ticks it. Editor-only: stripTaskA11y
15
+ * takes the attributes off the HTML the editor stores.
16
+ */
17
+ /** The box is the item's ::before, 16px at the start of its 28px indent (pl-7); V2 hit-tests 22px. */
18
+ var TASK_HIT_PX = 22;
19
+ var TASK_ITEM = "ul[data-task] > li";
20
+ var isTaskItem = (el) => !!el && el.tagName === "LI" && !!el.parentElement?.hasAttribute("data-task");
21
+ var isChecked = (li) => li.getAttribute("data-checked") === "true";
22
+ /** The task item a node sits in, inside `root` and outside any rich block. */
23
+ function taskItemOf(root, node) {
24
+ const li = (node ? node.nodeType === 1 ? node : node.parentElement : null)?.closest("li") ?? null;
25
+ return isTaskItem(li) && root.contains(li) && !li.closest("[data-rt-isolate]") ? li : null;
26
+ }
27
+ /** Ticks or unticks an item; keeps the editor-only a11y state in step. */
28
+ function setTaskChecked(li, checked) {
29
+ li.setAttribute("data-checked", checked ? "true" : "false");
30
+ li.setAttribute("aria-checked", checked ? "true" : "false");
31
+ }
32
+ /** Catalog insert: the empty line becomes `<ul data-task><li data-checked="false">`, caret inside. */
33
+ function insertChecklist(root) {
34
+ const t = takeEmptyBlock(root);
35
+ const ul = document.createElement("ul");
36
+ ul.setAttribute("data-task", "true");
37
+ ul.innerHTML = `<li data-checked="false"><br></li>`;
38
+ t.replaceWith(ul);
39
+ const li = ul.firstElementChild;
40
+ syncTaskA11y(root);
41
+ placeCaret(li);
42
+ return li;
43
+ }
44
+ /** "[] " / "[ ] " typed at the start of a paragraph turns it into a task item. Returns true when it converted. */
45
+ function checklistShortcut(root, data) {
46
+ if (data !== " ") return false;
47
+ const s = window.getSelection();
48
+ if (!s || !s.rangeCount || !s.isCollapsed) return false;
49
+ const blk = topBlock(root, s.anchorNode);
50
+ if (!blk || blk.tagName !== "P") return false;
51
+ const before = textBeforeCaret(blk)?.replace(/ /g, " ");
52
+ if (before !== "[] " && before !== "[ ] ") return false;
53
+ const r = document.createRange();
54
+ r.selectNodeContents(blk);
55
+ r.setEnd(s.anchorNode, s.anchorOffset);
56
+ r.deleteContents();
57
+ const ul = document.createElement("ul");
58
+ ul.setAttribute("data-task", "true");
59
+ const li = document.createElement("li");
60
+ li.setAttribute("data-checked", "false");
61
+ li.innerHTML = blk.textContent || blk.querySelector("img") ? blk.innerHTML : "<br>";
62
+ ul.append(li);
63
+ blk.replaceWith(ul);
64
+ syncTaskA11y(root);
65
+ placeCaret(li, false);
66
+ return true;
67
+ }
68
+ /**
69
+ * A press on an item's box toggles it (mousedown, so the caret does not move). Returns true when it ticked; the
70
+ * editor then prevents the default and emits.
71
+ */
72
+ function toggleTaskAtPointer(root, target, clientX) {
73
+ const li = target instanceof Node ? taskItemOf(root, target) : null;
74
+ if (!li || clientX - li.getBoundingClientRect().left >= 22) return false;
75
+ setTaskChecked(li, !isChecked(li));
76
+ return true;
77
+ }
78
+ /** Space with the caret at the very start of an item ticks it (a leading space is never text). Returns true when it did. */
79
+ function toggleTaskFromKey(root, key) {
80
+ if (key !== " ") return false;
81
+ const s = window.getSelection();
82
+ if (!s || !s.rangeCount || !s.isCollapsed) return false;
83
+ const li = taskItemOf(root, s.anchorNode);
84
+ if (!li) return false;
85
+ const r = document.createRange();
86
+ r.selectNodeContents(li);
87
+ r.setEnd(s.anchorNode, s.anchorOffset);
88
+ if (r.toString().replace(/​/g, "") !== "") return false;
89
+ setTaskChecked(li, !isChecked(li));
90
+ return true;
91
+ }
92
+ /**
93
+ * After each input: an empty item is unchecked (V2), and every item carries role="checkbox" + aria-checked. Call it
94
+ * from the editor's input handler and after load.
95
+ */
96
+ function syncTaskA11y(root) {
97
+ root.querySelectorAll(TASK_ITEM).forEach((li) => {
98
+ if (li.closest("[data-rt-isolate]")) return;
99
+ if (!(li.textContent ?? "").replace(/​/g, "").trim() && !li.querySelector("img") && isChecked(li)) li.setAttribute("data-checked", "false");
100
+ if (!li.hasAttribute("data-checked")) li.setAttribute("data-checked", "false");
101
+ li.setAttribute("role", "checkbox");
102
+ li.setAttribute("aria-checked", isChecked(li) ? "true" : "false");
103
+ });
104
+ }
105
+ /** Takes the editor-only a11y attributes off a clone before it is stored. */
106
+ function stripTaskA11y(clone) {
107
+ clone.querySelectorAll(TASK_ITEM).forEach((li) => {
108
+ li.removeAttribute("role");
109
+ li.removeAttribute("aria-checked");
110
+ });
111
+ }
112
+ //#endregion
113
+ export { TASK_HIT_PX, checklistShortcut, insertChecklist, setTaskChecked, stripTaskA11y, syncTaskA11y, taskItemOf, toggleTaskAtPointer, toggleTaskFromKey };
114
+
115
+ //# sourceMappingURL=checklist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checklist.js","names":[],"sources":["../../../src/controls/rich-text/checklist.tsx"],"sourcesContent":["/**\n * RICH TEXT · Checklist (kit-richtext-ui-spec.md §8): V2's task list, a native list stored as PT `listItem: \"check\"`\n * with `checked`, written in the editor as `<ul data-task=\"true\"><li data-checked=\"true|false\">`.\n *\n * The look is the editor's (editor.tsx, from V2 rich-text-editor.tsx:54-55); this module is the behaviour:\n * insert V2 ops.ts:147-155: the empty line becomes a one-item unchecked task list, caret inside\n * shortcut V2 ops.ts:224-240: \"[] \" or \"[ ] \" at the start of a paragraph\n * tick V2 rich-text-editor.tsx:1314-1321: a press within 22px of the item's left edge toggles it\n * new item V2 rich-text-editor.tsx:821: an empty item is unchecked (Enter after a ticked item starts unticked)\n * a11y signed deviation (e1, 2026-10-24; behaviour only, pixels V2-exact): each item is role=\"checkbox\" with\n * aria-checked, and Space with the caret at the item's start ticks it. Editor-only: stripTaskA11y\n * takes the attributes off the HTML the editor stores.\n */\nimport { placeCaret, textBeforeCaret, topBlock } from \"./dom\";\nimport { takeEmptyBlock } from \"./ops\";\n\n/** The box is the item's ::before, 16px at the start of its 28px indent (pl-7); V2 hit-tests 22px. */\nexport const TASK_HIT_PX = 22;\nconst TASK_ITEM = \"ul[data-task] > li\";\n\nconst isTaskItem = (el: Element | null): el is HTMLLIElement => !!el && el.tagName === \"LI\" && !!el.parentElement?.hasAttribute(\"data-task\");\nconst isChecked = (li: Element) => li.getAttribute(\"data-checked\") === \"true\";\n\n/** The task item a node sits in, inside `root` and outside any rich block. */\nexport function taskItemOf(root: HTMLElement, node: Node | null): HTMLLIElement | null {\n const el = node ? (node.nodeType === 1 ? (node as Element) : node.parentElement) : null;\n const li = el?.closest(\"li\") ?? null;\n return isTaskItem(li) && root.contains(li) && !li.closest(\"[data-rt-isolate]\") ? li : null;\n}\n\n/** Ticks or unticks an item; keeps the editor-only a11y state in step. */\nexport function setTaskChecked(li: HTMLLIElement, checked: boolean) {\n li.setAttribute(\"data-checked\", checked ? \"true\" : \"false\");\n li.setAttribute(\"aria-checked\", checked ? \"true\" : \"false\");\n}\n\n/** Catalog insert: the empty line becomes `<ul data-task><li data-checked=\"false\">`, caret inside. */\nexport function insertChecklist(root: HTMLElement): HTMLLIElement {\n const t = takeEmptyBlock(root);\n const ul = document.createElement(\"ul\");\n ul.setAttribute(\"data-task\", \"true\");\n ul.innerHTML = `<li data-checked=\"false\"><br></li>`;\n t.replaceWith(ul);\n const li = ul.firstElementChild as HTMLLIElement;\n syncTaskA11y(root);\n placeCaret(li);\n return li;\n}\n\n/** \"[] \" / \"[ ] \" typed at the start of a paragraph turns it into a task item. Returns true when it converted. */\nexport function checklistShortcut(root: HTMLElement, data: string | null): boolean {\n if (data !== \" \") return false;\n const s = window.getSelection();\n if (!s || !s.rangeCount || !s.isCollapsed) return false;\n const blk = topBlock(root, s.anchorNode);\n if (!blk || blk.tagName !== \"P\") return false;\n const before = textBeforeCaret(blk)?.replace(/ /g, \" \");\n if (before !== \"[] \" && before !== \"[ ] \") return false;\n const r = document.createRange();\n r.selectNodeContents(blk);\n r.setEnd(s.anchorNode!, s.anchorOffset);\n r.deleteContents();\n const ul = document.createElement(\"ul\");\n ul.setAttribute(\"data-task\", \"true\");\n const li = document.createElement(\"li\");\n li.setAttribute(\"data-checked\", \"false\");\n li.innerHTML = blk.textContent || blk.querySelector(\"img\") ? blk.innerHTML : \"<br>\";\n ul.append(li);\n blk.replaceWith(ul);\n syncTaskA11y(root);\n placeCaret(li, false);\n return true;\n}\n\n/**\n * A press on an item's box toggles it (mousedown, so the caret does not move). Returns true when it ticked; the\n * editor then prevents the default and emits.\n */\nexport function toggleTaskAtPointer(root: HTMLElement, target: EventTarget | null, clientX: number): boolean {\n const li = target instanceof Node ? taskItemOf(root, target) : null;\n if (!li || clientX - li.getBoundingClientRect().left >= TASK_HIT_PX) return false;\n setTaskChecked(li, !isChecked(li));\n return true;\n}\n\n/** Space with the caret at the very start of an item ticks it (a leading space is never text). Returns true when it did. */\nexport function toggleTaskFromKey(root: HTMLElement, key: string): boolean {\n if (key !== \" \") return false;\n const s = window.getSelection();\n if (!s || !s.rangeCount || !s.isCollapsed) return false;\n const li = taskItemOf(root, s.anchorNode);\n if (!li) return false;\n const r = document.createRange();\n r.selectNodeContents(li);\n r.setEnd(s.anchorNode!, s.anchorOffset);\n if (r.toString().replace(/​/g, \"\") !== \"\") return false;\n setTaskChecked(li, !isChecked(li));\n return true;\n}\n\n/**\n * After each input: an empty item is unchecked (V2), and every item carries role=\"checkbox\" + aria-checked. Call it\n * from the editor's input handler and after load.\n */\nexport function syncTaskA11y(root: HTMLElement) {\n root.querySelectorAll<HTMLLIElement>(TASK_ITEM).forEach((li) => {\n if (li.closest(\"[data-rt-isolate]\")) return;\n if (!(li.textContent ?? \"\").replace(/​/g, \"\").trim() && !li.querySelector(\"img\") && isChecked(li)) li.setAttribute(\"data-checked\", \"false\");\n if (!li.hasAttribute(\"data-checked\")) li.setAttribute(\"data-checked\", \"false\");\n li.setAttribute(\"role\", \"checkbox\");\n li.setAttribute(\"aria-checked\", isChecked(li) ? \"true\" : \"false\");\n });\n}\n\n/** Takes the editor-only a11y attributes off a clone before it is stored. */\nexport function stripTaskA11y(clone: HTMLElement) {\n clone.querySelectorAll(TASK_ITEM).forEach((li) => {\n li.removeAttribute(\"role\");\n li.removeAttribute(\"aria-checked\");\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,IAAa,cAAc;AAC3B,IAAM,YAAY;AAElB,IAAM,cAAc,OAA4C,CAAC,CAAC,MAAM,GAAG,YAAY,QAAQ,CAAC,CAAC,GAAG,eAAe,aAAa,WAAW;AAC3I,IAAM,aAAa,OAAgB,GAAG,aAAa,cAAc,MAAM;;AAGvE,SAAgB,WAAW,MAAmB,MAAyC;CAErF,MAAM,MADK,OAAQ,KAAK,aAAa,IAAK,OAAmB,KAAK,gBAAiB,KAAA,EACpE,QAAQ,IAAI,KAAK;CAChC,OAAO,WAAW,EAAE,KAAK,KAAK,SAAS,EAAE,KAAK,CAAC,GAAG,QAAQ,mBAAmB,IAAI,KAAK;AACxF;;AAGA,SAAgB,eAAe,IAAmB,SAAkB;CAClE,GAAG,aAAa,gBAAgB,UAAU,SAAS,OAAO;CAC1D,GAAG,aAAa,gBAAgB,UAAU,SAAS,OAAO;AAC5D;;AAGA,SAAgB,gBAAgB,MAAkC;CAChE,MAAM,IAAI,eAAe,IAAI;CAC7B,MAAM,KAAK,SAAS,cAAc,IAAI;CACtC,GAAG,aAAa,aAAa,MAAM;CACnC,GAAG,YAAY;CACf,EAAE,YAAY,EAAE;CAChB,MAAM,KAAK,GAAG;CACd,aAAa,IAAI;CACjB,WAAW,EAAE;CACb,OAAO;AACT;;AAGA,SAAgB,kBAAkB,MAAmB,MAA8B;CACjF,IAAI,SAAS,KAAK,OAAO;CACzB,MAAM,IAAI,OAAO,aAAa;CAC9B,IAAI,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC,EAAE,aAAa,OAAO;CAClD,MAAM,MAAM,SAAS,MAAM,EAAE,UAAU;CACvC,IAAI,CAAC,OAAO,IAAI,YAAY,KAAK,OAAO;CACxC,MAAM,SAAS,gBAAgB,GAAG,CAAC,EAAE,QAAQ,MAAM,GAAG;CACtD,IAAI,WAAW,SAAS,WAAW,QAAQ,OAAO;CAClD,MAAM,IAAI,SAAS,YAAY;CAC/B,EAAE,mBAAmB,GAAG;CACxB,EAAE,OAAO,EAAE,YAAa,EAAE,YAAY;CACtC,EAAE,eAAe;CACjB,MAAM,KAAK,SAAS,cAAc,IAAI;CACtC,GAAG,aAAa,aAAa,MAAM;CACnC,MAAM,KAAK,SAAS,cAAc,IAAI;CACtC,GAAG,aAAa,gBAAgB,OAAO;CACvC,GAAG,YAAY,IAAI,eAAe,IAAI,cAAc,KAAK,IAAI,IAAI,YAAY;CAC7E,GAAG,OAAO,EAAE;CACZ,IAAI,YAAY,EAAE;CAClB,aAAa,IAAI;CACjB,WAAW,IAAI,KAAK;CACpB,OAAO;AACT;;;;;AAMA,SAAgB,oBAAoB,MAAmB,QAA4B,SAA0B;CAC3G,MAAM,KAAK,kBAAkB,OAAO,WAAW,MAAM,MAAM,IAAI;CAC/D,IAAI,CAAC,MAAM,UAAU,GAAG,sBAAsB,CAAC,CAAC,QAAA,IAAqB,OAAO;CAC5E,eAAe,IAAI,CAAC,UAAU,EAAE,CAAC;CACjC,OAAO;AACT;;AAGA,SAAgB,kBAAkB,MAAmB,KAAsB;CACzE,IAAI,QAAQ,KAAK,OAAO;CACxB,MAAM,IAAI,OAAO,aAAa;CAC9B,IAAI,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC,EAAE,aAAa,OAAO;CAClD,MAAM,KAAK,WAAW,MAAM,EAAE,UAAU;CACxC,IAAI,CAAC,IAAI,OAAO;CAChB,MAAM,IAAI,SAAS,YAAY;CAC/B,EAAE,mBAAmB,EAAE;CACvB,EAAE,OAAO,EAAE,YAAa,EAAE,YAAY;CACtC,IAAI,EAAE,SAAS,CAAC,CAAC,QAAQ,MAAM,EAAE,MAAM,IAAI,OAAO;CAClD,eAAe,IAAI,CAAC,UAAU,EAAE,CAAC;CACjC,OAAO;AACT;;;;;AAMA,SAAgB,aAAa,MAAmB;CAC9C,KAAK,iBAAgC,SAAS,CAAC,CAAC,SAAS,OAAO;EAC9D,IAAI,GAAG,QAAQ,mBAAmB,GAAG;EACrC,IAAI,EAAE,GAAG,eAAe,GAAA,CAAI,QAAQ,MAAM,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,GAAG,cAAc,KAAK,KAAK,UAAU,EAAE,GAAG,GAAG,aAAa,gBAAgB,OAAO;EAC1I,IAAI,CAAC,GAAG,aAAa,cAAc,GAAG,GAAG,aAAa,gBAAgB,OAAO;EAC7E,GAAG,aAAa,QAAQ,UAAU;EAClC,GAAG,aAAa,gBAAgB,UAAU,EAAE,IAAI,SAAS,OAAO;CAClE,CAAC;AACH;;AAGA,SAAgB,cAAc,OAAoB;CAChD,MAAM,iBAAiB,SAAS,CAAC,CAAC,SAAS,OAAO;EAChD,GAAG,gBAAgB,MAAM;EACzB,GAAG,gBAAgB,cAAc;CACnC,CAAC;AACH"}
@@ -13,7 +13,7 @@
13
13
  import type { FieldPath, FieldSchema } from "../../schema/types";
14
14
  import type { UnknownValue } from "../unknown-value";
15
15
  /** What a field may hold, in the editor's vocabulary. The editor offers nothing outside it. */
16
- export type RichTextFeature = "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "blockquote" | "ul" | "ol" | "pre" | "hr" | "bold" | "italic" | "underline" | "strike" | "sup" | "sub" | "code" | "link" | "image" | "table" | "pullquote" | "takeaways" | "callout" | "toggle" | "video" | "file";
16
+ export type RichTextFeature = "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "blockquote" | "ul" | "ol" | "pre" | "hr" | "bold" | "italic" | "underline" | "strike" | "sup" | "sub" | "code" | "link" | "image" | "table" | "pullquote" | "takeaways" | "callout" | "toggle" | "video" | "file" | "checklist" | "button" | "embed" | "gallery" | "toc" | "footnote" | "columns" | "author" | "related" | "embedAspect";
17
17
  export declare const ALL_FEATURES: readonly RichTextFeature[];
18
18
  export interface RichTextRead {
19
19
  /** the HTML the editor opens on */
@@ -33,6 +33,20 @@ export interface RichTextRead {
33
33
  * the editor with change-to and remove (H4; see `remap`). Absent or empty = none.
34
34
  */
35
35
  undeclared?: UnknownValue[];
36
+ /**
37
+ * Where an author bio and related posts point (plan RT8), from the field's config: the authors and posts collections,
38
+ * and the entry being edited (left out of the automatic related modes). Author bio is offered only with `authors`.
39
+ * `topic: false`: the posts collection has no tags or category field, so "Same topic" cannot rank there (the entry
40
+ * write refuses it, relatedPosts.no_topic_field); a new block is Newest and Same topic is not offered. Absent = has one.
41
+ */
42
+ /** `postsNoun`: what one post is called (the posts collection's singular, "story"), so Hand-picked says "Create new story" */
43
+ references?: {
44
+ authors?: string;
45
+ posts?: string;
46
+ entryId?: string;
47
+ topic?: boolean;
48
+ postsNoun?: string;
49
+ };
36
50
  }
37
51
  export interface RichTextWrite {
38
52
  /** the value to store, in the surface's own shape */
@@ -26,7 +26,17 @@ var ALL_FEATURES = [
26
26
  "callout",
27
27
  "toggle",
28
28
  "video",
29
- "file"
29
+ "file",
30
+ "checklist",
31
+ "button",
32
+ "embed",
33
+ "gallery",
34
+ "toc",
35
+ "footnote",
36
+ "columns",
37
+ "author",
38
+ "related",
39
+ "embedAspect"
30
40
  ];
31
41
  //#endregion
32
42
  export { ALL_FEATURES };