@d3-polytree/editor 0.1.0 → 0.3.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/dist/index.cjs CHANGED
@@ -21,19 +21,530 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  Editor: () => Editor,
24
- default: () => index_default
24
+ EntryFactory: () => EntryFactory,
25
+ PfdnPropertiesProvider: () => PfdnPropertiesProvider,
26
+ PropertiesPanel: () => PropertiesPanel,
27
+ default: () => index_default,
28
+ entryFactoryModule: () => entryFactoryModule,
29
+ pfdnPropertiesProviderModule: () => pfdnPropertiesProviderModule,
30
+ propertiesPanelModule: () => propertiesPanelModule
25
31
  });
26
32
  module.exports = __toCommonJS(index_exports);
27
33
  var import_interactive_viewer = require("@d3-polytree/interactive-viewer");
28
34
  var import_viewer = require("@d3-polytree/viewer");
29
35
  var import_core = require("@d3-polytree/core");
30
- var import_properties_panel = require("@d3-polytree/properties-panel");
36
+
37
+ // src/properties-panel/utils.ts
38
+ function is(definition, elementType) {
39
+ return definition.$instanceOf(elementType);
40
+ }
41
+ function deepGet(obj, path) {
42
+ return path.split(".").reduce(
43
+ (o, k) => o == null ? void 0 : o[k],
44
+ obj
45
+ );
46
+ }
47
+ function deepSet(obj, path, value) {
48
+ const keys = path.split(".");
49
+ let cursor = obj;
50
+ for (let i = 0; i < keys.length - 1; i += 1) {
51
+ const key = keys[i];
52
+ if (cursor[key] == null || typeof cursor[key] !== "object") {
53
+ cursor[key] = {};
54
+ }
55
+ cursor = cursor[key];
56
+ }
57
+ cursor[keys[keys.length - 1]] = value;
58
+ }
59
+ function debounce(fn, wait) {
60
+ let timer;
61
+ return (...args) => {
62
+ if (timer) {
63
+ clearTimeout(timer);
64
+ }
65
+ timer = setTimeout(() => fn(...args), wait);
66
+ };
67
+ }
68
+ function startCase(input) {
69
+ return input.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([a-zA-Z])([0-9])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (c) => c.toUpperCase());
70
+ }
71
+ function isHexColor(value) {
72
+ return typeof value === "string" && /^#[0-9a-fA-F]{6}$/.test(value);
73
+ }
74
+
75
+ // src/properties-panel/EntryFactory.ts
76
+ function ensureNotNull(prop) {
77
+ if (!prop) {
78
+ throw new Error(`${prop} must be set.`);
79
+ }
80
+ return prop;
81
+ }
82
+ var EntryFactory = class {
83
+ constructor(eventBus) {
84
+ this._eventBus = eventBus;
85
+ }
86
+ setDefaultParameters(options) {
87
+ const eventBus = this._eventBus;
88
+ const defaultGet = (element, formNode) => {
89
+ const prop = ensureNotNull(options.modelProperty);
90
+ const value = deepGet(element, prop);
91
+ const input = formNode.querySelector("input");
92
+ if (input) {
93
+ input.value = value == null ? "" : String(value);
94
+ }
95
+ };
96
+ const defaultSet = (element, values) => {
97
+ const prop = ensureNotNull(options.modelProperty);
98
+ deepSet(element, prop, deepGet(values, prop));
99
+ return true;
100
+ };
101
+ const triggerUpdate = (propertyId, element) => {
102
+ eventBus.emit("PropertiesPanel.propertyChanged", propertyId, element);
103
+ };
104
+ return {
105
+ html: "",
106
+ description: "",
107
+ get: defaultGet,
108
+ set: defaultSet,
109
+ validate: () => ({}),
110
+ ...options,
111
+ triggerUpdate
112
+ };
113
+ }
114
+ textField(options) {
115
+ return textInputField(this.setDefaultParameters(options));
116
+ }
117
+ selectBox(options) {
118
+ return selectBoxField(this.setDefaultParameters(options));
119
+ }
120
+ colorPicker(options) {
121
+ return colorPickerField(this.setDefaultParameters(options));
122
+ }
123
+ spreadsheet(options) {
124
+ return this.setDefaultParameters(options);
125
+ }
126
+ };
127
+ EntryFactory.$inject = ["eventBus"];
128
+ function fieldWrapper(resource, inner) {
129
+ const label = resource.label ?? resource.id;
130
+ return `<div class="pfdjs-pp-field-wrapper"><label for="pfdjs-${resource.id}">${label}</label>${inner}</div>`;
131
+ }
132
+ function textInputField(resource) {
133
+ const type = resource.type ?? "text";
134
+ resource.html = fieldWrapper(
135
+ resource,
136
+ `<input id="pfdjs-${resource.id}" type="${type}" name="${resource.modelProperty}" />`
137
+ );
138
+ return resource;
139
+ }
140
+ function selectBoxField(resource) {
141
+ const allowEmpty = resource.allowEmpty ?? true;
142
+ const options = Array.isArray(resource.selectOptions) ? resource.selectOptions.concat(allowEmpty ? [{ name: "", value: "" }] : []) : [{ name: "", value: "" }];
143
+ const optionsHtml = options.map((o) => `<option value="${o.value}">${o.name}</option>`).join("");
144
+ resource.html = fieldWrapper(
145
+ resource,
146
+ `<select id="pfdjs-${resource.id}" name="${resource.modelProperty}">${optionsHtml}</select>`
147
+ );
148
+ resource.get = (element, formNode) => {
149
+ const value = element.get(resource.modelProperty) ?? "default";
150
+ formNode.querySelectorAll(`select#pfdjs-${resource.id} > option`).forEach((option) => {
151
+ option.selected = option.value === value;
152
+ });
153
+ };
154
+ return resource;
155
+ }
156
+ function colorPickerField(resource) {
157
+ resource.html = fieldWrapper(
158
+ resource,
159
+ `<input id="pfdjs-${resource.id}" type="color" name="${resource.modelProperty}" />`
160
+ );
161
+ resource.get = (element, formNode) => {
162
+ const input = formNode.querySelector("input[type=color]");
163
+ const value = deepGet(element, resource.modelProperty);
164
+ if (input && isHexColor(value)) {
165
+ input.value = value;
166
+ }
167
+ };
168
+ return resource;
169
+ }
170
+ var entryFactoryModule = {
171
+ __init__: ["entryFactory"],
172
+ entryFactory: ["type", EntryFactory]
173
+ };
174
+
175
+ // src/properties-panel/PfdnPropertiesProvider.ts
176
+ function nameProps(group, element, factory) {
177
+ if (is(element, "pfdn:Node")) {
178
+ group.entries.push(factory.textField({ id: "name", label: "Name", modelProperty: "name" }));
179
+ group.entries.push(factory.textField({ id: "tag", label: "Tag", modelProperty: "tag" }));
180
+ group.entries.push(
181
+ factory.textField({ id: "label.text", label: "Diagram label", modelProperty: "label.text" })
182
+ );
183
+ } else if (is(element, "pfdn:Settings")) {
184
+ group.entries.push(factory.textField({ id: "name", label: "Diagram name", modelProperty: "name" }));
185
+ group.entries.push(factory.textField({ id: "author", label: "Author's name", modelProperty: "author" }));
186
+ } else if (is(element, "pfdn:Link")) {
187
+ group.entries.push(
188
+ factory.textField({ id: "label.text", label: "Diagram label", modelProperty: "label.text" })
189
+ );
190
+ } else if (is(element, "pfdn:Label")) {
191
+ group.entries.push(factory.textField({ id: "text", label: "Label", modelProperty: "text" }));
192
+ }
193
+ }
194
+ function nodeFormatProps(group, element, factory, icons) {
195
+ if (!is(element, "pfdn:Node")) {
196
+ return;
197
+ }
198
+ const selectOptions = Object.keys(icons).map((key) => ({ value: key, name: startCase(key) }));
199
+ group.entries.push(
200
+ factory.selectBox({
201
+ id: "type",
202
+ label: "Icon",
203
+ modelProperty: "type",
204
+ allowEmpty: false,
205
+ selectOptions
206
+ })
207
+ );
208
+ group.entries.push(
209
+ factory.textField({ id: "size", label: "Size", modelProperty: "size", type: "number" })
210
+ );
211
+ }
212
+ function linkFormatProps(group, element, factory) {
213
+ if (!is(element, "pfdn:Link")) {
214
+ return;
215
+ }
216
+ group.entries.push(
217
+ factory.textField({ id: "lineWidth", label: "Line width", modelProperty: "lineWidth", type: "number" })
218
+ );
219
+ group.entries.push(
220
+ factory.colorPicker({ id: "lineColor", label: "Line color", modelProperty: "lineColor" })
221
+ );
222
+ }
223
+ function labelFormatProps(group, element, factory) {
224
+ if (is(element, "pfdn:Node") || is(element, "pfdn:Link")) {
225
+ group.entries.push(
226
+ factory.textField({
227
+ id: "label.fontSize",
228
+ label: "Font size",
229
+ modelProperty: "label.fontSize",
230
+ type: "number"
231
+ })
232
+ );
233
+ group.entries.push(
234
+ factory.colorPicker({ id: "label.color", label: "Color", modelProperty: "label.color" })
235
+ );
236
+ } else if (is(element, "pfdn:Label")) {
237
+ group.entries.push(
238
+ factory.textField({ id: "fontSize", label: "Font size", modelProperty: "fontSize", type: "number" })
239
+ );
240
+ group.entries.push(factory.colorPicker({ id: "color", label: "Color", modelProperty: "color" }));
241
+ }
242
+ }
243
+ function gridFormatProps(group, element, factory) {
244
+ if (!is(element, "pfdn:Settings")) {
245
+ return;
246
+ }
247
+ group.entries.push(
248
+ factory.colorPicker({ id: "backgroundColor", label: "Background color", modelProperty: "backgroundColor" })
249
+ );
250
+ group.entries.push(
251
+ factory.colorPicker({ id: "grid.lineColor", label: "Line color", modelProperty: "grid.lineColor" })
252
+ );
253
+ group.entries.push(
254
+ factory.textField({ id: "grid.size", label: "Square size", modelProperty: "grid.size", type: "number" })
255
+ );
256
+ group.entries.push(
257
+ factory.textField({ id: "grid.lineWidth", label: "Line width", modelProperty: "grid.lineWidth", type: "number" })
258
+ );
259
+ }
260
+ var PfdnPropertiesProvider = class {
261
+ constructor(icons, entryFactory, eventBus) {
262
+ this._icons = icons;
263
+ this._entryFactory = entryFactory;
264
+ this._eventBus = eventBus;
265
+ }
266
+ updateDrawing(definition) {
267
+ if (is(definition, "pfdn:Settings")) {
268
+ this._eventBus.emit("canvas.resized");
269
+ return;
270
+ }
271
+ this._eventBus.emit("element.updated", definition.id, definition);
272
+ if (is(definition, "pfdn:Link") || is(definition, "pfdn:Node")) {
273
+ const label = definition.label;
274
+ if (label) {
275
+ this._eventBus.emit("element.updated", label.id, label);
276
+ }
277
+ }
278
+ }
279
+ getTabs(element) {
280
+ const factory = this._entryFactory;
281
+ return [this._propertiesTab(element, factory), this._formatTab(element, factory)];
282
+ }
283
+ _propertiesTab(element, factory) {
284
+ const general = { id: "general", label: "General", entries: [] };
285
+ nameProps(general, element, factory);
286
+ return { id: "properties", label: "Properties", groups: [general] };
287
+ }
288
+ _formatTab(element, factory) {
289
+ const elementFormat = { id: "elementFormat", label: "Element format", entries: [] };
290
+ const labelFormat = { id: "labelFormat", label: "Label format", entries: [] };
291
+ const gridFormat = { id: "gridFormat", label: "Grid format", entries: [] };
292
+ nodeFormatProps(elementFormat, element, factory, this._icons);
293
+ linkFormatProps(elementFormat, element, factory);
294
+ labelFormatProps(labelFormat, element, factory);
295
+ gridFormatProps(gridFormat, element, factory);
296
+ return { id: "format", label: "Format", groups: [elementFormat, labelFormat, gridFormat] };
297
+ }
298
+ };
299
+ PfdnPropertiesProvider.$inject = ["icons", "entryFactory", "eventBus"];
300
+ var pfdnPropertiesProviderModule = {
301
+ __init__: ["propertiesProvider"],
302
+ propertiesProvider: ["type", PfdnPropertiesProvider]
303
+ };
304
+
305
+ // src/properties-panel/PropertiesPanel.ts
306
+ function fromHtml(html) {
307
+ const template = document.createElement("template");
308
+ template.innerHTML = html.trim();
309
+ return template.content.firstElementChild;
310
+ }
311
+ var FORM_CONTROLS = /* @__PURE__ */ new Set(["INPUT", "TEXTAREA", "SELECT"]);
312
+ var _PropertiesPanel = class _PropertiesPanel {
313
+ constructor(sideTabsProvider, eventBus, propertiesProvider, diagramSettings, commandStack) {
314
+ this._entries = {};
315
+ this._container = null;
316
+ this._tabsEl = null;
317
+ this._contentsEl = null;
318
+ this._eventBus = eventBus;
319
+ this._propertiesProvider = propertiesProvider;
320
+ this._diagramSettings = diagramSettings;
321
+ this._commandStack = commandStack;
322
+ this._registerUpdatePropertiesCommand();
323
+ this._registerSideTab(sideTabsProvider);
324
+ this._registerSelectionListener();
325
+ }
326
+ /**
327
+ * Register the `element.updateProperties` command. Its apply logic is
328
+ * editor-specific (a property `scope` plus the provider's drawing update), so
329
+ * the panel — which owns both — registers it on the shared stack rather than
330
+ * the core modelling orchestrator.
331
+ */
332
+ _registerUpdatePropertiesCommand() {
333
+ const apply = (ctx, props) => {
334
+ ctx.scope.set(ctx.definition, props);
335
+ this._propertiesProvider.updateDrawing(ctx.definition);
336
+ };
337
+ this._commandStack.registerHandler("element.updateProperties", {
338
+ execute: (ctx) => apply(ctx, ctx.after),
339
+ revert: (ctx) => apply(ctx, ctx.before)
340
+ });
341
+ }
342
+ _registerSideTab(provider) {
343
+ provider.registerSideTab(
344
+ {
345
+ title: "Properties",
346
+ iconClassName: "icon-sliders",
347
+ action: { created: (content) => this._drawPanel(content) }
348
+ },
349
+ 1
350
+ );
351
+ }
352
+ _registerSelectionListener() {
353
+ this._eventBus.on(
354
+ "selection.changed",
355
+ (oldSelection, newSelection) => {
356
+ let selected = this._diagramSettings;
357
+ if (newSelection.length === 1 && (oldSelection.length !== 1 || oldSelection[0].definition.id !== newSelection[0].definition.id)) {
358
+ selected = newSelection[0].definition;
359
+ }
360
+ this._update(selected);
361
+ }
362
+ );
363
+ }
364
+ _drawPanel(content) {
365
+ if (!content) {
366
+ return;
367
+ }
368
+ this._drawContainer(content);
369
+ this._update(this._diagramSettings);
370
+ }
371
+ _drawContainer(content) {
372
+ this._container = fromHtml(_PropertiesPanel.HTML_MARKUP);
373
+ content.insertBefore(this._container, content.firstChild);
374
+ this._tabsEl = this._container.querySelector(".tab-sheets");
375
+ this._contentsEl = this._container.querySelector(".pfdjs-pp-contents");
376
+ this._container.addEventListener("click", (event) => {
377
+ const tab = event.target.closest(".tab-sheet");
378
+ if (tab) {
379
+ this._selectTab(tab.getAttribute("data-tab-target"));
380
+ event.stopImmediatePropagation();
381
+ }
382
+ });
383
+ this._registerInputChangeHandlers();
384
+ }
385
+ _registerInputChangeHandlers() {
386
+ const container = this._container;
387
+ if (!container) {
388
+ return;
389
+ }
390
+ const debouncedApply = debounce((target) => this._applyChange(target), 300);
391
+ container.addEventListener("input", (event) => {
392
+ const target = event.target;
393
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
394
+ debouncedApply(target);
395
+ }
396
+ });
397
+ container.addEventListener("change", (event) => {
398
+ const target = event.target;
399
+ if (FORM_CONTROLS.has(target.tagName)) {
400
+ this._applyChange(target);
401
+ }
402
+ });
403
+ this._eventBus.on(
404
+ "PropertiesPanel.propertyChanged",
405
+ (propertyId, definition) => this._applyChangeByProperty(propertyId, definition)
406
+ );
407
+ }
408
+ _applyChange(target) {
409
+ const entryId = target.getAttribute("name");
410
+ if (!entryId) {
411
+ return;
412
+ }
413
+ this._commit(entryId, target.value);
414
+ }
415
+ _applyChangeByProperty(propertyId, definition) {
416
+ this._commit(propertyId, deepGet(definition, propertyId));
417
+ }
418
+ _commit(entryId, newValue) {
419
+ const entry = this._entries[entryId];
420
+ if (!entry) {
421
+ return;
422
+ }
423
+ const after = {};
424
+ deepSet(after, entryId, newValue);
425
+ const before = {};
426
+ deepSet(before, entryId, deepGet(entry.definition, entryId));
427
+ this._commandStack.execute("element.updateProperties", {
428
+ scope: entry.scope,
429
+ definition: entry.definition,
430
+ before,
431
+ after
432
+ });
433
+ }
434
+ _selectTab(tabId) {
435
+ this._tabsEl?.querySelectorAll(".tab-sheet").forEach((tab) => {
436
+ tab.classList.toggle("tab-sheet-active", tab.getAttribute("data-tab-target") === tabId);
437
+ });
438
+ this._contentsEl?.querySelectorAll(".pfdjs-pp-content").forEach((content) => {
439
+ content.classList.toggle("open", content.getAttribute("data-tab-target") === tabId);
440
+ });
441
+ }
442
+ _update(definition) {
443
+ if (this._tabsEl && this._contentsEl) {
444
+ this._tabsEl.replaceChildren();
445
+ this._contentsEl.replaceChildren();
446
+ }
447
+ if (definition) {
448
+ this._drawEntries(definition);
449
+ }
450
+ }
451
+ _drawEntries(definition) {
452
+ if (!this._tabsEl || !this._contentsEl) {
453
+ return;
454
+ }
455
+ const tabs = this._propertiesProvider.getTabs(definition);
456
+ const renderedTabs = [];
457
+ this._entries = {};
458
+ for (const tab of tabs) {
459
+ const content = fromHtml(`<div class="pfdjs-pp-content" data-tab-target="${tab.id}"></div>`);
460
+ let tabHasContent = false;
461
+ for (const group of tab.groups) {
462
+ if (group.entries.length === 0) {
463
+ continue;
464
+ }
465
+ const groupContent = fromHtml(
466
+ `<div class="pfdjs-pp-content-group" data-group-target="${group.id}"><div class="tab-content-group-title">${group.label}</div></div>`
467
+ );
468
+ for (const entry of group.entries) {
469
+ const entryNode = fromHtml(`<div>${entry.html}</div>`);
470
+ groupContent.appendChild(entryNode);
471
+ this._entries[entry.id] = { scope: entry, definition, formNode: entryNode };
472
+ }
473
+ content.appendChild(groupContent);
474
+ tabHasContent = true;
475
+ }
476
+ if (tabHasContent) {
477
+ this._contentsEl.appendChild(content);
478
+ renderedTabs.push({ id: tab.id, label: tab.label });
479
+ }
480
+ }
481
+ Object.values(this._entries).forEach((entry) => entry.scope.get(entry.definition, entry.formNode));
482
+ let firstTab = null;
483
+ for (const tab of renderedTabs) {
484
+ firstTab ?? (firstTab = tab.id);
485
+ this._tabsEl.appendChild(
486
+ fromHtml(
487
+ `<li class="tab-sheet" data-tab-target="${tab.id}"><a href="#">${tab.label}</a></li>`
488
+ )
489
+ );
490
+ }
491
+ this._selectTab(firstTab);
492
+ }
493
+ };
494
+ _PropertiesPanel.$inject = [
495
+ "sideTabsProvider",
496
+ "eventBus",
497
+ "propertiesProvider",
498
+ "d3polytree.definitions.settings",
499
+ "commandStack"
500
+ ];
501
+ _PropertiesPanel.HTML_MARKUP = '<div id="pfdjs-pp-container"><div class="pfdjs-pp-tabs"><ul class="tab-sheets"></ul></div><div class="pfdjs-pp-contents"></div></div>';
502
+ var PropertiesPanel = _PropertiesPanel;
503
+
504
+ // src/properties-panel/index.ts
505
+ var propertiesPanelModule = {
506
+ __init__: ["propertiesPanel"],
507
+ propertiesPanel: ["type", PropertiesPanel]
508
+ };
509
+
510
+ // src/index.ts
31
511
  var INITIAL_DIAGRAM = '<?xml version="1.0" encoding="UTF-8"?><pfdn:diagram xmlns:pfdn="http://pfdn" xmlns="http://pfdn"><settings author="No Author" name="No Name Diagram" status="1"><zoom><offset x="0" y="0" /><scale>1</scale></zoom><grid /></settings><node id="node_1" label="label_1" status="1"><position x="20" y="100" /></node><label id="label_1" fontSize="12" isReadOnly="true" status="1"><position x="33" y="140" /><text>Node 1</text></label></pfdn:diagram>';
