@bettercms-ai/kit 2.0.0-next.0 → 2.0.0-next.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 (85) hide show
  1. package/dist/adapters/lossy.d.ts +5 -1
  2. package/dist/adapters/lossy.js +10 -5
  3. package/dist/adapters/lossy.js.map +1 -1
  4. package/dist/adapters/prod-types.d.ts +9 -22
  5. package/dist/adapters/prod-types.js +36 -35
  6. package/dist/adapters/prod-types.js.map +1 -1
  7. package/dist/adapters/schema.js +56 -39
  8. package/dist/adapters/schema.js.map +1 -1
  9. package/dist/context.d.ts +39 -0
  10. package/dist/context.js +40 -1
  11. package/dist/context.js.map +1 -1
  12. package/dist/controls/link.js +112 -196
  13. package/dist/controls/link.js.map +1 -1
  14. package/dist/controls/location.js +3 -8
  15. package/dist/controls/location.js.map +1 -1
  16. package/dist/controls/media.d.ts +0 -14
  17. package/dist/controls/media.js +52 -95
  18. package/dist/controls/media.js.map +1 -1
  19. package/dist/controls/rich-text/article-shell.d.ts +145 -0
  20. package/dist/controls/rich-text/article-shell.js +510 -0
  21. package/dist/controls/rich-text/article-shell.js.map +1 -0
  22. package/dist/controls/rich-text/author.d.ts +25 -0
  23. package/dist/controls/rich-text/author.js +104 -0
  24. package/dist/controls/rich-text/author.js.map +1 -0
  25. package/dist/controls/rich-text/blocks.d.ts +13 -2
  26. package/dist/controls/rich-text/blocks.js +86 -638
  27. package/dist/controls/rich-text/blocks.js.map +1 -1
  28. package/dist/controls/rich-text/button.d.ts +23 -0
  29. package/dist/controls/rich-text/button.js +85 -0
  30. package/dist/controls/rich-text/button.js.map +1 -0
  31. package/dist/controls/rich-text/catalog.d.ts +3 -3
  32. package/dist/controls/rich-text/catalog.js +20 -4
  33. package/dist/controls/rich-text/catalog.js.map +1 -1
  34. package/dist/controls/rich-text/checklist.d.ts +24 -0
  35. package/dist/controls/rich-text/checklist.js +115 -0
  36. package/dist/controls/rich-text/checklist.js.map +1 -0
  37. package/dist/controls/rich-text/codec.d.ts +1 -1
  38. package/dist/controls/rich-text/codec.js +4 -1
  39. package/dist/controls/rich-text/codec.js.map +1 -1
  40. package/dist/controls/rich-text/columns.d.ts +41 -0
  41. package/dist/controls/rich-text/columns.js +181 -0
  42. package/dist/controls/rich-text/columns.js.map +1 -0
  43. package/dist/controls/rich-text/dom.d.ts +5 -4
  44. package/dist/controls/rich-text/dom.js +13 -4
  45. package/dist/controls/rich-text/dom.js.map +1 -1
  46. package/dist/controls/rich-text/editor.js +92 -10
  47. package/dist/controls/rich-text/editor.js.map +1 -1
  48. package/dist/controls/rich-text/embed.d.ts +28 -0
  49. package/dist/controls/rich-text/embed.js +210 -0
  50. package/dist/controls/rich-text/embed.js.map +1 -0
  51. package/dist/controls/rich-text/footnote.d.ts +41 -0
  52. package/dist/controls/rich-text/footnote.js +185 -0
  53. package/dist/controls/rich-text/footnote.js.map +1 -0
  54. package/dist/controls/rich-text/gallery.d.ts +28 -0
  55. package/dist/controls/rich-text/gallery.js +167 -0
  56. package/dist/controls/rich-text/gallery.js.map +1 -0
  57. package/dist/controls/rich-text/link-form.d.ts +3 -1
  58. package/dist/controls/rich-text/link-form.js +2 -2
  59. package/dist/controls/rich-text/link-form.js.map +1 -1
  60. package/dist/controls/rich-text/related.d.ts +36 -0
  61. package/dist/controls/rich-text/related.js +213 -0
  62. package/dist/controls/rich-text/related.js.map +1 -0
  63. package/dist/controls/rich-text/toc.d.ts +46 -0
  64. package/dist/controls/rich-text/toc.js +161 -0
  65. package/dist/controls/rich-text/toc.js.map +1 -0
  66. package/dist/core/core.d.ts +10 -3
  67. package/dist/core/core.js +22 -6
  68. package/dist/core/core.js.map +1 -1
  69. package/dist/core/facets.d.ts +8 -0
  70. package/dist/core/facets.js +9 -1
  71. package/dist/core/facets.js.map +1 -1
  72. package/dist/core/index.js +2 -2
  73. package/dist/index.js +4 -4
  74. package/dist/library/kit-catalog.json +45 -44
  75. package/dist/schema/types.d.ts +7 -0
  76. package/dist/schema/types.js.map +1 -1
  77. package/dist/styles.css +1 -1
  78. package/dist/ui/autocomplete.d.ts +29 -1
  79. package/dist/ui/autocomplete.js +102 -47
  80. package/dist/ui/autocomplete.js.map +1 -1
  81. package/dist/ui/button.js +3 -5
  82. package/dist/ui/button.js.map +1 -1
  83. package/dist/ui/index.js +2 -2
  84. package/dist/ui/toast.js +1 -1
  85. package/package.json +3 -1
