@domternal/vanilla 0.14.0 → 1.0.0
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 +24 -5
- package/dist/index.d.ts +17 -90
- package/dist/index.js +204 -246
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
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";
|
|
@@ -82,7 +83,8 @@ var DomternalEditor = class extends EventTarget {
|
|
|
82
83
|
extensions: [...defaults, ...options.extensions ?? []],
|
|
83
84
|
content: options.content ?? "",
|
|
84
85
|
editable: options.editable ?? true,
|
|
85
|
-
autofocus: options.autofocus ?? false
|
|
86
|
+
autofocus: options.autofocus ?? false,
|
|
87
|
+
...options.preset ? { preset: options.preset } : {}
|
|
86
88
|
});
|
|
87
89
|
this.#wireEditorEvents();
|
|
88
90
|
this.#onCreate?.(this.editor);
|
|
@@ -359,6 +361,8 @@ var DomternalToolbar = class extends EventTarget {
|
|
|
359
361
|
#lastGroupsRef = null;
|
|
360
362
|
/** Maps top-level button/dropdown name -> rendered trigger element. */
|
|
361
363
|
#buttonEls = /* @__PURE__ */ new Map();
|
|
364
|
+
/** Trigger markup last written, so a re-render does not rewrite it blindly. */
|
|
365
|
+
#triggerHtml = /* @__PURE__ */ new WeakMap();
|
|
362
366
|
/** Rendered dropdown panel elements (created lazily when opened). */
|
|
363
367
|
#dropdownPanelEl = null;
|
|
364
368
|
#cleanupFloating = null;
|
|
@@ -452,6 +456,17 @@ var DomternalToolbar = class extends EventTarget {
|
|
|
452
456
|
},
|
|
453
457
|
{ signal }
|
|
454
458
|
);
|
|
459
|
+
document.addEventListener(
|
|
460
|
+
"keydown",
|
|
461
|
+
(e) => {
|
|
462
|
+
if (!this.#controller.openDropdown) return;
|
|
463
|
+
if (e.key !== "Escape") return;
|
|
464
|
+
if (this.host.contains(document.activeElement)) return;
|
|
465
|
+
this.closeDropdown();
|
|
466
|
+
e.preventDefault();
|
|
467
|
+
},
|
|
468
|
+
{ signal }
|
|
469
|
+
);
|
|
455
470
|
this.host.addEventListener(
|
|
456
471
|
"keydown",
|
|
457
472
|
(e) => {
|
|
@@ -549,7 +564,9 @@ var DomternalToolbar = class extends EventTarget {
|
|
|
549
564
|
btn.setAttribute("aria-label", dd.label);
|
|
550
565
|
btn.title = dd.label;
|
|
551
566
|
btn.setAttribute("data-dropdown", dd.name);
|
|
552
|
-
|
|
567
|
+
const triggerHtml = this.#resolveDropdownTriggerHtml(dd);
|
|
568
|
+
this.#triggerHtml.set(btn, triggerHtml);
|
|
569
|
+
btn.innerHTML = triggerHtml;
|
|
553
570
|
btn.addEventListener("mousedown", (e) => {
|
|
554
571
|
e.preventDefault();
|
|
555
572
|
});
|
|
@@ -626,7 +643,8 @@ var DomternalToolbar = class extends EventTarget {
|
|
|
626
643
|
btn.setAttribute("aria-expanded", String(isOpen));
|
|
627
644
|
btn.tabIndex = flat === focusedIndex ? 0 : -1;
|
|
628
645
|
const newHtml = this.#resolveDropdownTriggerHtml(dd);
|
|
629
|
-
if (btn
|
|
646
|
+
if (this.#triggerHtml.get(btn) !== newHtml) {
|
|
647
|
+
this.#triggerHtml.set(btn, newHtml);
|
|
630
648
|
btn.innerHTML = newHtml;
|
|
631
649
|
}
|
|
632
650
|
}
|
|
@@ -872,129 +890,6 @@ var DomternalToolbar = class extends EventTarget {
|
|
|
872
890
|
}
|
|
873
891
|
};
|
|
874
892
|
|
|
875
|
-
// src/bubble-menu/itemResolver.ts
|
|
876
|
-
function buildItemMaps(editor) {
|
|
877
|
-
const itemMap = /* @__PURE__ */ new Map();
|
|
878
|
-
const dropdownMap = /* @__PURE__ */ new Map();
|
|
879
|
-
for (const item of editor.toolbarItems) {
|
|
880
|
-
if (item.type === "button") {
|
|
881
|
-
itemMap.set(item.name, item);
|
|
882
|
-
} else if (item.type === "dropdown") {
|
|
883
|
-
dropdownMap.set(item.name, item);
|
|
884
|
-
for (const sub of item.items) {
|
|
885
|
-
itemMap.set(sub.name, sub);
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
return {
|
|
890
|
-
itemMap,
|
|
891
|
-
dropdownMap,
|
|
892
|
-
bubbleDefaults: buildBubbleDefaults(editor)
|
|
893
|
-
};
|
|
894
|
-
}
|
|
895
|
-
function buildBubbleDefaults(editor) {
|
|
896
|
-
const byCtx = /* @__PURE__ */ new Map();
|
|
897
|
-
const addItem = (btn) => {
|
|
898
|
-
const ctx = btn["bubbleMenu"];
|
|
899
|
-
if (!ctx) return;
|
|
900
|
-
let arr = byCtx.get(ctx);
|
|
901
|
-
if (!arr) {
|
|
902
|
-
arr = [];
|
|
903
|
-
byCtx.set(ctx, arr);
|
|
904
|
-
}
|
|
905
|
-
arr.push(btn);
|
|
906
|
-
};
|
|
907
|
-
for (const item of editor.toolbarItems) {
|
|
908
|
-
if (item.type === "button") addItem(item);
|
|
909
|
-
else if (item.type === "dropdown") {
|
|
910
|
-
for (const sub of item.items) addItem(sub);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
const result = /* @__PURE__ */ new Map();
|
|
914
|
-
for (const [ctx, ctxItems] of byCtx) {
|
|
915
|
-
ctxItems.sort((a, b) => (b.priority ?? 100) - (a.priority ?? 100));
|
|
916
|
-
const list = [];
|
|
917
|
-
let lastGroup;
|
|
918
|
-
let sepIdx = 0;
|
|
919
|
-
for (const item of ctxItems) {
|
|
920
|
-
if (lastGroup !== void 0 && item.group !== lastGroup) {
|
|
921
|
-
list.push({ type: "separator", name: `bsep-${String(sepIdx++)}` });
|
|
922
|
-
}
|
|
923
|
-
list.push(item);
|
|
924
|
-
lastGroup = item.group;
|
|
925
|
-
}
|
|
926
|
-
result.set(ctx, list);
|
|
927
|
-
}
|
|
928
|
-
return result;
|
|
929
|
-
}
|
|
930
|
-
function resolveNames(names, itemMap, dropdownMap) {
|
|
931
|
-
const result = [];
|
|
932
|
-
let sepIdx = 0;
|
|
933
|
-
for (const name of names) {
|
|
934
|
-
if (name === "|") {
|
|
935
|
-
result.push({ type: "separator", name: `sep-${String(sepIdx++)}` });
|
|
936
|
-
continue;
|
|
937
|
-
}
|
|
938
|
-
const dropdown = dropdownMap.get(name);
|
|
939
|
-
if (dropdown) {
|
|
940
|
-
result.push(dropdown);
|
|
941
|
-
continue;
|
|
942
|
-
}
|
|
943
|
-
const item = itemMap.get(name);
|
|
944
|
-
if (item) result.push(item);
|
|
945
|
-
}
|
|
946
|
-
return result;
|
|
947
|
-
}
|
|
948
|
-
function getFormatItems(itemMap) {
|
|
949
|
-
return Array.from(itemMap.values()).filter((item) => item.group === "format").sort((a, b) => (b.priority ?? 100) - (a.priority ?? 100));
|
|
950
|
-
}
|
|
951
|
-
function detectContext(selection, ctxs) {
|
|
952
|
-
if ("$anchorCell" in selection) return null;
|
|
953
|
-
if (selection.node) return selection.node.type.name;
|
|
954
|
-
if (selection.empty) return null;
|
|
955
|
-
const fromCell = findCellNode(selection.$from);
|
|
956
|
-
if (fromCell) {
|
|
957
|
-
const toCell = findCellNode(selection.$to);
|
|
958
|
-
if (toCell && fromCell !== toCell) return null;
|
|
959
|
-
return "table";
|
|
960
|
-
}
|
|
961
|
-
const fromName = selection.$from.parent.type.name;
|
|
962
|
-
if (fromName in ctxs) return fromName;
|
|
963
|
-
if ("text" in ctxs && selection.$from.parent.type.spec.marks !== "") return "text";
|
|
964
|
-
const toName = selection.$to.parent.type.name;
|
|
965
|
-
if (toName in ctxs) return toName;
|
|
966
|
-
if ("text" in ctxs && selection.$to.parent.type.spec.marks !== "") return "text";
|
|
967
|
-
return null;
|
|
968
|
-
}
|
|
969
|
-
function filterBySchema(editor, contextName, schemaItems) {
|
|
970
|
-
if (contextName === "text" || contextName === "table") return schemaItems;
|
|
971
|
-
const schema = editor.state.schema;
|
|
972
|
-
if (!schema) return schemaItems;
|
|
973
|
-
const nodeType = schema.nodes[contextName];
|
|
974
|
-
if (!nodeType) return schemaItems;
|
|
975
|
-
return schemaItems.filter((item) => {
|
|
976
|
-
const markName = typeof item.isActive === "string" ? item.isActive : null;
|
|
977
|
-
if (!markName) return true;
|
|
978
|
-
const markType = schema.marks[markName];
|
|
979
|
-
if (!markType) return true;
|
|
980
|
-
return nodeType.allowsMarkType(markType);
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
function isInsideTableCell($pos) {
|
|
984
|
-
for (let d = $pos.depth; d > 0; d--) {
|
|
985
|
-
const name = $pos.node(d).type.name;
|
|
986
|
-
if (name === "tableCell" || name === "tableHeader") return true;
|
|
987
|
-
}
|
|
988
|
-
return false;
|
|
989
|
-
}
|
|
990
|
-
function findCellNode(pos) {
|
|
991
|
-
for (let d = pos.depth; d > 0; d--) {
|
|
992
|
-
const node = pos.node(d);
|
|
993
|
-
if (node.type.name === "tableCell" || node.type.name === "tableHeader") return node;
|
|
994
|
-
}
|
|
995
|
-
return null;
|
|
996
|
-
}
|
|
997
|
-
|
|
998
893
|
// src/bubble-menu/trailingState.ts
|
|
999
894
|
var INITIAL_TRAILING_STATE = {
|
|
1000
895
|
isNodeSelection: false,
|
|
@@ -1031,7 +926,8 @@ function computeTrailingState(editor, opts) {
|
|
|
1031
926
|
}
|
|
1032
927
|
return {
|
|
1033
928
|
isNodeSelection: isNode,
|
|
1034
|
-
|
|
929
|
+
// Hidden without a live notionColorOpen listener: the trigger would be dead.
|
|
930
|
+
showColorPickerButton: opts.hasNotionColorPicker && editor.listenerCount("notionColorOpen") > 0,
|
|
1035
931
|
showBlockMenuButton: opts.hasBlockContextMenu,
|
|
1036
932
|
blockMenuButtonDisabled: blockMenuDisabled,
|
|
1037
933
|
currentTextColorVar: textVar,
|
|
@@ -1080,6 +976,15 @@ var DomternalBubbleMenu = class extends EventTarget {
|
|
|
1080
976
|
#activeMap = /* @__PURE__ */ new Map();
|
|
1081
977
|
#disabledMap = /* @__PURE__ */ new Map();
|
|
1082
978
|
#trailing = { ...INITIAL_TRAILING_STATE };
|
|
979
|
+
// Structure rebuilt only on an item-list change, so a pressed button lives.
|
|
980
|
+
#structureKey = null;
|
|
981
|
+
#buttonEls = /* @__PURE__ */ new Map();
|
|
982
|
+
// Icon markup last WRITTEN. `btn.innerHTML` returns the browser's
|
|
983
|
+
// re-serialisation, so comparing against it rewrites every render and
|
|
984
|
+
// replaces the glyph under the pointer. Keyed by element: names repeat.
|
|
985
|
+
#iconHtml = /* @__PURE__ */ new WeakMap();
|
|
986
|
+
#colorTriggerEl = null;
|
|
987
|
+
#blockMenuTriggerEl = null;
|
|
1083
988
|
// Dropdown state (text-align dropdown inside bubble menu)
|
|
1084
989
|
#openDropdown = null;
|
|
1085
990
|
#dropdownPanelEl = null;
|
|
@@ -1162,7 +1067,7 @@ var DomternalBubbleMenu = class extends EventTarget {
|
|
|
1162
1067
|
if (this.#destroyed) return;
|
|
1163
1068
|
this.#explicitItems = items;
|
|
1164
1069
|
if (this.#maps) {
|
|
1165
|
-
this.#defaultItemList = items ?
|
|
1070
|
+
this.#defaultItemList = items ? resolveBubbleNames(items, this.#maps.itemMap, this.#maps.dropdownMap) : resolveBubbleNames(["bold", "italic", "underline"], this.#maps.itemMap, this.#maps.dropdownMap);
|
|
1166
1071
|
}
|
|
1167
1072
|
this.#updateResolvedItems();
|
|
1168
1073
|
this.#updateStates();
|
|
@@ -1186,6 +1091,7 @@ var DomternalBubbleMenu = class extends EventTarget {
|
|
|
1186
1091
|
setIcons(icons) {
|
|
1187
1092
|
if (this.#destroyed) return;
|
|
1188
1093
|
this.#icons = icons;
|
|
1094
|
+
this.#structureKey = null;
|
|
1189
1095
|
this.#scheduleRender();
|
|
1190
1096
|
}
|
|
1191
1097
|
/** Close any open dropdown (text-align). No-op if nothing is open. */
|
|
@@ -1218,9 +1124,9 @@ var DomternalBubbleMenu = class extends EventTarget {
|
|
|
1218
1124
|
const exts = ed.extensionManager.extensions;
|
|
1219
1125
|
this.#hasNotionColorPicker = exts.some((e) => e.name === "notionColorPicker");
|
|
1220
1126
|
this.#hasBlockContextMenu = exts.some((e) => e.name === "blockContextMenu");
|
|
1221
|
-
this.#maps =
|
|
1127
|
+
this.#maps = buildBubbleItemMaps(ed);
|
|
1222
1128
|
this.#effectiveContexts = this.#explicitContexts ?? (this.#explicitItems ? void 0 : defaultBubbleContexts(ed));
|
|
1223
|
-
this.#defaultItemList = this.#explicitItems ?
|
|
1129
|
+
this.#defaultItemList = this.#explicitItems ? resolveBubbleNames(this.#explicitItems, this.#maps.itemMap, this.#maps.dropdownMap) : resolveBubbleNames(["bold", "italic", "underline"], this.#maps.itemMap, this.#maps.dropdownMap);
|
|
1224
1130
|
const shouldShow = this.#shouldShowOpt ?? this.#buildDefaultShouldShow();
|
|
1225
1131
|
const plugin = createBubbleMenuPlugin({
|
|
1226
1132
|
pluginKey: this.#pluginKey,
|
|
@@ -1252,76 +1158,28 @@ var DomternalBubbleMenu = class extends EventTarget {
|
|
|
1252
1158
|
this.#render();
|
|
1253
1159
|
}
|
|
1254
1160
|
#buildDefaultShouldShow() {
|
|
1255
|
-
const
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
return ({ state }) => {
|
|
1259
|
-
const ctx = detectContext(
|
|
1260
|
-
state.selection,
|
|
1261
|
-
contexts
|
|
1262
|
-
);
|
|
1263
|
-
if (!ctx) return false;
|
|
1264
|
-
if (ctx in contexts) {
|
|
1265
|
-
const val = contexts[ctx];
|
|
1266
|
-
if (val === null) return false;
|
|
1267
|
-
return val === true || Array.isArray(val) && val.length > 0;
|
|
1268
|
-
}
|
|
1269
|
-
return defaults?.has(ctx) ?? false;
|
|
1270
|
-
};
|
|
1271
|
-
}
|
|
1272
|
-
return ({ state }) => {
|
|
1273
|
-
const sel = state.selection;
|
|
1274
|
-
if (sel.empty) return false;
|
|
1275
|
-
if (sel.node) return defaults?.has(sel.node.type.name) ?? false;
|
|
1276
|
-
if (isInsideTableCell(sel.$from)) return false;
|
|
1277
|
-
return sel.$from.parent.type.spec.marks !== "" || sel.$to.parent.type.spec.marks !== "";
|
|
1278
|
-
};
|
|
1161
|
+
const maps = this.#maps;
|
|
1162
|
+
if (!maps) return () => false;
|
|
1163
|
+
return createBubbleShouldShow(maps, this.#effectiveContexts);
|
|
1279
1164
|
}
|
|
1280
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
|
+
*/
|
|
1281
1171
|
#updateResolvedItems() {
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
if (contexts) {
|
|
1286
|
-
const ctx = detectContext(
|
|
1287
|
-
ed.state.selection,
|
|
1288
|
-
contexts
|
|
1289
|
-
);
|
|
1290
|
-
if (!ctx) {
|
|
1291
|
-
this.#resolvedItems = [];
|
|
1292
|
-
return;
|
|
1293
|
-
}
|
|
1294
|
-
if (ctx in contexts) {
|
|
1295
|
-
const val = contexts[ctx];
|
|
1296
|
-
if (val === null || Array.isArray(val) && val.length === 0) {
|
|
1297
|
-
this.#resolvedItems = [];
|
|
1298
|
-
return;
|
|
1299
|
-
}
|
|
1300
|
-
if (val === true) {
|
|
1301
|
-
this.#resolvedItems = filterBySchema(ed, ctx, getFormatItems(this.#maps.itemMap));
|
|
1302
|
-
return;
|
|
1303
|
-
}
|
|
1304
|
-
if (Array.isArray(val)) {
|
|
1305
|
-
const resolved = resolveNames(val, this.#maps.itemMap, this.#maps.dropdownMap);
|
|
1306
|
-
const buttons = resolved.filter(
|
|
1307
|
-
(i) => i.type !== "separator"
|
|
1308
|
-
);
|
|
1309
|
-
const allowed = new Set(filterBySchema(ed, ctx, buttons).map((b) => b.name));
|
|
1310
|
-
this.#resolvedItems = resolved.filter(
|
|
1311
|
-
(i) => i.type === "separator" || allowed.has(i.name)
|
|
1312
|
-
);
|
|
1313
|
-
return;
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
this.#resolvedItems = this.#maps.bubbleDefaults.get(ctx) ?? [];
|
|
1172
|
+
const maps = this.#maps;
|
|
1173
|
+
if (!maps) {
|
|
1174
|
+
this.#resolvedItems = [];
|
|
1317
1175
|
return;
|
|
1318
1176
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
}
|
|
1177
|
+
this.#resolvedItems = resolveBubbleMenuItems({
|
|
1178
|
+
editor: this.#editor,
|
|
1179
|
+
maps,
|
|
1180
|
+
contexts: this.#effectiveContexts,
|
|
1181
|
+
fallbackItems: this.#defaultItemList
|
|
1182
|
+
});
|
|
1325
1183
|
}
|
|
1326
1184
|
#updateStates() {
|
|
1327
1185
|
const ed = this.#editor;
|
|
@@ -1362,19 +1220,51 @@ var DomternalBubbleMenu = class extends EventTarget {
|
|
|
1362
1220
|
this.#render();
|
|
1363
1221
|
});
|
|
1364
1222
|
}
|
|
1223
|
+
/** Signature of what decides which nodes exist; equal keys reuse the DOM. */
|
|
1224
|
+
#computeStructureKey() {
|
|
1225
|
+
const items = this.#resolvedItems.map(
|
|
1226
|
+
(item) => item.type === "dropdown" ? `${item.type}:${item.name}:${item.items.map((sub) => sub.name).join(",")}` : `${item.type}:${item.name}`
|
|
1227
|
+
).join("|");
|
|
1228
|
+
const t = this.#trailing;
|
|
1229
|
+
return [
|
|
1230
|
+
items,
|
|
1231
|
+
t.showColorPickerButton && !t.isNodeSelection ? "color" : "",
|
|
1232
|
+
t.showBlockMenuButton && !t.isNodeSelection ? "block" : "",
|
|
1233
|
+
this.#customContent ? "custom" : ""
|
|
1234
|
+
].join("#");
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Runs on every transaction, and pointer motion alone dispatches those. A
|
|
1238
|
+
* render lands a frame late, so rebuilding wholesale can replace a button
|
|
1239
|
+
* between mousedown and mouseup: the two events share no element and the
|
|
1240
|
+
* browser fires no click at all. Structure only on an item-list change,
|
|
1241
|
+
* state in place otherwise, as the toolbar does.
|
|
1242
|
+
*/
|
|
1365
1243
|
#render() {
|
|
1366
|
-
this.host.replaceChildren();
|
|
1367
|
-
this.#cleanupDropdownFloating?.();
|
|
1368
|
-
this.#cleanupDropdownFloating = null;
|
|
1369
|
-
this.#dropdownAbortCtl?.abort();
|
|
1370
|
-
this.#dropdownAbortCtl = null;
|
|
1371
|
-
this.#dropdownPanelEl = null;
|
|
1372
1244
|
if (this.#openDropdown !== null) {
|
|
1373
1245
|
const stillExists = this.#resolvedItems.some(
|
|
1374
1246
|
(item) => item.type === "dropdown" && item.name === this.#openDropdown
|
|
1375
1247
|
);
|
|
1376
|
-
if (!stillExists)
|
|
1248
|
+
if (!stillExists) {
|
|
1249
|
+
this.#openDropdown = null;
|
|
1250
|
+
this.#detachDropdown();
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
const key = this.#computeStructureKey();
|
|
1254
|
+
if (key === this.#structureKey) {
|
|
1255
|
+
this.#updateButtons();
|
|
1256
|
+
} else {
|
|
1257
|
+
this.#renderStructure();
|
|
1258
|
+
this.#structureKey = key;
|
|
1377
1259
|
}
|
|
1260
|
+
this.#syncDropdownPanel();
|
|
1261
|
+
}
|
|
1262
|
+
#renderStructure() {
|
|
1263
|
+
this.host.replaceChildren();
|
|
1264
|
+
this.#buttonEls.clear();
|
|
1265
|
+
this.#colorTriggerEl = null;
|
|
1266
|
+
this.#blockMenuTriggerEl = null;
|
|
1267
|
+
this.#detachDropdown();
|
|
1378
1268
|
for (const item of this.#resolvedItems) {
|
|
1379
1269
|
if (item.type === "separator") {
|
|
1380
1270
|
this.host.appendChild(this.#createSeparator(item.name));
|
|
@@ -1385,12 +1275,18 @@ var DomternalBubbleMenu = class extends EventTarget {
|
|
|
1385
1275
|
}
|
|
1386
1276
|
}
|
|
1387
1277
|
const t = this.#trailing;
|
|
1388
|
-
|
|
1389
|
-
|
|
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
|
+
}
|
|
1390
1284
|
this.host.appendChild(this.#createColorTrigger());
|
|
1391
1285
|
}
|
|
1392
|
-
if (
|
|
1393
|
-
|
|
1286
|
+
if (showBlock) {
|
|
1287
|
+
if (this.#resolvedItems.length > 0 || showColor) {
|
|
1288
|
+
this.host.appendChild(this.#createSeparator("trailing-sep-block"));
|
|
1289
|
+
}
|
|
1394
1290
|
this.host.appendChild(this.#createBlockMenuTrigger());
|
|
1395
1291
|
}
|
|
1396
1292
|
if (this.#customContent) {
|
|
@@ -1415,13 +1311,14 @@ var DomternalBubbleMenu = class extends EventTarget {
|
|
|
1415
1311
|
btn.setAttribute("aria-label", item.label);
|
|
1416
1312
|
btn.setAttribute("aria-pressed", String(isActive));
|
|
1417
1313
|
btn.title = item.label;
|
|
1418
|
-
btn
|
|
1314
|
+
this.#setIconHtml(btn, resolveIcon(item.icon, this.#icons));
|
|
1419
1315
|
btn.addEventListener("mousedown", (e) => {
|
|
1420
1316
|
e.preventDefault();
|
|
1421
1317
|
});
|
|
1422
1318
|
btn.addEventListener("click", (e) => {
|
|
1423
1319
|
this.#onButtonClick(item, e);
|
|
1424
1320
|
});
|
|
1321
|
+
this.#buttonEls.set(item.name, btn);
|
|
1425
1322
|
return btn;
|
|
1426
1323
|
}
|
|
1427
1324
|
#createDropdownTrigger(dd) {
|
|
@@ -1441,7 +1338,7 @@ var DomternalBubbleMenu = class extends EventTarget {
|
|
|
1441
1338
|
trigger.setAttribute("aria-label", dd.label);
|
|
1442
1339
|
trigger.title = dd.label;
|
|
1443
1340
|
trigger.dataset["dropdown"] = dd.name;
|
|
1444
|
-
trigger
|
|
1341
|
+
this.#setIconHtml(trigger, triggerHtml);
|
|
1445
1342
|
trigger.addEventListener("mousedown", (e) => {
|
|
1446
1343
|
e.preventDefault();
|
|
1447
1344
|
});
|
|
@@ -1449,12 +1346,7 @@ var DomternalBubbleMenu = class extends EventTarget {
|
|
|
1449
1346
|
this.#onDropdownToggle(dd);
|
|
1450
1347
|
});
|
|
1451
1348
|
wrapper.appendChild(trigger);
|
|
1452
|
-
|
|
1453
|
-
const panel = this.#createDropdownPanel(dd);
|
|
1454
|
-
wrapper.appendChild(panel);
|
|
1455
|
-
this.#dropdownPanelEl = panel;
|
|
1456
|
-
this.#attachDropdownListeners(trigger, panel);
|
|
1457
|
-
}
|
|
1349
|
+
this.#buttonEls.set(dd.name, trigger);
|
|
1458
1350
|
return wrapper;
|
|
1459
1351
|
}
|
|
1460
1352
|
#createDropdownPanel(dd) {
|
|
@@ -1472,6 +1364,7 @@ var DomternalBubbleMenu = class extends EventTarget {
|
|
|
1472
1364
|
if (subActive) subBtn.classList.add("dm-toolbar-dropdown-item--active");
|
|
1473
1365
|
subBtn.setAttribute("role", "menuitem");
|
|
1474
1366
|
subBtn.setAttribute("aria-label", sub.label);
|
|
1367
|
+
subBtn.dataset["dropdownItem"] = sub.name;
|
|
1475
1368
|
subBtn.innerHTML = subHtml;
|
|
1476
1369
|
subBtn.addEventListener("mousedown", (e) => {
|
|
1477
1370
|
e.preventDefault();
|
|
@@ -1507,6 +1400,7 @@ var DomternalBubbleMenu = class extends EventTarget {
|
|
|
1507
1400
|
btn.addEventListener("click", () => {
|
|
1508
1401
|
this.openColorPicker(btn);
|
|
1509
1402
|
});
|
|
1403
|
+
this.#colorTriggerEl = btn;
|
|
1510
1404
|
return btn;
|
|
1511
1405
|
}
|
|
1512
1406
|
#createBlockMenuTrigger() {
|
|
@@ -1525,8 +1419,83 @@ var DomternalBubbleMenu = class extends EventTarget {
|
|
|
1525
1419
|
btn.addEventListener("click", () => {
|
|
1526
1420
|
this.openBlockContextMenu(btn);
|
|
1527
1421
|
});
|
|
1422
|
+
this.#blockMenuTriggerEl = btn;
|
|
1528
1423
|
return btn;
|
|
1529
1424
|
}
|
|
1425
|
+
// === In-place updates (the common render) ===
|
|
1426
|
+
#updateButtons() {
|
|
1427
|
+
for (const item of this.#resolvedItems) {
|
|
1428
|
+
if (item.type === "separator") continue;
|
|
1429
|
+
const el = this.#buttonEls.get(item.name);
|
|
1430
|
+
if (!el) continue;
|
|
1431
|
+
if (item.type === "dropdown") this.#updateDropdownTrigger(item, el);
|
|
1432
|
+
else this.#updateButton(item, el);
|
|
1433
|
+
}
|
|
1434
|
+
const t = this.#trailing;
|
|
1435
|
+
if (this.#colorTriggerEl) {
|
|
1436
|
+
this.#colorTriggerEl.classList.toggle("dm-toolbar-button--active", t.hasAnyColor);
|
|
1437
|
+
const glyph = this.#colorTriggerEl.querySelector(".dm-ncp-trigger-glyph");
|
|
1438
|
+
if (glyph) glyph.style.color = t.currentTextColorVar ?? "";
|
|
1439
|
+
const underline = this.#colorTriggerEl.querySelector(".dm-ncp-trigger-underline");
|
|
1440
|
+
if (underline) underline.style.backgroundColor = t.currentBgColorVar ?? "";
|
|
1441
|
+
}
|
|
1442
|
+
if (this.#blockMenuTriggerEl) {
|
|
1443
|
+
this.#blockMenuTriggerEl.disabled = t.blockMenuButtonDisabled;
|
|
1444
|
+
this.#blockMenuTriggerEl.title = t.blockMenuButtonDisabled ? "Block actions (select within a single block)" : "More options";
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
#updateButton(item, btn) {
|
|
1448
|
+
const isActive = this.#activeMap.get(item.name) ?? false;
|
|
1449
|
+
const isDisabled = this.#disabledMap.get(item.name) ?? false;
|
|
1450
|
+
btn.classList.toggle("dm-toolbar-button--active", isActive);
|
|
1451
|
+
btn.disabled = isDisabled;
|
|
1452
|
+
btn.setAttribute("aria-pressed", String(isActive));
|
|
1453
|
+
this.#setIconHtml(btn, resolveIcon(item.icon, this.#icons));
|
|
1454
|
+
}
|
|
1455
|
+
/** Writes icon markup only when it differs from what was written last. */
|
|
1456
|
+
#setIconHtml(el, html) {
|
|
1457
|
+
if (this.#iconHtml.get(el) === html) return;
|
|
1458
|
+
this.#iconHtml.set(el, html);
|
|
1459
|
+
el.innerHTML = html;
|
|
1460
|
+
}
|
|
1461
|
+
#updateDropdownTrigger(dd, trigger) {
|
|
1462
|
+
const dropdownActive = dd.items.some((sub) => this.#activeMap.get(sub.name) ?? false);
|
|
1463
|
+
const activeChild = dd.dynamicIcon ? dd.items.find((sub) => this.#activeMap.get(sub.name) ?? false) : void 0;
|
|
1464
|
+
const html = resolveIcon(activeChild?.icon ?? dd.icon, this.#icons) + DROPDOWN_CARET2;
|
|
1465
|
+
trigger.classList.toggle("dm-toolbar-button--active", dropdownActive);
|
|
1466
|
+
trigger.setAttribute("aria-expanded", String(this.#openDropdown === dd.name));
|
|
1467
|
+
this.#setIconHtml(trigger, html);
|
|
1468
|
+
}
|
|
1469
|
+
/** Builds the open dropdown's panel, or refreshes the marks on an open one. */
|
|
1470
|
+
#syncDropdownPanel() {
|
|
1471
|
+
const open = this.#openDropdown;
|
|
1472
|
+
if (open === null) {
|
|
1473
|
+
if (this.#dropdownPanelEl) this.#detachDropdown();
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
const dd = this.#resolvedItems.find(
|
|
1477
|
+
(item) => item.type === "dropdown" && item.name === open
|
|
1478
|
+
);
|
|
1479
|
+
if (!dd) return;
|
|
1480
|
+
const panel = this.#dropdownPanelEl;
|
|
1481
|
+
if (panel?.isConnected === true) {
|
|
1482
|
+
for (const sub of dd.items) {
|
|
1483
|
+
const subBtn = panel.querySelector(`[data-dropdown-item="${sub.name}"]`);
|
|
1484
|
+
subBtn?.classList.toggle(
|
|
1485
|
+
"dm-toolbar-dropdown-item--active",
|
|
1486
|
+
this.#activeMap.get(sub.name) ?? false
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
const trigger = this.#buttonEls.get(open);
|
|
1492
|
+
const wrapper = trigger?.parentElement;
|
|
1493
|
+
if (!trigger || !wrapper) return;
|
|
1494
|
+
const fresh = this.#createDropdownPanel(dd);
|
|
1495
|
+
wrapper.appendChild(fresh);
|
|
1496
|
+
this.#dropdownPanelEl = fresh;
|
|
1497
|
+
this.#attachDropdownListeners(trigger, fresh);
|
|
1498
|
+
}
|
|
1530
1499
|
// === Event handlers ===
|
|
1531
1500
|
#onButtonClick(item, event) {
|
|
1532
1501
|
if (this.#openDropdown) this.closeDropdown();
|
|
@@ -1844,6 +1813,14 @@ var DomternalFloatingMenu = class extends EventTarget {
|
|
|
1844
1813
|
}
|
|
1845
1814
|
}
|
|
1846
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
|
+
}
|
|
1847
1824
|
var TOKEN_LABELS = {
|
|
1848
1825
|
gray: "Gray",
|
|
1849
1826
|
brown: "Brown",
|
|
@@ -1920,8 +1897,7 @@ var DomternalNotionColorPicker = class extends EventTarget {
|
|
|
1920
1897
|
const ext = this.#editor.extensionManager.extensions.find(
|
|
1921
1898
|
(e) => e.name === "notionColorPicker"
|
|
1922
1899
|
);
|
|
1923
|
-
|
|
1924
|
-
this.#palette = extOpts?.palette ? [...extOpts.palette] : [];
|
|
1900
|
+
this.#palette = paletteFromExtensionOptions(ext?.options);
|
|
1925
1901
|
this.#onOpen = (...args) => {
|
|
1926
1902
|
const detail = args[0];
|
|
1927
1903
|
const incoming = detail?.anchorElement;
|
|
@@ -2002,9 +1978,7 @@ var DomternalNotionColorPicker = class extends EventTarget {
|
|
|
2002
1978
|
this.#editor.commands.setTextColorToken(token);
|
|
2003
1979
|
this.#syncFromSelection();
|
|
2004
1980
|
this.#updateActiveClasses();
|
|
2005
|
-
this.dispatchEvent(
|
|
2006
|
-
new CustomEvent("apply", { detail: { kind: "text", token } })
|
|
2007
|
-
);
|
|
1981
|
+
this.dispatchEvent(new CustomEvent("apply", { detail: { kind: "text", token } }));
|
|
2008
1982
|
}
|
|
2009
1983
|
/** Apply a background color token to the current selection. Picker stays open. */
|
|
2010
1984
|
applyBg(token) {
|
|
@@ -2012,9 +1986,7 @@ var DomternalNotionColorPicker = class extends EventTarget {
|
|
|
2012
1986
|
this.#editor.commands.setBackgroundColorToken(token);
|
|
2013
1987
|
this.#syncFromSelection();
|
|
2014
1988
|
this.#updateActiveClasses();
|
|
2015
|
-
this.dispatchEvent(
|
|
2016
|
-
new CustomEvent("apply", { detail: { kind: "bg", token } })
|
|
2017
|
-
);
|
|
1989
|
+
this.dispatchEvent(new CustomEvent("apply", { detail: { kind: "bg", token } }));
|
|
2018
1990
|
}
|
|
2019
1991
|
/** Display label for a palette token (title-case fallback). */
|
|
2020
1992
|
tokenLabel(token) {
|
|
@@ -2224,9 +2196,7 @@ var DomternalNotionColorPicker = class extends EventTarget {
|
|
|
2224
2196
|
return rect;
|
|
2225
2197
|
}
|
|
2226
2198
|
if (this.#anchorBubbleMenu?.isConnected) {
|
|
2227
|
-
const fresh = this.#anchorBubbleMenu.querySelector(
|
|
2228
|
-
".dm-ncp-trigger"
|
|
2229
|
-
);
|
|
2199
|
+
const fresh = this.#anchorBubbleMenu.querySelector(".dm-ncp-trigger");
|
|
2230
2200
|
if (fresh) {
|
|
2231
2201
|
this.#anchor = fresh;
|
|
2232
2202
|
const rect = fresh.getBoundingClientRect();
|
|
@@ -2296,15 +2266,13 @@ var DomternalNotionColorPicker = class extends EventTarget {
|
|
|
2296
2266
|
#onPanelKeydown(event) {
|
|
2297
2267
|
const cols = 5;
|
|
2298
2268
|
if (!this.#panel) return;
|
|
2299
|
-
const swatches = Array.from(
|
|
2300
|
-
this.#panel.querySelectorAll(".dm-ncp-swatch")
|
|
2301
|
-
);
|
|
2269
|
+
const swatches = Array.from(this.#panel.querySelectorAll(".dm-ncp-swatch"));
|
|
2302
2270
|
if (!swatches.length) return;
|
|
2303
2271
|
const active = document.activeElement;
|
|
2304
2272
|
if (!(active instanceof HTMLElement)) return;
|
|
2305
2273
|
const idx = swatches.indexOf(active);
|
|
2306
2274
|
if (idx === -1) return;
|
|
2307
|
-
let next
|
|
2275
|
+
let next;
|
|
2308
2276
|
switch (event.key) {
|
|
2309
2277
|
case "ArrowRight":
|
|
2310
2278
|
event.preventDefault();
|
|
@@ -2531,11 +2499,7 @@ var DomternalEmojiPicker = class extends EventTarget {
|
|
|
2531
2499
|
return panel;
|
|
2532
2500
|
}
|
|
2533
2501
|
#renderPanelChildren() {
|
|
2534
|
-
return [
|
|
2535
|
-
this.#renderSearch(),
|
|
2536
|
-
this.#renderTabs(),
|
|
2537
|
-
this.#renderGrid()
|
|
2538
|
-
];
|
|
2502
|
+
return [this.#renderSearch(), this.#renderTabs(), this.#renderGrid()];
|
|
2539
2503
|
}
|
|
2540
2504
|
#renderSearch() {
|
|
2541
2505
|
const wrapper = document.createElement("div");
|
|
@@ -2650,9 +2614,7 @@ var DomternalEmojiPicker = class extends EventTarget {
|
|
|
2650
2614
|
*/
|
|
2651
2615
|
#setSearchQuery(value) {
|
|
2652
2616
|
this.#searchQuery = value;
|
|
2653
|
-
const input = this.#panel?.querySelector(
|
|
2654
|
-
".dm-emoji-picker-search input"
|
|
2655
|
-
);
|
|
2617
|
+
const input = this.#panel?.querySelector(".dm-emoji-picker-search input");
|
|
2656
2618
|
if (input && input.value !== value) input.value = value;
|
|
2657
2619
|
}
|
|
2658
2620
|
/** Replace grid contents only (used when searchQuery changes). */
|
|
@@ -2716,9 +2678,7 @@ var DomternalEmojiPicker = class extends EventTarget {
|
|
|
2716
2678
|
#onGridKeydown(event) {
|
|
2717
2679
|
const grid = this.#panel?.querySelector(".dm-emoji-picker-grid");
|
|
2718
2680
|
if (!grid) return;
|
|
2719
|
-
const swatches = Array.from(
|
|
2720
|
-
grid.querySelectorAll(".dm-emoji-swatch")
|
|
2721
|
-
);
|
|
2681
|
+
const swatches = Array.from(grid.querySelectorAll(".dm-emoji-swatch"));
|
|
2722
2682
|
if (!swatches.length) return;
|
|
2723
2683
|
const active = document.activeElement;
|
|
2724
2684
|
const idx = active instanceof HTMLElement ? swatches.indexOf(active) : -1;
|
|
@@ -2730,7 +2690,7 @@ var DomternalEmojiPicker = class extends EventTarget {
|
|
|
2730
2690
|
return;
|
|
2731
2691
|
}
|
|
2732
2692
|
const cols = 8;
|
|
2733
|
-
let next
|
|
2693
|
+
let next;
|
|
2734
2694
|
switch (event.key) {
|
|
2735
2695
|
case "ArrowRight":
|
|
2736
2696
|
event.preventDefault();
|
|
@@ -2768,9 +2728,7 @@ var DomternalEmojiPicker = class extends EventTarget {
|
|
|
2768
2728
|
offsetValue: 4
|
|
2769
2729
|
});
|
|
2770
2730
|
}
|
|
2771
|
-
const input = this.#panel.querySelector(
|
|
2772
|
-
".dm-emoji-picker-search input"
|
|
2773
|
-
);
|
|
2731
|
+
const input = this.#panel.querySelector(".dm-emoji-picker-search input");
|
|
2774
2732
|
input?.focus({ preventScroll: true });
|
|
2775
2733
|
});
|
|
2776
2734
|
}
|
|
@@ -2809,6 +2767,6 @@ var DomternalEmojiPicker = class extends EventTarget {
|
|
|
2809
2767
|
}
|
|
2810
2768
|
};
|
|
2811
2769
|
|
|
2812
|
-
export { DEFAULT_EXTENSIONS, DROPDOWN_CARET, DomternalBubbleMenu, DomternalEditor, DomternalEmojiPicker, DomternalFloatingMenu, DomternalNotionColorPicker, DomternalToolbar, INITIAL_TRAILING_STATE, assertBrowser,
|
|
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 };
|
|
2813
2771
|
//# sourceMappingURL=index.js.map
|
|
2814
2772
|
//# sourceMappingURL=index.js.map
|