32
512
  var _Editor = class _Editor extends import_interactive_viewer.InteractiveViewer {
33
- constructor() {
34
- super(...arguments);
513
+ constructor(options = {}) {
514
+ super(options);
35
515
  /** The document a fresh editor opens with (used by {@link createDiagram}). */
36
516
  this.initialDiagram = INITIAL_DIAGRAM;
517
+ this._onKeydown = (event) => this._handleKeydown(event);
518
+ options.container?.addEventListener("keydown", this._onKeydown);
519
+ }
520
+ _handleKeydown(event) {
521
+ const target = event.target;
522
+ if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA")) {
523
+ return;
524
+ }
525
+ if (!this.getHost()) {
526
+ return;
527
+ }
528
+ const mod = event.ctrlKey || event.metaKey;
529
+ if (!mod) {
530
+ return;
531
+ }
532
+ const key = event.key.toLowerCase();
533
+ if (key === "z" && event.shiftKey) {
534
+ event.preventDefault();
535
+ this.redo();
536
+ } else if (key === "z") {
537
+ event.preventDefault();
538
+ this.undo();
539
+ } else if (key === "y") {
540
+ event.preventDefault();
541
+ this.redo();
542
+ }
543
+ }
544
+ /** Tear down the editor, removing the keyboard binding. */
545
+ destroy() {
546
+ this.options.container?.removeEventListener("keydown", this._onKeydown);
547
+ super.destroy();
37
548
  }
38
549
  /** (Re)open the initial diagram. */
39
550
  createDiagram() {
@@ -46,9 +557,16 @@ var _Editor = class _Editor extends import_interactive_viewer.InteractiveViewer
46
557
  ...import_viewer.Viewer.modules
47
558
  ];
48
559
  }