@@ -0,0 +1,181 @@
1
+ import { cn } from "../../lib/cn.js";
2
+ import { Button, IconButton } from "../../ui/button.js";
3
+ import { Segmented } from "../../ui/controls.js";
4
+ import { Tooltip } from "../../ui/overlay.js";
5
+ import { placeCaret, rid } from "./dom.js";
6
+ import * as React from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { IconMinus, IconPlus, IconTrash } from "@tabler/icons-react";
9
+ //#region src/controls/rich-text/columns.tsx
10
+ /**
11
+ * RICH TEXT · Columns (kit-richtext-ui-spec.md §4): V2's columns island, stored as the PT block object
12
+ * `{ _type: "columns", gap: "s" | "m" | "l", columns: { _key, content: PortableTextBlock[] }[] }`: 2–4 columns of
13
+ * text blocks only, depth 1 (plan R5). The editor holds each column as its HTML, as V2 does; the codec maps it.
14
+ *
15
+ * block V2 rich-text/blocks.tsx:536-570: a grid of dashed columns (gap s 8px · m 16px · l 28px), each a nested
16
+ * Editable (paragraphs and marks; paste is plain text), and the bar under it: Add column (≤4), Remove column
17
+ * (the last, ≥2), Gap S | M | L, Remove columns. Escape inside a column leaves the island (Island onKeyDown).
18
+ * editable V2 rich-text/blocks.tsx:54-120, ported here (the kit's own PlainEditable is plain text only).
19
+ */
20
+ var COLUMNS_MIN = 2;
21
+ var COLUMNS_MAX = 4;
22
+ var columnsDefault = () => ({
23
+ gap: "m",
24
+ cols: [{
25
+ id: rid("col"),
26
+ html: ""
27
+ }, {
28
+ id: rid("col"),
29
+ html: ""
30
+ }]
31
+ });
32
+ var GAP = {
33
+ s: "8px",
34
+ m: "16px",
35
+ l: "28px"
36
+ };
37
+ /** V2 blocks.tsx:191: the controls row under a block, muted at rest, full strength on hover / focus */
38
+ var BAR = "mt-1.5 flex flex-wrap items-center gap-1 text-kit-fg-3 opacity-70 transition-opacity group-hover/blk:opacity-100 group-focus-within/blk:opacity-100 pointer-coarse:opacity-100";
39
+ var Sep = () => /* @__PURE__ */ jsx("span", {
40
+ "aria-hidden": true,
41
+ className: "mx-0.5 h-4 w-px shrink-0 bg-kit-line"
42
+ });
43
+ /** A small rich-text region inside a block (V2 Editable): paragraphs and marks, plain-text paste, a placeholder. */
44
+ function Editable({ html, onChange, placeholder, className, readOnly, label, autoFocus, ...rest }) {
45
+ const ref = React.useRef(null);
46
+ React.useLayoutEffect(() => {
47
+ const el = ref.current;
48
+ if (!el || document.activeElement === el) return;
49
+ if (el.innerHTML !== html) el.innerHTML = html;
50
+ }, [html]);
51
+ React.useEffect(() => {
52
+ if (!autoFocus || !ref.current) return;
53
+ ref.current.focus({ preventScroll: false });
54
+ placeCaret(ref.current);
55
+ }, []);
56
+ return /* @__PURE__ */ jsx("div", {
57
+ ...rest,
58
+ ref,
59
+ role: "textbox",
60
+ "aria-label": label,
61
+ "aria-multiline": true,
62
+ tabIndex: readOnly ? -1 : 0,
63
+ contentEditable: !readOnly,
64
+ suppressContentEditableWarning: true,
65
+ spellCheck: true,
66
+ "data-placeholder": placeholder,
67
+ onInput: (e) => {
68
+ const el = e.currentTarget;
69
+ if (!el.textContent && !el.querySelector("img,br + br")) el.innerHTML = "";
70
+ onChange(el.innerHTML);
71
+ },
72
+ onPaste: (e) => {
73
+ e.preventDefault();
74
+ document.execCommand?.("insertText", false, e.clipboardData.getData("text/plain"));
75
+ },
76
+ className: cn("min-w-0 cursor-text whitespace-pre-wrap break-words outline-none focus-visible:shadow-none empty:before:pointer-events-none empty:before:text-kit-fg-4 empty:before:content-[attr(data-placeholder)]", className)
77
+ });
78
+ }
79
+ function ColumnsBlock({ m, set, el, ctx, autoFocus }) {
80
+ const ro = !!ctx.readOnly;
81
+ return /* @__PURE__ */ jsxs("div", {
82
+ className: "group/blk",
83
+ children: [/* @__PURE__ */ jsx("div", {
84
+ className: "grid",
85
+ style: {
86
+ gridTemplateColumns: `repeat(${m.cols.length}, minmax(0, 1fr))`,
87
+ gap: GAP[m.gap]
88
+ },
89
+ children: m.cols.map((c, i) => /* @__PURE__ */ jsx("div", {
90
+ className: "min-h-14 rounded-kit-md border border-dashed border-kit-line px-2.5 py-2 transition-colors focus-within:border-kit-line-strong hover:border-kit-line-strong",
91
+ children: /* @__PURE__ */ jsx(Editable, {
92
+ label: `Column ${i + 1}`,
93
+ html: c.html,
94
+ readOnly: ro,
95
+ autoFocus: autoFocus && i === 0,
96
+ placeholder: `Column ${i + 1}`,
97
+ onChange: (html) => set((x) => ({
98
+ ...x,
99
+ cols: x.cols.map((y) => y.id === c.id ? {
100
+ ...y,
101
+ html
102
+ } : y)
103
+ }), `Edit column ${i + 1}`),
104
+ className: "min-h-10 [&_p+p]:mt-2"
105
+ })
106
+ }, c.id))
107
+ }), ro ? null : /* @__PURE__ */ jsxs("div", {
108
+ className: BAR,
109
+ children: [
110
+ /* @__PURE__ */ jsx(Button, {
111
+ size: "xs",
112
+ variant: "ghost",
113
+ leftIcon: /* @__PURE__ */ jsx(IconPlus, { stroke: 1.5 }),
114
+ disabled: m.cols.length >= 4,
115
+ onClick: () => set((x) => ({
116
+ ...x,
117
+ cols: [...x.cols, {
118
+ id: rid("col"),
119
+ html: ""
120
+ }]
121
+ }), "Add column"),
122
+ children: "Add column"
123
+ }),
124
+ /* @__PURE__ */ jsx(Button, {
125
+ size: "xs",
126
+ variant: "ghost",
127
+ leftIcon: /* @__PURE__ */ jsx(IconMinus, { stroke: 1.5 }),
128
+ disabled: m.cols.length <= 2,
129
+ onClick: () => set((x) => ({
130
+ ...x,
131
+ cols: x.cols.slice(0, -1)
132
+ }), "Remove column"),
133
+ children: "Remove column"
134
+ }),
135
+ /* @__PURE__ */ jsx(Sep, {}),
136
+ /* @__PURE__ */ jsx("span", {
137
+ className: "px-1 text-kit-xs text-kit-fg-3",
138
+ children: "Gap"
139
+ }),
140
+ /* @__PURE__ */ jsx(Segmented, {
141
+ "aria-label": "Gap",
142
+ size: "sm",
143
+ value: m.gap,
144
+ onChange: (gap) => set((x) => ({
145
+ ...x,
146
+ gap
147
+ }), "Change column gap"),
148
+ items: [
149
+ {
150
+ value: "s",
151
+ label: "S"
152
+ },
153
+ {
154
+ value: "m",
155
+ label: "M"
156
+ },
157
+ {
158
+ value: "l",
159
+ label: "L"
160
+ }
161
+ ]
162
+ }),
163
+ /* @__PURE__ */ jsx("span", { className: "flex-1" }),
164
+ /* @__PURE__ */ jsx(Tooltip, {
165
+ content: "Remove columns",
166
+ children: /* @__PURE__ */ jsx(IconButton, {
167
+ size: "xs",
168
+ label: "Remove columns",
169
+ onClick: () => ctx.remove(el),
170
+ className: "hover:text-kit-danger-fg",
171
+ children: /* @__PURE__ */ jsx(IconTrash, { stroke: 1.5 })
172
+ })
173
+ })
174
+ ]
175
+ })]
176
+ });
177
+ }
178
+ //#endregion
179
+ export { COLUMNS_MAX, COLUMNS_MIN, ColumnsBlock, Editable, columnsDefault };
180
+
181
+ //# sourceMappingURL=columns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"columns.js","names":[],"sources":["../../../src/controls/rich-text/columns.tsx"],"sourcesContent":["/**\n * RICH TEXT · Columns (kit-richtext-ui-spec.md §4): V2's columns island, stored as the PT block object\n * `{ _type: \"columns\", gap: \"s\" | \"m\" | \"l\", columns: { _key, content: PortableTextBlock[] }[] }`: 2–4 columns of\n * text blocks only, depth 1 (plan R5). The editor holds each column as its HTML, as V2 does; the codec maps it.\n *\n * block V2 rich-text/blocks.tsx:536-570: a grid of dashed columns (gap s 8px · m 16px · l 28px), each a nested\n * Editable (paragraphs and marks; paste is plain text), and the bar under it: Add column (≤4), Remove column\n * (the last, ≥2), Gap S | M | L, Remove columns. Escape inside a column leaves the island (Island onKeyDown).\n * editable V2 rich-text/blocks.tsx:54-120, ported here (the kit's own PlainEditable is plain text only).\n */\nimport * as React from \"react\";\nimport { IconMinus, IconPlus, IconTrash } from \"@tabler/icons-react\";\nimport { cn } from \"../../lib/cn\";\nimport { Button, IconButton } from \"../../ui/button\";\nimport { Segmented } from \"../../ui/controls\";\nimport { Tooltip } from \"../../ui/overlay\";\nimport type { IslandCtx } from \"./blocks\";\nimport { placeCaret, rid, type IslandEl } from \"./dom\";\n\nexport type ColumnsGap = \"s\" | \"m\" | \"l\";\nexport interface ColumnsModel {\n gap: ColumnsGap;\n cols: { id: string; html: string }[];\n}\nexport const COLUMNS_MIN = 2;\nexport const COLUMNS_MAX = 4;\nexport const columnsDefault = (): ColumnsModel => ({ gap: \"m\", cols: [{ id: rid(\"col\"), html: \"\" }, { id: rid(\"col\"), html: \"\" }] });\nconst GAP: Record<ColumnsGap, string> = { s: \"8px\", m: \"16px\", l: \"28px\" };\n/** V2 blocks.tsx:191: the controls row under a block, muted at rest, full strength on hover / focus */\nconst BAR = \"mt-1.5 flex flex-wrap items-center gap-1 text-kit-fg-3 opacity-70 transition-opacity group-hover/blk:opacity-100 group-focus-within/blk:opacity-100 pointer-coarse:opacity-100\";\nconst Sep = () => <span aria-hidden className=\"mx-0.5 h-4 w-px shrink-0 bg-kit-line\" />;\n\n/** A small rich-text region inside a block (V2 Editable): paragraphs and marks, plain-text paste, a placeholder. */\nexport function Editable({\n html, onChange, placeholder, className, readOnly, label, autoFocus, ...rest\n}: {\n html: string;\n onChange: (v: string) => void;\n placeholder?: string;\n className?: string;\n readOnly?: boolean;\n label: string;\n autoFocus?: boolean;\n} & Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\">) {\n const ref = React.useRef<HTMLDivElement>(null);\n React.useLayoutEffect(() => {\n const el = ref.current;\n if (!el || document.activeElement === el) return;\n if (el.innerHTML !== html) el.innerHTML = html;\n }, [html]);\n React.useEffect(() => {\n if (!autoFocus || !ref.current) return;\n ref.current.focus({ preventScroll: false });\n placeCaret(ref.current);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n return (\n <div\n {...rest}\n ref={ref}\n role=\"textbox\"\n aria-label={label}\n aria-multiline\n tabIndex={readOnly ? -1 : 0}\n contentEditable={!readOnly}\n suppressContentEditableWarning\n spellCheck\n data-placeholder={placeholder}\n onInput={(e) => {\n const el = e.currentTarget;\n if (!el.textContent && !el.querySelector(\"img,br + br\")) el.innerHTML = \"\";\n onChange(el.innerHTML);\n }}\n onPaste={(e) => {\n e.preventDefault();\n document.execCommand?.(\"insertText\", false, e.clipboardData.getData(\"text/plain\"));\n }}\n className={cn(\n \"min-w-0 cursor-text whitespace-pre-wrap break-words outline-none focus-visible:shadow-none empty:before:pointer-events-none empty:before:text-kit-fg-4 empty:before:content-[attr(data-placeholder)]\",\n className,\n )}\n />\n );\n}\n\nexport function ColumnsBlock({\n m, set, el, ctx, autoFocus,\n}: {\n m: ColumnsModel;\n set: (fn: (m: ColumnsModel) => ColumnsModel, label?: string) => void;\n el: IslandEl;\n ctx: IslandCtx;\n autoFocus: boolean;\n}) {\n const ro = !!ctx.readOnly;\n return (\n <div className=\"group/blk\">\n <div className=\"grid\" style={{ gridTemplateColumns: `repeat(${m.cols.length}, minmax(0, 1fr))`, gap: GAP[m.gap] }}>\n {m.cols.map((c, i) => (\n <div key={c.id} className=\"min-h-14 rounded-kit-md border border-dashed border-kit-line px-2.5 py-2 transition-colors focus-within:border-kit-line-strong hover:border-kit-line-strong\">\n <Editable\n label={`Column ${i + 1}`}\n html={c.html}\n readOnly={ro}\n autoFocus={autoFocus && i === 0}\n placeholder={`Column ${i + 1}`}\n onChange={(html) => set((x) => ({ ...x, cols: x.cols.map((y) => (y.id === c.id ? { ...y, html } : y)) }), `Edit column ${i + 1}`)}\n className=\"min-h-10 [&_p+p]:mt-2\"\n />\n </div>\n ))}\n </div>\n {ro ? null : (\n <div className={BAR}>\n <Button\n size=\"xs\"\n variant=\"ghost\"\n leftIcon={<IconPlus stroke={1.5} />}\n disabled={m.cols.length >= COLUMNS_MAX}\n onClick={() => set((x) => ({ ...x, cols: [...x.cols, { id: rid(\"col\"), html: \"\" }] }), \"Add column\")}\n >\n Add column\n </Button>\n <Button\n size=\"xs\"\n variant=\"ghost\"\n leftIcon={<IconMinus stroke={1.5} />}\n disabled={m.cols.length <= COLUMNS_MIN}\n onClick={() => set((x) => ({ ...x, cols: x.cols.slice(0, -1) }), \"Remove column\")}\n >\n Remove column\n </Button>\n <Sep />\n <span className=\"px-1 text-kit-xs text-kit-fg-3\">Gap</span>\n <Segmented<ColumnsGap>\n aria-label=\"Gap\"\n size=\"sm\"\n value={m.gap}\n onChange={(gap) => set((x) => ({ ...x, gap }), \"Change column gap\")}\n items={[{ value: \"s\", label: \"S\" }, { value: \"m\", label: \"M\" }, { value: \"l\", label: \"L\" }]}\n />\n <span className=\"flex-1\" />\n <Tooltip content=\"Remove columns\">\n <IconButton size=\"xs\" label=\"Remove columns\" onClick={() => ctx.remove(el)} className=\"hover:text-kit-danger-fg\">\n <IconTrash stroke={1.5} />\n </IconButton>\n </Tooltip>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -28,9 +28,9 @@
28
28
  * <div data-body><p>…` (the message is plain text: paragraphs and line breaks)
29
29
  * toggle `bcmsBlock` builtin:toggle {title, body}: `<details data-rt="toggle"><summary>…<div data-body><p>…`
30
30
  * video the `video` block {src, title}: `<div data-rt="video" data-url data-title>`
31
- * embed `bcmsBlock` builtin:video {url, title} (the package's embed: `<iframe src title>` on the page), V2's
32
- * article Embed: `<figure data-rt="embed" data-url data-title><a href>…`. V2's Shape (`aspect`) has no
33
- * stored key, so it is not offered: every embed draws 16:9.
31
+ * embed V2's article Embed {url, title, aspect}: `<figure data-rt="embed" data-url data-title data-aspect><a href>…`
32
+ * (embed.tsx). Stored today as `bcmsBlock` builtin:video {url, title}, which keeps no aspect; the PT
33
+ * `embed` block (plan RT3) does.
34
34
  * file the `file` block {src, name, size}: `<div data-rt="file" data-url data-name data-size>`
35
35
  * component `bcmsBlock` "component" {componentId, overrides, variant} (CPO D28): `<div data-rt="component" data-ref
