@domternal/angular 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.
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { viewChild, input, output, signal, inject, NgZone, afterNextRender, effect, untracked, forwardRef, ViewEncapsulation, ChangeDetectionStrategy, Component, ElementRef, computed } from '@angular/core';
3
3
  import { NG_VALUE_ACCESSOR } from '@angular/forms';
4
- import { Document, Paragraph, Text, BaseKeymap, History, Editor, refocusEditorAfterCommand, positionFloatingOnce, defaultIcons, ToolbarController, defaultBubbleContexts, PluginKey, createBubbleMenuPlugin, createFloatingMenuPlugin, FloatingMenuController, positionFloating } from '@domternal/core';
4
+ import { Document, Paragraph, Text, BaseKeymap, History, Editor, refocusEditorAfterCommand, positionFloatingOnce, defaultIcons, ToolbarController, defaultBubbleContexts, PluginKey, createBubbleShouldShow, createBubbleMenuPlugin, buildBubbleItemMaps, resolveBubbleNames, resolveBubbleMenuItems, createFloatingMenuPlugin, FloatingMenuController, positionFloating } from '@domternal/core';
5
5
  export { Editor } from '@domternal/core';
6
6
  import { DomSanitizer } from '@angular/platform-browser';
7
7
 
@@ -206,8 +206,8 @@ class DomternalEditorComponent {
206
206
  this.editorCreated.emit(editor);
207
207
  });
208
208
  }
209
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
210
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.5", type: DomternalEditorComponent, isStandalone: true, selector: "domternal-editor", inputs: { extensions: { classPropertyName: "extensions", publicName: "extensions", isSignal: true, isRequired: false, transformFunction: null }, history: { classPropertyName: "history", publicName: "history", isSignal: true, isRequired: false, transformFunction: null }, content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, preset: { classPropertyName: "preset", publicName: "preset", isSignal: true, isRequired: false, transformFunction: null }, autofocus: { classPropertyName: "autofocus", publicName: "autofocus", isSignal: true, isRequired: false, transformFunction: null }, outputFormat: { classPropertyName: "outputFormat", publicName: "outputFormat", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { editorCreated: "editorCreated", contentUpdated: "contentUpdated", selectionChanged: "selectionChanged", focusChanged: "focusChanged", blurChanged: "blurChanged", editorDestroyed: "editorDestroyed" }, host: { attributes: { "data-dm-editor-ui": "" }, classAttribute: "dm-editor" }, providers: [
209
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: DomternalEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
210
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.20", type: DomternalEditorComponent, isStandalone: true, selector: "domternal-editor", inputs: { extensions: { classPropertyName: "extensions", publicName: "extensions", isSignal: true, isRequired: false, transformFunction: null }, history: { classPropertyName: "history", publicName: "history", isSignal: true, isRequired: false, transformFunction: null }, content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, preset: { classPropertyName: "preset", publicName: "preset", isSignal: true, isRequired: false, transformFunction: null }, autofocus: { classPropertyName: "autofocus", publicName: "autofocus", isSignal: true, isRequired: false, transformFunction: null }, outputFormat: { classPropertyName: "outputFormat", publicName: "outputFormat", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { editorCreated: "editorCreated", contentUpdated: "contentUpdated", selectionChanged: "selectionChanged", focusChanged: "focusChanged", blurChanged: "blurChanged", editorDestroyed: "editorDestroyed" }, host: { attributes: { "data-dm-editor-ui": "" }, classAttribute: "dm-editor" }, providers: [
211
211
  {
212
212
  provide: NG_VALUE_ACCESSOR,
213
213
  useExisting: forwardRef(() => DomternalEditorComponent),
@@ -215,7 +215,7 @@ class DomternalEditorComponent {
215
215
  },
216
216
  ], viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["editorRef"], descendants: true, isSignal: true }], ngImport: i0, template: '<div #editorRef></div>', isInline: true, styles: [":host{display:block}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
217
217
  }
