@domternal/vanilla 0.15.0 → 1.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.
package/README.md CHANGED
@@ -4,9 +4,10 @@
4
4
  [![MIT License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/domternal/domternal/blob/main/LICENSE)
5
5
 
6
6
  Framework-free DOM components for the [Domternal](https://domternal.dev) editor.
7
- Each component is a class you instantiate against a host element:
8
- `DomternalEditor`, `DomternalToolbar`, `DomternalBubbleMenu`, `DomternalFloatingMenu`,
9
- `DomternalEmojiPicker`, and `DomternalNotionColorPicker`. Every class extends
7
+ Most are classes you instantiate against a host element: `DomternalEditor`,
8
+ `DomternalToolbar`, `DomternalBubbleMenu`, `DomternalFloatingMenu`, and
9
+ `DomternalEmojiPicker`. `DomternalNotionColorPicker` is the exception: it takes only an
10
+ options object and resolves its own `.dm-editor` host from the editor. Every class extends
10
11
  `EventTarget`, exposes plain getters and mutator methods, dispatches `CustomEvent`s for state
11
12
  changes, and tears down with an idempotent `destroy()`. Use it in Astro, Svelte, Solid,
12
13
  Lit, Web Components, or plain HTML - anywhere without a framework runtime.
@@ -21,8 +22,8 @@ Lit, Web Components, or plain HTML - anywhere without a framework runtime.
21
22
  pnpm add @domternal/core @domternal/theme @domternal/vanilla
22
23
  ```
23
24
 
24
- `@domternal/core` is a peer dependency. `@domternal/theme` supplies the editor styles
25
- (import it once in your app).
25
+ `@domternal/core` (>=1.0.0) is a peer dependency. `@domternal/theme` supplies the editor
26
+ styles (import it once in your app).
26
27
 
27
28
  ## Usage
28
29
 
@@ -70,6 +71,24 @@ The matching mount points:
70
71
  > SSR. Module-scope imports stay SSR-safe, so gate instantiation behind a client-side
71
72
  > entry point (e.g. an Astro `<script>` block or `client:only`).
72
73
 
74
+ ## Options
75
+
76
+ `DomternalEditorOptions`, the second constructor argument:
77
+
78
+ | Option | Type | Default | Description |
79
+ |---|---|---|---|
80
+ | `extensions` | `AnyExtension[]` | `[]` | Extensions merged on top of `DEFAULT_EXTENSIONS`. |
81
+ | `history` | `boolean` | `true` | Whether the built-in History extension is loaded. Turn it off when an extension brings its own undo. |
82
+ | `content` | `Content` | `''` | Initial content, HTML string or JSON. |
83
+ | `editable` | `boolean` | `true` | Whether the editor is editable. |
84
+ | `preset` | `'classic' \| 'notion'` | `'classic'` | `'notion'` paints `dm-notion-mode` on the `.dm-editor` host and switches preset-aware extensions to their Notion behavior. Create-time only. |
85
+ | `autofocus` | `FocusPosition` | `false` | Where to place the caret on mount. |
86
+ | `outputFormat` | `'html' \| 'json'` | `'html'` | Format hint for host frameworks comparing controlled content. Does not change editor behavior. |
87
+
88
+ `onCreate`, `onUpdate`, `onSelectionChange`, `onFocus`, `onBlur`, and `onDestroy` callbacks are
89
+ accepted alongside them, and the same moments are dispatched on the instance as `create`,
90
+ `update`, `selectionchange`, `focus`, `blur`, and `destroy` `CustomEvent`s.
91
+
73
92
  ## Exports
74
93
 
75
94
  - `DomternalEditor` / `DomternalEditorOptions` - wraps core's `Editor`, plus
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { PluginKey, IconSet, Editor, AnyExtension, Content, EditorPreset, FocusPosition, JSONContent, ToolbarController, ToolbarLayoutEntry, ToolbarButton, ToolbarDropdown, BubbleMenuOptions, FloatingMenuController, FloatingMenuOptions, FloatingMenuItemsOverride, FloatingMenuKeymap } from '@domternal/core';
1
+ import { PluginKey, IconSet, AnyExtension, Editor, Content, EditorPreset, FocusPosition, JSONContent, ToolbarController, ToolbarLayoutEntry, ToolbarButton, ToolbarDropdown, BubbleMenuOptions, BubbleContexts, FloatingMenuController, FloatingMenuOptions, FloatingMenuItemsOverride, FloatingMenuKeymap } from '@domternal/core';
2
+ export { BubbleMenuItem, BubbleMenuSeparator, BubbleItemMaps as ItemMaps, ResolvedPosShape, SelectionShape, buildBubbleItemMaps as buildItemMaps, detectBubbleContext as detectContext, filterBubbleItemsBySchema as filterBySchema, getBubbleFormatItems as getFormatItems, isInsideTableCell, resolveBubbleNames as resolveNames } from '@domternal/core';
2
3
 
3
4
  /**
4
5
  * SSR-safe environment check.
@@ -440,85 +441,6 @@ declare function getComputedStyleAtCursor(editor: Editor, prop: string): string
440
441
  */
441
442
  declare function getInlineStyleAtCursor(editor: Editor, prop: string): string | null;
442
443
 
443
- interface ResolvedPosShape {
444
- parent: {
445
- type: {
446
- name: string;
447
- spec: {
448
- marks?: string;
449
- };
450
- };
451
- };
452
- depth: number;
453
- node: (depth: number) => {
454
- type: {
455
- name: string;
456
- };
457
- };
458
- }
459
- interface SelectionShape {
460
- empty: boolean;
461
- $from: ResolvedPosShape;
462
- $to: ResolvedPosShape;
463
- node?: {
464
- type: {
465
- name: string;
466
- };
467
- };
468
- }
469
- interface BubbleMenuSeparator {
470
- type: 'separator';
471
- name: string;
472
- }
473
- type BubbleMenuItem = ToolbarButton | ToolbarDropdown | BubbleMenuSeparator;
474
- interface ItemMaps {
475
- itemMap: Map<string, ToolbarButton>;
476
- dropdownMap: Map<string, ToolbarDropdown>;
477
- /** Bubble defaults indexed by context name (e.g. 'text', 'codeBlock'). */
478
- bubbleDefaults: Map<string, BubbleMenuItem[]>;
479
- }
480
- /**
481
- * Walks `editor.toolbarItems` and indexes them by name. Dropdowns are kept
482
- * separately so the bubble menu can resolve them by name (e.g. text-align).
483
- */
484
- declare function buildItemMaps(editor: Editor): ItemMaps;
485
- /**
486
- * Resolve a name array to BubbleMenuItems via the item/dropdown maps.
487
- * Dropdowns take priority over buttons sharing the same name. Pipe `|`
488
- * tokens become separator entries.
489
- */
490
- declare function resolveNames(names: string[], itemMap: Map<string, ToolbarButton>, dropdownMap: Map<string, ToolbarDropdown>): BubbleMenuItem[];
491
- /**
492
- * All `format`-group buttons sorted by priority (used by `context: true`
493
- * shorthand to show "all format marks for this context").
494
- */
495
- declare function getFormatItems(itemMap: Map<string, ToolbarButton>): ToolbarButton[];
496
- /**
497
- * Determine the active bubble-menu context based on the selection. Returns
498
- * `null` when no context matches (menu should not show).
499
- *
500
- * Resolution order:
501
- * 1. CellSelection (`$anchorCell`) - no menu inside table cell selections
502
- * 2. NodeSelection (image, HR, etc.) - return the node's type name
503
- * 3. Empty selection - no context (caret-only)
504
- * 4. Inside a table cell - return 'table' (when from/to share a cell)
505
- * 5. `$from.parent.type.name` if listed in contexts
506
- * 6. 'text' if the parent allows marks
507
- */
508
- declare function detectContext(selection: SelectionShape, ctxs: Record<string, string[] | true | null>): string | null;
509
- /**
510
- * Filter items by what the schema actually allows in the given context.
511
- * E.g. inside a `codeBlock` node, marks like Bold/Italic aren't permitted.
512
- *
513
- * Pass-through for 'text' and 'table' contexts (schema check would be
514
- * lossy there because marks are allowed but some items still apply).
515
- */
516
- declare function filterBySchema(editor: Editor, contextName: string, schemaItems: ToolbarButton[]): ToolbarButton[];
517
- /**
518
- * Detect whether `$pos` is inside a table cell (cell or header).
519
- */
520
- declare function isInsideTableCell($pos: ResolvedPosShape): boolean;
521
-
522
444
  /**
523
445
  * Live state for the bubble-menu trailing buttons.
524
446
  *
@@ -576,7 +498,7 @@ interface DomternalBubbleMenuOptions extends CustomContentOption {
576
498
  * to item name list, `true` (show all format items), or `null` (no menu for
577
499
  * this context). Defaults to `defaultBubbleContexts(editor)`.
578
500
  */
579
- contexts?: Record<string, string[] | true | null>;
501
+ contexts?: BubbleContexts;
580
502
  /** Custom icon overrides. Falls back to default Phosphor icons for unmapped keys. */
581
503
  icons?: IconSet;
582
504
  }
@@ -666,7 +588,7 @@ declare class DomternalBubbleMenu extends EventTarget {
666
588
  * plugin at construction; this only changes WHICH items render once the
667
589
  * menu is visible.
668
590
  */
669
- setContexts(contexts: Record<string, string[] | true | null> | undefined): void;
591
+ setContexts(contexts: BubbleContexts | undefined): void;
670
592
  /** Replace the icon set. `undefined` restores default Phosphor icons. */
671
593
  setIcons(icons: IconSet | undefined): void;
672
594
  /** Close any open dropdown (text-align). No-op if nothing is open. */
@@ -878,4 +800,4 @@ declare class DomternalEmojiPicker extends EventTarget {
878
800
  destroy(): void;
879
801
  }
880
802
 
881
- export { type BubbleMenuItem, type BubbleMenuSeparator, type BubbleMenuTrailingState, type CustomContentOption, DEFAULT_EXTENSIONS, DROPDOWN_CARET, DomternalBubbleMenu, type DomternalBubbleMenuOptions, DomternalEditor, type DomternalEditorOptions, DomternalEmojiPicker, type DomternalEmojiPickerOptions, DomternalFloatingMenu, type DomternalFloatingMenuOptions, DomternalNotionColorPicker, type DomternalNotionColorPickerOptions, DomternalToolbar, type DomternalToolbarOptions, type EmojiPickerItem, INITIAL_TRAILING_STATE, type IconCache, type ItemMaps, type ResolvedPosShape, type SelectionShape, assertBrowser, buildItemMaps, computeTrailingState, createIconCache, createPluginKey, detectContext, filterBySchema, getComputedStyleAtCursor, getFormatItems, getInlineStyleAtCursor, getTooltip, isBrowser, isInsideTableCell, renderIconInto, resolveIcon, resolveNames, subscribe };
803
+ export { type BubbleMenuTrailingState, type CustomContentOption, DEFAULT_EXTENSIONS, DROPDOWN_CARET, DomternalBubbleMenu, type DomternalBubbleMenuOptions, DomternalEditor, type DomternalEditorOptions, DomternalEmojiPicker, type DomternalEmojiPickerOptions, DomternalFloatingMenu, type DomternalFloatingMenuOptions, DomternalNotionColorPicker, type DomternalNotionColorPickerOptions, DomternalToolbar, type DomternalToolbarOptions, type EmojiPickerItem, INITIAL_TRAILING_STATE, type IconCache, assertBrowser, computeTrailingState, createIconCache, createPluginKey, getComputedStyleAtCursor, getInlineStyleAtCursor, getTooltip, isBrowser, renderIconInto, resolveIcon, subscribe };
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
- import { Document, Paragraph, Text, BaseKeymap, History, PluginKey, defaultIcons, Editor, ToolbarController, positionFloatingOnce, refocusEditorAfterCommand, defaultBubbleContexts, createBubbleMenuPlugin, createFloatingMenuPlugin, FloatingMenuController, positionFloating } from '@domternal/core';
1
+ import { Document, Paragraph, Text, BaseKeymap, History, PluginKey, defaultIcons, Editor, ToolbarController, positionFloatingOnce, refocusEditorAfterCommand, resolveBubbleNames, defaultBubbleContexts, buildBubbleItemMaps, createBubbleMenuPlugin, createBubbleShouldShow, resolveBubbleMenuItems, createFloatingMenuPlugin, FloatingMenuController, positionFloating } from '@domternal/core';
2
+ export { buildBubbleItemMaps as buildItemMaps, detectBubbleContext as detectContext, filterBubbleItemsBySchema as filterBySchema, getBubbleFormatItems as getFormatItems, isInsideTableCell, resolveBubbleNames as resolveNames } from '@domternal/core';
2
3
 
3
4
  // src/shared/isBrowser.ts
4
5
  var isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
@@ -889,129 +890,6 @@ var DomternalToolbar = class extends EventTarget {
889
890
  }
890
891
  };
891
892
 
892
- // src/bubble-menu/itemResolver.ts
893
- function buildItemMaps(editor) {
894
- const itemMap = /* @__PURE__ */ new Map();
895
- const dropdownMap = /* @__PURE__ */ new Map();
896
- for (const item of editor.toolbarItems) {
897
- if (item.type === "button") {
898
- itemMap.set(item.name, item);
899
- } else if (item.type === "dropdown") {
900
- dropdownMap.set(item.name, item);
901
- for (const sub of item.items) {
902
- itemMap.set(sub.name, sub);
903
- }
904
- }
905
- }
906
- return {
907
- itemMap,
908
- dropdownMap,
909
- bubbleDefaults: buildBubbleDefaults(editor)
910
- };
911
- }
912
- function buildBubbleDefaults(editor) {
913
- const byCtx = /* @__PURE__ */ new Map();
914
- const addItem = (btn) => {
915
- const ctx = btn["bubbleMenu"];
916
- if (!ctx) return;
917
- let arr = byCtx.get(ctx);
918
- if (!arr) {
919
- arr = [];
920
- byCtx.set(ctx, arr);
921
- }
922
- arr.push(btn);
923
- };
924
- for (const item of editor.toolbarItems) {
925
- if (item.type === "button") addItem(item);
926
- else if (item.type === "dropdown") {
927
- for (const sub of item.items) addItem(sub);
928
- }
929
- }
930
- const result = /* @__PURE__ */ new Map();
931
- for (const [ctx, ctxItems] of byCtx) {
932
- ctxItems.sort((a, b) => (b.priority ?? 100) - (a.priority ?? 100));
933
- const list = [];
934
- let lastGroup;
935
- let sepIdx = 0;
936
- for (const item of ctxItems) {
937
- if (lastGroup !== void 0 && item.group !== lastGroup) {
938
- list.push({ type: "separator", name: `bsep-${String(sepIdx++)}` });
939
- }
940
- list.push(item);
941
- lastGroup = item.group;
942
- }
943
- result.set(ctx, list);
944
- }
945
- return result;
946
- }
947
- function resolveNames(names, itemMap, dropdownMap) {
948
- const result = [];
949
- let sepIdx = 0;
950
- for (const name of names) {
951
- if (name === "|") {
952
- result.push({ type: "separator", name: `sep-${String(sepIdx++)}` });
953
- continue;
954
- }
955
- const dropdown = dropdownMap.get(name);
956
- if (dropdown) {
957
- result.push(dropdown);
958
- continue;
959
- }
960
- const item = itemMap.get(name);
961
- if (item) result.push(item);
962
- }
963
- return result;
964
- }
965
- function getFormatItems(itemMap) {
966
- return Array.from(itemMap.values()).filter((item) => item.group === "format").sort((a, b) => (b.priority ?? 100) - (a.priority ?? 100));
967
- }
968
- function detectContext(selection, ctxs) {
969
- if ("$anchorCell" in selection) return null;
970
- if (selection.node) return selection.node.type.name;
971
- if (selection.empty) return null;
972
- const fromCell = findCellNode(selection.$from);
973
- if (fromCell) {
974
- const toCell = findCellNode(selection.$to);
975
- if (toCell && fromCell !== toCell) return null;
976
- return "table";
977
- }
978
- const fromName = selection.$from.parent.type.name;
979
- if (fromName in ctxs) return fromName;
980
- if ("text" in ctxs && selection.$from.parent.type.spec.marks !== "") return "text";
981
- const toName = selection.$to.parent.type.name;
982
- if (toName in ctxs) return toName;
983
- if ("text" in ctxs && selection.$to.parent.type.spec.marks !== "") return "text";
984
- return null;
985
- }
986
- function filterBySchema(editor, contextName, schemaItems) {
987
- if (contextName === "text" || contextName === "table") return schemaItems;
988
- const schema = editor.state.schema;
989
- if (!schema) return schemaItems;
990
- const nodeType = schema.nodes[contextName];
991
- if (!nodeType) return schemaItems;
992
- return schemaItems.filter((item) => {
993
- const markName = typeof item.isActive === "string" ? item.isActive : null;
994
- if (!markName) return true;
995
- const markType = schema.marks[markName];
996
- if (!markType) return true;
997
- return nodeType.allowsMarkType(markType);
998
- });
999
- }
1000
- function isInsideTableCell($pos) {
1001
- for (let d = $pos.depth; d > 0; d--) {
1002
- const name = $pos.node(d).type.name;
1003
- if (name === "tableCell" || name === "tableHeader") return true;
1004
- }
1005
- return false;
1006
- }
1007
- function findCellNode(pos) {
1008
- for (let d = pos.depth; d > 0; d--) {
1009
- const node = pos.node(d);
1010
- if (node.type.name === "tableCell" || node.type.name === "tableHeader") return node;
1011
- }
1012
- return null;
1013
- }
1014
-
1015
893
  // src/bubble-menu/trailingState.ts
1016
894
  var INITIAL_TRAILING_STATE = {
1017
895
  isNodeSelection: false,
@@ -1189,7 +1067,7 @@ var DomternalBubbleMenu = class extends EventTarget {
1189
1067
  if (this.#destroyed) return;
1190
1068
  this.#explicitItems = items;
1191
1069
  if (this.#maps) {
1192
- this.#defaultItemList = items ? resolveNames(items, this.#maps.itemMap, this.#maps.dropdownMap) : resolveNames(["bold", "italic", "underline"], this.#maps.itemMap, this.#maps.dropdownMap);
1070
+ this.#defaultItemList = items ? resolveBubbleNames(items, this.#maps.itemMap, this.#maps.dropdownMap) : resolveBubbleNames(["bold", "italic", "underline"], this.#maps.itemMap, this.#maps.dropdownMap);
1193
1071
  }
1194
1072
  this.#updateResolvedItems();
1195
1073
  this.#updateStates();
@@ -1246,9 +1124,9 @@ var DomternalBubbleMenu = class extends EventTarget {
1246
1124
  const exts = ed.extensionManager.extensions;
1247
1125
  this.#hasNotionColorPicker = exts.some((e) => e.name === "notionColorPicker");
1248
1126
  this.#hasBlockContextMenu = exts.some((e) => e.name === "blockContextMenu");
1249
- this.#maps = buildItemMaps(ed);
1127
+ this.#maps = buildBubbleItemMaps(ed);
1250
1128
  this.#effectiveContexts = this.#explicitContexts ?? (this.#explicitItems ? void 0 : defaultBubbleContexts(ed));
1251
- this.#defaultItemList = this.#explicitItems ? resolveNames(this.#explicitItems, this.#maps.itemMap, this.#maps.dropdownMap) : resolveNames(["bold", "italic", "underline"], this.#maps.itemMap, this.#maps.dropdownMap);
1129
+ this.#defaultItemList = this.#explicitItems ? resolveBubbleNames(this.#explicitItems, this.#maps.itemMap, this.#maps.dropdownMap) : resolveBubbleNames(["bold", "italic", "underline"], this.#maps.itemMap, this.#maps.dropdownMap);
1252
1130
  const shouldShow = this.#shouldShowOpt ?? this.#buildDefaultShouldShow();
1253
1131
  const plugin = createBubbleMenuPlugin({
1254
1132
  pluginKey: this.#pluginKey,
@@ -1280,76 +1158,28 @@ var DomternalBubbleMenu = class extends EventTarget {
1280
1158
  this.#render();
1281
1159
  }
1282
1160
  #buildDefaultShouldShow() {
1283
- const contexts = this.#effectiveContexts;
1284
- const defaults = this.#maps?.bubbleDefaults;
1285
- if (contexts) {
1286
- return ({ state }) => {
1287
- const ctx = detectContext(
1288
- state.selection,
1289
- contexts
1290
- );
1291
- if (!ctx) return false;
1292
- if (ctx in contexts) {
1293
- const val = contexts[ctx];
1294
- if (val === null) return false;
1295
- return val === true || Array.isArray(val) && val.length > 0;
1296
- }
1297
- return defaults?.has(ctx) ?? false;
1298
- };
1299
- }
1300
- return ({ state }) => {
1301
- const sel = state.selection;
1302
- if (sel.empty) return false;
1303
- if (sel.node) return defaults?.has(sel.node.type.name) ?? false;
1304
- if (isInsideTableCell(sel.$from)) return false;
1305
- return sel.$from.parent.type.spec.marks !== "" || sel.$to.parent.type.spec.marks !== "";
1306
- };
1161
+ const maps = this.#maps;
1162
+ if (!maps) return () => false;
1163
+ return createBubbleShouldShow(maps, this.#effectiveContexts);
1307
1164
  }
1308
1165
  // === State updates (called per transaction) ===
1166
+ /**
1167
+ * One call per transaction, into the resolver core owns. Every renderer
1168
+ * asks the same question and has to get the same answer, so the answer is
1169
+ * not written here.
1170
+ */
1309
1171
  #updateResolvedItems() {
1310
- if (!this.#maps) return;
1311
- const ed = this.#editor;
1312
- const contexts = this.#effectiveContexts;
1313
- if (contexts) {
1314
- const ctx = detectContext(
1315
- ed.state.selection,
1316
- contexts
1317
- );
1318
- if (!ctx) {
1319
- this.#resolvedItems = [];
1320
- return;
1321
- }
1322
- if (ctx in contexts) {
1323
- const val = contexts[ctx];
1324
- if (val === null || Array.isArray(val) && val.length === 0) {
1325
- this.#resolvedItems = [];
1326
- return;
1327
- }
1328
- if (val === true) {
1329
- this.#resolvedItems = filterBySchema(ed, ctx, getFormatItems(this.#maps.itemMap));
1330
- return;
1331
- }
1332
- if (Array.isArray(val)) {
1333
- const resolved = resolveNames(val, this.#maps.itemMap, this.#maps.dropdownMap);
1334
- const buttons = resolved.filter(
1335
- (i) => i.type !== "separator"
1336
- );
1337
- const allowed = new Set(filterBySchema(ed, ctx, buttons).map((b) => b.name));
1338
- this.#resolvedItems = resolved.filter(
1339
- (i) => i.type === "separator" || allowed.has(i.name)
1340
- );
1341
- return;
1342
- }
1343
- }
1344
- this.#resolvedItems = this.#maps.bubbleDefaults.get(ctx) ?? [];
1172
+ const maps = this.#maps;
1173
+ if (!maps) {
1174
+ this.#resolvedItems = [];
1345
1175
  return;
1346
1176
  }
1347
- const sel = ed.state.selection;
1348
- if (sel.node && this.#maps.bubbleDefaults.has(sel.node.type.name)) {
1349
- this.#resolvedItems = this.#maps.bubbleDefaults.get(sel.node.type.name) ?? [];
1350
- } else {
1351
- this.#resolvedItems = this.#defaultItemList;
1352
- }
1177
+ this.#resolvedItems = resolveBubbleMenuItems({
1178
+ editor: this.#editor,
1179
+ maps,
1180
+ contexts: this.#effectiveContexts,
1181
+ fallbackItems: this.#defaultItemList
1182
+ });
1353
1183
  }
1354
1184
  #updateStates() {
1355
1185
  const ed = this.#editor;
@@ -1445,12 +1275,18 @@ var DomternalBubbleMenu = class extends EventTarget {
1445
1275
  }
1446
1276
  }
1447
1277
  const t = this.#trailing;
1448
- if (t.showColorPickerButton && !t.isNodeSelection) {
1449
- this.host.appendChild(this.#createSeparator("trailing-sep-color"));
1278
+ const showColor = t.showColorPickerButton && !t.isNodeSelection;
1279
+ const showBlock = t.showBlockMenuButton && !t.isNodeSelection;
1280
+ if (showColor) {
1281
+ if (this.#resolvedItems.length > 0) {
1282
+ this.host.appendChild(this.#createSeparator("trailing-sep-color"));
1283
+ }
1450
1284
  this.host.appendChild(this.#createColorTrigger());
1451
1285
  }
1452
- if (t.showBlockMenuButton && !t.isNodeSelection) {
1453
- this.host.appendChild(this.#createSeparator("trailing-sep-block"));
1286
+ if (showBlock) {
1287
+ if (this.#resolvedItems.length > 0 || showColor) {
1288
+ this.host.appendChild(this.#createSeparator("trailing-sep-block"));
1289
+ }
1454
1290
  this.host.appendChild(this.#createBlockMenuTrigger());
1455
1291
  }
1456
1292
  if (this.#customContent) {
@@ -1977,6 +1813,14 @@ var DomternalFloatingMenu = class extends EventTarget {
1977
1813
  }
1978
1814
  }
1979
1815
  };
1816
+ function paletteFromExtensionOptions(options) {
1817
+ if (typeof options !== "object" || options === null || !("palette" in options)) return [];
1818
+ const palette = options.palette;
1819
+ if (!Array.isArray(palette) || !palette.every((token) => typeof token === "string")) {
1820
+ return [];
1821
+ }
1822
+ return [...palette];
1823
+ }
1980
1824
  var TOKEN_LABELS = {
1981
1825
  gray: "Gray",
1982
1826
  brown: "Brown",
@@ -2053,8 +1897,7 @@ var DomternalNotionColorPicker = class extends EventTarget {
2053
1897
  const ext = this.#editor.extensionManager.extensions.find(
2054
1898
  (e) => e.name === "notionColorPicker"
2055
1899
  );
2056
- const extOpts = ext?.options ?? null;
2057
- this.#palette = extOpts?.palette ? [...extOpts.palette] : [];
1900
+ this.#palette = paletteFromExtensionOptions(ext?.options);
2058
1901
  this.#onOpen = (...args) => {
2059
1902
  const detail = args[0];
2060
1903
  const incoming = detail?.anchorElement;
@@ -2135,9 +1978,7 @@ var DomternalNotionColorPicker = class extends EventTarget {
2135
1978
  this.#editor.commands.setTextColorToken(token);
2136
1979
  this.#syncFromSelection();
2137
1980
  this.#updateActiveClasses();
2138
- this.dispatchEvent(
2139
- new CustomEvent("apply", { detail: { kind: "text", token } })
2140
- );
1981
+ this.dispatchEvent(new CustomEvent("apply", { detail: { kind: "text", token } }));
2141
1982
  }
2142
1983
  /** Apply a background color token to the current selection. Picker stays open. */
2143
1984
  applyBg(token) {
@@ -2145,9 +1986,7 @@ var DomternalNotionColorPicker = class extends EventTarget {
2145
1986
  this.#editor.commands.setBackgroundColorToken(token);
2146
1987
  this.#syncFromSelection();
2147
1988
  this.#updateActiveClasses();
2148
- this.dispatchEvent(
2149
- new CustomEvent("apply", { detail: { kind: "bg", token } })
2150
- );
1989
+ this.dispatchEvent(new CustomEvent("apply", { detail: { kind: "bg", token } }));
2151
1990
  }
2152
1991
  /** Display label for a palette token (title-case fallback). */
2153
1992
  tokenLabel(token) {
@@ -2357,9 +2196,7 @@ var DomternalNotionColorPicker = class extends EventTarget {
2357
2196
  return rect;
2358
2197
  }
2359
2198
  if (this.#anchorBubbleMenu?.isConnected) {
2360
- const fresh = this.#anchorBubbleMenu.querySelector(
2361
- ".dm-ncp-trigger"
2362
- );
2199
+ const fresh = this.#anchorBubbleMenu.querySelector(".dm-ncp-trigger");
2363
2200
  if (fresh) {
2364
2201
  this.#anchor = fresh;
2365
2202
  const rect = fresh.getBoundingClientRect();
@@ -2429,15 +2266,13 @@ var DomternalNotionColorPicker = class extends EventTarget {
2429
2266
  #onPanelKeydown(event) {
2430
2267
  const cols = 5;
2431
2268
  if (!this.#panel) return;
2432
- const swatches = Array.from(
2433
- this.#panel.querySelectorAll(".dm-ncp-swatch")
2434
- );
2269
+ const swatches = Array.from(this.#panel.querySelectorAll(".dm-ncp-swatch"));
2435
2270
  if (!swatches.length) return;
2436
2271
  const active = document.activeElement;
2437
2272
  if (!(active instanceof HTMLElement)) return;
2438
2273
  const idx = swatches.indexOf(active);
2439
2274
  if (idx === -1) return;
2440
- let next = idx;
2275
+ let next;
2441
2276
  switch (event.key) {
2442
2277
  case "ArrowRight":
2443
2278
  event.preventDefault();
@@ -2664,11 +2499,7 @@ var DomternalEmojiPicker = class extends EventTarget {
2664
2499
  return panel;
2665
2500
  }
2666
2501
  #renderPanelChildren() {
2667
- return [
2668
- this.#renderSearch(),
2669
- this.#renderTabs(),
2670
- this.#renderGrid()
2671
- ];
2502
+ return [this.#renderSearch(), this.#renderTabs(), this.#renderGrid()];
2672
2503
  }
2673
2504
  #renderSearch() {
2674
2505
  const wrapper = document.createElement("div");
@@ -2783,9 +2614,7 @@ var DomternalEmojiPicker = class extends EventTarget {
2783
2614
  */
2784
2615
  #setSearchQuery(value) {
2785
2616
  this.#searchQuery = value;
2786
- const input = this.#panel?.querySelector(
2787
- ".dm-emoji-picker-search input"
2788
- );
2617
+ const input = this.#panel?.querySelector(".dm-emoji-picker-search input");
2789
2618
  if (input && input.value !== value) input.value = value;
2790
2619
  }
2791
2620
  /** Replace grid contents only (used when searchQuery changes). */
@@ -2849,9 +2678,7 @@ var DomternalEmojiPicker = class extends EventTarget {
2849
2678
  #onGridKeydown(event) {
2850
2679
  const grid = this.#panel?.querySelector(".dm-emoji-picker-grid");
2851
2680
  if (!grid) return;
2852
- const swatches = Array.from(
2853
- grid.querySelectorAll(".dm-emoji-swatch")
2854
- );
2681
+ const swatches = Array.from(grid.querySelectorAll(".dm-emoji-swatch"));
2855
2682
  if (!swatches.length) return;
2856
2683
  const active = document.activeElement;
2857
2684
  const idx = active instanceof HTMLElement ? swatches.indexOf(active) : -1;
@@ -2863,7 +2690,7 @@ var DomternalEmojiPicker = class extends EventTarget {
2863
2690
  return;
2864
2691
  }
2865
2692
  const cols = 8;
2866
- let next = idx;
2693
+ let next;
2867
2694
  switch (event.key) {
2868
2695
  case "ArrowRight":
2869
2696
  event.preventDefault();
@@ -2901,9 +2728,7 @@ var DomternalEmojiPicker = class extends EventTarget {
2901
2728
  offsetValue: 4
2902
2729
  });
2903
2730
  }
2904
- const input = this.#panel.querySelector(
2905
- ".dm-emoji-picker-search input"
2906
- );
2731
+ const input = this.#panel.querySelector(".dm-emoji-picker-search input");
2907
2732
  input?.focus({ preventScroll: true });
2908
2733
  });
2909
2734
  }
@@ -2942,6 +2767,6 @@ var DomternalEmojiPicker = class extends EventTarget {
2942
2767
  }
2943
2768
  };
2944
2769
 
2945
- export { DEFAULT_EXTENSIONS, DROPDOWN_CARET, DomternalBubbleMenu, DomternalEditor, DomternalEmojiPicker, DomternalFloatingMenu, DomternalNotionColorPicker, DomternalToolbar, INITIAL_TRAILING_STATE, assertBrowser, buildItemMaps, computeTrailingState, createIconCache, createPluginKey, detectContext, filterBySchema, getComputedStyleAtCursor, getFormatItems, getInlineStyleAtCursor, getTooltip, isBrowser, isInsideTableCell, renderIconInto, resolveIcon, resolveNames, subscribe };
2770
+ export { DEFAULT_EXTENSIONS, DROPDOWN_CARET, DomternalBubbleMenu, DomternalEditor, DomternalEmojiPicker, DomternalFloatingMenu, DomternalNotionColorPicker, DomternalToolbar, INITIAL_TRAILING_STATE, assertBrowser, computeTrailingState, createIconCache, createPluginKey, getComputedStyleAtCursor, getInlineStyleAtCursor, getTooltip, isBrowser, renderIconInto, resolveIcon, subscribe };
2946
2771
  //# sourceMappingURL=index.js.map
2947
2772
  //# sourceMappingURL=index.js.map