36
36
  * data-id data-name data-overrides data-variant>` — a linked instance of a project component, shown as a card.
@@ -95,10 +95,11 @@ export interface VideoModel {
95
95
  title?: string;
96
96
  extra?: string;
97
97
  }
98
- /** V2 EmbedModel (article.ts) without `aspect`, which the stored block has no key for. */
98
+ /** V2 EmbedModel (article.ts); `aspect` absent draws 16:9 (embed.tsx). */
99
99
  export interface EmbedModel {
100
100
  url?: string;
101
101
  title?: string;
102
+ aspect?: "16:9" | "4:3" | "1:1" | "9:16";
102
103
  extra?: string;
103
104
  }
104
105
  export type EmbedProvider = "youtube" | "vimeo" | "x" | "codepen" | "figma" | "maps" | "spotify" | "podcast" | "audio" | "link";
@@ -30,9 +30,9 @@ import { boolLabels, cellText, cellValue, emptyTable, formatNumber, isCellObject
30
30
  * <div data-body><p>…` (the message is plain text: paragraphs and line breaks)
31
31
  * toggle `bcmsBlock` builtin:toggle {title, body}: `<details data-rt="toggle"><summary>…<div data-body><p>…`
32
32
  * video the `video` block {src, title}: `<div data-rt="video" data-url data-title>`