218
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalEditorComponent, decorators: [{
218
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: DomternalEditorComponent, decorators: [{
219
219
  type: Component,
220
220
  args: [{ selector: 'domternal-editor', template: '<div #editorRef></div>', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: { class: 'dm-editor', 'data-dm-editor-ui': '' }, providers: [
221
221
  {
@@ -685,8 +685,8 @@ class DomternalToolbarComponent {
685
685
  const buttons = this.elRef.nativeElement.querySelectorAll('.dm-toolbar-button');
686
686
  buttons[idx]?.focus();
687
687
  }
688
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
689
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DomternalToolbarComponent, isStandalone: true, selector: "domternal-toolbar", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, icons: { classPropertyName: "icons", publicName: "icons", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "toolbar", "data-dm-editor-ui": "" }, listeners: { "keydown": "onKeydown($event)" }, properties: { "attr.aria-label": "\"Editor formatting\"" }, classAttribute: "dm-toolbar" }, ngImport: i0, template: `
688
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: DomternalToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
689
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: DomternalToolbarComponent, isStandalone: true, selector: "domternal-toolbar", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, icons: { classPropertyName: "icons", publicName: "icons", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "toolbar", "data-dm-editor-ui": "" }, listeners: { "keydown": "onKeydown($event)" }, properties: { "attr.aria-label": "\"Editor formatting\"" }, classAttribute: "dm-toolbar" }, ngImport: i0, template: `
690
690
  @for (group of groups(); track group.name; let gi = $index) {
691
691
  @if (gi > 0) {
692
692
  <div class="dm-toolbar-separator" role="separator"></div>
@@ -788,7 +788,7 @@ class DomternalToolbarComponent {
788
788
  }
789
789
  `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
790
790
  }
791
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalToolbarComponent, decorators: [{
791
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: DomternalToolbarComponent, decorators: [{
792
792
  type: Component,
793
793
  args: [{
794
794
  selector: 'domternal-toolbar',
@@ -905,15 +905,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
905
905
  }]
906
906
  }], ctorParameters: () => [], propDecorators: { editor: [{ type: i0.Input, args: [{ isSignal: true, alias: "editor", required: true }] }], icons: [{ type: i0.Input, args: [{ isSignal: true, alias: "icons", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }] } });
907
907
 
908
- /** Check if a resolved position is inside a table cell or header. */
909
- function isInsideTableCell($pos) {
910
- for (let d = $pos.depth; d > 0; d--) {
911
- const name = $pos.node(d).type.name;
912
- if (name === 'tableCell' || name === 'tableHeader')
913
- return true;
914
- }
915
- return false;
916
- }
908
+ /* The item pipeline (which list this selection gets, what the schema allows,
909
+ which separators survive) is core's. This component renders the answer and
910
+ owns nothing about how it is reached. */
917
911
  class DomternalBubbleMenuComponent {
918
912
  editor = input.required(...(ngDevMode ? [{ debugName: "editor" }] : /* istanbul ignore next */ []));
919
913
  shouldShow = input(...(ngDevMode ? [undefined, { debugName: "shouldShow" }] : /* istanbul ignore next */ []));
@@ -922,7 +916,17 @@ class DomternalBubbleMenuComponent {
922
916
  updateDelay = input(0, ...(ngDevMode ? [{ debugName: "updateDelay" }] : /* istanbul ignore next */ []));
923
917
  /** Fixed item names (e.g. ['bold', 'italic', 'code']). Omit for auto mode (all format items). */
924
918
  items = input(undefined, ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
925
- /** Context-aware: map context names to item arrays, `true` for all valid items, or `null` to disable */
919
+ /**
920
+ * Context-aware: map context names to item arrays, `true` for all valid
921
+ * items, or `null` to disable.
922
+ *
923
+ * Typed through core's `BubbleContexts` rather than the same shape written
924
+ * out here. Angular emits an input's declared type into the package types,
925
+ * and an inline union came out of that emit as `Record<string, true |
926
+ * string[]>`: the `null` that disables a context was missing from the
927
+ * published type, so the documented way to switch one off did not
928
+ * type-check. A named type survives the trip intact.
929
+ */
926
930
  contexts = input(undefined, ...(ngDevMode ? [{ debugName: "contexts" }] : /* istanbul ignore next */ []));
927
931
  /**
928
932
  * Custom icon overrides. Falls back to default Phosphor icons for unmapped keys.
@@ -946,6 +950,15 @@ class DomternalBubbleMenuComponent {
946
950
  }
947
951
  /** Internal - updated on transactions. Not meant to be set from outside. */
948
952
  resolvedItems = signal([], ...(ngDevMode ? [{ debugName: "resolvedItems" }] : /* istanbul ignore next */ []));
953
+ /**
954
+ * The list core hands back, stored once. A default or fallback list comes
955
+ * back by reference and a signal set to its current value notifies nobody,
956
+ * so those passes cost nothing; a list resolved from names is rebuilt each
957
+ * time, as it always was.
958
+ */
959
+ setResolvedItems(items) {
960
+ this.resolvedItems.set(items);
961
+ }
949
962
  /** Internal - true when the BlockContextMenu extension is loaded; toggles the "..." trailing button. */
950
963
  showBlockMenuButton = signal(false, ...(ngDevMode ? [{ debugName: "showBlockMenuButton" }] : /* istanbul ignore next */ []));
951
964
  /** Internal - true when the selection spans more than one top-level block; the "..." trigger is disabled in that case because block-level commands have no unambiguous target. */
@@ -954,6 +967,12 @@ class DomternalBubbleMenuComponent {
954
967
  isNodeSelection = signal(false, ...(ngDevMode ? [{ debugName: "isNodeSelection" }] : /* istanbul ignore next */ []));
955
968
  /** Internal - true when the NotionColorPicker extension is loaded; toggles the "A" color trigger. */
956
969
  showColorPickerButton = signal(false, ...(ngDevMode ? [{ debugName: "showColorPickerButton" }] : /* istanbul ignore next */ []));
970
+ /**
971
+ * The trailing triggers, resolved once for the template: each is read twice,
972
+ * for the button and for the separator that may lead it.
973
+ */
974
+ showColorTrigger = computed(() => this.showColorPickerButton() && !this.isNodeSelection(), ...(ngDevMode ? [{ debugName: "showColorTrigger" }] : /* istanbul ignore next */ []));
975
+ showBlockTrigger = computed(() => this.showBlockMenuButton() && !this.isNodeSelection(), ...(ngDevMode ? [{ debugName: "showBlockTrigger" }] : /* istanbul ignore next */ []));
957
976
  /** notionColorPicker extension loaded (immutable per editor). */
958
977
  hasNotionColorPicker = false;
959
978
  /** Current text color CSS variable expression for the trigger glyph (null = default). */
@@ -967,12 +986,15 @@ class DomternalBubbleMenuComponent {
967
986
  sanitizer = inject(DomSanitizer);
968
987
  ngZone = inject(NgZone);
969
988
  activeVersion = signal(0, ...(ngDevMode ? [{ debugName: "activeVersion" }] : /* istanbul ignore next */ []));
970
- itemMap = new Map();
971
- dropdownMap = new Map();
989
+ /** Rebuilt per editor by `setupItemTracking`; empty until then. */
990
+ maps = {
991
+ itemMap: new Map(),
992
+ dropdownMap: new Map(),
993
+ bubbleDefaults: new Map(),
994
+ };
972
995
  activeMap = new Map();
973
996
  disabledMap = new Map();
974
997
  htmlCache = new Map();
975
- bubbleDefaults = new Map();
976
998
  transactionHandler = null;
977
999
  // Dropdown state for bubble-menu-embedded dropdowns (e.g. text-align).
978
1000
  openDropdown = signal(null, ...(ngDevMode ? [{ debugName: "openDropdown" }] : /* istanbul ignore next */ []));
@@ -983,11 +1005,8 @@ class DomternalBubbleMenuComponent {
983
1005
  dropdownCaret = '<svg class="dm-dropdown-caret" width="10" height="10" viewBox="0 0 10 10">' +
984
1006
  '<path d="M2 4l3 3 3-3" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
985
1007
  constructor() {
986
- // Each component instance needs a unique plugin key so that multiple
987
- // bubble-menus mounted in the same editor do not collide. Prefer
988
- // crypto.randomUUID over Math.random() for collision-free uniqueness
989
- // (Math.random across two simultaneous mounts has a small but real
990
- // collision probability, and SSR may share the random seed).
1008
+ // Each component instance needs a unique plugin key so that multiple bubble-
1009
+ // menus mounted in the same editor do not collide.
991
1010
  const crypto = globalThis.crypto;
992
1011
  const suffix = crypto?.randomUUID?.().slice(0, 8) ?? Math.random().toString(36).slice(2, 8);
993
1012
  this.pluginKey = new PluginKey('angularBubbleMenu-' + suffix);
@@ -998,37 +1017,15 @@ class DomternalBubbleMenuComponent {
998
1017
  });
999
1018
  afterNextRender(() => {
1000
1019
  const editor = this.editor();
1020
+ /* Item tracking first, and the order is load-bearing: it is what builds
1021
+ `this.maps`, and the visibility rule below reads that object once.
1022
+ With the plugin registered first the rule closed over an empty map,
1023
+ and `bubbleDefaults.has(...)` was false for every node selection, so
1024
+ an image never got a menu at all. */
1025
+ this.setupItemTracking(editor);
1001
1026
  const ctxs = this.resolveContexts(editor);
1002
- let shouldShowFn = this.shouldShow();
1003
- if (!shouldShowFn) {
1004
- if (ctxs) {
1005
- shouldShowFn = ({ state }) => {
1006
- const context = this.detectContext(state.selection, ctxs);
1007
- if (!context)
1008
- return false;
1009
- if (context in ctxs) {
1010
- const val = ctxs[context];
1011
- if (val === null)
1012
- return false;
1013
- return val === true || (Array.isArray(val) && val.length > 0);
1014
- }
1015
- return this.bubbleDefaults.has(context);
1016
- };
1017
- }
1018
- else {
1019
- // Auto/items mode: show when any endpoint's parent allows marks
1020
- shouldShowFn = ({ state }) => {
1021
- if (state.selection.empty)
1022
- return false;
1023
- if (state.selection.node)
1024
- return this.bubbleDefaults.has(state.selection.node.type.name);
1025
- if (isInsideTableCell(state.selection.$from))
1026
- return false;
1027
- return state.selection.$from.parent.type.spec.marks !== ''
1028
- || state.selection.$to.parent.type.spec.marks !== '';
1029
- };
1030
- }
1031
- }
1027
+ const shouldShowFn = this.shouldShow() ??
1028
+ createBubbleShouldShow(this.maps, ctxs);
1032
1029
  const plugin = createBubbleMenuPlugin({
1033
1030
  pluginKey: this.pluginKey,
1034
1031
  editor,
@@ -1039,7 +1036,6 @@ class DomternalBubbleMenuComponent {
1039
1036
  updateDelay: this.updateDelay(),
1040
1037
  });
1041
1038
  editor.registerPlugin(plugin);
1042
- this.setupItemTracking(editor);
1043
1039
  });
1044
1040
  }
1045
1041
  ngOnDestroy() {
@@ -1114,170 +1110,29 @@ class DomternalBubbleMenuComponent {
1114
1110
  }));
1115
1111
  }
1116
1112
  // === Internal ===
1117
- buildItemMap(editor) {
1118
- this.itemMap.clear();
1119
- this.dropdownMap.clear();
1120
- for (const item of editor.toolbarItems) {
1121
- if (item.type === 'button') {
1122
- this.itemMap.set(item.name, item);
1123
- }
1124
- else if (item.type === 'dropdown') {
1125
- this.dropdownMap.set(item.name, item);
1126
- for (const sub of item.items) {
1127
- this.itemMap.set(sub.name, sub);
1128
- }
1129
- }
1130
- }
1131
- }
1132
- resolveNames(names) {
1133
- const result = [];
1134
- let sepIdx = 0;
1135
- for (const name of names) {
1136
- if (name === '|') {
1137
- result.push({ type: 'separator', name: `sep-${String(sepIdx++)}` });
1138
- }
1139
- else {
1140
- const dropdown = this.dropdownMap.get(name);
1141
- if (dropdown) {
1142
- result.push(dropdown);
1143
- continue;
1144
- }
1145
- const item = this.itemMap.get(name);
1146
- if (item)
1147
- result.push(item);
1148
- }
1149
- }
1150
- return result;
1151
- }
1152
- getFormatItems() {
1153
- return Array.from(this.itemMap.values())
1154
- .filter(item => item.group === 'format')
1155
- .sort((a, b) => (b.priority ?? 100) - (a.priority ?? 100));
1156
- }
1157
- detectContext(selection, ctxs) {
1158
- // CellSelection (duck-type: has $anchorCell) - never show bubble menu
1159
- if ('$anchorCell' in selection)
1160
- return null;
1161
- if (selection.node)
1162
- return selection.node.type.name;
1163
- if (selection.empty)
1164
- return null;
1165
- // TextSelection inside a table cell → 'table' context.
1166
- // Cross-cell TextSelection (drag across cells) → hide bubble menu.
1167
- const fromCell = this.findCellNode(selection.$from);
1168
- if (fromCell) {
1169
- const toCell = this.findCellNode(selection.$to);
1170
- if (toCell && fromCell !== toCell)
1171
- return null;
1172
- return 'table';
1173
- }
1174
- const fromName = selection.$from.parent.type.name;
1175
- if (fromName in ctxs)
1176
- return fromName;
1177
- if ('text' in ctxs && selection.$from.parent.type.spec.marks !== '')
1178
- return 'text';
1179
- // Cross-block: also check $to so bold/italic is offered when any endpoint allows marks
1180
- const toName = selection.$to.parent.type.name;
1181
- if (toName in ctxs)
1182
- return toName;
1183
- if ('text' in ctxs && selection.$to.parent.type.spec.marks !== '')
1184
- return 'text';
1185
- return null;
1186
- }
1187
- findCellNode(pos) {
1188
- for (let d = pos.depth; d > 0; d--) {
1189
- const node = pos.node(d);
1190
- if (node.type.name === 'tableCell' || node.type.name === 'tableHeader') {
1191
- return node;
1192
- }
1193
- }
1194
- return null;
1195
- }
1196
- /** Filters out mark items that the context node type doesn't allow (e.g. bold on codeBlock) */
1197
- filterBySchema(editor, contextName, items) {
1198
- if (contextName === 'text' || contextName === 'table')
1199
- return items;
1200
- const schema = editor.state.schema;
1201
- if (!schema)
1202
- return items;
1203
- const nodeType = schema.nodes[contextName];
1204
- if (!nodeType)
1205
- return items;
1206
- return items.filter(item => {
1207
- const markName = typeof item.isActive === 'string' ? item.isActive : null;
1208
- if (!markName)
1209
- return true;
1210
- const markType = schema.marks[markName];
1211
- if (!markType)
1212
- return true;
1213
- return nodeType.allowsMarkType(markType);
1214
- });
1215
- }
1216
- buildBubbleDefaults(editor) {
1217
- this.bubbleDefaults.clear();
1218
- const byCtx = new Map();
1219
- const addItem = (btn) => {
1220
- const ctx = btn['bubbleMenu'];
1221
- if (!ctx)
1222
- return;
1223
- let arr = byCtx.get(ctx);
1224
- if (!arr) {
1225
- arr = [];
1226
- byCtx.set(ctx, arr);
1227
- }
1228
- arr.push(btn);
1229
- };
1230
- for (const item of editor.toolbarItems) {
1231
- if (item.type === 'button')
1232
- addItem(item);
1233
- else if (item.type === 'dropdown') {
1234
- for (const sub of item.items)
1235
- addItem(sub);
1236
- }
1237
- }
1238
- for (const [ctx, items] of byCtx) {
1239
- items.sort((a, b) => (b.priority ?? 100) - (a.priority ?? 100));
1240
- const result = [];
1241
- let lastGroup;
1242
- let sepIdx = 0;
1243
- for (const item of items) {
1244
- if (lastGroup !== undefined && item.group !== lastGroup) {
1245
- result.push({ type: 'separator', name: `bsep-${String(sepIdx++)}` });
1246
- }
1247
- result.push(item);
1248
- lastGroup = item.group;
1249
- }
1250
- this.bubbleDefaults.set(ctx, result);
1251
- }
1252
- }
1253
1113
  setupItemTracking(editor) {
1254
- this.buildItemMap(editor);
1255
- this.buildBubbleDefaults(editor);
1114
+ /* One pipeline, built once per editor. Every renderer asks core the same
1115
+ question, so none of them answers it. */
1116
+ this.maps = buildBubbleItemMaps(editor);
1256
1117
  this.showBlockMenuButton.set(editor.extensionManager.extensions.some((e) => e.name === 'blockContextMenu'));
1257
1118
  // Presence cached; the show decision needs a live listener (per sync below).
1258
1119
  this.hasNotionColorPicker =
1259
1120
  editor.extensionManager.extensions.some((e) => e.name === 'notionColorPicker');
1260
- const items = this.items();
1261
- const defaultItems = this.resolveNames(items ?? ['bold', 'italic', 'underline']);
1262
- if (this.resolveContexts(editor)) {
1263
- this.updateContextItems(editor);
1264
- }
1265
- else {
1266
- this.resolvedItems.set(defaultItems);
1267
- }
1121
+ const fallbackItems = resolveBubbleNames(this.items() ?? ['bold', 'italic', 'underline'], this.maps.itemMap, this.maps.dropdownMap);
1122
+ const refreshItems = () => {
1123
+ this.setResolvedItems(resolveBubbleMenuItems({
1124
+ editor,
1125
+ maps: this.maps,
1126
+ contexts: this.resolveContexts(editor),
1127
+ fallbackItems,
1128
+ }));
1129
+ };
1130
+ refreshItems();
1268
1131
  this.transactionHandler = () => {
1269
1132
  this.ngZone.run(() => {
1270
1133
  const sel = editor.state.selection;
1271
1134
  this.isNodeSelection.set(!!sel.node);
1272
- if (this.resolveContexts(editor)) {
1273
- this.updateContextItems(editor);
1274
- }
1275
- else if (sel.node && this.bubbleDefaults.has(sel.node.type.name)) {
1276
- this.resolvedItems.set(this.bubbleDefaults.get(sel.node.type.name) ?? []);
1277
- }
1278
- else {
1279
- this.resolvedItems.set(defaultItems);
1280
- }
1135
+ refreshItems();
1281
1136
  this.updateStates(editor);
1282
1137
  this.syncTrailingButtonsState(editor);
1283
1138
  this.activeVersion.update((v) => v + 1);
@@ -1332,35 +1187,6 @@ class DomternalBubbleMenuComponent {
1332
1187
  this.currentBgColorVar.set(bgToken ? `var(--dm-block-bg-${bgToken})` : null);
1333
1188
  this.hasAnyColor.set(textToken !== null || bgToken !== null);
1334
1189
  }
1335
- updateContextItems(editor) {
1336
- const ctxs = this.resolveContexts(editor);
1337
- if (!ctxs)
1338
- return;
1339
- const ctx = this.detectContext(editor.state.selection, ctxs);
1340
- if (!ctx) {
1341
- this.resolvedItems.set([]);
1342
- return;
1343
- }
1344
- if (ctx in ctxs) {
1345
- const val = ctxs[ctx];
1346
- if (val === null || (Array.isArray(val) && val.length === 0)) {
1347
- this.resolvedItems.set([]);
1348
- return;
1349
- }
1350
- if (val === true) {
1351
- this.resolvedItems.set(this.filterBySchema(editor, ctx, this.getFormatItems()));
1352
- }
1353
- else if (Array.isArray(val)) {
1354
- const resolved = this.resolveNames(val);
1355
- const buttons = resolved.filter((i) => i.type !== 'separator');
1356
- const filtered = new Set(this.filterBySchema(editor, ctx, buttons).map(b => b.name));
1357
- this.resolvedItems.set(resolved.filter(i => i.type === 'separator' || filtered.has(i.name)));
1358
- }
1359
- }
1360
- else {
1361
- this.resolvedItems.set(this.bubbleDefaults.get(ctx) ?? []);
1362
- }
1363
- }
1364
1190
  updateStates(editor) {
1365
1191
  let canProxy = null;
1366
1192
  try {
@@ -1440,21 +1266,13 @@ class DomternalBubbleMenuComponent {
1440
1266
  if (!isOpening)
1441
1267
  return;
1442
1268
  this.openDropdown.set(dropdown.name);
1443
- // NOTE: we deliberately do NOT broadcast `dm:dismiss-overlays` here.
1444
- // The bubble menu plugin in core listens for that event and would
1445
- // hide itself - taking our just-opened dropdown's trigger with it.
1446
- // Other overlays (color picker, link popover) close themselves via
1447
- // their own click-outside handlers when the user clicks our trigger,
1448
- // so cooperative dismissal still works.
1269
+ // NOTE: we deliberately do NOT broadcast `dm:dismiss-overlays` here. The
1270
+ // bubble menu plugin in core listens for that event and would hide itself -
1271
+ // taking our just-opened dropdown's trigger with it.
1449
1272
  const editorElBroadcast = this.menuEl().nativeElement.closest('.dm-editor');
1450
- // Position the panel against the trigger after Angular renders it.
1451
- // We deliberately keep the panel INSIDE `.dm-bubble-menu` so it
1452
- // inherits the bubble-menu-scoped button tokens (--dm-button-active-bg
1453
- // / --dm-button-active-color). Reparenting into `.dm-editor` was
1454
- // tempting for consistency with the color picker, but that dropped
1455
- // the active-state colors because those tokens aren't defined at
1456
- // editor scope. floating-ui handles cross-element positioning fine
1457
- // without reparenting.
1273
+ // Position the panel against the trigger after Angular renders it. We
1274
+ // deliberately keep the panel INSIDE `.dm-bubble-menu` so it inherits the
1275
+ // bubble-menu-scoped button tokens (--dm-button-active-bg / --dm-button-
1458
1276
  requestAnimationFrame(() => {
1459
1277
  const trigger = event?.currentTarget
1460
1278
  ?? this.menuEl().nativeElement.querySelector(`[data-dropdown="${dropdown.name}"]`);
@@ -1523,8 +1341,8 @@ class DomternalBubbleMenuComponent {
1523
1341
  this.dropdownCleanupFloating = null;
1524
1342
  this.openDropdown.set(null);
1525
1343
  }
1526
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1527
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DomternalBubbleMenuComponent, isStandalone: true, selector: "domternal-bubble-menu", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, shouldShow: { classPropertyName: "shouldShow", publicName: "shouldShow", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, updateDelay: { classPropertyName: "updateDelay", publicName: "updateDelay", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, contexts: { classPropertyName: "contexts", publicName: "contexts", isSignal: true, isRequired: false, transformFunction: null }, icons: { classPropertyName: "icons", publicName: "icons", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "menuEl", first: true, predicate: ["menuEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
1344
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: DomternalBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1345
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: DomternalBubbleMenuComponent, isStandalone: true, selector: "domternal-bubble-menu", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, shouldShow: { classPropertyName: "shouldShow", publicName: "shouldShow", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, updateDelay: { classPropertyName: "updateDelay", publicName: "updateDelay", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, contexts: { classPropertyName: "contexts", publicName: "contexts", isSignal: true, isRequired: false, transformFunction: null }, icons: { classPropertyName: "icons", publicName: "icons", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "menuEl", first: true, predicate: ["menuEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
1528
1346
  <div #menuEl class="dm-bubble-menu" role="toolbar" aria-label="Text formatting">
1529
1347
  @for (item of resolvedItems(); track item.name) {
1530
1348
  @if (item.type === 'separator') {
@@ -1571,8 +1389,10 @@ class DomternalBubbleMenuComponent {
1571
1389
  (click)="executeCommand(asButton(item), $event)"></button>
1572
1390
  }
1573
1391
  }
1574
- @if (showColorPickerButton() && !isNodeSelection()) {
1575
- <span class="dm-toolbar-separator" role="separator"></span>
1392
+ @if (showColorTrigger()) {
1393
+ @if (resolvedItems().length > 0) {
1394
+ <span class="dm-toolbar-separator" role="separator"></span>
1395
+ }
1576
1396
  <button #colorBtn type="button" class="dm-toolbar-button dm-ncp-trigger"
1577
1397
  [class.dm-toolbar-button--active]="hasAnyColor()"
1578
1398
  title="Text and background color"
@@ -1586,8 +1406,10 @@ class DomternalBubbleMenuComponent {
1586
1406
  [style.background-color]="currentBgColorVar()"></span>
1587
1407
  </button>
1588
1408
  }
1589
- @if (showBlockMenuButton() && !isNodeSelection()) {
1590
- <span class="dm-toolbar-separator" role="separator"></span>
1409
+ @if (showBlockTrigger()) {
1410
+ @if (resolvedItems().length > 0 || showColorTrigger()) {
1411
+ <span class="dm-toolbar-separator" role="separator"></span>
1412
+ }
1591
1413
  <button #blockMenuBtn type="button" class="dm-toolbar-button"
1592
1414
  [disabled]="blockMenuButtonDisabled()"
1593
1415
  [title]="blockMenuButtonDisabled() ? 'Block actions (select within a single block)' : 'More options'"
@@ -1601,7 +1423,7 @@ class DomternalBubbleMenuComponent {
1601
1423
  </div>
1602
1424
  `, isInline: true, styles: [":host{display:contents}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1603
1425
  }
1604
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalBubbleMenuComponent, decorators: [{
1426
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: DomternalBubbleMenuComponent, decorators: [{
1605
1427
  type: Component,
1606
1428
  args: [{ selector: 'domternal-bubble-menu', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
1607
1429
  <div #menuEl class="dm-bubble-menu" role="toolbar" aria-label="Text formatting">
@@ -1650,8 +1472,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
1650
1472
  (click)="executeCommand(asButton(item), $event)"></button>
1651
1473
  }
1652
1474
  }
1653
- @if (showColorPickerButton() && !isNodeSelection()) {
1654
- <span class="dm-toolbar-separator" role="separator"></span>
1475
+ @if (showColorTrigger()) {
1476
+ @if (resolvedItems().length > 0) {
1477
+ <span class="dm-toolbar-separator" role="separator"></span>
1478
+ }
1655
1479
  <button #colorBtn type="button" class="dm-toolbar-button dm-ncp-trigger"
1656
1480
  [class.dm-toolbar-button--active]="hasAnyColor()"
1657
1481
  title="Text and background color"
@@ -1665,8 +1489,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
1665
1489
  [style.background-color]="currentBgColorVar()"></span>
1666
1490
  </button>
1667
1491
  }
1668
- @if (showBlockMenuButton() && !isNodeSelection()) {
1669
- <span class="dm-toolbar-separator" role="separator"></span>
1492
+ @if (showBlockTrigger()) {
1493
+ @if (resolvedItems().length > 0 || showColorTrigger()) {
1494
+ <span class="dm-toolbar-separator" role="separator"></span>
1495
+ }
1670
1496
  <button #blockMenuBtn type="button" class="dm-toolbar-button"
1671
1497
  [disabled]="blockMenuButtonDisabled()"
1672
1498
  [title]="blockMenuButtonDisabled() ? 'Block actions (select within a single block)' : 'More options'"
@@ -1857,8 +1683,8 @@ class DomternalFloatingMenuComponent {
1857
1683
  return;
1858
1684
  }
1859
1685
  }
1860
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalFloatingMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1861
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DomternalFloatingMenuComponent, isStandalone: true, selector: "domternal-floating-menu", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, shouldShow: { classPropertyName: "shouldShow", publicName: "shouldShow", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, keymap: { classPropertyName: "keymap", publicName: "keymap", isSignal: true, isRequired: false, transformFunction: null }, icons: { classPropertyName: "icons", publicName: "icons", isSignal: true, isRequired: false, transformFunction: null }, requireExplicitTrigger: { classPropertyName: "requireExplicitTrigger", publicName: "requireExplicitTrigger", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "menuEl", first: true, predicate: ["menuEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
1686
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: DomternalFloatingMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1687
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: DomternalFloatingMenuComponent, isStandalone: true, selector: "domternal-floating-menu", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, shouldShow: { classPropertyName: "shouldShow", publicName: "shouldShow", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, keymap: { classPropertyName: "keymap", publicName: "keymap", isSignal: true, isRequired: false, transformFunction: null }, icons: { classPropertyName: "icons", publicName: "icons", isSignal: true, isRequired: false, transformFunction: null }, requireExplicitTrigger: { classPropertyName: "requireExplicitTrigger", publicName: "requireExplicitTrigger", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "menuEl", first: true, predicate: ["menuEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
1862
1688
  <div
1863
1689
  #menuEl
1864
1690
  class="dm-floating-menu"
@@ -1904,7 +1730,7 @@ class DomternalFloatingMenuComponent {
1904
1730
  </div>
1905
1731
  `, isInline: true, styles: [":host{display:contents}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1906
1732
  }
1907
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalFloatingMenuComponent, decorators: [{
1733
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: DomternalFloatingMenuComponent, decorators: [{
1908
1734
  type: Component,
1909
1735
  args: [{ selector: 'domternal-floating-menu', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
1910
1736
  <div
@@ -1959,10 +1785,10 @@ const CATEGORY_ICONS = {
1959
1785
  'Animals & Nature': '\u{1F431}',
1960
1786
  'Food & Drink': '\u{1F355}',
1961
1787
  'Travel & Places': '\u{1F697}',
1962
- 'Activities': '\u{26BD}',
1963
- 'Objects': '\u{1F4A1}',
1964
- 'Symbols': '\u{1F523}',
1965
- 'Flags': '\u{1F3C1}',
1788
+ Activities: '\u{26BD}',
1789
+ Objects: '\u{1F4A1}',
1790
+ Symbols: '\u{1F523}',
1791
+ Flags: '\u{1F3C1}',
1966
1792
  };
1967
1793
  class DomternalEmojiPickerComponent {
1968
1794
  editor = input.required(...(ngDevMode ? [{ debugName: "editor" }] : /* istanbul ignore next */ []));
@@ -1999,8 +1825,7 @@ class DomternalEmojiPickerComponent {
1999
1825
  if (searchFn) {
2000
1826
  return searchFn(query);
2001
1827
  }
2002
- return this.emojis().filter((item) => item.name.includes(query) ||
2003
- item.group.toLowerCase().includes(query));
1828
+ return this.emojis().filter((item) => item.name.includes(query) || item.group.toLowerCase().includes(query));
2004
1829
  }, ...(ngDevMode ? [{ debugName: "filteredEmojis" }] : /* istanbul ignore next */ []));
2005
1830
  frequentlyUsed = computed(() => {
2006
1831
  // Re-evaluate when panel opens (isOpen changes)
@@ -2017,12 +1842,17 @@ class DomternalEmojiPickerComponent {
2017
1842
  const nameMap = storage['_nameMap'];
2018
1843
  if (!nameMap)
2019
1844
  return [];
2020
- return names.slice(0, 16).map((n) => nameMap.get(n)).filter(Boolean);
1845
+ return names
1846
+ .slice(0, 16)
1847
+ .map((n) => nameMap.get(n))
1848
+ .filter(Boolean);
2021
1849
  }, ...(ngDevMode ? [{ debugName: "frequentlyUsed" }] : /* istanbul ignore next */ []));
2022
1850
  constructor() {
2023
1851
  effect(() => {
2024
1852
  const editor = this.editor();
2025
- untracked(() => { this.setupEventListener(editor); });
1853
+ untracked(() => {
1854
+ this.setupEventListener(editor);
1855
+ });
2026
1856
  });
2027
1857
  }
2028
1858
  ngOnDestroy() {
@@ -2064,7 +1894,8 @@ class DomternalEmojiPickerComponent {
2064
1894
  // Focus first emoji swatch after scroll completes
2065
1895
  setTimeout(() => {
2066
1896
  const firstSwatch = label.nextElementSibling;
2067
- if (firstSwatch instanceof HTMLElement && firstSwatch.classList.contains('dm-emoji-swatch')) {
1897
+ if (firstSwatch instanceof HTMLElement &&
1898
+ firstSwatch.classList.contains('dm-emoji-swatch')) {
2068
1899
  firstSwatch.focus();
2069
1900
  }
2070
1901
  }, 50);
@@ -2089,7 +1920,7 @@ class DomternalEmojiPickerComponent {
2089
1920
  return;
2090
1921
  }
2091
1922
  const cols = 8;
2092
- let next = idx;
1923
+ let next;
2093
1924
  switch (event.key) {
2094
1925
  case 'ArrowRight':
2095
1926
  event.preventDefault();
@@ -2112,7 +1943,8 @@ class DomternalEmojiPickerComponent {
2112
1943
  event.preventDefault();
2113
1944
  swatches[idx]?.click();
2114
1945
  return;
2115
- default: return;
1946
+ default:
1947
+ return;
2116
1948
  }
2117
1949
  swatches[next]?.focus();
2118
1950
  }
@@ -2184,14 +2016,18 @@ class DomternalEmojiPickerComponent {
2184
2016
  !this.elRef.nativeElement.contains(target) &&
2185
2017
  target !== this.anchorEl &&
2186
2018
  !this.anchorEl?.contains(target)) {
2187
- this.ngZone.run(() => { this.close(); });
2019
+ this.ngZone.run(() => {
2020
+ this.close();
2021
+ });
2188
2022
  }
2189
2023
  };
2190
2024
  document.addEventListener('mousedown', this.clickOutsideHandler);
2191
2025
  this.keydownHandler = (e) => {
2192
2026
  if (e.key === 'Escape' && this.isOpen()) {
2193
2027
  e.preventDefault();
2194
- this.ngZone.run(() => { this.close(); });
2028
+ this.ngZone.run(() => {
2029
+ this.close();
2030
+ });
2195
2031
  }
2196
2032
  };
2197
2033
  document.addEventListener('keydown', this.keydownHandler);
@@ -2227,8 +2063,8 @@ class DomternalEmojiPickerComponent {
2227
2063
  this.eventHandler = null;
2228
2064
  }
2229
2065
  }
2230
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalEmojiPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2231
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DomternalEmojiPickerComponent, isStandalone: true, selector: "domternal-emoji-picker", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, emojis: { classPropertyName: "emojis", publicName: "emojis", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "dm-emoji-picker-host" }, ngImport: i0, template: `
2066
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: DomternalEmojiPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2067
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: DomternalEmojiPickerComponent, isStandalone: true, selector: "domternal-emoji-picker", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null }, emojis: { classPropertyName: "emojis", publicName: "emojis", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "dm-emoji-picker-host" }, ngImport: i0, template: `
2232
2068
  @if (isOpen()) {
2233
2069
  <div class="dm-emoji-picker">
2234
2070
  <div class="dm-emoji-picker-search">
@@ -2255,11 +2091,18 @@ class DomternalEmojiPickerComponent {
2255
2091
  [attr.aria-label]="cat"
2256
2092
  (mousedown)="$event.preventDefault()"
2257
2093
  (click)="scrollToCategory(cat)"
2258
- >{{ categoryIcon(cat) }}</button>
2094
+ >
2095
+ {{ categoryIcon(cat) }}
2096
+ </button>
2259
2097
  }
2260
2098
  </div>
2261
2099
 
2262
- <div class="dm-emoji-picker-grid" #grid (scroll)="onGridScroll()" (keydown)="onGridKeydown($event)">
2100
+ <div
2101
+ class="dm-emoji-picker-grid"
2102
+ #grid
2103
+ (scroll)="onGridScroll()"
2104
+ (keydown)="onGridKeydown($event)"
2105
+ >
2263
2106
  @if (searchQuery()) {
2264
2107
  @for (item of filteredEmojis(); track item.name) {
2265
2108
  <button
@@ -2270,9 +2113,10 @@ class DomternalEmojiPickerComponent {
2270
2113
  [attr.aria-label]="formatName(item.name)"
2271
2114
  (mousedown)="$event.preventDefault()"
2272
2115
  (click)="selectEmoji(item)"
2273
- >{{ item.emoji }}</button>
2274
- }
2275
- @empty {
2116
+ >
2117
+ {{ item.emoji }}
2118
+ </button>
2119
+ } @empty {
2276
2120
  <div class="dm-emoji-picker-empty">No emoji found</div>
2277
2121
  }
2278
2122
  } @else {
@@ -2287,7 +2131,9 @@ class DomternalEmojiPickerComponent {
2287
2131
  [attr.aria-label]="formatName(item.name)"
2288
2132
  (mousedown)="$event.preventDefault()"
2289
2133
  (click)="selectEmoji(item)"
2290
- >{{ item.emoji }}</button>
2134
+ >
2135
+ {{ item.emoji }}
2136
+ </button>
2291
2137
  }
2292
2138
  }
2293
2139
  @for (cat of categoryNames(); track cat) {
@@ -2301,7 +2147,9 @@ class DomternalEmojiPickerComponent {
2301
2147
  [attr.aria-label]="formatName(item.name)"
2302
2148
  (mousedown)="$event.preventDefault()"
2303
2149
  (click)="selectEmoji(item)"
2304
- >{{ item.emoji }}</button>
2150
+ >
2151
+ {{ item.emoji }}
2152
+ </button>
2305
2153
  }
2306
2154
  }
2307
2155
  }
@@ -2310,13 +2158,13 @@ class DomternalEmojiPickerComponent {
2310
2158
  }
2311
2159
  `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
2312
2160
  }
2313
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalEmojiPickerComponent, decorators: [{
2161
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: DomternalEmojiPickerComponent, decorators: [{
2314
2162
  type: Component,
2315
2163
  args: [{
2316
2164
  selector: 'domternal-emoji-picker',
2317
2165
  changeDetection: ChangeDetectionStrategy.OnPush,
2318
2166
  encapsulation: ViewEncapsulation.None,
2319
- host: { 'class': 'dm-emoji-picker-host' },
2167
+ host: { class: 'dm-emoji-picker-host' },
2320
2168
  template: `
2321
2169
  @if (isOpen()) {
2322
2170
  <div class="dm-emoji-picker">
@@ -2344,11 +2192,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
2344
2192
  [attr.aria-label]="cat"
2345
2193
  (mousedown)="$event.preventDefault()"
2346
2194
  (click)="scrollToCategory(cat)"
2347
- >{{ categoryIcon(cat) }}</button>
2195
+ >
2196
+ {{ categoryIcon(cat) }}
2197
+ </button>
2348
2198
  }
2349
2199
  </div>
2350
2200
 
2351
- <div class="dm-emoji-picker-grid" #grid (scroll)="onGridScroll()" (keydown)="onGridKeydown($event)">
2201
+ <div
2202
+ class="dm-emoji-picker-grid"
2203
+ #grid
2204
+ (scroll)="onGridScroll()"
2205
+ (keydown)="onGridKeydown($event)"
2206
+ >
2352
2207
  @if (searchQuery()) {
2353
2208
  @for (item of filteredEmojis(); track item.name) {
2354
2209
  <button
@@ -2359,9 +2214,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
2359
2214
  [attr.aria-label]="formatName(item.name)"
2360
2215
  (mousedown)="$event.preventDefault()"
2361
2216
  (click)="selectEmoji(item)"
2362
- >{{ item.emoji }}</button>
2363
- }
2364
- @empty {
2217
+ >
2218
+ {{ item.emoji }}
2219
+ </button>
2220
+ } @empty {
2365
2221
  <div class="dm-emoji-picker-empty">No emoji found</div>
2366
2222
  }
2367
2223
  } @else {
@@ -2376,7 +2232,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
2376
2232
  [attr.aria-label]="formatName(item.name)"
2377
2233
  (mousedown)="$event.preventDefault()"
2378
2234
  (click)="selectEmoji(item)"
2379
- >{{ item.emoji }}</button>
2235
+ >
2236
+ {{ item.emoji }}
2237
+ </button>
2380
2238
  }
2381
2239
  }
2382
2240
  @for (cat of categoryNames(); track cat) {
@@ -2390,7 +2248,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
2390
2248
  [attr.aria-label]="formatName(item.name)"
2391
2249
  (mousedown)="$event.preventDefault()"
2392
2250
  (click)="selectEmoji(item)"
2393
- >{{ item.emoji }}</button>
2251
+ >
2252
+ {{ item.emoji }}
2253
+ </button>
2394
2254
  }
2395
2255
  }
2396
2256
  }
@@ -2401,6 +2261,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
2401
2261
  }]
2402
2262
  }], ctorParameters: () => [], propDecorators: { editor: [{ type: i0.Input, args: [{ isSignal: true, alias: "editor", required: true }] }], emojis: [{ type: i0.Input, args: [{ isSignal: true, alias: "emojis", required: true }] }] } });
2403
2263
 
2264
+ function paletteFromExtensionOptions(options) {
2265
+ if (typeof options !== 'object' || options === null || !('palette' in options))
2266
+ return [];
2267
+ const palette = options.palette;
2268
+ if (!Array.isArray(palette) || !palette.every((token) => typeof token === 'string')) {
2269
+ return [];
2270
+ }
2271
+ return [...palette];
2272
+ }
2404
2273
  /**
2405
2274
  * Display labels for the named-token palette. Used in tooltips / aria labels;
2406
2275
  * unknown tokens fall back to a title-cased version of the raw key.
@@ -2438,7 +2307,9 @@ class DomternalNotionColorPickerComponent {
2438
2307
  constructor() {
2439
2308
  effect(() => {
2440
2309
  const editor = this.editor();
2441
- untracked(() => { this.setupEventListener(editor); });
2310
+ untracked(() => {
2311
+ this.setupEventListener(editor);
2312
+ });
2442
2313
  });
2443
2314
  }
2444
2315
  ngOnDestroy() {
@@ -2478,7 +2349,7 @@ class DomternalNotionColorPickerComponent {
2478
2349
  const idx = active ? swatches.indexOf(active) : -1;
2479
2350
  if (idx === -1)
2480
2351
  return;
2481
- let next = idx;
2352
+ let next;
2482
2353
  switch (event.key) {
2483
2354
  case 'ArrowRight':
2484
2355
  event.preventDefault();
@@ -2600,13 +2471,17 @@ class DomternalNotionColorPickerComponent {
2600
2471
  return;
2601
2472
  if (this.anchorEl?.contains(target))
2602
2473
  return;
2603
- this.ngZone.run(() => { this.close({ refocus: false }); });
2474
+ this.ngZone.run(() => {
2475
+ this.close({ refocus: false });
2476
+ });
2604
2477
  };
2605
2478
  document.addEventListener('mousedown', this.clickOutsideHandler);
2606
2479
  this.keydownHandler = (e) => {
2607
2480
  if (e.key === 'Escape' && this.isOpen()) {
2608
2481
  e.preventDefault();
2609
- this.ngZone.run(() => { this.close({ refocus: true }); });
2482
+ this.ngZone.run(() => {
2483
+ this.close({ refocus: true });
2484
+ });
2610
2485
  }
2611
2486
  };
2612
2487
  document.addEventListener('keydown', this.keydownHandler);
@@ -2619,11 +2494,15 @@ class DomternalNotionColorPickerComponent {
2619
2494
  // (hideout, route change). Positioning against a detached anchor is
2620
2495
  // a no-op; close instead. Matches React/Vue behaviour.
2621
2496
  if (this.anchorEl && !this.anchorEl.isConnected) {
2622
- this.ngZone.run(() => { this.close({ refocus: false }); });
2497
+ this.ngZone.run(() => {
2498
+ this.close({ refocus: false });
2499
+ });
2623
2500
  return;
2624
2501
  }
2625
2502
  if (this.editor().state.selection.empty) {
2626
- this.ngZone.run(() => { this.close({ refocus: false }); });
2503
+ this.ngZone.run(() => {
2504
+ this.close({ refocus: false });
2505
+ });
2627
2506
  }
2628
2507
  else {
2629
2508
  // Selection changed but stays non-empty (e.g. shift+arrow): refresh
@@ -2677,8 +2556,7 @@ class DomternalNotionColorPickerComponent {
2677
2556
  }
2678
2557
  readPalette() {
2679
2558
  const ext = this.editor().extensionManager.extensions.find((e) => e.name === 'notionColorPicker');
2680
- const options = (ext?.options ?? null);
2681
- return options?.palette ? [...options.palette] : [];
2559
+ return paletteFromExtensionOptions(ext?.options);
2682
2560
  }
2683
2561
  getStorage() {
2684
2562
  const storage = this.editor().storage;
@@ -2702,8 +2580,8 @@ class DomternalNotionColorPickerComponent {
2702
2580
  this.eventHandler = null;
2703
2581
  }
2704
2582
  }
2705
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalNotionColorPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2706
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DomternalNotionColorPickerComponent, isStandalone: true, selector: "domternal-notion-color-picker", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
2583
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: DomternalNotionColorPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2584
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: DomternalNotionColorPickerComponent, isStandalone: true, selector: "domternal-notion-color-picker", inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
2707
2585
  @if (isOpen()) {
2708
2586
  <div
2709
2587
  class="dm-notion-color-picker"
@@ -2777,7 +2655,7 @@ class DomternalNotionColorPickerComponent {
2777
2655
  }
2778
2656
  `, isInline: true, styles: [":host{display:contents}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
2779
2657
  }
2780
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DomternalNotionColorPickerComponent, decorators: [{
2658
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: DomternalNotionColorPickerComponent, decorators: [{
2781
2659
  type: Component,
2782
2660
  args: [{ selector: 'domternal-notion-color-picker', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
2783
2661
  @if (isOpen()) {