@embedpdf/plugin-ui 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/dist/index.js ADDED
@@ -0,0 +1,845 @@
1
+ // src/lib/ui-plugin.ts
2
+ import { BasePlugin, arePropsEqual } from "@embedpdf/core";
3
+
4
+ // src/lib/ui-component.ts
5
+ var UIComponent = class {
6
+ constructor(componentConfig, registry) {
7
+ this.children = [];
8
+ this.updateCallbacks = [];
9
+ this.hadUpdateBeforeListeners = false;
10
+ this.componentConfig = componentConfig;
11
+ const props = componentConfig.props || {};
12
+ if (typeof props === "function") {
13
+ const initialProps = props(componentConfig.initialState);
14
+ this.props = { ...initialProps, id: componentConfig.id };
15
+ } else {
16
+ this.props = { ...props, id: componentConfig.id };
17
+ }
18
+ this.type = componentConfig.type;
19
+ this.registry = registry;
20
+ }
21
+ addChild(id, child, priority = 0, className) {
22
+ this.children.push({ id, component: child, priority, className });
23
+ this.sortChildren();
24
+ }
25
+ // Helper to sort children by priority
26
+ sortChildren() {
27
+ this.children.sort((a, b) => a.priority - b.priority);
28
+ }
29
+ removeChild(child) {
30
+ this.children = this.children.filter((c) => c.component !== child);
31
+ }
32
+ clearChildren() {
33
+ this.children = [];
34
+ }
35
+ get getRenderType() {
36
+ return this.componentConfig.render || this.type;
37
+ }
38
+ getRenderer() {
39
+ return this.registry[this.getRenderType];
40
+ }
41
+ getChildren() {
42
+ return this.children;
43
+ }
44
+ // Optionally, a component can provide a function to extend the context for its children.
45
+ // For instance, a header could supply a "direction" based on its position.
46
+ getChildContext(context) {
47
+ const childContextProp = this.componentConfig.getChildContext;
48
+ if (typeof childContextProp === "function") {
49
+ return { ...context, ...childContextProp(this.props) };
50
+ } else if (childContextProp && typeof childContextProp === "object") {
51
+ return { ...context, ...childContextProp };
52
+ }
53
+ return context;
54
+ }
55
+ update(newProps) {
56
+ const { id, ...otherProps } = newProps;
57
+ this.props = { ...this.props, ...otherProps };
58
+ if (this.updateCallbacks.length === 0) {
59
+ this.hadUpdateBeforeListeners = true;
60
+ }
61
+ this.notifyUpdate();
62
+ }
63
+ onUpdate(callback) {
64
+ this.updateCallbacks.push(callback);
65
+ return this.hadUpdateBeforeListeners;
66
+ }
67
+ offUpdate(callback) {
68
+ this.updateCallbacks = this.updateCallbacks.filter((cb) => cb !== callback);
69
+ }
70
+ notifyUpdate() {
71
+ this.updateCallbacks.forEach((cb) => cb());
72
+ }
73
+ };
74
+
75
+ // src/lib/actions.ts
76
+ var UI_INIT_COMPONENTS = "UI_INIT_COMPONENTS";
77
+ var UI_SET_HEADER_VISIBLE = "UI_SET_HEADER_VISIBLE";
78
+ var UI_TOGGLE_PANEL = "UI_TOGGLE_PANEL";
79
+ var UI_SHOW_COMMAND_MENU = "UI_SHOW_COMMAND_MENU";
80
+ var UI_HIDE_COMMAND_MENU = "UI_HIDE_COMMAND_MENU";
81
+ var UI_UPDATE_COMPONENT_STATE = "UI_UPDATE_COMPONENT_STATE";
82
+ var uiInitComponents = (state) => ({
83
+ type: UI_INIT_COMPONENTS,
84
+ payload: state
85
+ });
86
+ var uiTogglePanel = (payload) => ({
87
+ type: UI_TOGGLE_PANEL,
88
+ payload
89
+ });
90
+ var uiSetHeaderVisible = (payload) => ({
91
+ type: UI_SET_HEADER_VISIBLE,
92
+ payload
93
+ });
94
+ var uiShowCommandMenu = (payload) => ({
95
+ type: UI_SHOW_COMMAND_MENU,
96
+ payload
97
+ });
98
+ var uiHideCommandMenu = (payload) => ({
99
+ type: UI_HIDE_COMMAND_MENU,
100
+ payload
101
+ });
102
+ var uiUpdateComponentState = (payload) => ({
103
+ type: UI_UPDATE_COMPONENT_STATE,
104
+ payload
105
+ });
106
+
107
+ // src/lib/reducer.ts
108
+ var initialState = {
109
+ panel: {},
110
+ header: {},
111
+ groupedItems: {},
112
+ divider: {},
113
+ iconButton: {},
114
+ tabButton: {},
115
+ selectButton: {},
116
+ custom: {},
117
+ floating: {},
118
+ commandMenu: {}
119
+ };
120
+ var uiReducer = (state = initialState, action) => {
121
+ switch (action.type) {
122
+ case UI_INIT_COMPONENTS:
123
+ return {
124
+ ...state,
125
+ ...action.payload
126
+ };
127
+ case UI_TOGGLE_PANEL: {
128
+ const prevPanel = state.panel[action.payload.id] || {};
129
+ const { open: nextOpen, visibleChild: nextVisibleChild } = action.payload;
130
+ const prevVisibleChild = prevPanel.visibleChild;
131
+ let open = prevPanel.open;
132
+ let visibleChild = prevPanel.visibleChild;
133
+ if (nextVisibleChild === prevVisibleChild) {
134
+ open = nextOpen !== void 0 ? nextOpen : !prevPanel.open;
135
+ } else {
136
+ visibleChild = nextVisibleChild;
137
+ open = true;
138
+ }
139
+ return {
140
+ ...state,
141
+ panel: {
142
+ ...state.panel,
143
+ [action.payload.id]: {
144
+ ...prevPanel,
145
+ open,
146
+ visibleChild
147
+ }
148
+ }
149
+ };
150
+ }
151
+ case UI_SET_HEADER_VISIBLE:
152
+ return {
153
+ ...state,
154
+ header: {
155
+ ...state.header,
156
+ [action.payload.id]: {
157
+ ...state.header[action.payload.id],
158
+ visible: action.payload.visible,
159
+ visibleChild: action.payload.visibleChild
160
+ }
161
+ }
162
+ };
163
+ case UI_SHOW_COMMAND_MENU:
164
+ return {
165
+ ...state,
166
+ commandMenu: {
167
+ ...state.commandMenu,
168
+ [action.payload.id]: {
169
+ activeCommand: action.payload.commandId,
170
+ triggerElement: action.payload.triggerElement,
171
+ position: action.payload.position,
172
+ open: true,
173
+ flatten: action.payload.flatten
174
+ }
175
+ }
176
+ };
177
+ case UI_HIDE_COMMAND_MENU:
178
+ return {
179
+ ...state,
180
+ commandMenu: {
181
+ ...state.commandMenu,
182
+ [action.payload.id]: {
183
+ ...state.commandMenu[action.payload.id],
184
+ open: false,
185
+ activeCommand: null,
186
+ triggerElement: void 0,
187
+ position: void 0,
188
+ flatten: false
189
+ }
190
+ }
191
+ };
192
+ case UI_UPDATE_COMPONENT_STATE: {
193
+ const { componentType, componentId, patch } = action.payload;
194
+ if (!state[componentType] || !state[componentType][componentId]) return state;
195
+ const current = state[componentType][componentId];
196
+ const filteredPatch = Object.fromEntries(Object.entries(patch).filter(([k]) => k in current));
197
+ if (Object.keys(filteredPatch).length === 0) return state;
198
+ return {
199
+ ...state,
200
+ [componentType]: {
201
+ ...state[componentType],
202
+ [componentId]: {
203
+ ...current,
204
+ ...filteredPatch
205
+ }
206
+ }
207
+ };
208
+ }
209
+ default:
210
+ return state;
211
+ }
212
+ };
213
+
214
+ // src/lib/utils.ts
215
+ function defineComponent() {
216
+ return (c) => c;
217
+ }
218
+ function createEventController() {
219
+ const eventMap = /* @__PURE__ */ new Map();
220
+ return {
221
+ emit(eventType, data) {
222
+ const callbacks = eventMap.get(eventType);
223
+ if (callbacks) {
224
+ callbacks.forEach((callback) => callback(data));
225
+ }
226
+ },
227
+ on(eventType, callback) {
228
+ if (!eventMap.has(eventType)) {
229
+ eventMap.set(eventType, /* @__PURE__ */ new Set());
230
+ }
231
+ const callbacks = eventMap.get(eventType);
232
+ callbacks.add(callback);
233
+ return () => this.off(eventType, callback);
234
+ },
235
+ off(eventType, callback) {
236
+ const callbacks = eventMap.get(eventType);
237
+ if (callbacks) {
238
+ callbacks.delete(callback);
239
+ if (callbacks.size === 0) {
240
+ eventMap.delete(eventType);
241
+ }
242
+ }
243
+ }
244
+ };
245
+ }
246
+
247
+ // src/lib/menu/utils.ts
248
+ function resolveMenuItem(item, state) {
249
+ const dyn = (v) => typeof v === "function" ? v(state) : v;
250
+ if (item.type === "group") {
251
+ return {
252
+ ...item,
253
+ label: dyn(item.label) ?? ""
254
+ };
255
+ }
256
+ return {
257
+ ...item,
258
+ icon: dyn(item.icon) ?? "",
259
+ label: dyn(item.label) ?? "",
260
+ visible: dyn(item.visible) ?? true,
261
+ active: dyn(item.active) ?? false,
262
+ disabled: dyn(item.disabled) ?? false
263
+ };
264
+ }
265
+ function isActive(item, state) {
266
+ const resolved = resolveMenuItem(item, state);
267
+ if (resolved.type === "group") {
268
+ return false;
269
+ }
270
+ return resolved.active ? true : false;
271
+ }
272
+ function isVisible(item, state) {
273
+ const resolved = resolveMenuItem(item, state);
274
+ if (resolved.type === "group") {
275
+ return false;
276
+ }
277
+ return resolved.visible ? true : false;
278
+ }
279
+ function isDisabled(item, state) {
280
+ const resolved = resolveMenuItem(item, state);
281
+ if (resolved.type === "group") {
282
+ return false;
283
+ }
284
+ return resolved.disabled ? true : false;
285
+ }
286
+
287
+ // src/lib/menu/menu-manager.ts
288
+ var _MenuManager = class _MenuManager {
289
+ constructor(items = {}, pluginRegistry) {
290
+ this.registry = {};
291
+ this.shortcutMap = {};
292
+ // maps shortcut to menu item id
293
+ this.eventController = createEventController();
294
+ this.pluginRegistry = pluginRegistry;
295
+ this.registerItems(items);
296
+ this.setupKeyboardListeners();
297
+ }
298
+ /**
299
+ * Get the current state of the plugin registry
300
+ */
301
+ get state() {
302
+ return this.pluginRegistry.getStore().getState();
303
+ }
304
+ /**
305
+ * Register a single menu item
306
+ */
307
+ registerItem(item) {
308
+ if (this.registry[item.id]) {
309
+ console.warn(`Menu item with ID ${item.id} already exists and will be overwritten`);
310
+ }
311
+ this.registry[item.id] = item;
312
+ if ("shortcut" in item && item.shortcut) {
313
+ this.shortcutMap[this.normalizeShortcut(item.shortcut)] = item.id;
314
+ }
315
+ }
316
+ /**
317
+ * Register multiple menu items at once
318
+ */
319
+ registerItems(items) {
320
+ Object.values(items).forEach((item) => {
321
+ this.registerItem(item);
322
+ });
323
+ }
324
+ /**
325
+ * Resolve a menu item by ID
326
+ */
327
+ resolve(id) {
328
+ const raw = this.registry[id];
329
+ return resolveMenuItem(raw, this.state);
330
+ }
331
+ /**
332
+ * Get a menu item by ID with type information
333
+ */
334
+ getMenuItem(id) {
335
+ const item = this.resolve(id);
336
+ if (!item) return void 0;
337
+ return {
338
+ item,
339
+ isGroup: item.type === "group",
340
+ isMenu: item.type === "menu",
341
+ isAction: item.type === "action"
342
+ };
343
+ }
344
+ /**
345
+ * Get a action by ID (only returns Action type items)
346
+ */
347
+ getAction(id) {
348
+ const resolved = this.getMenuItem(id);
349
+ if (!resolved || !resolved.isAction) return void 0;
350
+ return resolved.item;
351
+ }
352
+ /**
353
+ * Get menu or action by ID
354
+ */
355
+ getMenuOrAction(id) {
356
+ const resolved = this.getMenuItem(id);
357
+ if (!resolved) return void 0;
358
+ return resolved.item;
359
+ }
360
+ /**
361
+ * Get all registered menu items
362
+ */
363
+ getAllItems() {
364
+ return { ...this.registry };
365
+ }
366
+ /**
367
+ * Get menu items by their IDs
368
+ */
369
+ getItemsByIds(ids) {
370
+ return ids.map((id) => this.resolve(id)).filter((item) => item !== void 0);
371
+ }
372
+ /**
373
+ * Get child items for a given menu ID
374
+ * If flatten is true, it will recursively include submenu children but not groups
375
+ */
376
+ getChildItems(menuId, options = {}) {
377
+ const item = this.resolve(menuId);
378
+ if (!item || !("children" in item) || !item.children?.length) {
379
+ return [];
380
+ }
381
+ const children = this.getItemsByIds(item.children);
382
+ if (!options.flatten) {
383
+ return children;
384
+ }
385
+ const flattened = [];
386
+ for (const child of children) {
387
+ if (child.type === "group") {
388
+ flattened.push(child);
389
+ } else if (child.type === "menu") {
390
+ const menuChildren = this.getChildItems(child.id, { flatten: true });
391
+ flattened.push(...menuChildren);
392
+ } else {
393
+ flattened.push(child);
394
+ }
395
+ }
396
+ return flattened;
397
+ }
398
+ /**
399
+ * Execute a command by ID
400
+ */
401
+ executeCommand(id, options = {}) {
402
+ const resolved = this.getMenuItem(id);
403
+ if (!resolved) {
404
+ console.warn(`Menu item '${id}' not found`);
405
+ return;
406
+ }
407
+ if (resolved.item.type === "group") {
408
+ console.warn(`Cannot execute group '${id}'`);
409
+ return;
410
+ }
411
+ const { item } = resolved;
412
+ if (item.disabled) {
413
+ console.warn(`Menu item '${id}' is disabled`);
414
+ return;
415
+ }
416
+ if (resolved.isAction) {
417
+ item.action(this.pluginRegistry, this.state);
418
+ this.eventController.emit(_MenuManager.EVENTS.COMMAND_EXECUTED, {
419
+ command: item,
420
+ source: options.source || "api"
421
+ });
422
+ } else if ("children" in item && item.children?.length) {
423
+ this.handleSubmenu(item, options);
424
+ }
425
+ }
426
+ /**
427
+ * Execute a command from a keyboard shortcut
428
+ */
429
+ executeShortcut(shortcut) {
430
+ const normalizedShortcut = this.normalizeShortcut(shortcut);
431
+ const itemId = this.shortcutMap[normalizedShortcut];
432
+ if (itemId) {
433
+ this.executeCommand(itemId, { source: "shortcut" });
434
+ this.eventController.emit(_MenuManager.EVENTS.SHORTCUT_EXECUTED, {
435
+ shortcut: normalizedShortcut,
436
+ itemId
437
+ });
438
+ return true;
439
+ }
440
+ return false;
441
+ }
442
+ /**
443
+ * Subscribe to menu events
444
+ */
445
+ on(eventType, callback) {
446
+ return this.eventController.on(eventType, callback);
447
+ }
448
+ /**
449
+ * Remove an event subscription
450
+ */
451
+ off(eventType, callback) {
452
+ this.eventController.off(eventType, callback);
453
+ }
454
+ /**
455
+ * Handle a menu item that has children (showing a submenu)
456
+ */
457
+ handleSubmenu(menuItem, options) {
458
+ this.eventController.emit(_MenuManager.EVENTS.MENU_REQUESTED, {
459
+ menuId: menuItem.id,
460
+ triggerElement: options.triggerElement,
461
+ position: options.position,
462
+ flatten: options.flatten || false
463
+ });
464
+ }
465
+ /**
466
+ * Set up keyboard listeners for shortcuts
467
+ */
468
+ setupKeyboardListeners() {
469
+ if (typeof window === "undefined") return;
470
+ const handleKeyDown = (event) => {
471
+ const target = event.target;
472
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) {
473
+ return;
474
+ }
475
+ const shortcut = this.buildShortcutString(event);
476
+ if (shortcut && this.executeShortcut(shortcut)) {
477
+ event.preventDefault();
478
+ }
479
+ };
480
+ document.addEventListener("keydown", handleKeyDown);
481
+ }
482
+ /**
483
+ * Convert a KeyboardEvent to a shortcut string
484
+ */
485
+ buildShortcutString(event) {
486
+ const modifiers = [];
487
+ if (event.ctrlKey) modifiers.push("Ctrl");
488
+ if (event.shiftKey) modifiers.push("Shift");
489
+ if (event.altKey) modifiers.push("Alt");
490
+ if (event.metaKey) modifiers.push("Meta");
491
+ const key = event.key;
492
+ const isModifier = ["Control", "Shift", "Alt", "Meta"].includes(key);
493
+ if (!isModifier) {
494
+ const displayKey = key.length === 1 ? key.toUpperCase() : key;
495
+ return [...modifiers, displayKey].join("+");
496
+ }
497
+ return null;
498
+ }
499
+ /**
500
+ * Normalize a shortcut string for consistent comparison
501
+ */
502
+ normalizeShortcut(shortcut) {
503
+ return shortcut.split("+").map((part) => part.trim()).join("+");
504
+ }
505
+ /**
506
+ * Get capabilities for the MenuManager
507
+ */
508
+ capabilities() {
509
+ return {
510
+ registerItem: this.registerItem.bind(this),
511
+ registerItems: this.registerItems.bind(this),
512
+ executeCommand: this.executeCommand.bind(this),
513
+ getAction: this.getAction.bind(this),
514
+ getMenuOrAction: this.getMenuOrAction.bind(this),
515
+ getChildItems: this.getChildItems.bind(this),
516
+ getItemsByIds: this.getItemsByIds.bind(this),
517
+ getAllItems: this.getAllItems.bind(this)
518
+ };
519
+ }
520
+ };
521
+ // Event types
522
+ _MenuManager.EVENTS = {
523
+ COMMAND_EXECUTED: "menu:command_executed",
524
+ MENU_REQUESTED: "menu:requested",
525
+ SHORTCUT_EXECUTED: "menu:shortcut_executed"
526
+ };
527
+ var MenuManager = _MenuManager;
528
+
529
+ // src/lib/icons/icon-manager.ts
530
+ var IconManager = class {
531
+ constructor(icons) {
532
+ this.icons = {};
533
+ this.registerIcons(icons);
534
+ }
535
+ /**
536
+ * Register a single icon
537
+ */
538
+ registerIcon(icon) {
539
+ if (this.icons[icon.id]) {
540
+ console.warn(`Icon with ID ${icon.id} already exists and will be overwritten`);
541
+ }
542
+ this.icons[icon.id] = icon;
543
+ }
544
+ /**
545
+ * Register multiple icons at once
546
+ */
547
+ registerIcons(icons) {
548
+ if (Array.isArray(icons)) {
549
+ icons.forEach((icon) => this.registerIcon(icon));
550
+ } else {
551
+ Object.entries(icons).forEach(([id, icon]) => this.registerIcon(icon));
552
+ }
553
+ }
554
+ /**
555
+ * Get all registered icons
556
+ */
557
+ getAllIcons() {
558
+ return { ...this.icons };
559
+ }
560
+ /**
561
+ * Get an icon by its ID
562
+ */
563
+ getIcon(id) {
564
+ return this.icons[id];
565
+ }
566
+ /**
567
+ * Check if an identifier is an SVG string
568
+ */
569
+ isSvgString(identifier) {
570
+ return identifier.trim().startsWith("<svg") && identifier.includes("</svg>");
571
+ }
572
+ /**
573
+ * Check if a string is an SVG data URI
574
+ */
575
+ isSvgDataUri(value) {
576
+ return value.startsWith("data:image/svg+xml;base64,");
577
+ }
578
+ /**
579
+ * Get the SVG string for an icon identifier
580
+ * If the identifier is a raw SVG string, it is returned as is
581
+ * If the identifier is an icon ID, the registered SVG is returned
582
+ */
583
+ getSvgString(identifier) {
584
+ if (this.isSvgString(identifier)) {
585
+ return identifier;
586
+ }
587
+ if (this.isSvgDataUri(identifier)) {
588
+ return this.dataUriToSvgString(identifier);
589
+ }
590
+ return this.getIcon(identifier)?.svg;
591
+ }
592
+ /**
593
+ * Utility method to parse a data URI
594
+ */
595
+ dataUriToSvgString(dataUri) {
596
+ const base64 = dataUri.substring("data:image/svg+xml;base64,".length);
597
+ return atob(base64);
598
+ }
599
+ /**
600
+ * Convert an SVG string to a data URI
601
+ */
602
+ svgStringToDataUri(svgString) {
603
+ const base64 = btoa(svgString);
604
+ return `data:image/svg+xml;base64,${base64}`;
605
+ }
606
+ capabilities() {
607
+ return {
608
+ registerIcon: this.registerIcon.bind(this),
609
+ registerIcons: this.registerIcons.bind(this),
610
+ getIcon: this.getIcon.bind(this),
611
+ getAllIcons: this.getAllIcons.bind(this),
612
+ getSvgString: this.getSvgString.bind(this),
613
+ isSvgString: this.isSvgString.bind(this),
614
+ isSvgDataUri: this.isSvgDataUri.bind(this),
615
+ dataUriToSvgString: this.dataUriToSvgString.bind(this),
616
+ svgStringToDataUri: this.svgStringToDataUri.bind(this)
617
+ };
618
+ }
619
+ };
620
+
621
+ // src/lib/ui-plugin.ts
622
+ var UIPlugin = class extends BasePlugin {
623
+ constructor(id, registry, config) {
624
+ super(id, registry);
625
+ this.componentRenderers = {};
626
+ this.components = {};
627
+ this.mapStateCallbacks = {};
628
+ this.globalStoreSubscription = () => {
629
+ };
630
+ this.config = config;
631
+ this.menuManager = new MenuManager(config.menuItems || {}, this.registry);
632
+ this.iconManager = new IconManager(config.icons || []);
633
+ this.setupCommandEventHandlers();
634
+ this.globalStoreSubscription = this.registry.getStore().subscribe((_action, newState) => {
635
+ this.onGlobalStoreChange(newState);
636
+ });
637
+ }
638
+ async initialize() {
639
+ this.buildComponents();
640
+ this.linkGroupedItems();
641
+ this.setInitialStateUIComponents();
642
+ }
643
+ // Set up handlers for command events
644
+ setupCommandEventHandlers() {
645
+ this.menuManager.on(MenuManager.EVENTS.MENU_REQUESTED, (data) => {
646
+ const { menuId, triggerElement, position, flatten } = data;
647
+ const isOpen = this.state.commandMenu.commandMenu?.activeCommand === menuId;
648
+ if (isOpen) {
649
+ return this.dispatch(uiHideCommandMenu({ id: "commandMenu" }));
650
+ }
651
+ this.dispatch(
652
+ uiShowCommandMenu({
653
+ id: "commandMenu",
654
+ commandId: menuId,
655
+ triggerElement,
656
+ position,
657
+ flatten
658
+ })
659
+ );
660
+ });
661
+ this.menuManager.on(MenuManager.EVENTS.COMMAND_EXECUTED, (data) => {
662
+ console.log("Command executed:", data.command.id, "source:", data.source);
663
+ });
664
+ }
665
+ addComponent(id, componentConfig) {
666
+ if (this.components[id]) {
667
+ console.warn(`Component with ID ${id} already exists and will be overwritten`);
668
+ }
669
+ const component = new UIComponent(componentConfig, this.componentRenderers);
670
+ this.components[id] = component;
671
+ if (typeof componentConfig.mapStateToProps === "function") {
672
+ this.mapStateCallbacks[id] = componentConfig.mapStateToProps;
673
+ }
674
+ return component;
675
+ }
676
+ buildComponents() {
677
+ Object.entries(this.config.components).forEach(([id, componentConfig]) => {
678
+ this.addComponent(id, componentConfig);
679
+ });
680
+ }
681
+ linkGroupedItems() {
682
+ Object.values(this.components).forEach((component) => {
683
+ if (isItemWithSlots(component)) {
684
+ const props = component.componentConfig;
685
+ props.slots?.forEach((slot) => {
686
+ const child = this.components[slot.componentId];
687
+ if (child) {
688
+ component.addChild(slot.componentId, child, slot.priority, slot.className);
689
+ } else {
690
+ console.warn(
691
+ `Child component ${slot.componentId} not found for GroupedItems ${props.id}`
692
+ );
693
+ }
694
+ });
695
+ }
696
+ });
697
+ }
698
+ setInitialStateUIComponents() {
699
+ const defaultState = initialState;
700
+ Object.entries(this.config.components).forEach(([componentId, definition]) => {
701
+ if (definition.initialState) {
702
+ defaultState[definition.type][componentId] = definition.initialState;
703
+ } else {
704
+ defaultState[definition.type][componentId] = {};
705
+ }
706
+ });
707
+ this.dispatch(uiInitComponents(defaultState));
708
+ }
709
+ onGlobalStoreChange(state) {
710
+ for (const [id, uiComponent] of Object.entries(this.components)) {
711
+ const mapFn = this.mapStateCallbacks[id];
712
+ if (!mapFn) continue;
713
+ const { id: _id, ...ownProps } = uiComponent.props;
714
+ const partial = mapFn(state, ownProps);
715
+ const merged = { ...ownProps, ...partial };
716
+ if (!arePropsEqual(ownProps, merged)) {
717
+ uiComponent.update(partial);
718
+ }
719
+ }
720
+ }
721
+ addSlot(parentId, slotId, priority, className) {
722
+ const parentComponent = this.components[parentId];
723
+ if (!parentComponent) {
724
+ console.error(`Parent component ${parentId} not found`);
725
+ return;
726
+ }
727
+ if (!isItemWithSlots(parentComponent)) {
728
+ console.error(`Parent component ${parentId} does not support slots`);
729
+ return;
730
+ }
731
+ const childComponent = this.components[slotId];
732
+ if (!childComponent) {
733
+ console.error(`Child component ${slotId} not found`);
734
+ return;
735
+ }
736
+ const parentChildren = parentComponent.getChildren();
737
+ let slotPriority = priority;
738
+ if (slotPriority === void 0) {
739
+ const maxPriority = parentChildren.length > 0 ? Math.max(...parentChildren.map((child) => child.priority)) : 0;
740
+ slotPriority = maxPriority + 10;
741
+ }
742
+ parentComponent.addChild(slotId, childComponent, slotPriority, className);
743
+ }
744
+ buildCapability() {
745
+ return {
746
+ registerComponentRenderer: (type, renderer) => {
747
+ this.componentRenderers[type] = renderer;
748
+ },
749
+ getComponent: (id) => {
750
+ return this.components[id];
751
+ },
752
+ registerComponent: this.addComponent.bind(this),
753
+ getCommandMenu: () => Object.values(this.components).find((component) => isCommandMenuComponent(component)),
754
+ hideCommandMenu: () => this.debouncedDispatch(uiHideCommandMenu({ id: "commandMenu" }), 100),
755
+ getFloatingComponents: (scrollerPosition) => Object.values(this.components).filter((component) => isFloatingComponent(component)).filter(
756
+ (component) => !scrollerPosition || component.props.scrollerPosition === scrollerPosition
757
+ ),
758
+ getHeadersByPlacement: (placement) => Object.values(this.components).filter((component) => isHeaderComponent(component)).filter((component) => component.props.placement === placement),
759
+ getPanelsByLocation: (location) => Object.values(this.components).filter((component) => isPanelComponent(component)).filter((component) => component.props.location === location),
760
+ addSlot: this.addSlot.bind(this),
761
+ togglePanel: (payload) => {
762
+ this.dispatch(uiTogglePanel(payload));
763
+ },
764
+ setHeaderVisible: (payload) => {
765
+ this.dispatch(uiSetHeaderVisible(payload));
766
+ },
767
+ updateComponentState: (payload) => {
768
+ this.dispatch(uiUpdateComponentState(payload));
769
+ },
770
+ ...this.iconManager.capabilities(),
771
+ ...this.menuManager.capabilities()
772
+ };
773
+ }
774
+ async destroy() {
775
+ this.globalStoreSubscription();
776
+ this.components = {};
777
+ this.componentRenderers = {};
778
+ this.mapStateCallbacks = {};
779
+ }
780
+ };
781
+ UIPlugin.id = "ui";
782
+ function isItemWithSlots(component) {
783
+ return isGroupedItemsComponent(component) || isHeaderComponent(component) || isPanelComponent(component) || isFloatingComponent(component) || isCustomComponent(component);
784
+ }
785
+ function isGroupedItemsComponent(component) {
786
+ return component.type === "groupedItems";
787
+ }
788
+ function isHeaderComponent(component) {
789
+ return component.type === "header";
790
+ }
791
+ function isPanelComponent(component) {
792
+ return component.type === "panel";
793
+ }
794
+ function isFloatingComponent(component) {
795
+ return component.type === "floating";
796
+ }
797
+ function isCommandMenuComponent(component) {
798
+ return component.type === "commandMenu";
799
+ }
800
+ function isCustomComponent(component) {
801
+ return component.type === "custom";
802
+ }
803
+
804
+ // src/lib/manifest.ts
805
+ var UI_PLUGIN_ID = "ui";
806
+ var manifest = {
807
+ id: UI_PLUGIN_ID,
808
+ name: "UI Plugin",
809
+ version: "1.0.0",
810
+ provides: ["ui"],
811
+ requires: [],
812
+ optional: [],
813
+ defaultConfig: {
814
+ enabled: true,
815
+ components: {}
816
+ }
817
+ };
818
+
819
+ // src/lib/menu/types.ts
820
+ function hasActive(command) {
821
+ return "active" in command;
822
+ }
823
+
824
+ // src/lib/index.ts
825
+ var UIPluginPackage = {
826
+ manifest,
827
+ create: (registry, _engine, config) => new UIPlugin(UI_PLUGIN_ID, registry, config),
828
+ reducer: uiReducer,
829
+ initialState
830
+ };
831
+ export {
832
+ UIComponent,
833
+ UIPlugin,
834
+ UIPluginPackage,
835
+ UI_PLUGIN_ID,
836
+ createEventController,
837
+ defineComponent,
838
+ hasActive,
839
+ isActive,
840
+ isDisabled,
841
+ isVisible,
842
+ manifest,
843
+ resolveMenuItem
844
+ };
845
+ //# sourceMappingURL=index.js.map