33
- * embed `bcmsBlock` builtin:video {url, title} (the package's embed: `<iframe src title>` on the page), V2's
34
- * article Embed: `<figure data-rt="embed" data-url data-title><a href>…`. V2's Shape (`aspect`) has no
35
- * stored key, so it is not offered: every embed draws 16:9.
33
+ * embed V2's article Embed {url, title, aspect}: `<figure data-rt="embed" data-url data-title data-aspect><a href>…`
34
+ * (embed.tsx). Stored today as `bcmsBlock` builtin:video {url, title}, which keeps no aspect; the PT
35
+ * `embed` block (plan RT3) does.
36
36
  * file the `file` block {src, name, size}: `<div data-rt="file" data-url data-name data-size>`
37
37
  * component `bcmsBlock` "component" {componentId, overrides, variant} (CPO D28): `<div data-rt="component" data-ref
38
38
  * data-id data-name data-overrides data-variant>` — a linked instance of a project component, shown as a card.
@@ -64,6 +64,12 @@ var CALLOUT_TONES = [
64
64
  "tip",
65
65
  "warning"
66
66
  ];
67
+ var EMBED_ASPECT_VALUES = [
68
+ "16:9",
69
+ "4:3",
70
+ "1:1",
71
+ "9:16"
72
+ ];
67
73
  var hostOf = (url) => {
68
74
  try {
69
75
  return new URL(url).hostname.replace(/^www\./, "");
@@ -260,7 +266,7 @@ function renderStatic(kind, model) {
260
266
  }
261
267
  case "embed": {
262
268
  const m = model;
263
- return `<figure data-rt="embed"${attr("data-url", m.url)}${attr("data-title", m.title)}${xAttr(m.extra)}>${m.url ? `<a href="${escAttr(m.url)}">${esc(m.title || m.url)}</a>` : ""}</figure>`;
269
+ return `<figure data-rt="embed"${attr("data-url", m.url)}${attr("data-title", m.title)}${attr("data-aspect", m.aspect)}${xAttr(m.extra)}>${m.url ? `<a href="${escAttr(m.url)}">${esc(m.title || m.url)}</a>` : ""}</figure>`;
264
270
  }
265
271
  case "file": {
266
272
  const m = model;
@@ -810,8 +816,10 @@ function parseIsland(kind, el) {
810
816
  const m = {};
811
817
  const url = attrOf(el, "data-url");
812
818
  const title = attrOf(el, "data-title");
819
+ const aspect = attrOf(el, "data-aspect");
813
820
  if (url !== void 0) m.url = url;
814
821
  if (title !== void 0) m.title = title;
822
+ if (aspect !== void 0 && EMBED_ASPECT_VALUES.includes(aspect)) m.aspect = aspect;
815
823
  const extra = attrOf(el, "data-extra");
816
824
  if (extra) m.extra = extra;
817
825
  return m;
@@ -925,6 +933,7 @@ function serialize(root) {
925
933
  c.replaceWith(tpl.content);
926
934
  });
927
935
  clone.querySelectorAll("[data-hint]").forEach((n) => n.removeAttribute("data-hint"));
936
+ clone.querySelectorAll("ul[data-task] > li").forEach((li) => (li.removeAttribute("role"), li.removeAttribute("aria-checked")));
928
937
  while (clone.children.length > 1 && clone.lastElementChild && isEmptyP(clone.lastElementChild)) clone.lastElementChild.remove();
929
938
  const html = clone.innerHTML.replace(/​/g, "");
930
939
  return html === "<p><br></p>" || html === "<p></p>" ? "" : html;