49
- /** Create a node (and its associated label) at an optional position. */
560
+ /**
561
+ * Create a node (and its associated label) at an optional position.
562
+ *
563
+ * Routed through the command stack so it persists and is undoable (since B10
564
+ * the draw-layer `.created` event no longer persists on its own).
565
+ */
50
566
  createNode(parameters = {}) {
51
- return this.get("modellingNodes").create(parameters);
567
+ const ctx = { className: "node", parameters: [parameters] };
568
+ this.get("commandStack").execute("element.create", ctx);
569
+ return ctx.created;
52
570
  }
53
571
  /** Select an element by definition (e.g. to prepare a delete). */
54
572
  select(definition) {
@@ -61,6 +579,22 @@ var _Editor = class _Editor extends import_interactive_viewer.InteractiveViewer
61
579
  deleteSelected() {
62
580
  this.get("selection").deleteSelected();
63
581
  }
582
+ /** Undo the last edit (a whole gesture is one step). No-op if nothing to undo. */
583
+ undo() {
584
+ this.get("commandStack").undo();
585
+ }
586
+ /** Redo the last undone edit. No-op if nothing to redo. */
587
+ redo() {
588
+ this.get("commandStack").redo();
589
+ }
590
+ /** Whether there is an edit to undo. */
591
+ canUndo() {
592
+ return this.get("commandStack").canUndo();
593
+ }
594
+ /** Whether there is an undone edit to redo. */
595
+ canRedo() {
596
+ return this.get("commandStack").canRedo();
597
+ }
64
598
  };
65
599
  /** Editing modules on top of the interaction layer. */
66
600
  _Editor.editionModules = [
@@ -73,14 +607,20 @@ _Editor.editionModules = [
73
607
  import_core.resizeElementModule,
74
608
  // the properties panel (registers a side tab; side-tabs + search-panel are
75
609
  // inherited from InteractiveViewer)
76
- import_properties_panel.entryFactoryModule,
77
- import_properties_panel.pfdnPropertiesProviderModule,
78
- import_properties_panel.propertiesPanelModule
610
+ entryFactoryModule,
611
+ pfdnPropertiesProviderModule,
612
+ propertiesPanelModule
79
613
  ];
80
614
  var Editor = _Editor;
81
615
  var index_default = Editor;
82
616
  // Annotate the CommonJS export names for ESM import in node:
83
617
  0 && (module.exports = {
84
- Editor
618
+ Editor,
619
+ EntryFactory,
620
+ PfdnPropertiesProvider,
621
+ PropertiesPanel,
622
+ entryFactoryModule,
623
+ pfdnPropertiesProviderModule,
624
+ propertiesPanelModule
85
625
  });
86
626
  //# sourceMappingURL=index.cjs.map