@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
package/dist/context.d.ts CHANGED
@@ -168,7 +168,46 @@ export interface KitServices {
168
168
  * picker. The host passes the same function for the life of the form. Absent → the picker has no site tab.
169
169
  */
170
170
  useSiteIcons?: () => KitSiteIcon[];
171
+ /**
172
+ * The entries a rich text's related posts or author bio block shows (kit-richtext-blocks-plan.md RT8). `ids`: the
173
+ * hand-picked entries (an author bio's one author, related posts in hand-picked mode), answered in that order with
174
+ * any the host cannot find left out. `query`: related posts chosen for the reader (owner 2026-10-24: `topic` = same
175
+ * tags or category, `newest` = the collection newest first; `count` of them, `excludeId` the entry being edited).
176
+ * Absent → the blocks show their empty state; a rejection's message is shown as said.
177
+ */
178
+ resolveRichTextRefs?: (req: KitRichTextRefRequest) => Promise<KitRichTextRef[]>;
179
+ }
180
+ /** What a related posts or author bio block asks the host for (KitServices.resolveRichTextRefs). */
181
+ export interface KitRichTextRefRequest {
182
+ kind: "relatedPosts" | "authorBio";
183
+ ids?: string[];
184
+ query?: {
185
+ collection?: string;
186
+ mode: "topic" | "newest";
187
+ count: number;
188
+ excludeId?: string;
189
+ };
190
+ }
191
+ /** One resolved entry, as the block draws it: a post card (title, cover, category) or an author (avatar, title as name, bio). */
192
+ export interface KitRichTextRef {
193
+ id: string;
194
+ title: string;
195
+ href?: string;
196
+ cover?: string;
197
+ category?: string;
198
+ avatar?: string;
199
+ bio?: string;
171
200
  }
201
+ /**
202
+ * A React HOOK over KitServices.resolveRichTextRefs: `unavailable` when the host offers no resolver (draw the empty
203
+ * state), else `loading`, then `items` or `error` (a sentence). `req` null asks nothing (a block with nothing picked).
204
+ */
205
+ export declare function useRichTextRefs(req: KitRichTextRefRequest | null): {
206
+ items: KitRichTextRef[];
207
+ loading: boolean;
208
+ error?: string;
209
+ unavailable: boolean;
210
+ };
172
211
  /** Register app services once at module load (e.g. the Content feature registers its media library). */
173
212
  export declare function registerKitServices(s: KitServices): void;
174
213
  export declare function KitServicesProvider({ value, children }: {
package/dist/context.js CHANGED
@@ -37,6 +37,45 @@ var AI_PRESETS = [
37
37
  label: "Translate to Spanish"
38
38
  }
39
39
  ];
40
+ /**
41
+ * A React HOOK over KitServices.resolveRichTextRefs: `unavailable` when the host offers no resolver (draw the empty
42
+ * state), else `loading`, then `items` or `error` (a sentence). `req` null asks nothing (a block with nothing picked).
43
+ */
44
+ function useRichTextRefs(req) {
45
+ const { resolveRichTextRefs } = useKitServices();
46
+ const key = req ? JSON.stringify(req) : "";
47
+ const [state, setState] = React.useState(null);
48
+ React.useEffect(() => {
49
+ if (!resolveRichTextRefs || !key) return;
50
+ let live = true;
51
+ resolveRichTextRefs(JSON.parse(key)).then((items) => live && setState({
52
+ key,
53
+ items
54
+ }), (e) => live && setState({
55
+ key,
56
+ items: [],
57
+ error: e instanceof Error ? e.message : String(e)
58
+ }));
59
+ return () => void (live = false);
60
+ }, [resolveRichTextRefs, key]);
61
+ if (!resolveRichTextRefs) return {
62
+ items: [],
63
+ loading: false,
64
+ unavailable: true
65
+ };
66
+ if (!key) return {
67
+ items: [],
68
+ loading: false,
69
+ unavailable: false
70
+ };
71
+ const done = state?.key === key ? state : null;
72
+ return {
73
+ items: done?.items ?? [],
74
+ loading: !done,
75
+ error: done?.error,
76
+ unavailable: false
77
+ };
78
+ }
40
79
  var registry = {};
41
80
  var listeners = /* @__PURE__ */ new Set();
42
81
  /** Register app services once at module load (e.g. the Content feature registers its media library). */
@@ -212,6 +251,6 @@ var FormCtx = React.createContext(null);
212
251
  var FormProvider = FormCtx.Provider;
213
252
  var useForm = () => React.useContext(FormCtx);
214
253
  //#endregion
215
- export { AI_PRESETS, DEFAULT_ENV, FieldHeaderSlot, FormProvider, KitEnvProvider, KitSectionTitle, KitServicesProvider, SITE_PALETTE, fieldAccessOf, registerKitServices, resolveFieldAccess, seesDevSurfaces, useForm, useKitEnv, useKitServices, visibleFields, visibleSchema };
254
+ export { AI_PRESETS, DEFAULT_ENV, FieldHeaderSlot, FormProvider, KitEnvProvider, KitSectionTitle, KitServicesProvider, SITE_PALETTE, fieldAccessOf, registerKitServices, resolveFieldAccess, seesDevSurfaces, useForm, useKitEnv, useKitServices, useRichTextRefs, visibleFields, visibleSchema };
216
255
 
217
256
  //# sourceMappingURL=context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","names":[],"sources":["../src/context.tsx"],"sourcesContent":["/**\n * Kit runtime contexts (V2 HEAD 9829bf1 kit/context.tsx).\n *\n * - KitServices — pluggable app services (media library picker, upload sink, toast, AI rewrite, comment threads).\n * Hosts register theirs once (`registerKitServices`) or provide them (`KitServicesProvider`); the kit\n * never imports a feature. A service that is absent hides the surface that needs it.\n * - KitEnv — the editing environment a control renders in: variant (panel | document), doc identity, site\n * palette, permissions, presence, comments. Has safe defaults so a control can render alone.\n * - FormApi — present only inside <SchemaForm>: value, writes, navigation, issues, baselines, history.\n */\nimport * as React from \"react\";\nimport type { Cap } from \"./schema/sources\";\nimport type { FieldAccess, FieldPath, FieldSchema, Fieldset, TypeSchema } from \"./schema/types\";\nimport type { KitIssue } from \"./lib/validate\";\nimport type { ControlProps } from \"./types\";\nimport type { LinkTarget } from \"./controls/link\";\n\n/* ── Services ─────────────────────────────────────────────────────────── */\n/** The media asset a picker hands back (the host's media library row, narrowed to what the kit reads). */\nexport interface KitMediaAsset {\n id: string;\n url: string;\n name?: string;\n alt?: string;\n kind?: \"image\" | \"video\" | \"file\" | \"svg\";\n width?: number;\n height?: number;\n size?: number;\n}\nexport interface KitMediaPickerProps {\n open: boolean;\n onOpenChange: (v: boolean) => void;\n onPick: (assets: KitMediaAsset[]) => void;\n multiple?: boolean;\n accept?: (\"image\" | \"video\" | \"file\" | \"svg\")[];\n /** What the dialog is for, in the author's words (\"Replace icon\"). Absent: the host's own title. */\n title?: string;\n /** An icon's Remove, in the dialog's footer (its row has no room for one). Absent: no Remove. */\n onRemove?: () => void;\n /**\n * An icon's first tab: the icons already on this site, each with the value it is stored as. The field's own icon\n * leads, marked `current`: picking it closes the dialog with no write. Absent: no such tab.\n */\n icons?: KitSiteIcon[];\n onPickIcon?: (value: unknown) => void;\n}\n/** One icon already on the site (KitServices.useSiteIcons), as the picker's first tab draws it. */\nexport interface KitSiteIcon {\n url: string;\n value: unknown;\n assetId?: string;\n current?: boolean;\n}\nexport interface KitToastOptions {\n /** the second line under the message (V2 sonner `description`) */\n description?: string;\n action?: { label: string; onClick: () => void };\n duration?: number;\n icon?: React.ReactNode;\n}\n/** The host's toaster. The kit has no toast dependency (V2 re-exported sonner's). */\nexport interface KitToast {\n (message: string, opts?: KitToastOptions): void;\n success: (message: string, opts?: KitToastOptions) => void;\n}\n/** Rewrite with AI presets (V2 lib/ai.ts). The host's `aiRewrite` service produces the text. */\nexport type AiPreset = \"improve\" | \"shorter\" | \"longer\" | \"simplify\" | \"grammar\" | \"translate\";\nexport const AI_PRESETS: { id: AiPreset; label: string }[] = [\n { id: \"improve\", label: \"Improve\" },\n { id: \"shorter\", label: \"Shorter\" },\n { id: \"longer\", label: \"Longer\" },\n { id: \"simplify\", label: \"Simplify\" },\n { id: \"grammar\", label: \"Fix spelling & grammar\" },\n { id: \"translate\", label: \"Translate to Spanish\" },\n];\n/** The in-place field comments popover (V2 FieldThreads over the comments store), drawn by the host. */\nexport interface KitFieldCommentsProps {\n docId: string;\n /** pathKey of the field (\"items.1.answer\") */\n fieldKey: string;\n label: string;\n /** the field's text, first 80 characters */\n quote?: string;\n onClose: () => void;\n /** \"Open in panel\": set when the host has a comments panel (`env.onComment`) */\n onOpenPanel?: () => void;\n}\n/** One field's ⋯ menu, as the host's extra items see it (KitServices.FieldMenuExtras). */\nexport interface KitFieldMenuExtrasProps {\n field: FieldSchema;\n path: FieldPath;\n /** the field is read-only for the lens: a host offers nothing that edits */\n readOnly: boolean;\n}\n/* The popover around FieldComments keeps itself open on Escape while focus is on an element marked `data-esc-owner`\n (V2 guardEscape): the composer marks its input so Escape clears or blurs the draft instead of losing it. */\nexport interface KitServices {\n /** The media library picker. */\n MediaPicker?: React.ComponentType<KitMediaPickerProps>;\n /** Called after an upload so the host can file the assets into its media library. */\n onUploaded?: (assets: KitMediaAsset[]) => void;\n /**\n * Image placements keep their edits BESIDE the url (`${name}Alt` · `${name}Focal` · `${name}Crop` ·\n * `${name}Decorative`) instead of turning the value into an object — the Visuals block model.\n */\n imageSiblings?: boolean;\n /** Real metadata for an image url (the host's media library), so captions never show placeholders. */\n resolveImage?: (url: string) => { name?: string; width?: number; height?: number; size?: number } | undefined;\n /** The host's full image editor (crop, rotate, focal, alt…), opened in place. */\n editImage?: (req: { docId: string; path: FieldPath; title: string }) => void;\n /** The project's forms for `reference: { to: [\"form\"] }` (the host's Forms store). */\n forms?: () => { id: string; title: string; subtitle: string }[];\n /** Toasts for Copy / Paste / Reset / AI (each with Undo). Absent → the action still runs, silently. */\n toast?: KitToast;\n /**\n * A slug's \"Save new address\" with \"Redirect it to the new address\" ticked (V2 SlugControl): the host files the\n * redirect from the old slug to the new one. Absent → no checkbox is offered, and the toast says the old address\n * will return \"Not found\", which is what happens. A rejection is named in a toast (the new slug is already saved).\n */\n redirectSlug?: (from: string, to: string) => void | Promise<void>;\n /**\n * A slug's \"Save new address\": the host writes the new address before any redirect is filed. Absent → the form's\n * value is the save (V2). A rejection is named in a toast and the old-address box stays for another try.\n */\n saveSlug?: (to: string) => Promise<void>;\n /** Rewrite with AI. Absent → the ⋯ menu offers no rewrite. `attempt` counts \"Try again\" presses. */\n aiRewrite?: (text: string, preset: AiPreset, attempt: number) => string | Promise<string>;\n /** In-place field comments. Absent and no `env.onComment` → the field shows no add-comment control. */\n FieldComments?: React.ComponentType<KitFieldCommentsProps>;\n /**\n * A React hook: open (unresolved) threads on one field, read from the same store FieldComments draws (V2 counted\n * `useBlockThreads(docId, key)`). Used while the host has no `env.onComment`; absent → SchemaForm `comments` counts.\n */\n useFieldCommentCount?: (docId: string, fieldKey: string) => number;\n /**\n * The host's own control for a type (a FieldRenderer arm in the dashboard), drawn for a stored value a kit control\n * hands off because it cannot carry it unchanged (rich text, LegacyFallback). Bare inside the kit FieldShell, which\n * owns the label row and the help; the box is tagged `data-kit-legacy`. Absent → the field says why in words.\n */\n LegacyControl?: React.ComponentType<ControlProps>;\n /**\n * The host's own items for a field's ⋯ menu (the entry editor's Move up · Move down · Move to panel), drawn after\n * History and before Rewrite with AI. It renders kit `MenuItem`s, with its own leading `MenuSeparator`, or null.\n * Absent → the menu is exactly V2's.\n */\n FieldMenuExtras?: React.ComponentType<KitFieldMenuExtrasProps>;\n /**\n * A React HOOK: the project's pages and CMS items a link can point at (controls/link.tsx). The host passes the same\n * function for the life of the form (it is called on every render of a link control). Absent → the link picker says\n * page search isn't available and takes a typed path. `error` is a whole sentence naming what failed (\"Pages couldn't\n * be loaded: …\", \"Items from Blog couldn't be loaded: …\"); the picker shows it under whatever did load.\n */\n useLinkTargets?: () => { targets: LinkTarget[]; loading?: boolean; error?: string };\n /**\n * A React HOOK: every SVG the site already uses (its pages, header and footer, the library's SVGs), for an icon's\n * picker. The host passes the same function for the life of the form. Absent → the picker has no site tab.\n */\n useSiteIcons?: () => KitSiteIcon[];\n}\nlet registry: KitServices = {};\nconst listeners = new Set<() => void>();\n/** Register app services once at module load (e.g. the Content feature registers its media library). */\nexport function registerKitServices(s: KitServices) {\n registry = { ...registry, ...s };\n listeners.forEach((l) => l());\n}\nconst ServicesCtx = React.createContext<KitServices | null>(null);\nexport function KitServicesProvider({ value, children }: { value: KitServices; children: React.ReactNode }) {\n return <ServicesCtx.Provider value={value}>{children}</ServicesCtx.Provider>;\n}\nexport function useKitServices(): KitServices {\n const local = React.useContext(ServicesCtx);\n const global = React.useSyncExternalStore(\n (cb) => {\n listeners.add(cb);\n return () => listeners.delete(cb);\n },\n () => registry,\n () => registry,\n );\n return local ? { ...global, ...local } : global;\n}\n\n/** A host header element a top-level field (by path key) renders its actions into, instead of a label row. */\nexport const FieldHeaderSlot = React.createContext<{ key: string; el: HTMLElement } | null>(null);\n\n/* ── Environment ──────────────────────────────────────────────────────── */\nexport type KitVariant = \"panel\" | \"document\";\nexport interface KitPeer {\n id?: string;\n name: string;\n avatar?: string;\n /** presence colour (avatar ring, field border, caret flag) */\n color?: string;\n /** \"editing\" (soft lock + border) or \"viewing\" */\n status?: \"editing\" | \"viewing\";\n}\nexport interface Swatch {\n name: string;\n value: string;\n token?: string;\n}\n\n/** V2's demo site palette (Caldera). Hosts pass the project's brand palette through `env.palette`. */\nexport const SITE_PALETTE: Swatch[] = [\n { name: \"Ember\", value: \"#E0663B\", token: \"brand.ember\" },\n { name: \"Ochre\", value: \"#D97706\", token: \"brand.ochre\" },\n { name: \"Moss\", value: \"#4F6B4A\", token: \"brand.moss\" },\n { name: \"Lake\", value: \"#2563EB\", token: \"accent.lake\" },\n { name: \"Dusk\", value: \"#7C3AED\", token: \"accent.dusk\" },\n { name: \"Berry\", value: \"#DB2777\", token: \"accent.berry\" },\n { name: \"Char\", value: \"#1E1B18\", token: \"neutral.900\" },\n { name: \"Slate\", value: \"#4B5563\", token: \"neutral.600\" },\n { name: \"Bone\", value: \"#E7E0D6\", token: \"neutral.200\" },\n { name: \"Paper\", value: \"#F6F1EA\", token: \"neutral.50\" },\n];\n\nexport interface KitEnv {\n variant: KitVariant;\n /** Identity of the document/block being edited (rich-text sync, comments, history). */\n docId: string;\n /** The document's title — slug generation. */\n docTitle?: string;\n /** Public URL prefix for slug previews, e.g. \"caldera.bettercms.site/journal/\". */\n urlPrefix?: string;\n /** The document has been published (slug changes warn about the old address). */\n isPublished?: boolean;\n /**\n * The form's slug is the document's ADDRESS (a page's or an entry's URL). Only then does a changed slug on a published\n * document say the old address will stop working and offer the redirect. A slug prop or a key is not an address, so\n * it gets no such box (V2 draws it on document hosts only: its component-prop forms pass no published state).\n */\n slugIsAddress?: boolean;\n palette: Swatch[];\n /** The whole form is read-only for the lens (the host's content.edit rule, a generated twin…). */\n readOnly: boolean;\n /**\n * D58: the host's access check for the lens (`useCan` at the host). The kit never imports the store; it asks this for\n * field-level `access` and for developer surfaces (field keys, IDs, API names: `schema.view` or `api.view`).\n * Hosts that omit it (staff tools, demos) get allow-all.\n */\n can: (cap: Cap) => boolean;\n /** pathKey → teammates in that field */\n presence: Record<string, KitPeer[]>;\n /** pathKey → open comment threads */\n comments: Record<string, number>;\n /** Host comment panel. When absent, field comments live in the in-place popover only. */\n /** `quote`: the words selected in a rich text field when the comment came from its toolbar */\n onComment?: (path: FieldPath, field: FieldSchema, quote?: string) => void;\n /** The whole form is a global block (\"Navigation\") — every field is synced. */\n syncedFrom?: string;\n /** \"Open {title}\" on a reference card: the host edits the referenced entry in place (stacked panel). */\n onOpenReference?: (ref: { collectionId: string; entryId: string; title: string }) => void;\n /** CPO D39: \"inline\" (default) shows help under the label; \"tooltip\" moves it behind the ⓘ — for dense hosts (grid cells, popovers). */\n helpDisplay?: \"inline\" | \"tooltip\";\n /** D61 Review changes: the host's change markers, present only while its History panel is open. */\n review?: KitReview;\n /**\n * \"quiet\": a meta-form, like the schema editor's field settings (founder call, 2026-10-05). No comment, ⋯ or per-field\n * undo on the fields, and an on/off field is a label-left row with its switch on the right (the inspector's PRow), not a\n * switch card. \"preview\": the same chrome-free fields, controls exactly as editors see them (a schema preview, where a\n * comment would land on a sample, never a real document). Default \"full\".\n */\n fieldChrome?: \"full\" | \"preview\" | \"quiet\";\n /**\n * A host's own control for one field, by its path key (\"type\", \"options\"), drawn inside the usual shell. The schema\n * editor's field settings plug their Type, Options, Key and Show this field controls in here. Absent = kit controls only.\n */\n controls?: Record<string, React.ComponentType<ControlProps>>;\n /**\n * A collapsed fieldset's summary as chips, from the host (the schema editor's field settings: \"Required\", \"3 options\").\n * `undefined` falls back to the kit's own one-line summary. Keep it stable (a new function re-renders the form).\n */\n sectionSummary?: (set: Fieldset, value: Record<string, unknown>) => string[] | undefined;\n}\n\n/**\n * D61 Review changes, as the kit sees it. The host owns the history model; the kit only asks, per field path, whether\n * it changed in the range, and renders the host's marker (a 2px bar in the author's colour at the field's left edge,\n * with its hover card) where the at-rest \"changed\" dot would be.\n */\nexport interface KitReview {\n /** changes at or inside this field in the range (0 = unchanged) */\n count: (path: FieldPath) => number;\n /** the presence colour of whoever changed it last (list rows draw their own bar) */\n color: (path: FieldPath) => string | undefined;\n /** the host's change bar and hover card for a changed field (null when unchanged) */\n bar: (path: FieldPath, field: FieldSchema) => React.ReactNode;\n /** Show only changes: unchanged fields fold into one quiet line each run */\n onlyChanges: boolean;\n}\n\n/** The title of the fieldset a field sits in (null outside one). A quiet meta-form drops a field label that only repeats\n * its section's title (\"Options\" › \"Options\"): the section heading names it. */\nexport const KitSectionTitle = React.createContext<string | null>(null);\nexport const DEFAULT_ENV: KitEnv = {\n variant: \"panel\",\n docId: \"kit\",\n palette: [],\n readOnly: false,\n can: () => true,\n presence: {},\n comments: {},\n};\nconst EnvCtx = React.createContext<KitEnv>(DEFAULT_ENV);\n\n/** D58 field access: fields in the `seo` group default to `{ edit: \"seo.edit\" }`. */\nexport const fieldAccessOf = (field: FieldSchema): FieldAccess | undefined => field.access ?? (field.group === \"seo\" ? { edit: \"seo.edit\" } : undefined);\n\n/**\n * One field for the lens: hidden (no `view`), read-only (plain text: no `edit`, the field is `readOnly`, or the whole form\n * is read-only and the field has no edit cap of its own), or editable. An explicit `edit` cap wins over the form's\n * read-only rule, so an SEO specialist edits SEO fields inside a form they can't otherwise change.\n */\nexport function resolveFieldAccess(field: FieldSchema, env: Pick<KitEnv, \"can\" | \"readOnly\">): { hidden: boolean; readOnly: boolean } {\n const a = fieldAccessOf(field);\n if (a?.view && !env.can(a.view)) return { hidden: true, readOnly: true };\n if (field.readOnly === true) return { hidden: false, readOnly: true };\n if (a?.edit) return { hidden: false, readOnly: !env.can(a.edit) };\n return { hidden: false, readOnly: env.readOnly };\n}\n/** Drop the fields the lens can't `view` (recursively), so they are neither rendered nor validated. Same object back when\n * nothing is hidden. Values of dropped fields stay in the document untouched. */\nexport function visibleFields(fields: FieldSchema[], can: KitEnv[\"can\"]): FieldSchema[] {\n let changed = false;\n const out: FieldSchema[] = [];\n for (const f of fields) {\n const a = fieldAccessOf(f);\n if (a?.view && !can(a.view)) {\n changed = true;\n continue;\n }\n let g = f;\n if (f.fields) {\n const n = visibleFields(f.fields, can);\n if (n !== f.fields) g = { ...g, fields: n };\n }\n if (Array.isArray(f.of)) {\n const n = visibleFields(f.of, can);\n if (n !== f.of) g = { ...g, of: n };\n }\n if (f.slotSettings) {\n const n = visibleFields(f.slotSettings, can);\n if (n !== f.slotSettings) g = { ...g, slotSettings: n };\n }\n if (g !== f) changed = true;\n out.push(g);\n }\n return changed ? out : fields;\n}\nexport function visibleSchema(schema: TypeSchema, can: KitEnv[\"can\"]): TypeSchema {\n const fields = visibleFields(schema.fields, can);\n return fields === schema.fields ? schema : { ...schema, fields };\n}\n\n/** Developer surfaces (field keys, API names, IDs): `schema.view` or `api.view`. */\nexport const seesDevSurfaces = (env: Pick<KitEnv, \"can\">) => env.can(\"schema.view\") || env.can(\"api.view\");\nexport const KitEnvProvider = EnvCtx.Provider;\nexport const useKitEnv = () => React.useContext(EnvCtx);\n\n/* ── Form runtime (inside <SchemaForm>) ──────────────────────────────── */\nexport interface HistoryEntry {\n value: unknown;\n at: number;\n by: string;\n label?: string;\n}\nexport interface FormApi {\n schema: TypeSchema;\n value: Record<string, unknown>;\n /** write a value; `record: false` skips the field undo/history stack (e.g. programmatic sync) */\n setAt: (path: FieldPath, v: unknown, opts?: { record?: boolean; label?: string }) => void;\n getAt: (path: FieldPath) => unknown;\n /** value when the form mounted (field undo baseline) */\n baselineAt: (path: FieldPath) => unknown;\n /** published value (changed dot, Reset to published); undefined when never published */\n publishedAt: (path: FieldPath) => unknown;\n hasPublished: boolean;\n /** component instances (CPO D28): top-level fields inherit from a component unless overridden */\n inherited?: { from: string; isOverridden: (key: string) => boolean; reset: (key: string) => void };\n /** the current navigation path (drill-in stack) */\n path: FieldPath;\n navigate: (path: FieldPath) => void;\n /** the form draws frames for a non-root path. False while SchemaForm renders only the root frame: History and\n * drill-in rows hide or go inert instead of navigating to a path nothing shows (no silent no-op). */\n canNavigate: boolean;\n resolveSchema?: (blockType: string) => TypeSchema | undefined;\n /** blocks offered by \"+ Add block\" in slots (filtered by each slot's allow list) */\n blockLibrary?: TypeSchema[];\n /** all issues by pathKey */\n issues: Map<string, KitIssue>;\n /** the issue shown for a key (rules on blur, required after blur or a publish attempt) */\n visibleIssue: (key: string) => KitIssue | undefined;\n /** visible issues under a path prefix (rollup dots on rows, crumbs and tabs) */\n rollup: (prefix: FieldPath) => { errors: number; warnings: number };\n touch: (key: string) => void;\n showAllErrors: boolean;\n /** per-field undo (Undo last change to {label}) */\n canUndo: (key: string) => boolean;\n undo: (key: string) => void;\n /** versions of a field this session (newest first) */\n history: (key: string) => HistoryEntry[];\n /** the last change to this field came from AI (⋯ shows \"Undo AI change\") */\n aiKeys: Set<string>;\n markAi: (key: string, on: boolean) => void;\n /** remembered UI state per frame (selected group tab, open fieldsets) */\n memory: Map<string, unknown>;\n}\nconst FormCtx = React.createContext<FormApi | null>(null);\nexport const FormProvider = FormCtx.Provider;\nexport const useForm = () => React.useContext(FormCtx);\n"],"mappings":"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"}
1
+ {"version":3,"file":"context.js","names":[],"sources":["../src/context.tsx"],"sourcesContent":["/**\n * Kit runtime contexts (V2 HEAD 9829bf1 kit/context.tsx).\n *\n * - KitServices — pluggable app services (media library picker, upload sink, toast, AI rewrite, comment threads).\n * Hosts register theirs once (`registerKitServices`) or provide them (`KitServicesProvider`); the kit\n * never imports a feature. A service that is absent hides the surface that needs it.\n * - KitEnv — the editing environment a control renders in: variant (panel | document), doc identity, site\n * palette, permissions, presence, comments. Has safe defaults so a control can render alone.\n * - FormApi — present only inside <SchemaForm>: value, writes, navigation, issues, baselines, history.\n */\nimport * as React from \"react\";\nimport type { Cap } from \"./schema/sources\";\nimport type { FieldAccess, FieldPath, FieldSchema, Fieldset, TypeSchema } from \"./schema/types\";\nimport type { KitIssue } from \"./lib/validate\";\nimport type { ControlProps } from \"./types\";\nimport type { LinkTarget } from \"./controls/link\";\n\n/* ── Services ─────────────────────────────────────────────────────────── */\n/** The media asset a picker hands back (the host's media library row, narrowed to what the kit reads). */\nexport interface KitMediaAsset {\n id: string;\n url: string;\n name?: string;\n alt?: string;\n kind?: \"image\" | \"video\" | \"file\" | \"svg\";\n width?: number;\n height?: number;\n size?: number;\n}\nexport interface KitMediaPickerProps {\n open: boolean;\n onOpenChange: (v: boolean) => void;\n onPick: (assets: KitMediaAsset[]) => void;\n multiple?: boolean;\n accept?: (\"image\" | \"video\" | \"file\" | \"svg\")[];\n /** What the dialog is for, in the author's words (\"Replace icon\"). Absent: the host's own title. */\n title?: string;\n /** An icon's Remove, in the dialog's footer (its row has no room for one). Absent: no Remove. */\n onRemove?: () => void;\n /**\n * An icon's first tab: the icons already on this site, each with the value it is stored as. The field's own icon\n * leads, marked `current`: picking it closes the dialog with no write. Absent: no such tab.\n */\n icons?: KitSiteIcon[];\n onPickIcon?: (value: unknown) => void;\n}\n/** One icon already on the site (KitServices.useSiteIcons), as the picker's first tab draws it. */\nexport interface KitSiteIcon {\n url: string;\n value: unknown;\n assetId?: string;\n current?: boolean;\n}\nexport interface KitToastOptions {\n /** the second line under the message (V2 sonner `description`) */\n description?: string;\n action?: { label: string; onClick: () => void };\n duration?: number;\n icon?: React.ReactNode;\n}\n/** The host's toaster. The kit has no toast dependency (V2 re-exported sonner's). */\nexport interface KitToast {\n (message: string, opts?: KitToastOptions): void;\n success: (message: string, opts?: KitToastOptions) => void;\n}\n/** Rewrite with AI presets (V2 lib/ai.ts). The host's `aiRewrite` service produces the text. */\nexport type AiPreset = \"improve\" | \"shorter\" | \"longer\" | \"simplify\" | \"grammar\" | \"translate\";\nexport const AI_PRESETS: { id: AiPreset; label: string }[] = [\n { id: \"improve\", label: \"Improve\" },\n { id: \"shorter\", label: \"Shorter\" },\n { id: \"longer\", label: \"Longer\" },\n { id: \"simplify\", label: \"Simplify\" },\n { id: \"grammar\", label: \"Fix spelling & grammar\" },\n { id: \"translate\", label: \"Translate to Spanish\" },\n];\n/** The in-place field comments popover (V2 FieldThreads over the comments store), drawn by the host. */\nexport interface KitFieldCommentsProps {\n docId: string;\n /** pathKey of the field (\"items.1.answer\") */\n fieldKey: string;\n label: string;\n /** the field's text, first 80 characters */\n quote?: string;\n onClose: () => void;\n /** \"Open in panel\": set when the host has a comments panel (`env.onComment`) */\n onOpenPanel?: () => void;\n}\n/** One field's ⋯ menu, as the host's extra items see it (KitServices.FieldMenuExtras). */\nexport interface KitFieldMenuExtrasProps {\n field: FieldSchema;\n path: FieldPath;\n /** the field is read-only for the lens: a host offers nothing that edits */\n readOnly: boolean;\n}\n/* The popover around FieldComments keeps itself open on Escape while focus is on an element marked `data-esc-owner`\n (V2 guardEscape): the composer marks its input so Escape clears or blurs the draft instead of losing it. */\nexport interface KitServices {\n /** The media library picker. */\n MediaPicker?: React.ComponentType<KitMediaPickerProps>;\n /** Called after an upload so the host can file the assets into its media library. */\n onUploaded?: (assets: KitMediaAsset[]) => void;\n /**\n * Image placements keep their edits BESIDE the url (`${name}Alt` · `${name}Focal` · `${name}Crop` ·\n * `${name}Decorative`) instead of turning the value into an object — the Visuals block model.\n */\n imageSiblings?: boolean;\n /** Real metadata for an image url (the host's media library), so captions never show placeholders. */\n resolveImage?: (url: string) => { name?: string; width?: number; height?: number; size?: number } | undefined;\n /** The host's full image editor (crop, rotate, focal, alt…), opened in place. */\n editImage?: (req: { docId: string; path: FieldPath; title: string }) => void;\n /** The project's forms for `reference: { to: [\"form\"] }` (the host's Forms store). */\n forms?: () => { id: string; title: string; subtitle: string }[];\n /** Toasts for Copy / Paste / Reset / AI (each with Undo). Absent → the action still runs, silently. */\n toast?: KitToast;\n /**\n * A slug's \"Save new address\" with \"Redirect it to the new address\" ticked (V2 SlugControl): the host files the\n * redirect from the old slug to the new one. Absent → no checkbox is offered, and the toast says the old address\n * will return \"Not found\", which is what happens. A rejection is named in a toast (the new slug is already saved).\n */\n redirectSlug?: (from: string, to: string) => void | Promise<void>;\n /**\n * A slug's \"Save new address\": the host writes the new address before any redirect is filed. Absent → the form's\n * value is the save (V2). A rejection is named in a toast and the old-address box stays for another try.\n */\n saveSlug?: (to: string) => Promise<void>;\n /** Rewrite with AI. Absent → the ⋯ menu offers no rewrite. `attempt` counts \"Try again\" presses. */\n aiRewrite?: (text: string, preset: AiPreset, attempt: number) => string | Promise<string>;\n /** In-place field comments. Absent and no `env.onComment` → the field shows no add-comment control. */\n FieldComments?: React.ComponentType<KitFieldCommentsProps>;\n /**\n * A React hook: open (unresolved) threads on one field, read from the same store FieldComments draws (V2 counted\n * `useBlockThreads(docId, key)`). Used while the host has no `env.onComment`; absent → SchemaForm `comments` counts.\n */\n useFieldCommentCount?: (docId: string, fieldKey: string) => number;\n /**\n * The host's own control for a type (a FieldRenderer arm in the dashboard), drawn for a stored value a kit control\n * hands off because it cannot carry it unchanged (rich text, LegacyFallback). Bare inside the kit FieldShell, which\n * owns the label row and the help; the box is tagged `data-kit-legacy`. Absent → the field says why in words.\n */\n LegacyControl?: React.ComponentType<ControlProps>;\n /**\n * The host's own items for a field's ⋯ menu (the entry editor's Move up · Move down · Move to panel), drawn after\n * History and before Rewrite with AI. It renders kit `MenuItem`s, with its own leading `MenuSeparator`, or null.\n * Absent → the menu is exactly V2's.\n */\n FieldMenuExtras?: React.ComponentType<KitFieldMenuExtrasProps>;\n /**\n * A React HOOK: the project's pages and CMS items a link can point at (controls/link.tsx). The host passes the same\n * function for the life of the form (it is called on every render of a link control). Absent → the link picker says\n * page search isn't available and takes a typed path. `error` is a whole sentence naming what failed (\"Pages couldn't\n * be loaded: …\", \"Items from Blog couldn't be loaded: …\"); the picker shows it under whatever did load.\n */\n useLinkTargets?: () => { targets: LinkTarget[]; loading?: boolean; error?: string };\n /**\n * A React HOOK: every SVG the site already uses (its pages, header and footer, the library's SVGs), for an icon's\n * picker. The host passes the same function for the life of the form. Absent → the picker has no site tab.\n */\n useSiteIcons?: () => KitSiteIcon[];\n /**\n * The entries a rich text's related posts or author bio block shows (kit-richtext-blocks-plan.md RT8). `ids`: the\n * hand-picked entries (an author bio's one author, related posts in hand-picked mode), answered in that order with\n * any the host cannot find left out. `query`: related posts chosen for the reader (owner 2026-10-24: `topic` = same\n * tags or category, `newest` = the collection newest first; `count` of them, `excludeId` the entry being edited).\n * Absent → the blocks show their empty state; a rejection's message is shown as said.\n */\n resolveRichTextRefs?: (req: KitRichTextRefRequest) => Promise<KitRichTextRef[]>;\n}\n\n/** What a related posts or author bio block asks the host for (KitServices.resolveRichTextRefs). */\nexport interface KitRichTextRefRequest {\n kind: \"relatedPosts\" | \"authorBio\";\n ids?: string[];\n query?: { collection?: string; mode: \"topic\" | \"newest\"; count: number; excludeId?: string };\n}\n/** One resolved entry, as the block draws it: a post card (title, cover, category) or an author (avatar, title as name, bio). */\nexport interface KitRichTextRef {\n id: string;\n title: string;\n href?: string;\n cover?: string;\n category?: string;\n avatar?: string;\n bio?: string;\n}\n\n/**\n * A React HOOK over KitServices.resolveRichTextRefs: `unavailable` when the host offers no resolver (draw the empty\n * state), else `loading`, then `items` or `error` (a sentence). `req` null asks nothing (a block with nothing picked).\n */\nexport function useRichTextRefs(req: KitRichTextRefRequest | null): { items: KitRichTextRef[]; loading: boolean; error?: string; unavailable: boolean } {\n const { resolveRichTextRefs } = useKitServices();\n const key = req ? JSON.stringify(req) : \"\";\n const [state, setState] = React.useState<{ key: string; items: KitRichTextRef[]; error?: string } | null>(null);\n React.useEffect(() => {\n if (!resolveRichTextRefs || !key) return;\n let live = true;\n resolveRichTextRefs(JSON.parse(key) as KitRichTextRefRequest).then(\n (items) => live && setState({ key, items }),\n (e: unknown) => live && setState({ key, items: [], error: e instanceof Error ? e.message : String(e) }),\n );\n return () => void (live = false);\n }, [resolveRichTextRefs, key]);\n if (!resolveRichTextRefs) return { items: [], loading: false, unavailable: true };\n if (!key) return { items: [], loading: false, unavailable: false };\n const done = state?.key === key ? state : null;\n return { items: done?.items ?? [], loading: !done, error: done?.error, unavailable: false };\n}\nlet registry: KitServices = {};\nconst listeners = new Set<() => void>();\n/** Register app services once at module load (e.g. the Content feature registers its media library). */\nexport function registerKitServices(s: KitServices) {\n registry = { ...registry, ...s };\n listeners.forEach((l) => l());\n}\nconst ServicesCtx = React.createContext<KitServices | null>(null);\nexport function KitServicesProvider({ value, children }: { value: KitServices; children: React.ReactNode }) {\n return <ServicesCtx.Provider value={value}>{children}</ServicesCtx.Provider>;\n}\nexport function useKitServices(): KitServices {\n const local = React.useContext(ServicesCtx);\n const global = React.useSyncExternalStore(\n (cb) => {\n listeners.add(cb);\n return () => listeners.delete(cb);\n },\n () => registry,\n () => registry,\n );\n return local ? { ...global, ...local } : global;\n}\n\n/** A host header element a top-level field (by path key) renders its actions into, instead of a label row. */\nexport const FieldHeaderSlot = React.createContext<{ key: string; el: HTMLElement } | null>(null);\n\n/* ── Environment ──────────────────────────────────────────────────────── */\nexport type KitVariant = \"panel\" | \"document\";\nexport interface KitPeer {\n id?: string;\n name: string;\n avatar?: string;\n /** presence colour (avatar ring, field border, caret flag) */\n color?: string;\n /** \"editing\" (soft lock + border) or \"viewing\" */\n status?: \"editing\" | \"viewing\";\n}\nexport interface Swatch {\n name: string;\n value: string;\n token?: string;\n}\n\n/** V2's demo site palette (Caldera). Hosts pass the project's brand palette through `env.palette`. */\nexport const SITE_PALETTE: Swatch[] = [\n { name: \"Ember\", value: \"#E0663B\", token: \"brand.ember\" },\n { name: \"Ochre\", value: \"#D97706\", token: \"brand.ochre\" },\n { name: \"Moss\", value: \"#4F6B4A\", token: \"brand.moss\" },\n { name: \"Lake\", value: \"#2563EB\", token: \"accent.lake\" },\n { name: \"Dusk\", value: \"#7C3AED\", token: \"accent.dusk\" },\n { name: \"Berry\", value: \"#DB2777\", token: \"accent.berry\" },\n { name: \"Char\", value: \"#1E1B18\", token: \"neutral.900\" },\n { name: \"Slate\", value: \"#4B5563\", token: \"neutral.600\" },\n { name: \"Bone\", value: \"#E7E0D6\", token: \"neutral.200\" },\n { name: \"Paper\", value: \"#F6F1EA\", token: \"neutral.50\" },\n];\n\nexport interface KitEnv {\n variant: KitVariant;\n /** Identity of the document/block being edited (rich-text sync, comments, history). */\n docId: string;\n /** The document's title — slug generation. */\n docTitle?: string;\n /** Public URL prefix for slug previews, e.g. \"caldera.bettercms.site/journal/\". */\n urlPrefix?: string;\n /** The document has been published (slug changes warn about the old address). */\n isPublished?: boolean;\n /**\n * The form's slug is the document's ADDRESS (a page's or an entry's URL). Only then does a changed slug on a published\n * document say the old address will stop working and offer the redirect. A slug prop or a key is not an address, so\n * it gets no such box (V2 draws it on document hosts only: its component-prop forms pass no published state).\n */\n slugIsAddress?: boolean;\n palette: Swatch[];\n /** The whole form is read-only for the lens (the host's content.edit rule, a generated twin…). */\n readOnly: boolean;\n /**\n * D58: the host's access check for the lens (`useCan` at the host). The kit never imports the store; it asks this for\n * field-level `access` and for developer surfaces (field keys, IDs, API names: `schema.view` or `api.view`).\n * Hosts that omit it (staff tools, demos) get allow-all.\n */\n can: (cap: Cap) => boolean;\n /** pathKey → teammates in that field */\n presence: Record<string, KitPeer[]>;\n /** pathKey → open comment threads */\n comments: Record<string, number>;\n /** Host comment panel. When absent, field comments live in the in-place popover only. */\n /** `quote`: the words selected in a rich text field when the comment came from its toolbar */\n onComment?: (path: FieldPath, field: FieldSchema, quote?: string) => void;\n /** The whole form is a global block (\"Navigation\") — every field is synced. */\n syncedFrom?: string;\n /** \"Open {title}\" on a reference card: the host edits the referenced entry in place (stacked panel). */\n onOpenReference?: (ref: { collectionId: string; entryId: string; title: string }) => void;\n /** CPO D39: \"inline\" (default) shows help under the label; \"tooltip\" moves it behind the ⓘ — for dense hosts (grid cells, popovers). */\n helpDisplay?: \"inline\" | \"tooltip\";\n /** D61 Review changes: the host's change markers, present only while its History panel is open. */\n review?: KitReview;\n /**\n * \"quiet\": a meta-form, like the schema editor's field settings (founder call, 2026-10-05). No comment, ⋯ or per-field\n * undo on the fields, and an on/off field is a label-left row with its switch on the right (the inspector's PRow), not a\n * switch card. \"preview\": the same chrome-free fields, controls exactly as editors see them (a schema preview, where a\n * comment would land on a sample, never a real document). Default \"full\".\n */\n fieldChrome?: \"full\" | \"preview\" | \"quiet\";\n /**\n * A host's own control for one field, by its path key (\"type\", \"options\"), drawn inside the usual shell. The schema\n * editor's field settings plug their Type, Options, Key and Show this field controls in here. Absent = kit controls only.\n */\n controls?: Record<string, React.ComponentType<ControlProps>>;\n /**\n * A collapsed fieldset's summary as chips, from the host (the schema editor's field settings: \"Required\", \"3 options\").\n * `undefined` falls back to the kit's own one-line summary. Keep it stable (a new function re-renders the form).\n */\n sectionSummary?: (set: Fieldset, value: Record<string, unknown>) => string[] | undefined;\n}\n\n/**\n * D61 Review changes, as the kit sees it. The host owns the history model; the kit only asks, per field path, whether\n * it changed in the range, and renders the host's marker (a 2px bar in the author's colour at the field's left edge,\n * with its hover card) where the at-rest \"changed\" dot would be.\n */\nexport interface KitReview {\n /** changes at or inside this field in the range (0 = unchanged) */\n count: (path: FieldPath) => number;\n /** the presence colour of whoever changed it last (list rows draw their own bar) */\n color: (path: FieldPath) => string | undefined;\n /** the host's change bar and hover card for a changed field (null when unchanged) */\n bar: (path: FieldPath, field: FieldSchema) => React.ReactNode;\n /** Show only changes: unchanged fields fold into one quiet line each run */\n onlyChanges: boolean;\n}\n\n/** The title of the fieldset a field sits in (null outside one). A quiet meta-form drops a field label that only repeats\n * its section's title (\"Options\" › \"Options\"): the section heading names it. */\nexport const KitSectionTitle = React.createContext<string | null>(null);\nexport const DEFAULT_ENV: KitEnv = {\n variant: \"panel\",\n docId: \"kit\",\n palette: [],\n readOnly: false,\n can: () => true,\n presence: {},\n comments: {},\n};\nconst EnvCtx = React.createContext<KitEnv>(DEFAULT_ENV);\n\n/** D58 field access: fields in the `seo` group default to `{ edit: \"seo.edit\" }`. */\nexport const fieldAccessOf = (field: FieldSchema): FieldAccess | undefined => field.access ?? (field.group === \"seo\" ? { edit: \"seo.edit\" } : undefined);\n\n/**\n * One field for the lens: hidden (no `view`), read-only (plain text: no `edit`, the field is `readOnly`, or the whole form\n * is read-only and the field has no edit cap of its own), or editable. An explicit `edit` cap wins over the form's\n * read-only rule, so an SEO specialist edits SEO fields inside a form they can't otherwise change.\n */\nexport function resolveFieldAccess(field: FieldSchema, env: Pick<KitEnv, \"can\" | \"readOnly\">): { hidden: boolean; readOnly: boolean } {\n const a = fieldAccessOf(field);\n if (a?.view && !env.can(a.view)) return { hidden: true, readOnly: true };\n if (field.readOnly === true) return { hidden: false, readOnly: true };\n if (a?.edit) return { hidden: false, readOnly: !env.can(a.edit) };\n return { hidden: false, readOnly: env.readOnly };\n}\n/** Drop the fields the lens can't `view` (recursively), so they are neither rendered nor validated. Same object back when\n * nothing is hidden. Values of dropped fields stay in the document untouched. */\nexport function visibleFields(fields: FieldSchema[], can: KitEnv[\"can\"]): FieldSchema[] {\n let changed = false;\n const out: FieldSchema[] = [];\n for (const f of fields) {\n const a = fieldAccessOf(f);\n if (a?.view && !can(a.view)) {\n changed = true;\n continue;\n }\n let g = f;\n if (f.fields) {\n const n = visibleFields(f.fields, can);\n if (n !== f.fields) g = { ...g, fields: n };\n }\n if (Array.isArray(f.of)) {\n const n = visibleFields(f.of, can);\n if (n !== f.of) g = { ...g, of: n };\n }\n if (f.slotSettings) {\n const n = visibleFields(f.slotSettings, can);\n if (n !== f.slotSettings) g = { ...g, slotSettings: n };\n }\n if (g !== f) changed = true;\n out.push(g);\n }\n return changed ? out : fields;\n}\nexport function visibleSchema(schema: TypeSchema, can: KitEnv[\"can\"]): TypeSchema {\n const fields = visibleFields(schema.fields, can);\n return fields === schema.fields ? schema : { ...schema, fields };\n}\n\n/** Developer surfaces (field keys, API names, IDs): `schema.view` or `api.view`. */\nexport const seesDevSurfaces = (env: Pick<KitEnv, \"can\">) => env.can(\"schema.view\") || env.can(\"api.view\");\nexport const KitEnvProvider = EnvCtx.Provider;\nexport const useKitEnv = () => React.useContext(EnvCtx);\n\n/* ── Form runtime (inside <SchemaForm>) ──────────────────────────────── */\nexport interface HistoryEntry {\n value: unknown;\n at: number;\n by: string;\n label?: string;\n}\nexport interface FormApi {\n schema: TypeSchema;\n value: Record<string, unknown>;\n /** write a value; `record: false` skips the field undo/history stack (e.g. programmatic sync) */\n setAt: (path: FieldPath, v: unknown, opts?: { record?: boolean; label?: string }) => void;\n getAt: (path: FieldPath) => unknown;\n /** value when the form mounted (field undo baseline) */\n baselineAt: (path: FieldPath) => unknown;\n /** published value (changed dot, Reset to published); undefined when never published */\n publishedAt: (path: FieldPath) => unknown;\n hasPublished: boolean;\n /** component instances (CPO D28): top-level fields inherit from a component unless overridden */\n inherited?: { from: string; isOverridden: (key: string) => boolean; reset: (key: string) => void };\n /** the current navigation path (drill-in stack) */\n path: FieldPath;\n navigate: (path: FieldPath) => void;\n /** the form draws frames for a non-root path. False while SchemaForm renders only the root frame: History and\n * drill-in rows hide or go inert instead of navigating to a path nothing shows (no silent no-op). */\n canNavigate: boolean;\n resolveSchema?: (blockType: string) => TypeSchema | undefined;\n /** blocks offered by \"+ Add block\" in slots (filtered by each slot's allow list) */\n blockLibrary?: TypeSchema[];\n /** all issues by pathKey */\n issues: Map<string, KitIssue>;\n /** the issue shown for a key (rules on blur, required after blur or a publish attempt) */\n visibleIssue: (key: string) => KitIssue | undefined;\n /** visible issues under a path prefix (rollup dots on rows, crumbs and tabs) */\n rollup: (prefix: FieldPath) => { errors: number; warnings: number };\n touch: (key: string) => void;\n showAllErrors: boolean;\n /** per-field undo (Undo last change to {label}) */\n canUndo: (key: string) => boolean;\n undo: (key: string) => void;\n /** versions of a field this session (newest first) */\n history: (key: string) => HistoryEntry[];\n /** the last change to this field came from AI (⋯ shows \"Undo AI change\") */\n aiKeys: Set<string>;\n markAi: (key: string, on: boolean) => void;\n /** remembered UI state per frame (selected group tab, open fieldsets) */\n memory: Map<string, unknown>;\n}\nconst FormCtx = React.createContext<FormApi | null>(null);\nexport const FormProvider = FormCtx.Provider;\nexport const useForm = () => React.useContext(FormCtx);\n"],"mappings":"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"}
@@ -1 +1,7 @@
1
- export {};
1
+ /**
2
+ * Lane L3: a reference to forms only (`reference: { to: ["form"] }`, a component's `form` prop) draws the form picker
3
+ * when the host provides the project's forms; any other reference keeps the reference control. It wraps the registered
4
+ * reference control, so it imports reference-register itself: a bundler may reorder the barrel's bare imports (the
5
+ * 2.0 dist ran this one first, and reference-register then replaced the wrapper).
6
+ */
7
+ import "./reference-register";
@@ -1,14 +1,10 @@
1
1
  import { useKitServices } from "../context.js";
2
2
  import { CONTROLS } from "../engine/kit-field.js";
3
3
  import { ReferenceControl } from "./reference.js";
4
+ import "./reference-register.js";
4
5
  import { FormPickerControl, isFormOnly } from "./form-picker.js";
5
6
  import { createElement } from "react";
6
7
  //#region src/controls/form-picker-register.ts
7
- /**
8
- * Lane L3: a reference to forms only (`reference: { to: ["form"] }`, a component's `form` prop) draws the form picker
9
- * when the host provides the project's forms; any other reference keeps the reference control. Imported after
10
- * reference-register (kit barrel), so it wraps the registered reference control.
11
- */
12
8
  var Reference = CONTROLS.reference ?? ReferenceControl;
13
9
  CONTROLS.reference = function ReferenceOrFormControl(props) {
14
10
  const { forms, useFormOptions } = useKitServices();
@@ -1 +1 @@
1
- {"version":3,"file":"form-picker-register.js","names":[],"sources":["../../src/controls/form-picker-register.ts"],"sourcesContent":["/**\n * Lane L3: a reference to forms only (`reference: { to: [\"form\"] }`, a component's `form` prop) draws the form picker\n * when the host provides the project's forms; any other reference keeps the reference control. Imported after\n * reference-register (kit barrel), so it wraps the registered reference control.\n */\nimport { createElement } from \"react\";\nimport type { ControlProps } from \"../types\";\nimport { useKitServices } from \"../context\";\nimport { CONTROLS } from \"../engine/kit-field\";\nimport { ReferenceControl } from \"./reference\";\nimport { FormPickerControl, isFormOnly } from \"./form-picker\";\n\nconst Reference = CONTROLS.reference ?? ReferenceControl;\nCONTROLS.reference = function ReferenceOrFormControl(props: ControlProps) {\n const { forms, useFormOptions } = useKitServices();\n return createElement(isFormOnly(props.field) && (forms || useFormOptions) ? FormPickerControl : Reference, props);\n};\n"],"mappings":";;;;;;;;;;;AAYA,IAAM,YAAY,SAAS,aAAa;AACxC,SAAS,YAAY,SAAS,uBAAuB,OAAqB;CACxE,MAAM,EAAE,OAAO,mBAAmB,eAAe;CACjD,OAAO,cAAc,WAAW,MAAM,KAAK,MAAM,SAAS,kBAAkB,oBAAoB,WAAW,KAAK;AAClH"}
1
+ {"version":3,"file":"form-picker-register.js","names":[],"sources":["../../src/controls/form-picker-register.ts"],"sourcesContent":["/**\n * Lane L3: a reference to forms only (`reference: { to: [\"form\"] }`, a component's `form` prop) draws the form picker\n * when the host provides the project's forms; any other reference keeps the reference control. It wraps the registered\n * reference control, so it imports reference-register itself: a bundler may reorder the barrel's bare imports (the\n * 2.0 dist ran this one first, and reference-register then replaced the wrapper).\n */\nimport \"./reference-register\";\nimport { createElement } from \"react\";\nimport type { ControlProps } from \"../types\";\nimport { useKitServices } from \"../context\";\nimport { CONTROLS } from \"../engine/kit-field\";\nimport { ReferenceControl } from \"./reference\";\nimport { FormPickerControl, isFormOnly } from \"./form-picker\";\n\nconst Reference = CONTROLS.reference ?? ReferenceControl;\nCONTROLS.reference = function ReferenceOrFormControl(props: ControlProps) {\n const { forms, useFormOptions } = useKitServices();\n return createElement(isFormOnly(props.field) && (forms || useFormOptions) ? FormPickerControl : Reference, props);\n};\n"],"mappings":";;;;;;;AAcA,IAAM,YAAY,SAAS,aAAa;AACxC,SAAS,YAAY,SAAS,uBAAuB,OAAqB;CACxE,MAAM,EAAE,OAAO,mBAAmB,eAAe;CACjD,OAAO,cAAc,WAAW,MAAM,KAAK,MAAM,SAAS,kBAAkB,oBAAoB,WAAW,KAAK;AAClH"}
@@ -2,7 +2,7 @@
2
2
  * ICON — the icon field (DESIGN 8.14), V2 controls/icon.tsx fitted to what prod stores.
3
3
  *
4
4
  * Values (backend reference-validation `icon`, the classic PropsPanel IconValueInput):
5
- * "arrow-right" a name from the BetterCMS icon set (DOCK_ICON_NAMES, 472 names)
5
+ * "arrow-right" a name from the BetterCMS icon set (DOCK_ICON_NAMES, 473 names)
6
6
  * "https://…/logo.svg" an uploaded icon: an image URL (picked from the media library; a component prop
7
7
  * stores the URL string, like the classic panel's codec="url")
8
8
  * { url, … } an uploaded icon stored as an image value: shown and kept until replaced
@@ -16,7 +16,7 @@ import { Tooltip } from "@base-ui/react/tooltip";
16
16
  * ICON — the icon field (DESIGN 8.14), V2 controls/icon.tsx fitted to what prod stores.
17
17
  *
18
18
  * Values (backend reference-validation `icon`, the classic PropsPanel IconValueInput):
19
- * "arrow-right" a name from the BetterCMS icon set (DOCK_ICON_NAMES, 472 names)
19
+ * "arrow-right" a name from the BetterCMS icon set (DOCK_ICON_NAMES, 473 names)
20
20
  * "https://…/logo.svg" an uploaded icon: an image URL (picked from the media library; a component prop
21
21
  * stores the URL string, like the classic panel's codec="url")
22
22
  * { url, … } an uploaded icon stored as an image value: shown and kept until replaced
@@ -1 +1 @@
1
- {"version":3,"file":"icon.js","names":[],"sources":["../../src/controls/icon.tsx"],"sourcesContent":["/**\n * ICON — the icon field (DESIGN 8.14), V2 controls/icon.tsx fitted to what prod stores.\n *\n * Values (backend reference-validation `icon`, the classic PropsPanel IconValueInput):\n * \"arrow-right\" a name from the BetterCMS icon set (DOCK_ICON_NAMES, 472 names)\n * \"https://…/logo.svg\" an uploaded icon: an image URL (picked from the media library; a component prop\n * stores the URL string, like the classic panel's codec=\"url\")\n * { url, … } an uploaded icon stored as an image value: shown and kept until replaced\n * null no icon (\"No icon\"; the backend skips an empty value)\n *\n * The grid offers the BetterCMS set, the one prod accepts. Search also matches a name's Tabler twin through the\n * Tabler ↔ dock adapter (adapters/icons.ts), so \"map\" finds earth-americas. A stored name outside the set (a stale\n * Tabler or Lucide name) is shown with its reason and kept until the author picks another.\n *\n * Glyphs: the host draws them (KitServices.IconGlyph: the dashboard's DockIcon, the set the site renders). With no\n * host renderer the kit draws the Tabler twin, loaded on first use; a name with no Tabler twin shows its name.\n */\nimport * as React from \"react\";\nimport type { Icon as TablerIcon } from \"@tabler/icons-react\";\nimport { IconAlertTriangle, IconChevronDown, IconPhoto, IconSearch, IconX } from \"@tabler/icons-react\";\nimport { cn } from \"../lib/cn\";\nimport { isObj } from \"../lib/path\";\nimport { iconLabel } from \"../lib/icons\";\nimport { useKitServices } from \"../context\";\nimport { DOCK_ICON_NAMES, DOCK_ICON_NAME_SET } from \"../adapters/prod-types\";\nimport { tablerIconOf } from \"../adapters/icons\";\nimport type { ControlProps } from \"../types\";\nimport { Button } from \"../ui/button\";\nimport { Tooltip as TooltipPrimitive } from \"@base-ui/react/tooltip\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../ui/overlay\";\nimport { TooltipLayer } from \"../ui/tooltip-root\";\n\n/* ── Glyphs ───────────────────────────────────────────────────────────── */\ndeclare module \"../context\" {\n interface KitServices {\n /**\n * Draws one icon of the BetterCMS set by name, aria-hidden (the name beside it is the accessible text): the glyphs\n * the site renders. The dashboard passes `DockIcon` (src/components/dock/DockIcon). Absent → the Tabler twin.\n */\n IconGlyph?: React.ComponentType<{ name: string; className?: string }>;\n }\n}\n\nlet tablerSet: Record<string, unknown> | null = null;\nlet tablerLoad: Promise<void> | null = null;\nconst pascal = (kebab: string) => `Icon${kebab.split(\"-\").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(\"\")}`;\n\n/** The full Tabler set, loaded once and only when no host renderer is provided. */\nfunction useTablerSet(need: boolean): Record<string, unknown> | null {\n const [, landed] = React.useReducer((n: number) => n + 1, 0);\n React.useEffect(() => {\n if (!need || tablerSet) return;\n let live = true;\n tablerLoad ??= import(\"@tabler/icons-react\").then((m) => {\n tablerSet = m as unknown as Record<string, unknown>;\n });\n tablerLoad.then(() => live && landed(), () => {});\n return () => {\n live = false;\n };\n }, [need]);\n return tablerSet;\n}\n\nfunction Glyph({ name, className }: { name: string; className?: string }) {\n const Host = useKitServices().IconGlyph;\n const set = useTablerSet(!Host);\n if (Host) return <Host name={name} className={className} />;\n const T = set?.[pascal(tablerIconOf(name))] as TablerIcon | undefined;\n if (T) return <T className={className} stroke={1.5} aria-hidden />;\n // no Tabler twin (or the set is still loading): the name itself, never a stand-in shape\n return (\n <span aria-hidden className=\"max-w-full truncate px-0.5 text-[9px] leading-3 text-kit-fg-3\">\n {name}\n </span>\n );\n}\n\n/* ── Values ───────────────────────────────────────────────────────────── */\ntype Read =\n | { kind: \"empty\" }\n | { kind: \"name\"; name: string }\n | { kind: \"unknown\"; name: string }\n | { kind: \"upload\"; url: string }\n | { kind: \"unreadable\" };\nconst isUrlish = (s: string) => /[/.]/.test(s);\nfunction readIcon(v: unknown): Read {\n if (v == null || v === \"\") return { kind: \"empty\" };\n if (typeof v === \"string\") return DOCK_ICON_NAME_SET.has(v) ? { kind: \"name\", name: v } : isUrlish(v) ? { kind: \"upload\", url: v } : { kind: \"unknown\", name: v };\n if (isObj(v) && typeof v.url === \"string\" && v.url) return { kind: \"upload\", url: v.url };\n return { kind: \"unreadable\" };\n}\nconst fileName = (url: string) => decodeURIComponent(url.split(/[?#]/)[0].split(\"/\").pop() || url);\n\n/** Names picked this session, newest first (shown as \"Recent\"). */\nconst recents: string[] = [];\nconst pushRecent = (name: string) => {\n const i = recents.indexOf(name);\n if (i !== -1) recents.splice(i, 1);\n recents.unshift(name);\n recents.length = Math.min(recents.length, COLS);\n};\n\n/* ── The grid: one tab stop, arrow keys, Home / End ───────────────────── */\nconst COLS = 8;\nconst norm = (q: string) => q.toLowerCase().trim().replace(/\\s+/g, \"-\");\ninterface GridItem {\n key: string;\n label: string;\n node: React.ReactNode;\n}\n\ntype TipHandle = ReturnType<typeof TooltipPrimitive.createHandle<string>>;\n\n/** Up / down within a grid keep the column; past its edge they land in the same column of the next / previous grid. */\nfunction verticalTarget(grids: HTMLButtonElement[][], g: number, j: number, down: boolean): HTMLButtonElement | \"search\" | undefined {\n const col = j % COLS;\n const cur = grids[g];\n const within = down ? j + COLS : j - COLS;\n if (within >= 0 && within < cur.length) return cur[within];\n if (down && Math.floor(j / COLS) < Math.floor((cur.length - 1) / COLS)) return cur[cur.length - 1]; // a short last row\n const next = grids[down ? g + 1 : g - 1];\n if (!next) return down ? undefined : \"search\";\n if (down) return next[Math.min(col, next.length - 1)];\n const lastRow = Math.floor((next.length - 1) / COLS) * COLS;\n return next[Math.min(lastRow + col, next.length - 1)];\n}\n\nfunction IconGrid({ items, selected, onPick, label, tip }: { items: GridItem[]; selected?: string; onPick: (key: string) => void; label: string; tip: TipHandle }) {\n const ref = React.useRef<HTMLDivElement>(null);\n const stop = items.some((i) => i.key === selected) ? selected : items[0]?.key;\n const move = (e: React.KeyboardEvent) => {\n // one keyboard space across every group in the picker (Recent, the set, search results)\n const scope = ref.current?.closest(\"[data-icon-picker]\") ?? ref.current;\n const grids = Array.from(scope?.querySelectorAll<HTMLElement>(\"[data-icon-grid]\") ?? []).map((g) => Array.from(g.querySelectorAll<HTMLButtonElement>(\"[data-grid-item]\")));\n const btns = grids.flat();\n const i = btns.indexOf(document.activeElement as HTMLButtonElement);\n if (i === -1) return;\n const g = grids.findIndex((list) => list.includes(btns[i]));\n const j = grids[g].indexOf(btns[i]);\n let to: HTMLButtonElement | \"search\" | undefined;\n if (e.key === \"ArrowRight\") to = btns[Math.min(i + 1, btns.length - 1)];\n else if (e.key === \"ArrowLeft\") to = btns[Math.max(i - 1, 0)];\n else if (e.key === \"Home\") to = btns[0];\n else if (e.key === \"End\") to = btns[btns.length - 1];\n else if (e.key === \"ArrowDown\" || e.key === \"ArrowUp\") to = verticalTarget(grids, g, j, e.key === \"ArrowDown\") ?? btns[i];\n else return;\n e.preventDefault();\n if (to === \"search\") scope?.querySelector<HTMLInputElement>(\"input[data-icon-search]\")?.focus();\n else to?.focus();\n };\n return (\n <div ref={ref} data-icon-grid className=\"grid gap-0.5\" style={{ gridTemplateColumns: `repeat(${COLS}, minmax(0, 1fr))` }} role=\"listbox\" aria-label={label} onKeyDown={move}>\n {items.map((it) => (\n // one shared tooltip for the whole picker (hundreds of icons): each trigger hands it its label\n <TooltipPrimitive.Trigger\n key={it.key}\n handle={tip}\n payload={it.label}\n delay={500}\n render={\n <button\n type=\"button\"\n role=\"option\"\n data-grid-item\n data-icon={it.key}\n aria-selected={it.key === selected}\n aria-label={it.label}\n tabIndex={it.key === stop ? 0 : -1}\n onClick={() => onPick(it.key)}\n className={cn(\n \"flex aspect-square items-center justify-center rounded-kit-md text-kit-fg-2 transition-colors hover:bg-kit-hover hover:text-kit-fg\",\n it.key === selected && \"bg-kit-selected text-kit-fg ring-1 ring-inset ring-kit-line-strong\",\n )}\n >\n {it.node}\n </button>\n }\n />\n ))}\n </div>\n );\n}\n\nconst NONE = \"__none\";\n\n/** The picker: search, Recent, then the whole set (\"No icon\" first). Search spans the name and its Tabler twin.\n * `field`: the icon field's popover (V2 IconFieldPicker, a 22rem list); otherwise the app-UI grid (V2 IconPickerGrid, max-h-56). */\nexport function IconPicker({ value, onPick, onChooseMedia, field }: { value?: string; onPick: (name: string | null) => void; onChooseMedia?: () => void; field?: boolean }) {\n const [q, setQ] = React.useState(\"\");\n const root = React.useRef<HTMLDivElement>(null);\n const tip = React.useMemo(() => TooltipPrimitive.createHandle<string>(), []);\n const needle = norm(q);\n const item = (name: string): GridItem => ({ key: name, label: iconLabel(name), node: <Glyph name={name} className=\"size-[18px]\" /> });\n const pick = (key: string) => onPick(key === NONE ? null : key);\n const hits = needle ? DOCK_ICON_NAMES.filter((n) => n.includes(needle) || tablerIconOf(n).includes(needle)) : [];\n const recent = recents.filter((n) => DOCK_ICON_NAME_SET.has(n));\n return (\n <div ref={root} data-icon-picker className=\"flex flex-col gap-2\">\n <TooltipPrimitive.Root handle={tip}>{({ payload }) => (payload ? <TooltipLayer>{payload}</TooltipLayer> : null)}</TooltipPrimitive.Root>\n <label className=\"flex h-8 items-center gap-2 rounded-kit-md bg-kit-muted px-2.5 text-kit-fg-3 focus-within:bg-kit-field-bg focus-within:ring-1 focus-within:ring-kit-line-strong\">\n <IconSearch className=\"size-4 shrink-0\" stroke={1.5} aria-hidden />\n <input\n autoFocus\n data-icon-search\n value={q}\n onChange={(e) => setQ(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n root.current?.querySelector<HTMLButtonElement>(\"[data-grid-item]\")?.focus();\n }\n }}\n placeholder=\"Search icons\"\n aria-label=\"Search icons\"\n className=\"h-full min-w-0 flex-1 bg-transparent text-kit-sm text-kit-fg outline-none placeholder:text-kit-fg-3\"\n />\n </label>\n <div className={field ? \"max-h-[22rem] overflow-y-auto pr-0.5\" : \"max-h-56 overflow-y-auto pr-0.5\"}>\n {needle ? (\n <div className=\"flex flex-col gap-2\">\n {hits.length ? (\n <Group>\n <IconGrid label=\"Icons\" selected={value} onPick={pick} tip={tip} items={hits.map(item)} />\n </Group>\n ) : (\n <p className=\"py-6 text-center text-kit-sm text-kit-fg-3\">No icons match “{q.trim()}”</p>\n )}\n </div>\n ) : (\n <div className=\"flex flex-col gap-2\">\n {recent.length ? (\n <Group title=\"Recent\">\n <IconGrid label=\"Recent icons\" selected={value} onPick={pick} tip={tip} items={recent.map(item)} />\n </Group>\n ) : null}\n <Group title={recent.length ? \"All icons\" : undefined}>\n <IconGrid\n label=\"Icons\"\n selected={value ?? NONE}\n onPick={pick}\n tip={tip}\n items={[{ key: NONE, label: \"No icon\", node: <IconX className=\"size-4\" stroke={1.5} aria-hidden /> }, ...DOCK_ICON_NAMES.map(item)]}\n />\n </Group>\n </div>\n )}\n </div>\n {onChooseMedia ? (\n <div className=\"flex items-center justify-between gap-2 border-t border-kit-line pt-2\">\n <span className=\"whitespace-nowrap text-kit-xs text-kit-fg-3\">Images keep their colours.</span>\n <Button size=\"xs\" variant=\"ghost\" leftIcon={<IconPhoto stroke={1.5} />} onClick={onChooseMedia}>\n Choose from media\n </Button>\n </div>\n ) : null}\n </div>\n );\n}\n\nfunction Group({ title, children }: { title?: string; children: React.ReactNode }) {\n return (\n <div className=\"flex flex-col gap-1\">\n {title ? <div className=\"px-0.5 text-kit-2xs font-medium text-kit-fg-3\">{title}</div> : null}\n {children}\n </div>\n );\n}\n\n/* ── The control ──────────────────────────────────────────────────────── */\nexport function IconControl({ field, value, onChange, readOnly, id, invalid }: ControlProps) {\n const { MediaPicker, mediaSrc } = useKitServices();\n const [open, setOpen] = React.useState(false);\n const [mediaOpen, setMediaOpen] = React.useState(false);\n const read = readIcon(value);\n\n const commit = (v: string | null) => {\n onChange(v);\n if (v && DOCK_ICON_NAME_SET.has(v)) pushRecent(v);\n setOpen(false);\n };\n\n const tile =\n read.kind === \"name\" ? (\n <Glyph name={read.name} className=\"size-4\" />\n ) : read.kind === \"upload\" ? (\n <img src={mediaSrc ? mediaSrc(read.url) : read.url} alt=\"\" className=\"size-4 object-contain\" />\n ) : read.kind === \"empty\" ? (\n <span className=\"size-3 rounded-kit-md border border-dashed border-[var(--kit-emphasis-line)]\" />\n ) : (\n <IconAlertTriangle className=\"size-3.5 text-kit-warning-fg\" stroke={1.5} aria-hidden />\n );\n const text =\n read.kind === \"name\"\n ? iconLabel(read.name)\n : read.kind === \"upload\"\n ? fileName(read.url)\n : read.kind === \"unknown\"\n ? `“${read.name}” isn't in the icon set`\n : read.kind === \"unreadable\"\n ? \"This value isn't an icon this picker can read\"\n : \"Choose an icon\";\n const warn = read.kind === \"unknown\" || read.kind === \"unreadable\";\n\n return (\n <>\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger\n render={\n <button\n id={id}\n type=\"button\"\n disabled={readOnly}\n aria-invalid={invalid || undefined}\n className={cn(\n \"flex h-8 w-full items-center gap-2.5 rounded-kit-md border border-kit-line-strong bg-kit-field-bg px-1 pr-2.5 text-left text-kit-sm transition-colors hover:border-kit-line-strong data-[popup-open]:border-kit-line-strong data-[popup-open]:bg-kit-field-bg\",\n invalid && \"border-[var(--kit-field-invalid)]\",\n )}\n />\n }\n >\n <span className=\"flex size-6 items-center justify-center rounded-kit-md border border-kit-line bg-kit-control-bg text-kit-fg\">{tile}</span>\n <span className={cn(\"min-w-0 flex-1 truncate\", warn ? \"text-kit-warning-fg\" : read.kind === \"empty\" ? \"text-kit-fg-3\" : \"text-kit-fg\")}>{text}</span>\n <IconChevronDown className=\"size-3.5 text-kit-fg-3\" stroke={1.5} aria-hidden />\n </PopoverTrigger>\n <PopoverContent className=\"w-[320px] p-2\" align=\"start\" aria-label={`${field.title}: choose an icon`}>\n <IconPicker\n field\n value={read.kind === \"name\" ? read.name : read.kind === \"empty\" ? undefined : \"\"}\n onPick={commit}\n onChooseMedia={\n MediaPicker\n ? () => {\n setOpen(false);\n setMediaOpen(true);\n }\n : undefined\n }\n />\n </PopoverContent>\n </Popover>\n {MediaPicker && mediaOpen ? (\n <MediaPicker open={mediaOpen} onOpenChange={setMediaOpen} accept={[\"svg\", \"image\"]} onPick={(assets) => assets[0]?.url && commit(assets[0].url)} />\n ) : null}\n </>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"icon.js","names":[],"sources":["../../src/controls/icon.tsx"],"sourcesContent":["/**\n * ICON — the icon field (DESIGN 8.14), V2 controls/icon.tsx fitted to what prod stores.\n *\n * Values (backend reference-validation `icon`, the classic PropsPanel IconValueInput):\n * \"arrow-right\" a name from the BetterCMS icon set (DOCK_ICON_NAMES, 473 names)\n * \"https://…/logo.svg\" an uploaded icon: an image URL (picked from the media library; a component prop\n * stores the URL string, like the classic panel's codec=\"url\")\n * { url, … } an uploaded icon stored as an image value: shown and kept until replaced\n * null no icon (\"No icon\"; the backend skips an empty value)\n *\n * The grid offers the BetterCMS set, the one prod accepts. Search also matches a name's Tabler twin through the\n * Tabler ↔ dock adapter (adapters/icons.ts), so \"map\" finds earth-americas. A stored name outside the set (a stale\n * Tabler or Lucide name) is shown with its reason and kept until the author picks another.\n *\n * Glyphs: the host draws them (KitServices.IconGlyph: the dashboard's DockIcon, the set the site renders). With no\n * host renderer the kit draws the Tabler twin, loaded on first use; a name with no Tabler twin shows its name.\n */\nimport * as React from \"react\";\nimport type { Icon as TablerIcon } from \"@tabler/icons-react\";\nimport { IconAlertTriangle, IconChevronDown, IconPhoto, IconSearch, IconX } from \"@tabler/icons-react\";\nimport { cn } from \"../lib/cn\";\nimport { isObj } from \"../lib/path\";\nimport { iconLabel } from \"../lib/icons\";\nimport { useKitServices } from \"../context\";\nimport { DOCK_ICON_NAMES, DOCK_ICON_NAME_SET } from \"../adapters/prod-types\";\nimport { tablerIconOf } from \"../adapters/icons\";\nimport type { ControlProps } from \"../types\";\nimport { Button } from \"../ui/button\";\nimport { Tooltip as TooltipPrimitive } from \"@base-ui/react/tooltip\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../ui/overlay\";\nimport { TooltipLayer } from \"../ui/tooltip-root\";\n\n/* ── Glyphs ───────────────────────────────────────────────────────────── */\ndeclare module \"../context\" {\n interface KitServices {\n /**\n * Draws one icon of the BetterCMS set by name, aria-hidden (the name beside it is the accessible text): the glyphs\n * the site renders. The dashboard passes `DockIcon` (src/components/dock/DockIcon). Absent → the Tabler twin.\n */\n IconGlyph?: React.ComponentType<{ name: string; className?: string }>;\n }\n}\n\nlet tablerSet: Record<string, unknown> | null = null;\nlet tablerLoad: Promise<void> | null = null;\nconst pascal = (kebab: string) => `Icon${kebab.split(\"-\").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(\"\")}`;\n\n/** The full Tabler set, loaded once and only when no host renderer is provided. */\nfunction useTablerSet(need: boolean): Record<string, unknown> | null {\n const [, landed] = React.useReducer((n: number) => n + 1, 0);\n React.useEffect(() => {\n if (!need || tablerSet) return;\n let live = true;\n tablerLoad ??= import(\"@tabler/icons-react\").then((m) => {\n tablerSet = m as unknown as Record<string, unknown>;\n });\n tablerLoad.then(() => live && landed(), () => {});\n return () => {\n live = false;\n };\n }, [need]);\n return tablerSet;\n}\n\nfunction Glyph({ name, className }: { name: string; className?: string }) {\n const Host = useKitServices().IconGlyph;\n const set = useTablerSet(!Host);\n if (Host) return <Host name={name} className={className} />;\n const T = set?.[pascal(tablerIconOf(name))] as TablerIcon | undefined;\n if (T) return <T className={className} stroke={1.5} aria-hidden />;\n // no Tabler twin (or the set is still loading): the name itself, never a stand-in shape\n return (\n <span aria-hidden className=\"max-w-full truncate px-0.5 text-[9px] leading-3 text-kit-fg-3\">\n {name}\n </span>\n );\n}\n\n/* ── Values ───────────────────────────────────────────────────────────── */\ntype Read =\n | { kind: \"empty\" }\n | { kind: \"name\"; name: string }\n | { kind: \"unknown\"; name: string }\n | { kind: \"upload\"; url: string }\n | { kind: \"unreadable\" };\nconst isUrlish = (s: string) => /[/.]/.test(s);\nfunction readIcon(v: unknown): Read {\n if (v == null || v === \"\") return { kind: \"empty\" };\n if (typeof v === \"string\") return DOCK_ICON_NAME_SET.has(v) ? { kind: \"name\", name: v } : isUrlish(v) ? { kind: \"upload\", url: v } : { kind: \"unknown\", name: v };\n if (isObj(v) && typeof v.url === \"string\" && v.url) return { kind: \"upload\", url: v.url };\n return { kind: \"unreadable\" };\n}\nconst fileName = (url: string) => decodeURIComponent(url.split(/[?#]/)[0].split(\"/\").pop() || url);\n\n/** Names picked this session, newest first (shown as \"Recent\"). */\nconst recents: string[] = [];\nconst pushRecent = (name: string) => {\n const i = recents.indexOf(name);\n if (i !== -1) recents.splice(i, 1);\n recents.unshift(name);\n recents.length = Math.min(recents.length, COLS);\n};\n\n/* ── The grid: one tab stop, arrow keys, Home / End ───────────────────── */\nconst COLS = 8;\nconst norm = (q: string) => q.toLowerCase().trim().replace(/\\s+/g, \"-\");\ninterface GridItem {\n key: string;\n label: string;\n node: React.ReactNode;\n}\n\ntype TipHandle = ReturnType<typeof TooltipPrimitive.createHandle<string>>;\n\n/** Up / down within a grid keep the column; past its edge they land in the same column of the next / previous grid. */\nfunction verticalTarget(grids: HTMLButtonElement[][], g: number, j: number, down: boolean): HTMLButtonElement | \"search\" | undefined {\n const col = j % COLS;\n const cur = grids[g];\n const within = down ? j + COLS : j - COLS;\n if (within >= 0 && within < cur.length) return cur[within];\n if (down && Math.floor(j / COLS) < Math.floor((cur.length - 1) / COLS)) return cur[cur.length - 1]; // a short last row\n const next = grids[down ? g + 1 : g - 1];\n if (!next) return down ? undefined : \"search\";\n if (down) return next[Math.min(col, next.length - 1)];\n const lastRow = Math.floor((next.length - 1) / COLS) * COLS;\n return next[Math.min(lastRow + col, next.length - 1)];\n}\n\nfunction IconGrid({ items, selected, onPick, label, tip }: { items: GridItem[]; selected?: string; onPick: (key: string) => void; label: string; tip: TipHandle }) {\n const ref = React.useRef<HTMLDivElement>(null);\n const stop = items.some((i) => i.key === selected) ? selected : items[0]?.key;\n const move = (e: React.KeyboardEvent) => {\n // one keyboard space across every group in the picker (Recent, the set, search results)\n const scope = ref.current?.closest(\"[data-icon-picker]\") ?? ref.current;\n const grids = Array.from(scope?.querySelectorAll<HTMLElement>(\"[data-icon-grid]\") ?? []).map((g) => Array.from(g.querySelectorAll<HTMLButtonElement>(\"[data-grid-item]\")));\n const btns = grids.flat();\n const i = btns.indexOf(document.activeElement as HTMLButtonElement);\n if (i === -1) return;\n const g = grids.findIndex((list) => list.includes(btns[i]));\n const j = grids[g].indexOf(btns[i]);\n let to: HTMLButtonElement | \"search\" | undefined;\n if (e.key === \"ArrowRight\") to = btns[Math.min(i + 1, btns.length - 1)];\n else if (e.key === \"ArrowLeft\") to = btns[Math.max(i - 1, 0)];\n else if (e.key === \"Home\") to = btns[0];\n else if (e.key === \"End\") to = btns[btns.length - 1];\n else if (e.key === \"ArrowDown\" || e.key === \"ArrowUp\") to = verticalTarget(grids, g, j, e.key === \"ArrowDown\") ?? btns[i];\n else return;\n e.preventDefault();\n if (to === \"search\") scope?.querySelector<HTMLInputElement>(\"input[data-icon-search]\")?.focus();\n else to?.focus();\n };\n return (\n <div ref={ref} data-icon-grid className=\"grid gap-0.5\" style={{ gridTemplateColumns: `repeat(${COLS}, minmax(0, 1fr))` }} role=\"listbox\" aria-label={label} onKeyDown={move}>\n {items.map((it) => (\n // one shared tooltip for the whole picker (hundreds of icons): each trigger hands it its label\n <TooltipPrimitive.Trigger\n key={it.key}\n handle={tip}\n payload={it.label}\n delay={500}\n render={\n <button\n type=\"button\"\n role=\"option\"\n data-grid-item\n data-icon={it.key}\n aria-selected={it.key === selected}\n aria-label={it.label}\n tabIndex={it.key === stop ? 0 : -1}\n onClick={() => onPick(it.key)}\n className={cn(\n \"flex aspect-square items-center justify-center rounded-kit-md text-kit-fg-2 transition-colors hover:bg-kit-hover hover:text-kit-fg\",\n it.key === selected && \"bg-kit-selected text-kit-fg ring-1 ring-inset ring-kit-line-strong\",\n )}\n >\n {it.node}\n </button>\n }\n />\n ))}\n </div>\n );\n}\n\nconst NONE = \"__none\";\n\n/** The picker: search, Recent, then the whole set (\"No icon\" first). Search spans the name and its Tabler twin.\n * `field`: the icon field's popover (V2 IconFieldPicker, a 22rem list); otherwise the app-UI grid (V2 IconPickerGrid, max-h-56). */\nexport function IconPicker({ value, onPick, onChooseMedia, field }: { value?: string; onPick: (name: string | null) => void; onChooseMedia?: () => void; field?: boolean }) {\n const [q, setQ] = React.useState(\"\");\n const root = React.useRef<HTMLDivElement>(null);\n const tip = React.useMemo(() => TooltipPrimitive.createHandle<string>(), []);\n const needle = norm(q);\n const item = (name: string): GridItem => ({ key: name, label: iconLabel(name), node: <Glyph name={name} className=\"size-[18px]\" /> });\n const pick = (key: string) => onPick(key === NONE ? null : key);\n const hits = needle ? DOCK_ICON_NAMES.filter((n) => n.includes(needle) || tablerIconOf(n).includes(needle)) : [];\n const recent = recents.filter((n) => DOCK_ICON_NAME_SET.has(n));\n return (\n <div ref={root} data-icon-picker className=\"flex flex-col gap-2\">\n <TooltipPrimitive.Root handle={tip}>{({ payload }) => (payload ? <TooltipLayer>{payload}</TooltipLayer> : null)}</TooltipPrimitive.Root>\n <label className=\"flex h-8 items-center gap-2 rounded-kit-md bg-kit-muted px-2.5 text-kit-fg-3 focus-within:bg-kit-field-bg focus-within:ring-1 focus-within:ring-kit-line-strong\">\n <IconSearch className=\"size-4 shrink-0\" stroke={1.5} aria-hidden />\n <input\n autoFocus\n data-icon-search\n value={q}\n onChange={(e) => setQ(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n root.current?.querySelector<HTMLButtonElement>(\"[data-grid-item]\")?.focus();\n }\n }}\n placeholder=\"Search icons\"\n aria-label=\"Search icons\"\n className=\"h-full min-w-0 flex-1 bg-transparent text-kit-sm text-kit-fg outline-none placeholder:text-kit-fg-3\"\n />\n </label>\n <div className={field ? \"max-h-[22rem] overflow-y-auto pr-0.5\" : \"max-h-56 overflow-y-auto pr-0.5\"}>\n {needle ? (\n <div className=\"flex flex-col gap-2\">\n {hits.length ? (\n <Group>\n <IconGrid label=\"Icons\" selected={value} onPick={pick} tip={tip} items={hits.map(item)} />\n </Group>\n ) : (\n <p className=\"py-6 text-center text-kit-sm text-kit-fg-3\">No icons match “{q.trim()}”</p>\n )}\n </div>\n ) : (\n <div className=\"flex flex-col gap-2\">\n {recent.length ? (\n <Group title=\"Recent\">\n <IconGrid label=\"Recent icons\" selected={value} onPick={pick} tip={tip} items={recent.map(item)} />\n </Group>\n ) : null}\n <Group title={recent.length ? \"All icons\" : undefined}>\n <IconGrid\n label=\"Icons\"\n selected={value ?? NONE}\n onPick={pick}\n tip={tip}\n items={[{ key: NONE, label: \"No icon\", node: <IconX className=\"size-4\" stroke={1.5} aria-hidden /> }, ...DOCK_ICON_NAMES.map(item)]}\n />\n </Group>\n </div>\n )}\n </div>\n {onChooseMedia ? (\n <div className=\"flex items-center justify-between gap-2 border-t border-kit-line pt-2\">\n <span className=\"whitespace-nowrap text-kit-xs text-kit-fg-3\">Images keep their colours.</span>\n <Button size=\"xs\" variant=\"ghost\" leftIcon={<IconPhoto stroke={1.5} />} onClick={onChooseMedia}>\n Choose from media\n </Button>\n </div>\n ) : null}\n </div>\n );\n}\n\nfunction Group({ title, children }: { title?: string; children: React.ReactNode }) {\n return (\n <div className=\"flex flex-col gap-1\">\n {title ? <div className=\"px-0.5 text-kit-2xs font-medium text-kit-fg-3\">{title}</div> : null}\n {children}\n </div>\n );\n}\n\n/* ── The control ──────────────────────────────────────────────────────── */\nexport function IconControl({ field, value, onChange, readOnly, id, invalid }: ControlProps) {\n const { MediaPicker, mediaSrc } = useKitServices();\n const [open, setOpen] = React.useState(false);\n const [mediaOpen, setMediaOpen] = React.useState(false);\n const read = readIcon(value);\n\n const commit = (v: string | null) => {\n onChange(v);\n if (v && DOCK_ICON_NAME_SET.has(v)) pushRecent(v);\n setOpen(false);\n };\n\n const tile =\n read.kind === \"name\" ? (\n <Glyph name={read.name} className=\"size-4\" />\n ) : read.kind === \"upload\" ? (\n <img src={mediaSrc ? mediaSrc(read.url) : read.url} alt=\"\" className=\"size-4 object-contain\" />\n ) : read.kind === \"empty\" ? (\n <span className=\"size-3 rounded-kit-md border border-dashed border-[var(--kit-emphasis-line)]\" />\n ) : (\n <IconAlertTriangle className=\"size-3.5 text-kit-warning-fg\" stroke={1.5} aria-hidden />\n );\n const text =\n read.kind === \"name\"\n ? iconLabel(read.name)\n : read.kind === \"upload\"\n ? fileName(read.url)\n : read.kind === \"unknown\"\n ? `“${read.name}” isn't in the icon set`\n : read.kind === \"unreadable\"\n ? \"This value isn't an icon this picker can read\"\n : \"Choose an icon\";\n const warn = read.kind === \"unknown\" || read.kind === \"unreadable\";\n\n return (\n <>\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger\n render={\n <button\n id={id}\n type=\"button\"\n disabled={readOnly}\n aria-invalid={invalid || undefined}\n className={cn(\n \"flex h-8 w-full items-center gap-2.5 rounded-kit-md border border-kit-line-strong bg-kit-field-bg px-1 pr-2.5 text-left text-kit-sm transition-colors hover:border-kit-line-strong data-[popup-open]:border-kit-line-strong data-[popup-open]:bg-kit-field-bg\",\n invalid && \"border-[var(--kit-field-invalid)]\",\n )}\n />\n }\n >\n <span className=\"flex size-6 items-center justify-center rounded-kit-md border border-kit-line bg-kit-control-bg text-kit-fg\">{tile}</span>\n <span className={cn(\"min-w-0 flex-1 truncate\", warn ? \"text-kit-warning-fg\" : read.kind === \"empty\" ? \"text-kit-fg-3\" : \"text-kit-fg\")}>{text}</span>\n <IconChevronDown className=\"size-3.5 text-kit-fg-3\" stroke={1.5} aria-hidden />\n </PopoverTrigger>\n <PopoverContent className=\"w-[320px] p-2\" align=\"start\" aria-label={`${field.title}: choose an icon`}>\n <IconPicker\n field\n value={read.kind === \"name\" ? read.name : read.kind === \"empty\" ? undefined : \"\"}\n onPick={commit}\n onChooseMedia={\n MediaPicker\n ? () => {\n setOpen(false);\n setMediaOpen(true);\n }\n : undefined\n }\n />\n </PopoverContent>\n </Popover>\n {MediaPicker && mediaOpen ? (\n <MediaPicker open={mediaOpen} onOpenChange={setMediaOpen} accept={[\"svg\", \"image\"]} onPick={(assets) => assets[0]?.url && commit(assets[0].url)} />\n ) : null}\n </>\n );\n}\n"],"mappings":"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"}
@@ -191,19 +191,22 @@ export interface LinkPickerProps {
191
191
  className?: string;
192
192
  }
193
193
  export declare function LinkPicker({ value, onChange, onPick, onEnter, onEscape, autoFocus, startSearching, id, "aria-label": ariaLabel, size, bare, lockedType, inlineList, types, invalid, "aria-describedby": describedBy, className, }: LinkPickerProps): import("react/jsx-runtime").JSX.Element;
194
- export declare function LinkOptions({ v, onChange, compact, disabled }: {
194
+ export declare function LinkOptions({ v, onChange, compact, disabled, hideNewTab, }: {
195
195
  v: LinkValue;
196
196
  onChange: (v: LinkValue) => void;
197
197
  compact?: boolean;
198
- disabled?: boolean;
198
+ disabled?: boolean; /** the place forbids a new tab (a rich-text field without allowTargetBlank) */
199
+ hideNewTab?: boolean;
199
200
  }): import("react/jsx-runtime").JSX.Element;
200
- export declare function LinkForm({ value, onChange, onEnter, hideOptions, types, autoFocus, readOnly, className, }: {
201
+ export declare function LinkForm({ value, onChange, onEnter, hideOptions, hideNewTab, types, autoFocus, readOnly, className, }: {
201
202
  value: LinkValue;
202
203
  onChange: (v: LinkValue) => void;
203
204
  /** Enter in the target with a value that can be saved */
204
205
  onEnter?: (v: LinkValue) => void;
205
206
  /** string-only values: no new tab / rel */
206
207
  hideOptions?: boolean;
208
+ /** no new tab here (rel stays) */
209
+ hideNewTab?: boolean;
207
210
  /** the link types this place allows. Default: all */
208
211
  types?: LinkType[];
209
212
  /** focus the Type control on open (D48: never the search) */
@@ -212,15 +215,19 @@ export declare function LinkForm({ value, onChange, onEnter, hideOptions, types,
212
215
  readOnly?: boolean;
213
216
  className?: string;
214
217
  }): import("react/jsx-runtime").JSX.Element;
215
- export declare function LinkPopoverBody({ initial, onSave, onDetach, onCancel, hideOptions, types, }: {
218
+ export declare function LinkPopoverBody({ initial, onSave, onDetach, onCancel, hideOptions, hideNewTab, types, dirty, }: {
216
219
  initial: LinkValue;
217
220
  onSave: (v: LinkValue) => void;
218
221
  onDetach?: () => void;
219
222
  onCancel?: () => void;
220
223
  /** string-only fields: no new tab / rel */
221
224
  hideOptions?: boolean;
225
+ /** no new tab here (rel stays): a rich-text field without allowTargetBlank */
226
+ hideNewTab?: boolean;
222
227
  /** the link types this place allows. Default: all */
223
228
  types?: LinkType[];
229
+ /** something the host draws beside the form changed (a button's label), so Apply has something to save */
230
+ dirty?: boolean;
224
231
  }): import("react/jsx-runtime").JSX.Element;
225
232
  export declare function LinkQuickEdit({ value, onCommit, onDone, ariaLabel, size, bare, types, }: {
226
233
  value: LinkValue;