@embedpdf/plugin-ui 1.5.0 → 2.0.0-next.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.
Files changed (127) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +1313 -655
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/actions.d.ts +110 -74
  6. package/dist/lib/index.d.ts +8 -7
  7. package/dist/lib/reducer.d.ts +5 -5
  8. package/dist/lib/schema.d.ts +257 -0
  9. package/dist/lib/selectors.d.ts +16 -0
  10. package/dist/lib/types.d.ts +185 -202
  11. package/dist/lib/ui-plugin.d.ts +66 -18
  12. package/dist/lib/utils/consts.d.ts +25 -0
  13. package/dist/lib/utils/index.d.ts +5 -0
  14. package/dist/lib/utils/responsive-utils.d.ts +10 -0
  15. package/dist/lib/utils/schema-merger.d.ts +15 -0
  16. package/dist/lib/utils/stylesheet-generator.d.ts +40 -0
  17. package/dist/lib/utils/ui-props.d.ts +14 -0
  18. package/dist/preact/adapter.d.ts +4 -2
  19. package/dist/preact/index.cjs +1 -1
  20. package/dist/preact/index.cjs.map +1 -1
  21. package/dist/preact/index.js +450 -49
  22. package/dist/preact/index.js.map +1 -1
  23. package/dist/preact/utils.d.ts +1 -0
  24. package/dist/react/adapter.d.ts +2 -2
  25. package/dist/react/index.cjs +1 -1
  26. package/dist/react/index.cjs.map +1 -1
  27. package/dist/react/index.js +449 -48
  28. package/dist/react/index.js.map +1 -1
  29. package/dist/react/utils.d.ts +1 -0
  30. package/dist/shared/auto-menu-renderer.d.ts +13 -0
  31. package/dist/shared/hooks/index.d.ts +4 -0
  32. package/dist/shared/hooks/use-item-renderer.d.ts +14 -0
  33. package/dist/shared/hooks/use-register-anchor.d.ts +17 -0
  34. package/dist/shared/hooks/use-schema-renderer.d.ts +61 -0
  35. package/dist/shared/hooks/use-selection-menu.d.ts +11 -0
  36. package/dist/shared/hooks/use-ui.d.ts +13 -5
  37. package/dist/shared/index.d.ts +4 -1
  38. package/dist/shared/provider.d.ts +68 -0
  39. package/dist/shared/registries/anchor-registry.d.ts +16 -0
  40. package/dist/shared/registries/component-registry.d.ts +20 -0
  41. package/dist/shared/registries/index.d.ts +3 -0
  42. package/dist/shared/registries/renderers-registry.d.ts +8 -0
  43. package/dist/shared/root.d.ts +12 -0
  44. package/dist/shared/types.d.ts +67 -0
  45. package/dist/shared-preact/auto-menu-renderer.d.ts +13 -0
  46. package/dist/shared-preact/hooks/index.d.ts +4 -0
  47. package/dist/shared-preact/hooks/use-item-renderer.d.ts +14 -0
  48. package/dist/shared-preact/hooks/use-register-anchor.d.ts +17 -0
  49. package/dist/shared-preact/hooks/use-schema-renderer.d.ts +61 -0
  50. package/dist/shared-preact/hooks/use-selection-menu.d.ts +11 -0
  51. package/dist/shared-preact/hooks/use-ui.d.ts +13 -5
  52. package/dist/shared-preact/index.d.ts +4 -1
  53. package/dist/shared-preact/provider.d.ts +68 -0
  54. package/dist/shared-preact/registries/anchor-registry.d.ts +16 -0
  55. package/dist/shared-preact/registries/component-registry.d.ts +20 -0
  56. package/dist/shared-preact/registries/index.d.ts +3 -0
  57. package/dist/shared-preact/registries/renderers-registry.d.ts +8 -0
  58. package/dist/shared-preact/root.d.ts +12 -0
  59. package/dist/shared-preact/types.d.ts +67 -0
  60. package/dist/shared-react/auto-menu-renderer.d.ts +13 -0
  61. package/dist/shared-react/hooks/index.d.ts +4 -0
  62. package/dist/shared-react/hooks/use-item-renderer.d.ts +14 -0
  63. package/dist/shared-react/hooks/use-register-anchor.d.ts +17 -0
  64. package/dist/shared-react/hooks/use-schema-renderer.d.ts +61 -0
  65. package/dist/shared-react/hooks/use-selection-menu.d.ts +11 -0
  66. package/dist/shared-react/hooks/use-ui.d.ts +13 -5
  67. package/dist/shared-react/index.d.ts +4 -1
  68. package/dist/shared-react/provider.d.ts +68 -0
  69. package/dist/shared-react/registries/anchor-registry.d.ts +16 -0
  70. package/dist/shared-react/registries/component-registry.d.ts +20 -0
  71. package/dist/shared-react/registries/index.d.ts +3 -0
  72. package/dist/shared-react/registries/renderers-registry.d.ts +8 -0
  73. package/dist/shared-react/root.d.ts +12 -0
  74. package/dist/shared-react/types.d.ts +67 -0
  75. package/dist/svelte/auto-menu-renderer.svelte.d.ts +15 -0
  76. package/dist/svelte/hooks/index.d.ts +5 -0
  77. package/dist/svelte/hooks/use-item-renderer.svelte.d.ts +24 -0
  78. package/dist/svelte/hooks/use-register-anchor.svelte.d.ts +18 -0
  79. package/dist/svelte/hooks/use-schema-renderer.svelte.d.ts +78 -0
  80. package/dist/svelte/hooks/use-selection-menu.svelte.d.ts +9 -0
  81. package/dist/svelte/hooks/use-ui.svelte.d.ts +34 -0
  82. package/dist/svelte/index.cjs +2 -0
  83. package/dist/svelte/index.cjs.map +1 -0
  84. package/dist/svelte/index.d.ts +6 -0
  85. package/dist/svelte/index.js +553 -0
  86. package/dist/svelte/index.js.map +1 -0
  87. package/dist/svelte/provider.svelte.d.ts +33 -0
  88. package/dist/svelte/registries/anchor-registry.svelte.d.ts +14 -0
  89. package/dist/svelte/registries/component-registry.svelte.d.ts +17 -0
  90. package/dist/svelte/registries/index.d.ts +3 -0
  91. package/dist/svelte/registries/renderers-registry.svelte.d.ts +3 -0
  92. package/dist/svelte/root.svelte.d.ts +8 -0
  93. package/dist/svelte/types.d.ts +67 -0
  94. package/dist/vue/auto-menu-renderer.vue.d.ts +15 -0
  95. package/dist/vue/hooks/index.d.ts +5 -0
  96. package/dist/vue/hooks/use-item-renderer.d.ts +16 -0
  97. package/dist/vue/hooks/use-register-anchor.d.ts +19 -0
  98. package/dist/vue/hooks/use-schema-renderer.d.ts +63 -0
  99. package/dist/vue/hooks/use-selection-menu.d.ts +28 -0
  100. package/dist/vue/hooks/use-ui.d.ts +940 -0
  101. package/dist/vue/index.cjs +2 -0
  102. package/dist/vue/index.cjs.map +1 -0
  103. package/dist/vue/index.d.ts +6 -0
  104. package/dist/vue/index.js +544 -0
  105. package/dist/vue/index.js.map +1 -0
  106. package/dist/vue/provider.vue.d.ts +43 -0
  107. package/dist/vue/registries/anchor-registry.d.ts +14 -0
  108. package/dist/vue/registries/component-registry.d.ts +17 -0
  109. package/dist/vue/registries/index.d.ts +3 -0
  110. package/dist/vue/registries/renderers-registry.d.ts +3 -0
  111. package/dist/vue/root.vue.d.ts +13 -0
  112. package/dist/vue/types.d.ts +67 -0
  113. package/package.json +32 -9
  114. package/dist/lib/menu/menu-manager.d.ts +0 -98
  115. package/dist/lib/menu/types.d.ts +0 -91
  116. package/dist/lib/menu/utils.d.ts +0 -6
  117. package/dist/lib/ui-component.d.ts +0 -30
  118. package/dist/lib/utils.d.ts +0 -33
  119. package/dist/shared/components/component-wrapper.d.ts +0 -5
  120. package/dist/shared/components/index.d.ts +0 -1
  121. package/dist/shared/components/plugin-ui-provider.d.ts +0 -37
  122. package/dist/shared-preact/components/component-wrapper.d.ts +0 -5
  123. package/dist/shared-preact/components/index.d.ts +0 -1
  124. package/dist/shared-preact/components/plugin-ui-provider.d.ts +0 -37
  125. package/dist/shared-react/components/component-wrapper.d.ts +0 -5
  126. package/dist/shared-react/components/index.d.ts +0 -1
  127. package/dist/shared-react/components/plugin-ui-provider.d.ts +0 -37
package/dist/index.js CHANGED
@@ -1,744 +1,1369 @@
1
- import { BasePlugin, arePropsEqual } from "@embedpdf/core";
2
- class UIComponent {
3
- constructor(componentConfig, registry) {
4
- this.children = [];
5
- this.updateCallbacks = [];
6
- this.hadUpdateBeforeListeners = false;
7
- this.componentConfig = componentConfig;
8
- const props = componentConfig.props || {};
9
- if (typeof props === "function") {
10
- const initialProps = props(componentConfig.initialState);
11
- this.props = { ...initialProps, id: componentConfig.id };
12
- } else {
13
- this.props = { ...props, id: componentConfig.id };
1
+ import { BasePlugin, createBehaviorEmitter, createEmitter, createScopedEmitter } from "@embedpdf/core";
2
+ const UI_PLUGIN_ID = "ui";
3
+ const manifest = {
4
+ id: UI_PLUGIN_ID,
5
+ name: "UI Plugin",
6
+ version: "1.0.0",
7
+ provides: ["ui"],
8
+ requires: ["commands"],
9
+ // Depends on commands
10
+ optional: ["i18n"],
11
+ defaultConfig: {
12
+ enabled: true,
13
+ schema: {
14
+ id: "empty",
15
+ version: "1.0.0",
16
+ toolbars: {},
17
+ menus: {},
18
+ panels: {},
19
+ selectionMenus: {}
14
20
  }
15
- this.type = componentConfig.type;
16
- this.registry = registry;
17
- }
18
- addChild(id, child, priority = 0, className) {
19
- this.children.push({ id, component: child, priority, className });
20
- this.sortChildren();
21
- }
22
- // Helper to sort children by priority
23
- sortChildren() {
24
- this.children.sort((a, b) => a.priority - b.priority);
25
- }
26
- removeChild(child) {
27
- this.children = this.children.filter((c) => c.component !== child);
28
21
  }
29
- clearChildren() {
30
- this.children = [];
31
- }
32
- get getRenderType() {
33
- return this.componentConfig.render || this.type;
34
- }
35
- getRenderer() {
36
- return this.registry[this.getRenderType];
37
- }
38
- getChildren() {
39
- return this.children;
22
+ };
23
+ const INIT_UI_STATE = "UI/INIT_STATE";
24
+ const CLEANUP_UI_STATE = "UI/CLEANUP_STATE";
25
+ const SET_ACTIVE_TOOLBAR = "UI/SET_ACTIVE_TOOLBAR";
26
+ const CLOSE_TOOLBAR_SLOT = "UI/CLOSE_TOOLBAR_SLOT";
27
+ const SET_ACTIVE_PANEL = "UI/SET_ACTIVE_PANEL";
28
+ const CLOSE_PANEL_SLOT = "UI/CLOSE_PANEL_SLOT";
29
+ const SET_PANEL_TAB = "UI/SET_PANEL_TAB";
30
+ const OPEN_MODAL = "UI/OPEN_MODAL";
31
+ const CLOSE_MODAL = "UI/CLOSE_MODAL";
32
+ const OPEN_MENU = "UI/OPEN_MENU";
33
+ const CLOSE_MENU = "UI/CLOSE_MENU";
34
+ const CLOSE_ALL_MENUS = "UI/CLOSE_ALL_MENUS";
35
+ const SET_DISABLED_CATEGORIES = "UI/SET_DISABLED_CATEGORIES";
36
+ const initUIState = (documentId, schema) => ({
37
+ type: INIT_UI_STATE,
38
+ payload: { documentId, schema }
39
+ });
40
+ const cleanupUIState = (documentId) => ({
41
+ type: CLEANUP_UI_STATE,
42
+ payload: { documentId }
43
+ });
44
+ const setActiveToolbar = (documentId, placement, slot, toolbarId) => ({
45
+ type: SET_ACTIVE_TOOLBAR,
46
+ payload: { documentId, placement, slot, toolbarId }
47
+ });
48
+ const closeToolbarSlot = (documentId, placement, slot) => ({
49
+ type: CLOSE_TOOLBAR_SLOT,
50
+ payload: { documentId, placement, slot }
51
+ });
52
+ const setActivePanel = (documentId, placement, slot, panelId, activeTab) => ({
53
+ type: SET_ACTIVE_PANEL,
54
+ payload: { documentId, placement, slot, panelId, activeTab }
55
+ });
56
+ const closePanelSlot = (documentId, placement, slot) => ({
57
+ type: CLOSE_PANEL_SLOT,
58
+ payload: { documentId, placement, slot }
59
+ });
60
+ const setPanelTab = (documentId, panelId, tabId) => ({
61
+ type: SET_PANEL_TAB,
62
+ payload: { documentId, panelId, tabId }
63
+ });
64
+ const openModal = (documentId, modalId) => ({
65
+ type: OPEN_MODAL,
66
+ payload: { documentId, modalId }
67
+ });
68
+ const closeModal = (documentId) => ({
69
+ type: CLOSE_MODAL,
70
+ payload: { documentId }
71
+ });
72
+ const openMenu = (documentId, menuState) => ({
73
+ type: OPEN_MENU,
74
+ payload: { documentId, menuState }
75
+ });
76
+ const closeMenu = (documentId, menuId) => ({
77
+ type: CLOSE_MENU,
78
+ payload: { documentId, menuId }
79
+ });
80
+ const closeAllMenus = (documentId) => ({
81
+ type: CLOSE_ALL_MENUS,
82
+ payload: { documentId }
83
+ });
84
+ const setDisabledCategories = (categories) => ({
85
+ type: SET_DISABLED_CATEGORIES,
86
+ payload: { categories }
87
+ });
88
+ function mergeUISchema(base, override) {
89
+ return {
90
+ ...base,
91
+ ...override,
92
+ toolbars: mergeToolbars(base.toolbars, override.toolbars),
93
+ menus: mergeMenus(base.menus, override.menus),
94
+ panels: mergePanels(base.panels, override.panels)
95
+ };
96
+ }
97
+ function mergeToolbars(base, override) {
98
+ if (!override) return base;
99
+ const result = { ...base };
100
+ for (const [id, toolbar] of Object.entries(override)) {
101
+ if (result[id]) {
102
+ result[id] = {
103
+ ...result[id],
104
+ ...toolbar,
105
+ items: toolbar.items ?? result[id].items,
106
+ responsive: toolbar.responsive ?? result[id].responsive
107
+ };
108
+ } else {
109
+ result[id] = toolbar;
110
+ }
40
111
  }
41
- // Optionally, a component can provide a function to extend the context for its children.
42
- // For instance, a header could supply a "direction" based on its position.
43
- getChildContext(context) {
44
- const childContextProp = this.componentConfig.getChildContext;
45
- if (typeof childContextProp === "function") {
46
- return { ...context, ...childContextProp(this.props) };
47
- } else if (childContextProp && typeof childContextProp === "object") {
48
- return { ...context, ...childContextProp };
112
+ return result;
113
+ }
114
+ function mergeMenus(base, override) {
115
+ if (!override) return base;
116
+ const result = { ...base };
117
+ for (const [id, menu] of Object.entries(override)) {
118
+ if (result[id]) {
119
+ result[id] = {
120
+ ...result[id],
121
+ ...menu,
122
+ items: menu.items ?? result[id].items
123
+ };
124
+ } else {
125
+ result[id] = menu;
49
126
  }
50
- return context;
51
127
  }
52
- update(newProps) {
53
- const { id, ...otherProps } = newProps;
54
- this.props = { ...this.props, ...otherProps };
55
- if (this.updateCallbacks.length === 0) {
56
- this.hadUpdateBeforeListeners = true;
128
+ return result;
129
+ }
130
+ function mergePanels(base, override) {
131
+ if (!override) return base;
132
+ const result = { ...base };
133
+ for (const [id, panel] of Object.entries(override)) {
134
+ if (result[id]) {
135
+ result[id] = {
136
+ ...result[id],
137
+ ...panel,
138
+ content: panel.content ?? result[id].content
139
+ };
140
+ } else {
141
+ result[id] = panel;
57
142
  }
58
- this.notifyUpdate();
59
143
  }
60
- onUpdate(callback) {
61
- this.updateCallbacks.push(callback);
62
- return this.hadUpdateBeforeListeners;
144
+ return result;
145
+ }
146
+ function removeFromSchema(schema, options) {
147
+ const result = { ...schema };
148
+ if (options.toolbars) {
149
+ result.toolbars = { ...result.toolbars };
150
+ options.toolbars.forEach((id) => delete result.toolbars[id]);
63
151
  }
64
- offUpdate(callback) {
65
- this.updateCallbacks = this.updateCallbacks.filter((cb) => cb !== callback);
152
+ if (options.menus) {
153
+ result.menus = { ...result.menus };
154
+ options.menus.forEach((id) => delete result.menus[id]);
66
155
  }
67
- notifyUpdate() {
68
- this.updateCallbacks.forEach((cb) => cb());
156
+ if (options.panels) {
157
+ result.panels = { ...result.panels };
158
+ options.panels.forEach((id) => delete result.panels[id]);
69
159
  }
160
+ if (options.commands) {
161
+ result.toolbars = removeCommandsFromToolbars(result.toolbars, options.commands);
162
+ result.menus = removeCommandsFromMenus(result.menus, options.commands);
163
+ }
164
+ return result;
70
165
  }
71
- const UI_INIT_COMPONENTS = "UI_INIT_COMPONENTS";
72
- const UI_SET_HEADER_VISIBLE = "UI_SET_HEADER_VISIBLE";
73
- const UI_TOGGLE_PANEL = "UI_TOGGLE_PANEL";
74
- const UI_SHOW_COMMAND_MENU = "UI_SHOW_COMMAND_MENU";
75
- const UI_HIDE_COMMAND_MENU = "UI_HIDE_COMMAND_MENU";
76
- const UI_UPDATE_COMPONENT_STATE = "UI_UPDATE_COMPONENT_STATE";
77
- const uiInitComponents = (state) => ({
78
- type: UI_INIT_COMPONENTS,
79
- payload: state
80
- });
81
- const uiTogglePanel = (payload) => ({
82
- type: UI_TOGGLE_PANEL,
83
- payload
84
- });
85
- const uiSetHeaderVisible = (payload) => ({
86
- type: UI_SET_HEADER_VISIBLE,
87
- payload
88
- });
89
- const uiShowCommandMenu = (payload) => ({
90
- type: UI_SHOW_COMMAND_MENU,
91
- payload
92
- });
93
- const uiHideCommandMenu = (payload) => ({
94
- type: UI_HIDE_COMMAND_MENU,
95
- payload
96
- });
97
- const uiUpdateComponentState = (payload) => ({
98
- type: UI_UPDATE_COMPONENT_STATE,
99
- payload
100
- });
101
- const initialState = {
102
- panel: {},
103
- header: {},
104
- groupedItems: {},
105
- divider: {},
106
- iconButton: {},
107
- tabButton: {},
108
- selectButton: {},
109
- custom: {},
110
- floating: {},
111
- commandMenu: {}
112
- };
113
- const uiReducer = (state = initialState, action) => {
114
- switch (action.type) {
115
- case UI_INIT_COMPONENTS:
116
- return {
117
- ...state,
118
- ...action.payload
119
- };
120
- case UI_TOGGLE_PANEL: {
121
- const prevPanel = state.panel[action.payload.id] || {};
122
- const { open: nextOpen, visibleChild: nextVisibleChild } = action.payload;
123
- const prevVisibleChild = prevPanel.visibleChild;
124
- let open = prevPanel.open;
125
- let visibleChild = prevPanel.visibleChild;
126
- if (nextVisibleChild === prevVisibleChild) {
127
- open = nextOpen !== void 0 ? nextOpen : !prevPanel.open;
128
- } else {
129
- visibleChild = nextVisibleChild;
130
- open = true;
131
- }
132
- return {
133
- ...state,
134
- panel: {
135
- ...state.panel,
136
- [action.payload.id]: {
137
- ...prevPanel,
138
- open,
139
- visibleChild
140
- }
166
+ function removeCommandsFromToolbars(toolbars, commandIds) {
167
+ const result = {};
168
+ for (const [id, toolbar] of Object.entries(toolbars)) {
169
+ result[id] = {
170
+ ...toolbar,
171
+ items: toolbar.items.filter((item) => {
172
+ if (item.type === "command-button") {
173
+ return !commandIds.includes(item.commandId);
141
174
  }
142
- };
143
- }
144
- case UI_SET_HEADER_VISIBLE:
145
- return {
146
- ...state,
147
- header: {
148
- ...state.header,
149
- [action.payload.id]: {
150
- ...state.header[action.payload.id],
151
- visible: action.payload.visible,
152
- visibleChild: action.payload.visibleChild
153
- }
175
+ if (item.type === "group") {
176
+ return item.items.some(
177
+ (child) => child.type === "command-button" ? !commandIds.includes(child.commandId) : true
178
+ );
154
179
  }
155
- };
156
- case UI_SHOW_COMMAND_MENU:
157
- return {
158
- ...state,
159
- commandMenu: {
160
- ...state.commandMenu,
161
- [action.payload.id]: {
162
- activeCommand: action.payload.commandId,
163
- triggerElement: action.payload.triggerElement,
164
- position: action.payload.position,
165
- open: true,
166
- flatten: action.payload.flatten
167
- }
180
+ if (item.type === "tab-group") {
181
+ return item.tabs.some((tab) => !commandIds.includes(tab.commandId));
168
182
  }
169
- };
170
- case UI_HIDE_COMMAND_MENU:
171
- return {
172
- ...state,
173
- commandMenu: {
174
- ...state.commandMenu,
175
- [action.payload.id]: {
176
- ...state.commandMenu[action.payload.id],
177
- open: false,
178
- activeCommand: null,
179
- triggerElement: void 0,
180
- position: void 0,
181
- flatten: false
182
- }
183
+ return true;
184
+ })
185
+ };
186
+ }
187
+ return result;
188
+ }
189
+ function removeCommandsFromMenus(menus, commandIds) {
190
+ const result = {};
191
+ for (const [id, menu] of Object.entries(menus)) {
192
+ result[id] = {
193
+ ...menu,
194
+ items: menu.items.filter((item) => {
195
+ if (item.type === "command") {
196
+ return !commandIds.includes(item.commandId);
183
197
  }
184
- };
185
- case UI_UPDATE_COMPONENT_STATE: {
186
- const { componentType, componentId, patch } = action.payload;
187
- if (!state[componentType] || !state[componentType][componentId]) return state;
188
- const current = state[componentType][componentId];
189
- const filteredPatch = Object.fromEntries(Object.entries(patch).filter(([k]) => k in current));
190
- if (Object.keys(filteredPatch).length === 0) return state;
191
- return {
192
- ...state,
193
- [componentType]: {
194
- ...state[componentType],
195
- [componentId]: {
196
- ...current,
197
- ...filteredPatch
198
- }
198
+ if (item.type === "section") {
199
+ return item.items.some(
200
+ (child) => child.type === "command" ? !commandIds.includes(child.commandId) : true
201
+ );
199
202
  }
200
- };
201
- }
202
- default:
203
- return state;
203
+ return true;
204
+ })
205
+ };
204
206
  }
205
- };
206
- function defineComponent() {
207
- return (c) => c;
207
+ return result;
208
208
  }
209
- function createEventController() {
210
- const eventMap = /* @__PURE__ */ new Map();
211
- return {
212
- emit(eventType, data) {
213
- const callbacks = eventMap.get(eventType);
214
- if (callbacks) {
215
- callbacks.forEach((callback) => callback(data));
209
+ function resolveResponsiveMetadata(schema, currentLocale) {
210
+ var _a;
211
+ if (!((_a = schema.responsive) == null ? void 0 : _a.breakpoints)) {
212
+ return null;
213
+ }
214
+ const effectiveBreakpoints = applyLocaleOverrides(
215
+ schema.responsive.breakpoints,
216
+ schema.responsive.localeOverrides,
217
+ currentLocale
218
+ );
219
+ const items = /* @__PURE__ */ new Map();
220
+ const breakpoints = /* @__PURE__ */ new Map();
221
+ for (const [breakpointId, config] of Object.entries(effectiveBreakpoints)) {
222
+ breakpoints.set(breakpointId, {
223
+ minWidth: config.minWidth,
224
+ maxWidth: config.maxWidth
225
+ });
226
+ }
227
+ const allItemIds = /* @__PURE__ */ new Set();
228
+ const collectItemIds = (items2) => {
229
+ items2.forEach((item) => {
230
+ allItemIds.add(item.id);
231
+ if (item.type === "group" && item.items) {
232
+ collectItemIds(item.items);
216
233
  }
217
- },
218
- on(eventType, callback) {
219
- if (!eventMap.has(eventType)) {
220
- eventMap.set(eventType, /* @__PURE__ */ new Set());
234
+ if (item.type === "tab-group" && item.tabs) {
235
+ collectItemIds(item.tabs);
221
236
  }
222
- const callbacks = eventMap.get(eventType);
223
- callbacks.add(callback);
224
- return () => this.off(eventType, callback);
225
- },
226
- off(eventType, callback) {
227
- const callbacks = eventMap.get(eventType);
228
- if (callbacks) {
229
- callbacks.delete(callback);
230
- if (callbacks.size === 0) {
231
- eventMap.delete(eventType);
232
- }
237
+ if (item.type === "section" && item.items) {
238
+ collectItemIds(item.items);
233
239
  }
234
- }
240
+ });
235
241
  };
242
+ collectItemIds(schema.items);
243
+ for (const itemId of allItemIds) {
244
+ const rules = [];
245
+ let defaultVisible = true;
246
+ const sortedBreakpoints = Array.from(Object.entries(effectiveBreakpoints)).sort((a, b) => {
247
+ const aMin = a[1].minWidth ?? 0;
248
+ const bMin = b[1].minWidth ?? 0;
249
+ return aMin - bMin;
250
+ });
251
+ sortedBreakpoints.forEach(([breakpointId, config], index) => {
252
+ var _a2, _b;
253
+ const isHidden = (_a2 = config.hide) == null ? void 0 : _a2.includes(itemId);
254
+ const isShown = (_b = config.show) == null ? void 0 : _b.includes(itemId);
255
+ if (!isHidden && !isShown) {
256
+ return;
257
+ }
258
+ rules.push({
259
+ breakpointId,
260
+ minWidth: config.minWidth,
261
+ maxWidth: config.maxWidth,
262
+ visible: isShown || !isHidden,
263
+ priority: index
264
+ });
265
+ if (index === 0) {
266
+ defaultVisible = isShown || !isHidden;
267
+ }
268
+ });
269
+ if (rules.length > 0) {
270
+ items.set(itemId, {
271
+ itemId,
272
+ shouldRender: true,
273
+ // Always render for SSR
274
+ visibilityRules: rules,
275
+ defaultVisible
276
+ });
277
+ }
278
+ }
279
+ return { items, breakpoints };
236
280
  }
237
- function resolveMenuItem(item, state) {
238
- const dyn = (v) => typeof v === "function" ? v(state) : v;
239
- if (item.type === "group") {
240
- return {
241
- ...item,
242
- label: dyn(item.label) ?? ""
281
+ function applyLocaleOverrides(baseBreakpoints, localeOverrides, currentLocale) {
282
+ if (!currentLocale || !(localeOverrides == null ? void 0 : localeOverrides.groups)) {
283
+ return baseBreakpoints;
284
+ }
285
+ const matchingGroup = localeOverrides.groups.find(
286
+ (group) => group.locales.includes(currentLocale)
287
+ );
288
+ if (!matchingGroup) {
289
+ return baseBreakpoints;
290
+ }
291
+ const effective = {};
292
+ for (const [breakpointId, baseRule] of Object.entries(baseBreakpoints)) {
293
+ const override = matchingGroup.breakpoints[breakpointId];
294
+ if (!override) {
295
+ effective[breakpointId] = baseRule;
296
+ continue;
297
+ }
298
+ effective[breakpointId] = {
299
+ // Width constraints never change!
300
+ minWidth: baseRule.minWidth,
301
+ maxWidth: baseRule.maxWidth,
302
+ // Merge hide lists (base + additional) or use replacement
303
+ hide: override.replaceHide ? override.replaceHide : [...baseRule.hide || [], ...override.hide || []],
304
+ // Merge show lists (base + additional) or use replacement
305
+ show: override.replaceShow ? override.replaceShow : [...baseRule.show || [], ...override.show || []]
243
306
  };
244
307
  }
245
- return {
246
- ...item,
247
- icon: dyn(item.icon) ?? "",
248
- iconProps: dyn(item.iconProps) ?? {},
249
- label: dyn(item.label) ?? "",
250
- visible: dyn(item.visible) ?? true,
251
- active: dyn(item.active) ?? false,
252
- disabled: dyn(item.disabled) ?? false
253
- };
308
+ return effective;
309
+ }
310
+ function getItemResponsiveMetadata(itemId, schema, currentLocale) {
311
+ const metadata = resolveResponsiveMetadata(schema, currentLocale);
312
+ return (metadata == null ? void 0 : metadata.items.get(itemId)) ?? null;
313
+ }
314
+ const UI_ATTRIBUTES = {
315
+ /** Root element marker */
316
+ ROOT: "data-epdf",
317
+ /** Style element marker for deduplication */
318
+ STYLES: "data-epdf-s",
319
+ /** Item ID for responsive and dependency rules */
320
+ ITEM: "data-epdf-i",
321
+ /** Item categories for category-based hiding */
322
+ CATEGORIES: "data-epdf-cat",
323
+ /** Disabled categories list on root element */
324
+ DISABLED_CATEGORIES: "data-epdf-dis"
325
+ };
326
+ const UI_SELECTORS = {
327
+ ROOT: `[${UI_ATTRIBUTES.ROOT}]`,
328
+ STYLES: `[${UI_ATTRIBUTES.STYLES}]`,
329
+ ITEM: (id) => `[${UI_ATTRIBUTES.ITEM}="${id}"]`,
330
+ CATEGORIES: (category) => `[${UI_ATTRIBUTES.CATEGORIES}~="${category}"]`,
331
+ DISABLED_CATEGORY: (category) => `[${UI_ATTRIBUTES.DISABLED_CATEGORIES}~="${category}"]`
332
+ };
333
+ const DEFAULT_CONFIG = {
334
+ useContainerQueries: true
335
+ };
336
+ function generateUIStylesheet(schema, options = {}) {
337
+ const cfg = { ...DEFAULT_CONFIG, ...options.config };
338
+ const locale = options.locale;
339
+ const analysis = analyzeSchema(schema, locale);
340
+ const sections = [];
341
+ sections.push(generateHeader(locale));
342
+ const responsiveCSS = generateResponsiveRules(analysis, cfg);
343
+ if (responsiveCSS) sections.push(responsiveCSS);
344
+ const categoryCSS = generateCategoryRules(analysis);
345
+ if (categoryCSS) sections.push(categoryCSS);
346
+ const dependencyCSS = generateDependencyRules(analysis, cfg);
347
+ if (dependencyCSS) sections.push(dependencyCSS);
348
+ return sections.filter((s) => s.trim()).join("\n\n");
254
349
  }
255
- function isActive(item, state) {
256
- const resolved = resolveMenuItem(item, state);
257
- if (resolved.type === "group") {
258
- return false;
350
+ function extractCategories(schema) {
351
+ const analysis = analyzeSchema(schema);
352
+ return Array.from(analysis.categories).sort();
353
+ }
354
+ function getStylesheetConfig(config = {}) {
355
+ return { ...DEFAULT_CONFIG, ...config };
356
+ }
357
+ function analyzeSchema(schema, locale) {
358
+ const categories = /* @__PURE__ */ new Set();
359
+ const itemCategories = /* @__PURE__ */ new Map();
360
+ const dependencies = [];
361
+ const menuBreakpoints = /* @__PURE__ */ new Map();
362
+ const responsiveItems = /* @__PURE__ */ new Map();
363
+ for (const [menuId, menu] of Object.entries(schema.menus)) {
364
+ analyzeMenu(
365
+ menuId,
366
+ menu,
367
+ categories,
368
+ itemCategories,
369
+ dependencies,
370
+ menuBreakpoints,
371
+ responsiveItems,
372
+ locale
373
+ );
374
+ }
375
+ for (const [toolbarId, toolbar] of Object.entries(schema.toolbars)) {
376
+ analyzeToolbar(
377
+ toolbarId,
378
+ toolbar,
379
+ categories,
380
+ itemCategories,
381
+ dependencies,
382
+ responsiveItems,
383
+ locale
384
+ );
259
385
  }
260
- return resolved.active ? true : false;
386
+ for (const [panelId, panel] of Object.entries(schema.panels)) {
387
+ analyzePanel(panelId, panel, categories, itemCategories, dependencies);
388
+ }
389
+ for (const [selMenuId, selMenu] of Object.entries(schema.selectionMenus || {})) {
390
+ analyzeSelectionMenu(
391
+ selMenuId,
392
+ selMenu,
393
+ categories,
394
+ itemCategories,
395
+ dependencies,
396
+ responsiveItems,
397
+ locale
398
+ );
399
+ }
400
+ return { categories, itemCategories, dependencies, menuBreakpoints, responsiveItems };
261
401
  }
262
- function isVisible(item, state) {
263
- const resolved = resolveMenuItem(item, state);
264
- if (resolved.type === "group") {
265
- return false;
402
+ function analyzeMenu(menuId, menu, categories, itemCategories, dependencies, menuBreakpoints, responsiveItems, locale) {
403
+ collectCategoriesAndDependency(
404
+ menuId,
405
+ menu.categories,
406
+ menu.visibilityDependsOn,
407
+ categories,
408
+ itemCategories,
409
+ dependencies
410
+ );
411
+ analyzeMenuItems(menu.items, categories, itemCategories, dependencies);
412
+ const metadata = resolveResponsiveMetadata(menu, locale);
413
+ if (metadata) {
414
+ metadata.items.forEach((itemMeta, itemId) => {
415
+ responsiveItems.set(itemId, itemMeta);
416
+ });
266
417
  }
267
- return resolved.visible ? true : false;
418
+ const breakpointVisibilities = computeMenuBreakpointVisibilities(menu, itemCategories, locale);
419
+ menuBreakpoints.set(menuId, breakpointVisibilities);
268
420
  }
269
- function isDisabled(item, state) {
270
- const resolved = resolveMenuItem(item, state);
271
- if (resolved.type === "group") {
272
- return false;
421
+ function analyzeMenuItems(items, categories, itemCategories, dependencies) {
422
+ for (const item of items) {
423
+ collectCategoriesAndDependency(
424
+ item.id,
425
+ item.categories,
426
+ item.visibilityDependsOn,
427
+ categories,
428
+ itemCategories,
429
+ dependencies
430
+ );
431
+ if (item.type === "section") {
432
+ analyzeMenuItems(item.items, categories, itemCategories, dependencies);
433
+ }
273
434
  }
274
- return resolved.disabled ? true : false;
275
435
  }
276
- function getIconProps(item, state) {
277
- const resolved = resolveMenuItem(item, state);
278
- if (resolved.type === "group") {
279
- return {};
436
+ function computeMenuBreakpointVisibilities(menu, itemCategories, locale) {
437
+ var _a;
438
+ const breakpointVisibilities = [];
439
+ const metadata = resolveResponsiveMetadata(menu, locale);
440
+ if (((_a = menu.responsive) == null ? void 0 : _a.breakpoints) && metadata) {
441
+ const sortedBreakpoints = Array.from(metadata.breakpoints.entries()).sort(
442
+ (a, b) => (a[1].minWidth ?? 0) - (b[1].minWidth ?? 0)
443
+ );
444
+ for (const [_bpId, bp] of sortedBreakpoints) {
445
+ const visibleItems = computeVisibleItemsAtBreakpoint(metadata, bp);
446
+ const visibleCats = /* @__PURE__ */ new Set();
447
+ for (const itemId of visibleItems) {
448
+ const cats = itemCategories.get(itemId);
449
+ if (cats) cats.forEach((c) => visibleCats.add(c));
450
+ }
451
+ breakpointVisibilities.push({
452
+ minWidth: bp.minWidth,
453
+ maxWidth: bp.maxWidth,
454
+ visibleCategories: visibleCats
455
+ });
456
+ }
457
+ } else {
458
+ const allCats = /* @__PURE__ */ new Set();
459
+ collectAllMenuItemCategories(menu.items, itemCategories, allCats);
460
+ breakpointVisibilities.push({ visibleCategories: allCats });
280
461
  }
281
- return resolved.iconProps ?? {};
462
+ return breakpointVisibilities;
282
463
  }
283
- const _MenuManager = class _MenuManager {
284
- constructor(items = {}, pluginRegistry) {
285
- this.registry = {};
286
- this.shortcutMap = {};
287
- this.eventController = createEventController();
288
- this.pluginRegistry = pluginRegistry;
289
- this.registerItems(items);
290
- this.setupKeyboardListeners();
464
+ function collectAllMenuItemCategories(items, itemCategories, result) {
465
+ for (const item of items) {
466
+ const cats = itemCategories.get(item.id);
467
+ if (cats) cats.forEach((c) => result.add(c));
468
+ if (item.type === "section") {
469
+ collectAllMenuItemCategories(item.items, itemCategories, result);
470
+ }
291
471
  }
292
- /**
293
- * Get the current state of the plugin registry
294
- */
295
- get state() {
296
- return this.pluginRegistry.getStore().getState();
472
+ }
473
+ function computeVisibleItemsAtBreakpoint(metadata, targetBp) {
474
+ const visible = [];
475
+ metadata.items.forEach((itemMeta, itemId) => {
476
+ let isVisible = itemMeta.defaultVisible;
477
+ for (const rule of itemMeta.visibilityRules) {
478
+ const ruleApplies = (rule.minWidth === void 0 || targetBp.minWidth !== void 0 && targetBp.minWidth >= rule.minWidth) && (rule.maxWidth === void 0 || targetBp.maxWidth !== void 0 && targetBp.maxWidth <= rule.maxWidth);
479
+ if (ruleApplies) {
480
+ isVisible = rule.visible;
481
+ }
482
+ }
483
+ if (isVisible) {
484
+ visible.push(itemId);
485
+ }
486
+ });
487
+ return visible;
488
+ }
489
+ function analyzeToolbar(toolbarId, toolbar, categories, itemCategories, dependencies, responsiveItems, locale) {
490
+ collectCategoriesAndDependency(
491
+ toolbarId,
492
+ toolbar.categories,
493
+ toolbar.visibilityDependsOn,
494
+ categories,
495
+ itemCategories,
496
+ dependencies
497
+ );
498
+ const metadata = resolveResponsiveMetadata(toolbar, locale);
499
+ if (metadata) {
500
+ metadata.items.forEach((itemMeta, itemId) => {
501
+ responsiveItems.set(itemId, itemMeta);
502
+ });
297
503
  }
298
- /**
299
- * Register a single menu item
300
- */
301
- registerItem(item) {
302
- if (this.registry[item.id]) {
303
- console.warn(`Menu item with ID ${item.id} already exists and will be overwritten`);
504
+ analyzeToolbarItems(toolbar.items, categories, itemCategories, dependencies);
505
+ }
506
+ function analyzeToolbarItems(items, categories, itemCategories, dependencies) {
507
+ for (const item of items) {
508
+ collectCategoriesAndDependency(
509
+ item.id,
510
+ item.categories,
511
+ item.visibilityDependsOn,
512
+ categories,
513
+ itemCategories,
514
+ dependencies
515
+ );
516
+ if (item.type === "group" && item.items) {
517
+ analyzeToolbarItems(item.items, categories, itemCategories, dependencies);
304
518
  }
305
- this.registry[item.id] = item;
306
- if ("shortcut" in item && item.shortcut) {
307
- this.shortcutMap[this.normalizeShortcut(item.shortcut)] = item.id;
519
+ if (item.type === "tab-group" && item.tabs) {
520
+ analyzeTabItems(item.tabs, categories, itemCategories, dependencies);
308
521
  }
309
522
  }
310
- /**
311
- * Register multiple menu items at once
312
- */
313
- registerItems(items) {
314
- Object.values(items).forEach((item) => {
315
- this.registerItem(item);
316
- });
523
+ }
524
+ function analyzeTabItems(tabs, categories, itemCategories, dependencies) {
525
+ for (const tab of tabs) {
526
+ collectCategoriesAndDependency(
527
+ tab.id,
528
+ tab.categories,
529
+ tab.visibilityDependsOn,
530
+ categories,
531
+ itemCategories,
532
+ dependencies
533
+ );
317
534
  }
318
- /**
319
- * Resolve a menu item by ID
320
- */
321
- resolve(id) {
322
- const raw = this.registry[id];
323
- return resolveMenuItem(raw, this.state);
535
+ }
536
+ function analyzePanel(panelId, panel, categories, itemCategories, dependencies) {
537
+ collectCategoriesAndDependency(
538
+ panelId,
539
+ panel.categories,
540
+ panel.visibilityDependsOn,
541
+ categories,
542
+ itemCategories,
543
+ dependencies
544
+ );
545
+ if (panel.content.type === "tabs") {
546
+ for (const tab of panel.content.tabs) {
547
+ collectCategoriesAndDependency(
548
+ tab.id,
549
+ tab.categories,
550
+ tab.visibilityDependsOn,
551
+ categories,
552
+ itemCategories,
553
+ dependencies
554
+ );
555
+ }
324
556
  }
325
- /**
326
- * Get a menu item by ID with type information
327
- */
328
- getMenuItem(id) {
329
- const item = this.resolve(id);
330
- if (!item) return void 0;
331
- return {
332
- item,
333
- isGroup: item.type === "group",
334
- isMenu: item.type === "menu",
335
- isAction: item.type === "action"
336
- };
557
+ }
558
+ function analyzeSelectionMenu(selMenuId, selMenu, categories, itemCategories, dependencies, responsiveItems, locale) {
559
+ collectCategoriesAndDependency(
560
+ selMenuId,
561
+ selMenu.categories,
562
+ selMenu.visibilityDependsOn,
563
+ categories,
564
+ itemCategories,
565
+ dependencies
566
+ );
567
+ if (selMenu.responsive) {
568
+ const metadata = resolveResponsiveMetadata(selMenu, locale);
569
+ if (metadata) {
570
+ metadata.items.forEach((itemMeta, itemId) => {
571
+ responsiveItems.set(itemId, itemMeta);
572
+ });
573
+ }
337
574
  }
338
- /**
339
- * Get a action by ID (only returns Action type items)
340
- */
341
- getAction(id) {
342
- const resolved = this.getMenuItem(id);
343
- if (!resolved || !resolved.isAction) return void 0;
344
- return resolved.item;
575
+ analyzeSelectionMenuItems(selMenu.items, categories, itemCategories, dependencies);
576
+ }
577
+ function analyzeSelectionMenuItems(items, categories, itemCategories, dependencies) {
578
+ for (const item of items) {
579
+ collectCategoriesAndDependency(
580
+ item.id,
581
+ item.categories,
582
+ item.visibilityDependsOn,
583
+ categories,
584
+ itemCategories,
585
+ dependencies
586
+ );
587
+ if (item.type === "group" && item.items) {
588
+ analyzeSelectionMenuItems(item.items, categories, itemCategories, dependencies);
589
+ }
345
590
  }
346
- /**
347
- * Get menu or action by ID
348
- */
349
- getMenuOrAction(id) {
350
- const resolved = this.getMenuItem(id);
351
- if (!resolved) return void 0;
352
- return resolved.item;
591
+ }
592
+ function collectCategoriesAndDependency(itemId, itemCats, visibilityDep, categories, itemCategories, dependencies) {
593
+ var _a;
594
+ if (itemCats == null ? void 0 : itemCats.length) {
595
+ itemCats.forEach((c) => categories.add(c));
596
+ itemCategories.set(itemId, itemCats);
353
597
  }
354
- /**
355
- * Get all registered menu items
356
- */
357
- getAllItems() {
358
- return { ...this.registry };
598
+ if (visibilityDep && (visibilityDep.menuId || ((_a = visibilityDep.itemIds) == null ? void 0 : _a.length))) {
599
+ dependencies.push({
600
+ itemId,
601
+ dependsOnMenuId: visibilityDep.menuId,
602
+ dependsOnItemIds: visibilityDep.itemIds
603
+ });
359
604
  }
360
- /**
361
- * Get menu items by their IDs
362
- */
363
- getItemsByIds(ids) {
364
- return ids.map((id) => this.resolve(id)).filter((item) => item !== void 0);
605
+ }
606
+ function generateHeader(locale) {
607
+ const localeInfo = locale ? ` (locale: ${locale})` : "";
608
+ return `/* ═══════════════════════════════════════════════════════════════════════════ */
609
+ /* EmbedPDF UI Stylesheet - Auto-generated${localeInfo} */
610
+ /* DO NOT EDIT MANUALLY - This file is generated from your UI schema */
611
+ /* ═══════════════════════════════════════════════════════════════════════════ */`;
612
+ }
613
+ function generateResponsiveRules(analysis, cfg) {
614
+ const rules = [];
615
+ const queryType = cfg.useContainerQueries ? "@container" : "@media";
616
+ const processedItems = /* @__PURE__ */ new Set();
617
+ analysis.responsiveItems.forEach((itemMeta, itemId) => {
618
+ if (processedItems.has(itemId)) return;
619
+ processedItems.add(itemId);
620
+ const itemRules = generateItemResponsiveRules(itemId, itemMeta, queryType);
621
+ if (itemRules) rules.push(itemRules);
622
+ });
623
+ if (rules.length === 0) return "";
624
+ return `/* ─── Responsive Visibility Rules ─── */
625
+ /* Items show/hide based on container width */
626
+
627
+ ${rules.join("\n\n")}`;
628
+ }
629
+ function generateItemResponsiveRules(itemId, metadata, queryType, cfg) {
630
+ if (metadata.visibilityRules.length === 0) return null;
631
+ const rules = [];
632
+ const selector = UI_SELECTORS.ITEM(itemId);
633
+ if (!metadata.defaultVisible) {
634
+ rules.push(`${selector} { display: none; }`);
365
635
  }
366
- /**
367
- * Get child items for a given menu ID
368
- * If flatten is true, it will recursively include submenu children but not groups
369
- */
370
- getChildItems(menuId, options = {}) {
371
- var _a;
372
- const item = this.resolve(menuId);
373
- if (!item || !("children" in item) || !((_a = item.children) == null ? void 0 : _a.length)) {
374
- return [];
375
- }
376
- const children = this.getItemsByIds(item.children);
377
- if (!options.flatten) {
378
- return children;
379
- }
380
- const flattened = [];
381
- for (const child of children) {
382
- if (child.type === "group") {
383
- flattened.push(child);
384
- } else if (child.type === "menu") {
385
- const menuChildren = this.getChildItems(child.id, { flatten: true });
386
- flattened.push(...menuChildren);
387
- } else {
388
- flattened.push(child);
636
+ for (const rule of metadata.visibilityRules) {
637
+ const conditions = [];
638
+ if (rule.minWidth !== void 0) {
639
+ conditions.push(`(min-width: ${rule.minWidth}px)`);
640
+ }
641
+ if (rule.maxWidth !== void 0) {
642
+ conditions.push(`(max-width: ${rule.maxWidth}px)`);
643
+ }
644
+ if (conditions.length > 0) {
645
+ const display = rule.visible ? "flex" : "none";
646
+ rules.push(`${queryType} ${conditions.join(" and ")} {
647
+ ${selector} { display: ${display}; }
648
+ }`);
649
+ }
650
+ }
651
+ return rules.length > 0 ? rules.join("\n") : null;
652
+ }
653
+ function generateCategoryRules(analysis, cfg) {
654
+ if (analysis.categories.size === 0) return "";
655
+ const rules = [];
656
+ const sortedCategories = Array.from(analysis.categories).sort();
657
+ for (const category of sortedCategories) {
658
+ rules.push(
659
+ `${UI_SELECTORS.ROOT}[${UI_ATTRIBUTES.DISABLED_CATEGORIES}~="${category}"] [${UI_ATTRIBUTES.CATEGORIES}~="${category}"] {
660
+ display: none !important;
661
+ }`
662
+ );
663
+ }
664
+ return `/* ─── Category Visibility Rules ─── */
665
+ /* Items hide when ANY of their categories is disabled */
666
+ /* Use: data-disabled-categories="category1 category2" on root element */
667
+
668
+ ${rules.join("\n\n")}`;
669
+ }
670
+ function generateDependencyRules(analysis, cfg) {
671
+ if (analysis.dependencies.length === 0) return "";
672
+ const rules = [];
673
+ const queryType = cfg.useContainerQueries ? "@container" : "@media";
674
+ for (const dep of analysis.dependencies) {
675
+ const depRules = generateSingleDependencyRules(dep, analysis, queryType);
676
+ if (depRules.length > 0) {
677
+ rules.push(...depRules);
678
+ }
679
+ }
680
+ if (rules.length === 0) return "";
681
+ return `/* ─── Dependency Visibility Rules ─── */
682
+ /* Container elements hide when all their dependencies are hidden */
683
+
684
+ ${rules.join("\n\n")}`;
685
+ }
686
+ function generateSingleDependencyRules(dep, analysis, queryType, cfg) {
687
+ var _a;
688
+ const rules = [];
689
+ if (dep.dependsOnMenuId) {
690
+ const breakpoints = analysis.menuBreakpoints.get(dep.dependsOnMenuId);
691
+ if (breakpoints && breakpoints.length > 0) {
692
+ rules.push(`/* "${dep.itemId}" depends on menu "${dep.dependsOnMenuId}" */`);
693
+ for (const bp of breakpoints) {
694
+ if (bp.visibleCategories.size === 0) continue;
695
+ const categorySelectors = Array.from(bp.visibleCategories).sort().map((cat) => UI_SELECTORS.DISABLED_CATEGORY(cat)).join("");
696
+ const cssRule = `${UI_SELECTORS.ROOT}${categorySelectors} ${UI_SELECTORS.ITEM(dep.itemId)} {
697
+ display: none !important;
698
+ }`;
699
+ const conditions = [];
700
+ if (bp.minWidth !== void 0) conditions.push(`(min-width: ${bp.minWidth}px)`);
701
+ if (bp.maxWidth !== void 0) conditions.push(`(max-width: ${bp.maxWidth}px)`);
702
+ if (conditions.length > 0) {
703
+ rules.push(`${queryType} ${conditions.join(" and ")} {
704
+ ${cssRule}
705
+ }`);
706
+ } else {
707
+ rules.push(cssRule);
708
+ }
389
709
  }
390
710
  }
391
- return flattened;
392
711
  }
393
- /**
394
- * Execute a command by ID
395
- */
396
- executeCommand(id, options = {}) {
397
- var _a;
398
- const resolved = this.getMenuItem(id);
399
- if (!resolved) {
400
- console.warn(`Menu item '${id}' not found`);
401
- return;
402
- }
403
- if (resolved.item.type === "group") {
404
- console.warn(`Cannot execute group '${id}'`);
405
- return;
406
- }
407
- const { item } = resolved;
408
- if (item.disabled) {
409
- console.warn(`Menu item '${id}' is disabled`);
410
- return;
411
- }
412
- if (resolved.isAction) {
413
- item.action(this.pluginRegistry, this.state);
414
- this.eventController.emit(_MenuManager.EVENTS.COMMAND_EXECUTED, {
415
- command: item,
416
- source: options.source || "api"
417
- });
418
- } else if ("children" in item && ((_a = item.children) == null ? void 0 : _a.length)) {
419
- this.handleSubmenu(item, options);
712
+ if ((_a = dep.dependsOnItemIds) == null ? void 0 : _a.length) {
713
+ const relevantCategories = /* @__PURE__ */ new Set();
714
+ for (const itemId of dep.dependsOnItemIds) {
715
+ const cats = analysis.itemCategories.get(itemId);
716
+ if (cats) cats.forEach((c) => relevantCategories.add(c));
717
+ }
718
+ if (relevantCategories.size > 0) {
719
+ const categorySelectors = Array.from(relevantCategories).sort().map((cat) => UI_SELECTORS.DISABLED_CATEGORY(cat)).join("");
720
+ rules.push(`/* "${dep.itemId}" depends on items: ${dep.dependsOnItemIds.join(", ")} */
721
+ ${UI_SELECTORS.ROOT}${categorySelectors} ${UI_SELECTORS.ITEM(dep.itemId)} {
722
+ display: none !important;
723
+ }`);
420
724
  }
421
725
  }
422
- /**
423
- * Execute a command from a keyboard shortcut
424
- */
425
- executeShortcut(shortcut) {
426
- const normalizedShortcut = this.normalizeShortcut(shortcut);
427
- const itemId = this.shortcutMap[normalizedShortcut];
428
- if (itemId) {
429
- this.executeCommand(itemId, { source: "shortcut" });
430
- this.eventController.emit(_MenuManager.EVENTS.SHORTCUT_EXECUTED, {
431
- shortcut: normalizedShortcut,
432
- itemId
726
+ return rules;
727
+ }
728
+ function getUIItemProps(item, extra) {
729
+ var _a;
730
+ const props = {
731
+ [UI_ATTRIBUTES.ITEM]: item.id,
732
+ [UI_ATTRIBUTES.CATEGORIES]: ((_a = item.categories) == null ? void 0 : _a.join(" ")) || void 0,
733
+ ...extra
734
+ };
735
+ return props;
736
+ }
737
+ const _UIPlugin = class _UIPlugin extends BasePlugin {
738
+ constructor(id, registry, config) {
739
+ var _a, _b;
740
+ super(id, registry);
741
+ this.cachedStylesheet = null;
742
+ this.cachedLocale = null;
743
+ this.i18n = null;
744
+ this.i18nCleanup = null;
745
+ this.categoryChanged$ = createBehaviorEmitter();
746
+ this.stylesheetInvalidated$ = createEmitter();
747
+ this.toolbarChanged$ = createScopedEmitter((documentId, data) => ({ documentId, ...data }), { cache: false });
748
+ this.panelChanged$ = createScopedEmitter(
749
+ (documentId, data) => ({ documentId, ...data }),
750
+ { cache: false }
751
+ );
752
+ this.modalChanged$ = createScopedEmitter(
753
+ (documentId, data) => ({ documentId, ...data }),
754
+ { cache: false }
755
+ );
756
+ this.menuChanged$ = createScopedEmitter(
757
+ (documentId, data) => ({ documentId, ...data }),
758
+ { cache: false }
759
+ );
760
+ this.schema = config.schema;
761
+ this.stylesheetConfig = config.stylesheetConfig || {};
762
+ if ((_a = config.disabledCategories) == null ? void 0 : _a.length) {
763
+ this.dispatch(setDisabledCategories(config.disabledCategories));
764
+ }
765
+ this.i18n = ((_b = registry.getPlugin("i18n")) == null ? void 0 : _b.provides()) ?? null;
766
+ if (this.i18n) {
767
+ this.i18nCleanup = this.i18n.onLocaleChange(({ currentLocale }) => {
768
+ this.handleLocaleChange(currentLocale);
433
769
  });
434
- return true;
770
+ this.cachedLocale = this.i18n.getLocale();
435
771
  }
436
- return false;
437
772
  }
438
- /**
439
- * Subscribe to menu events
440
- */
441
- on(eventType, callback) {
442
- return this.eventController.on(eventType, callback);
773
+ async initialize() {
774
+ this.logger.info("UIPlugin", "Initialize", "UI plugin initialized");
775
+ }
776
+ async destroy() {
777
+ if (this.i18nCleanup) {
778
+ this.i18nCleanup();
779
+ this.i18nCleanup = null;
780
+ }
781
+ this.toolbarChanged$.clear();
782
+ this.panelChanged$.clear();
783
+ this.modalChanged$.clear();
784
+ this.menuChanged$.clear();
785
+ this.stylesheetInvalidated$.clear();
786
+ super.destroy();
787
+ }
788
+ onDocumentLoadingStarted(documentId) {
789
+ this.dispatch(initUIState(documentId, this.schema));
790
+ }
791
+ onDocumentClosed(documentId) {
792
+ this.dispatch(cleanupUIState(documentId));
793
+ this.toolbarChanged$.clearScope(documentId);
794
+ this.panelChanged$.clearScope(documentId);
795
+ this.modalChanged$.clearScope(documentId);
796
+ this.menuChanged$.clearScope(documentId);
443
797
  }
444
798
  /**
445
- * Remove an event subscription
799
+ * Handle locale changes from i18n plugin.
800
+ * Invalidates stylesheet and emits change event.
446
801
  */
447
- off(eventType, callback) {
448
- this.eventController.off(eventType, callback);
802
+ handleLocaleChange(newLocale) {
803
+ if (this.cachedLocale === newLocale) return;
804
+ this.logger.debug(
805
+ "UIPlugin",
806
+ "LocaleChange",
807
+ `Locale changed: ${this.cachedLocale} -> ${newLocale}`
808
+ );
809
+ this.cachedLocale = newLocale;
810
+ this.invalidateStylesheet();
811
+ this.stylesheetInvalidated$.emit();
449
812
  }
450
813
  /**
451
- * Handle a menu item that has children (showing a submenu)
814
+ * Get the generated CSS stylesheet.
815
+ * Automatically regenerates if locale has changed.
816
+ * This is pure logic - DOM injection is handled by framework layer.
452
817
  */
453
- handleSubmenu(menuItem, options) {
454
- this.eventController.emit(_MenuManager.EVENTS.MENU_REQUESTED, {
455
- menuId: menuItem.id,
456
- triggerElement: options.triggerElement,
457
- position: options.position,
458
- flatten: options.flatten || false
818
+ getStylesheet() {
819
+ var _a;
820
+ const currentLocale = ((_a = this.i18n) == null ? void 0 : _a.getLocale()) ?? null;
821
+ if (this.cachedStylesheet && this.cachedLocale === currentLocale) {
822
+ return this.cachedStylesheet;
823
+ }
824
+ this.cachedStylesheet = generateUIStylesheet(this.schema, {
825
+ config: this.stylesheetConfig,
826
+ locale: currentLocale ?? void 0
459
827
  });
828
+ this.cachedLocale = currentLocale;
829
+ return this.cachedStylesheet;
460
830
  }
461
831
  /**
462
- * Set up keyboard listeners for shortcuts
832
+ * Get the current locale (if i18n is available)
463
833
  */
464
- setupKeyboardListeners() {
465
- if (typeof window === "undefined") return;
466
- const handleKeyDown = (event) => {
467
- const target = event.target;
468
- if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) {
469
- return;
470
- }
471
- const shortcut = this.buildShortcutString(event);
472
- if (shortcut && this.executeShortcut(shortcut)) {
473
- event.preventDefault();
474
- }
475
- };
476
- document.addEventListener("keydown", handleKeyDown);
834
+ getLocale() {
835
+ var _a;
836
+ return ((_a = this.i18n) == null ? void 0 : _a.getLocale()) ?? null;
477
837
  }
478
838
  /**
479
- * Convert a KeyboardEvent to a shortcut string
839
+ * Regenerate stylesheet (call after schema changes)
480
840
  */
481
- buildShortcutString(event) {
482
- const modifiers = [];
483
- if (event.ctrlKey) modifiers.push("Ctrl");
484
- if (event.shiftKey) modifiers.push("Shift");
485
- if (event.altKey) modifiers.push("Alt");
486
- if (event.metaKey) modifiers.push("Meta");
487
- const key = event.key;
488
- const isModifier = ["Control", "Shift", "Alt", "Meta"].includes(key);
489
- if (!isModifier) {
490
- const displayKey = key.length === 1 ? key.toUpperCase() : key;
491
- return [...modifiers, displayKey].join("+");
841
+ invalidateStylesheet() {
842
+ this.cachedStylesheet = null;
843
+ }
844
+ onStylesheetInvalidated(listener) {
845
+ return this.stylesheetInvalidated$.on(listener);
846
+ }
847
+ // ─────────────────────────────────────────────────────────
848
+ // Category Management
849
+ // ─────────────────────────────────────────────────────────
850
+ disableCategoryImpl(category) {
851
+ const current = new Set(this.state.disabledCategories);
852
+ if (!current.has(category)) {
853
+ current.add(category);
854
+ this.dispatch(setDisabledCategories(Array.from(current)));
855
+ this.categoryChanged$.emit({ disabledCategories: Array.from(current) });
492
856
  }
493
- return null;
494
857
  }
495
- /**
496
- * Normalize a shortcut string for consistent comparison
497
- */
498
- normalizeShortcut(shortcut) {
499
- return shortcut.split("+").map((part) => part.trim()).join("+");
858
+ enableCategoryImpl(category) {
859
+ const current = new Set(this.state.disabledCategories);
860
+ if (current.has(category)) {
861
+ current.delete(category);
862
+ this.dispatch(setDisabledCategories(Array.from(current)));
863
+ this.categoryChanged$.emit({ disabledCategories: Array.from(current) });
864
+ }
500
865
  }
501
- /**
502
- * Get capabilities for the MenuManager
503
- */
504
- capabilities() {
866
+ toggleCategoryImpl(category) {
867
+ if (this.state.disabledCategories.includes(category)) {
868
+ this.enableCategoryImpl(category);
869
+ } else {
870
+ this.disableCategoryImpl(category);
871
+ }
872
+ }
873
+ setDisabledCategoriesImpl(categories) {
874
+ this.dispatch(setDisabledCategories(categories));
875
+ this.categoryChanged$.emit({ disabledCategories: categories });
876
+ }
877
+ // ─────────────────────────────────────────────────────────
878
+ // Capability
879
+ // ─────────────────────────────────────────────────────────
880
+ buildCapability() {
505
881
  return {
506
- registerItem: this.registerItem.bind(this),
507
- registerItems: this.registerItems.bind(this),
508
- executeCommand: this.executeCommand.bind(this),
509
- getAction: this.getAction.bind(this),
510
- getMenuOrAction: this.getMenuOrAction.bind(this),
511
- getChildItems: this.getChildItems.bind(this),
512
- getItemsByIds: this.getItemsByIds.bind(this),
513
- getAllItems: this.getAllItems.bind(this)
882
+ // Active document operations
883
+ setActiveToolbar: (placement, slot, toolbarId, documentId) => this.setToolbarForDocument(placement, slot, toolbarId, documentId),
884
+ setActivePanel: (placement, slot, panelId, documentId, activeTab) => this.setPanelForDocument(placement, slot, panelId, documentId, activeTab),
885
+ togglePanel: (placement, slot, panelId, documentId, activeTab) => this.togglePanelForDocument(placement, slot, panelId, documentId, activeTab),
886
+ openModal: (modalId, documentId) => this.openModalForDocument(modalId, documentId),
887
+ openMenu: (menuId, triggeredByCommandId, triggeredByItemId, documentId) => this.openMenuForDocument(menuId, triggeredByCommandId, triggeredByItemId, documentId),
888
+ toggleMenu: (menuId, triggeredByCommandId, triggeredByItemId, documentId) => this.toggleMenuForDocument(menuId, triggeredByCommandId, triggeredByItemId, documentId),
889
+ // Document-scoped operations
890
+ forDocument: (documentId) => this.createUIScope(documentId),
891
+ // Schema
892
+ getSchema: () => this.schema,
893
+ mergeSchema: (partial) => {
894
+ this.schema = mergeUISchema(this.schema, partial);
895
+ },
896
+ // Category management
897
+ disableCategory: (category) => this.disableCategoryImpl(category),
898
+ enableCategory: (category) => this.enableCategoryImpl(category),
899
+ toggleCategory: (category) => this.toggleCategoryImpl(category),
900
+ setDisabledCategories: (categories) => this.setDisabledCategoriesImpl(categories),
901
+ getDisabledCategories: () => this.state.disabledCategories,
902
+ isCategoryDisabled: (category) => this.state.disabledCategories.includes(category),
903
+ // Events
904
+ onToolbarChanged: this.toolbarChanged$.onGlobal,
905
+ onPanelChanged: this.panelChanged$.onGlobal,
906
+ onModalChanged: this.modalChanged$.onGlobal,
907
+ onMenuChanged: this.menuChanged$.onGlobal,
908
+ onCategoryChanged: this.categoryChanged$.on
514
909
  };
515
910
  }
516
- };
517
- _MenuManager.EVENTS = {
518
- COMMAND_EXECUTED: "menu:command_executed",
519
- MENU_REQUESTED: "menu:requested",
520
- SHORTCUT_EXECUTED: "menu:shortcut_executed"
521
- };
522
- let MenuManager = _MenuManager;
523
- const _UIPlugin = class _UIPlugin extends BasePlugin {
524
- // Add this
525
- constructor(id, registry, config) {
526
- super(id, registry);
527
- this.componentRenderers = {};
528
- this.components = {};
529
- this.mapStateCallbacks = {};
530
- this.globalStoreSubscription = () => {
911
+ // ─────────────────────────────────────────────────────────
912
+ // Document Scoping
913
+ // ─────────────────────────────────────────────────────────
914
+ createUIScope(documentId) {
915
+ return {
916
+ // ───── Toolbars ─────
917
+ setActiveToolbar: (placement, slot, toolbarId) => this.setToolbarForDocument(placement, slot, toolbarId, documentId),
918
+ getActiveToolbar: (placement, slot) => this.getToolbarForDocument(placement, slot, documentId),
919
+ closeToolbarSlot: (placement, slot) => this.closeToolbarForDocument(placement, slot, documentId),
920
+ isToolbarOpen: (placement, slot, toolbarId) => this.isToolbarOpenForDocument(placement, slot, toolbarId, documentId),
921
+ // ───── Panels ─────
922
+ setActivePanel: (placement, slot, panelId, activeTab) => this.setPanelForDocument(placement, slot, panelId, documentId, activeTab),
923
+ getActivePanel: (placement, slot) => this.getPanelForDocument(placement, slot, documentId),
924
+ closePanelSlot: (placement, slot) => this.closePanelForDocument(placement, slot, documentId),
925
+ togglePanel: (placement, slot, panelId, activeTab) => this.togglePanelForDocument(placement, slot, panelId, documentId, activeTab),
926
+ isPanelOpen: (placement, slot, panelId) => this.isPanelOpenForDocument(placement, slot, panelId, documentId),
927
+ // ───── Panel tabs ─────
928
+ setPanelTab: (panelId, tabId) => this.setPanelTabForDocument(panelId, tabId, documentId),
929
+ getPanelTab: (panelId) => this.getPanelTabForDocument(panelId, documentId),
930
+ // ───── Modals ─────
931
+ openModal: (modalId) => this.openModalForDocument(modalId, documentId),
932
+ closeModal: () => this.closeModalForDocument(documentId),
933
+ getActiveModal: () => this.getActiveModalForDocument(documentId),
934
+ // ───── Menus ─────
935
+ openMenu: (menuId, triggeredByCommandId, triggeredByItemId) => this.openMenuForDocument(menuId, triggeredByCommandId, triggeredByItemId, documentId),
936
+ closeMenu: (menuId) => this.closeMenuForDocument(menuId, documentId),
937
+ toggleMenu: (menuId, triggeredByCommandId, triggeredByItemId) => this.toggleMenuForDocument(menuId, triggeredByCommandId, triggeredByItemId, documentId),
938
+ closeAllMenus: () => this.closeAllMenusForDocument(documentId),
939
+ isMenuOpen: (menuId) => this.isMenuOpenForDocument(menuId, documentId),
940
+ getOpenMenus: () => this.getOpenMenusForDocument(documentId),
941
+ // ───── Schema & state ─────
942
+ getSchema: () => this.schema,
943
+ getState: () => this.getDocumentStateOrThrow(documentId),
944
+ // ───── Scoped events ─────
945
+ onToolbarChanged: this.toolbarChanged$.forScope(documentId),
946
+ onPanelChanged: this.panelChanged$.forScope(documentId),
947
+ onModalChanged: this.modalChanged$.forScope(documentId),
948
+ onMenuChanged: this.menuChanged$.forScope(documentId)
531
949
  };
532
- this.config = config;
533
- this.menuManager = new MenuManager(config.menuItems || {}, this.registry);
534
- this.setupCommandEventHandlers();
535
- this.globalStoreSubscription = this.registry.getStore().subscribe((_action, newState) => {
536
- this.onGlobalStoreChange(newState);
537
- });
538
950
  }
539
- async initialize() {
540
- this.buildComponents();
541
- this.linkGroupedItems();
542
- this.setInitialStateUIComponents();
543
- }
544
- // Set up handlers for command events
545
- setupCommandEventHandlers() {
546
- this.menuManager.on(MenuManager.EVENTS.MENU_REQUESTED, (data) => {
547
- var _a;
548
- const { menuId, triggerElement, position, flatten } = data;
549
- const isOpen = ((_a = this.state.commandMenu.commandMenu) == null ? void 0 : _a.activeCommand) === menuId;
550
- if (isOpen) {
551
- return this.dispatch(uiHideCommandMenu({ id: "commandMenu" }));
552
- }
553
- this.dispatch(
554
- uiShowCommandMenu({
555
- id: "commandMenu",
556
- commandId: menuId,
557
- triggerElement,
558
- position,
559
- flatten
560
- })
561
- );
562
- });
563
- this.menuManager.on(MenuManager.EVENTS.COMMAND_EXECUTED, (data) => {
564
- this.logger.debug("UIPlugin", "CommandExecuted", `Command executed: ${data.command.id}`, {
565
- commandId: data.command.id,
566
- source: data.source
567
- });
568
- });
951
+ // ─────────────────────────────────────────────────────────
952
+ // State Helpers
953
+ // ─────────────────────────────────────────────────────────
954
+ getDocumentState(documentId) {
955
+ const id = documentId ?? this.getActiveDocumentId();
956
+ return this.state.documents[id] ?? null;
569
957
  }
570
- addComponent(id, componentConfig) {
571
- if (this.components[id]) {
572
- this.logger.warn(
573
- "UIPlugin",
574
- "ComponentAlreadyExists",
575
- `Component with ID ${id} already exists and will be overwritten`
576
- );
577
- }
578
- const component = new UIComponent(componentConfig, this.componentRenderers);
579
- this.components[id] = component;
580
- if (typeof componentConfig.mapStateToProps === "function") {
581
- this.mapStateCallbacks[id] = componentConfig.mapStateToProps;
958
+ getDocumentStateOrThrow(documentId) {
959
+ const state = this.getDocumentState(documentId);
960
+ if (!state) {
961
+ throw new Error(`UI state not found for document: ${documentId ?? "active"}`);
582
962
  }
583
- return component;
963
+ return state;
584
964
  }
585
- buildComponents() {
586
- Object.entries(this.config.components).forEach(([id, componentConfig]) => {
587
- this.addComponent(id, componentConfig);
588
- });
965
+ // ─────────────────────────────────────────────────────────
966
+ // Core Operations - Toolbars
967
+ // ─────────────────────────────────────────────────────────
968
+ setToolbarForDocument(placement, slot, toolbarId, documentId) {
969
+ const id = documentId ?? this.getActiveDocumentId();
970
+ this.dispatch(setActiveToolbar(id, placement, slot, toolbarId));
971
+ this.toolbarChanged$.emit(id, { placement, slot, toolbarId });
589
972
  }
590
- linkGroupedItems() {
591
- Object.values(this.components).forEach((component) => {
592
- var _a;
593
- if (isItemWithSlots(component)) {
594
- const props = component.componentConfig;
595
- (_a = props.slots) == null ? void 0 : _a.forEach((slot) => {
596
- const child = this.components[slot.componentId];
597
- if (child) {
598
- component.addChild(slot.componentId, child, slot.priority, slot.className);
599
- } else {
600
- this.logger.warn(
601
- "UIPlugin",
602
- "ChildComponentNotFound",
603
- `Child component ${slot.componentId} not found for GroupedItems ${props.id}`
604
- );
605
- }
606
- });
607
- }
608
- });
973
+ getToolbarForDocument(placement, slot, documentId) {
974
+ const slotKey = `${placement}-${slot}`;
975
+ const toolbarSlot = this.getDocumentStateOrThrow(documentId).activeToolbars[slotKey];
976
+ return (toolbarSlot == null ? void 0 : toolbarSlot.isOpen) ? toolbarSlot.toolbarId : null;
609
977
  }
610
- setInitialStateUIComponents() {
611
- const defaultState = initialState;
612
- Object.entries(this.config.components).forEach(([componentId, definition]) => {
613
- if (definition.initialState) {
614
- defaultState[definition.type][componentId] = definition.initialState;
615
- } else {
616
- defaultState[definition.type][componentId] = {};
617
- }
618
- });
619
- this.dispatch(uiInitComponents(defaultState));
620
- }
621
- onGlobalStoreChange(state) {
622
- for (const [id, uiComponent] of Object.entries(this.components)) {
623
- const mapFn = this.mapStateCallbacks[id];
624
- if (!mapFn) continue;
625
- const { id: _id, ...ownProps } = uiComponent.props;
626
- const partial = mapFn(state, ownProps);
627
- const merged = { ...ownProps, ...partial };
628
- if (!arePropsEqual(ownProps, merged)) {
629
- uiComponent.update(partial);
630
- }
631
- }
978
+ closeToolbarForDocument(placement, slot, documentId) {
979
+ const id = documentId ?? this.getActiveDocumentId();
980
+ this.dispatch(closeToolbarSlot(id, placement, slot));
981
+ this.toolbarChanged$.emit(id, { placement, slot, toolbarId: "" });
632
982
  }
633
- addSlot(parentId, slotId, priority, className) {
634
- const parentComponent = this.components[parentId];
635
- if (!parentComponent) {
636
- this.logger.error(
637
- "UIPlugin",
638
- "ParentComponentNotFound",
639
- `Parent component ${parentId} not found`
640
- );
641
- return;
642
- }
643
- if (!isItemWithSlots(parentComponent)) {
644
- this.logger.error(
645
- "UIPlugin",
646
- "ParentComponentDoesNotSupportSlots",
647
- `Parent component ${parentId} does not support slots`
648
- );
649
- return;
650
- }
651
- const childComponent = this.components[slotId];
652
- if (!childComponent) {
653
- this.logger.error(
654
- "UIPlugin",
655
- "ChildComponentNotFound",
656
- `Child component ${slotId} not found`
657
- );
658
- return;
983
+ isToolbarOpenForDocument(placement, slot, toolbarId, documentId) {
984
+ const slotKey = `${placement}-${slot}`;
985
+ const toolbarSlot = this.getDocumentStateOrThrow(documentId).activeToolbars[slotKey];
986
+ if (!toolbarSlot || !toolbarSlot.isOpen) return false;
987
+ return toolbarId ? toolbarSlot.toolbarId === toolbarId : true;
988
+ }
989
+ // ─────────────────────────────────────────────────────────
990
+ // Core Operations - Panels
991
+ // ─────────────────────────────────────────────────────────
992
+ setPanelForDocument(placement, slot, panelId, documentId, activeTab) {
993
+ const id = documentId ?? this.getActiveDocumentId();
994
+ this.dispatch(setActivePanel(id, placement, slot, panelId, activeTab));
995
+ this.panelChanged$.emit(id, { placement, slot, panelId });
996
+ }
997
+ getPanelForDocument(placement, slot, documentId) {
998
+ const slotKey = `${placement}-${slot}`;
999
+ const panelSlot = this.getDocumentStateOrThrow(documentId).activePanels[slotKey];
1000
+ return (panelSlot == null ? void 0 : panelSlot.isOpen) ? panelSlot.panelId : null;
1001
+ }
1002
+ closePanelForDocument(placement, slot, documentId) {
1003
+ const id = documentId ?? this.getActiveDocumentId();
1004
+ this.dispatch(closePanelSlot(id, placement, slot));
1005
+ this.panelChanged$.emit(id, { placement, slot, panelId: "" });
1006
+ }
1007
+ togglePanelForDocument(placement, slot, panelId, documentId, activeTab) {
1008
+ const id = documentId ?? this.getActiveDocumentId();
1009
+ const slotKey = `${placement}-${slot}`;
1010
+ const panelSlot = this.getDocumentStateOrThrow(id).activePanels[slotKey];
1011
+ if ((panelSlot == null ? void 0 : panelSlot.panelId) === panelId && (panelSlot == null ? void 0 : panelSlot.isOpen)) {
1012
+ this.dispatch(closePanelSlot(id, placement, slot));
1013
+ this.panelChanged$.emit(id, { placement, slot, panelId: "" });
1014
+ } else {
1015
+ this.dispatch(setActivePanel(id, placement, slot, panelId, activeTab));
1016
+ this.panelChanged$.emit(id, { placement, slot, panelId });
659
1017
  }
660
- const parentChildren = parentComponent.getChildren();
661
- let slotPriority = priority;
662
- if (slotPriority === void 0) {
663
- const maxPriority = parentChildren.length > 0 ? Math.max(...parentChildren.map((child) => child.priority)) : 0;
664
- slotPriority = maxPriority + 10;
1018
+ }
1019
+ isPanelOpenForDocument(placement, slot, panelId, documentId) {
1020
+ const slotKey = `${placement}-${slot}`;
1021
+ const panelSlot = this.getDocumentStateOrThrow(documentId).activePanels[slotKey];
1022
+ if (!panelSlot || !panelSlot.isOpen) return false;
1023
+ return panelId ? panelSlot.panelId === panelId : true;
1024
+ }
1025
+ // ─────────────────────────────────────────────────────────
1026
+ // Core Operations - Panel Tabs
1027
+ // ─────────────────────────────────────────────────────────
1028
+ setPanelTabForDocument(panelId, tabId, documentId) {
1029
+ const id = documentId ?? this.getActiveDocumentId();
1030
+ this.dispatch(setPanelTab(id, panelId, tabId));
1031
+ }
1032
+ getPanelTabForDocument(panelId, documentId) {
1033
+ return this.getDocumentStateOrThrow(documentId).panelTabs[panelId] ?? null;
1034
+ }
1035
+ // ─────────────────────────────────────────────────────────
1036
+ // Core Operations - Modals
1037
+ // ─────────────────────────────────────────────────────────
1038
+ openModalForDocument(modalId, documentId) {
1039
+ const id = documentId ?? this.getActiveDocumentId();
1040
+ this.dispatch(openModal(id, modalId));
1041
+ this.modalChanged$.emit(id, { modalId });
1042
+ }
1043
+ closeModalForDocument(documentId) {
1044
+ const id = documentId ?? this.getActiveDocumentId();
1045
+ this.dispatch(closeModal(id));
1046
+ this.modalChanged$.emit(id, { modalId: null });
1047
+ }
1048
+ getActiveModalForDocument(documentId) {
1049
+ return this.getDocumentStateOrThrow(documentId).activeModal;
1050
+ }
1051
+ // ─────────────────────────────────────────────────────────
1052
+ // Core Operations - Menus
1053
+ // ─────────────────────────────────────────────────────────
1054
+ openMenuForDocument(menuId, triggeredByCommandId, triggeredByItemId, documentId) {
1055
+ const id = documentId ?? this.getActiveDocumentId();
1056
+ this.dispatch(openMenu(id, { menuId, triggeredByCommandId, triggeredByItemId }));
1057
+ this.menuChanged$.emit(id, { menuId, isOpen: true });
1058
+ }
1059
+ closeMenuForDocument(menuId, documentId) {
1060
+ const id = documentId ?? this.getActiveDocumentId();
1061
+ this.dispatch(closeMenu(id, menuId));
1062
+ this.menuChanged$.emit(id, { menuId, isOpen: false });
1063
+ }
1064
+ toggleMenuForDocument(menuId, triggeredByCommandId, triggeredByItemId, documentId) {
1065
+ const id = documentId ?? this.getActiveDocumentId();
1066
+ const isOpen = !!this.getDocumentStateOrThrow(id).openMenus[menuId];
1067
+ if (isOpen) {
1068
+ this.dispatch(closeMenu(id, menuId));
1069
+ this.menuChanged$.emit(id, { menuId, isOpen: false });
1070
+ } else {
1071
+ this.dispatch(openMenu(id, { menuId, triggeredByCommandId, triggeredByItemId }));
1072
+ this.menuChanged$.emit(id, { menuId, isOpen: true });
665
1073
  }
666
- parentComponent.addChild(slotId, childComponent, slotPriority, className);
667
1074
  }
668
- buildCapability() {
669
- return {
670
- registerComponentRenderer: (type, renderer) => {
671
- this.componentRenderers[type] = renderer;
672
- },
673
- getComponent: (id) => {
674
- return this.components[id];
675
- },
676
- registerComponent: this.addComponent.bind(this),
677
- getCommandMenu: () => Object.values(this.components).find((component) => isCommandMenuComponent(component)),
678
- hideCommandMenu: () => this.cooldownDispatch(uiHideCommandMenu({ id: "commandMenu" }), 100),
679
- getFloatingComponents: (scrollerPosition) => Object.values(this.components).filter((component) => isFloatingComponent(component)).filter(
680
- (component) => !scrollerPosition || component.props.scrollerPosition === scrollerPosition
681
- ),
682
- getHeadersByPlacement: (placement) => Object.values(this.components).filter((component) => isHeaderComponent(component)).filter((component) => component.props.placement === placement),
683
- getPanelsByLocation: (location) => Object.values(this.components).filter((component) => isPanelComponent(component)).filter((component) => component.props.location === location),
684
- addSlot: this.addSlot.bind(this),
685
- togglePanel: (payload) => {
686
- this.dispatch(uiTogglePanel(payload));
687
- },
688
- setHeaderVisible: (payload) => {
689
- this.dispatch(uiSetHeaderVisible(payload));
690
- },
691
- updateComponentState: (payload) => {
692
- this.dispatch(uiUpdateComponentState(payload));
693
- },
694
- ...this.menuManager.capabilities()
695
- };
1075
+ closeAllMenusForDocument(documentId) {
1076
+ const id = documentId ?? this.getActiveDocumentId();
1077
+ this.dispatch(closeAllMenus(id));
696
1078
  }
697
- async destroy() {
698
- this.globalStoreSubscription();
699
- this.components = {};
700
- this.componentRenderers = {};
701
- this.mapStateCallbacks = {};
1079
+ isMenuOpenForDocument(menuId, documentId) {
1080
+ return !!this.getDocumentStateOrThrow(documentId).openMenus[menuId];
1081
+ }
1082
+ getOpenMenusForDocument(documentId) {
1083
+ return Object.values(this.getDocumentStateOrThrow(documentId).openMenus);
702
1084
  }
703
1085
  };
704
1086
  _UIPlugin.id = "ui";
705
1087
  let UIPlugin = _UIPlugin;
706
- function isItemWithSlots(component) {
707
- return isGroupedItemsComponent(component) || isHeaderComponent(component) || isPanelComponent(component) || isFloatingComponent(component) || isCustomComponent(component);
708
- }
709
- function isGroupedItemsComponent(component) {
710
- return component.type === "groupedItems";
1088
+ const initialDocumentState = {
1089
+ activeToolbars: {},
1090
+ activePanels: {},
1091
+ activeModal: null,
1092
+ openMenus: {},
1093
+ panelTabs: {}
1094
+ };
1095
+ const initialState = {
1096
+ documents: {},
1097
+ disabledCategories: []
1098
+ };
1099
+ const uiReducer = (state = initialState, action) => {
1100
+ switch (action.type) {
1101
+ case INIT_UI_STATE: {
1102
+ const { documentId, schema } = action.payload;
1103
+ const activeToolbars = {};
1104
+ Object.values(schema.toolbars).forEach((toolbar) => {
1105
+ if (toolbar.permanent && toolbar.position) {
1106
+ const slotKey = `${toolbar.position.placement}-${toolbar.position.slot}`;
1107
+ activeToolbars[slotKey] = {
1108
+ toolbarId: toolbar.id,
1109
+ isOpen: true
1110
+ // Permanent toolbars are always open
1111
+ };
1112
+ }
1113
+ });
1114
+ return {
1115
+ ...state,
1116
+ documents: {
1117
+ ...state.documents,
1118
+ [documentId]: {
1119
+ ...initialDocumentState,
1120
+ activeToolbars
1121
+ // Initialize with permanent toolbars
1122
+ }
1123
+ }
1124
+ };
1125
+ }
1126
+ case CLEANUP_UI_STATE: {
1127
+ const { documentId } = action.payload;
1128
+ const { [documentId]: removed, ...remaining } = state.documents;
1129
+ return {
1130
+ ...state,
1131
+ documents: remaining
1132
+ };
1133
+ }
1134
+ case SET_ACTIVE_TOOLBAR: {
1135
+ const { documentId, placement, slot, toolbarId } = action.payload;
1136
+ const docState = state.documents[documentId] || initialDocumentState;
1137
+ const slotKey = `${placement}-${slot}`;
1138
+ return {
1139
+ ...state,
1140
+ documents: {
1141
+ ...state.documents,
1142
+ [documentId]: {
1143
+ ...docState,
1144
+ activeToolbars: {
1145
+ ...docState.activeToolbars,
1146
+ [slotKey]: {
1147
+ toolbarId,
1148
+ isOpen: true
1149
+ }
1150
+ }
1151
+ }
1152
+ }
1153
+ };
1154
+ }
1155
+ case SET_ACTIVE_PANEL: {
1156
+ const { documentId, placement, slot, panelId, activeTab } = action.payload;
1157
+ const docState = state.documents[documentId] || initialDocumentState;
1158
+ const slotKey = `${placement}-${slot}`;
1159
+ return {
1160
+ ...state,
1161
+ documents: {
1162
+ ...state.documents,
1163
+ [documentId]: {
1164
+ ...docState,
1165
+ activePanels: {
1166
+ ...docState.activePanels,
1167
+ [slotKey]: {
1168
+ panelId,
1169
+ isOpen: true
1170
+ }
1171
+ },
1172
+ ...activeTab && {
1173
+ panelTabs: {
1174
+ ...docState.panelTabs,
1175
+ [panelId]: activeTab
1176
+ }
1177
+ }
1178
+ }
1179
+ }
1180
+ };
1181
+ }
1182
+ case CLOSE_PANEL_SLOT: {
1183
+ const { documentId, placement, slot } = action.payload;
1184
+ const docState = state.documents[documentId];
1185
+ if (!docState) return state;
1186
+ const slotKey = `${placement}-${slot}`;
1187
+ const panelSlot = docState.activePanels[slotKey];
1188
+ if (!panelSlot) return state;
1189
+ return {
1190
+ ...state,
1191
+ documents: {
1192
+ ...state.documents,
1193
+ [documentId]: {
1194
+ ...docState,
1195
+ activePanels: {
1196
+ ...docState.activePanels,
1197
+ [slotKey]: {
1198
+ ...panelSlot,
1199
+ isOpen: false
1200
+ // Keep panel, just close it
1201
+ }
1202
+ }
1203
+ }
1204
+ }
1205
+ };
1206
+ }
1207
+ case CLOSE_TOOLBAR_SLOT: {
1208
+ const { documentId, placement, slot } = action.payload;
1209
+ const docState = state.documents[documentId];
1210
+ if (!docState) return state;
1211
+ const slotKey = `${placement}-${slot}`;
1212
+ const toolbarSlot = docState.activeToolbars[slotKey];
1213
+ if (!toolbarSlot) return state;
1214
+ return {
1215
+ ...state,
1216
+ documents: {
1217
+ ...state.documents,
1218
+ [documentId]: {
1219
+ ...docState,
1220
+ activeToolbars: {
1221
+ ...docState.activeToolbars,
1222
+ [slotKey]: {
1223
+ ...toolbarSlot,
1224
+ isOpen: false
1225
+ // Keep toolbar, just close it
1226
+ }
1227
+ }
1228
+ }
1229
+ }
1230
+ };
1231
+ }
1232
+ case SET_PANEL_TAB: {
1233
+ const { documentId, panelId, tabId } = action.payload;
1234
+ const docState = state.documents[documentId] || initialDocumentState;
1235
+ return {
1236
+ ...state,
1237
+ documents: {
1238
+ ...state.documents,
1239
+ [documentId]: {
1240
+ ...docState,
1241
+ panelTabs: {
1242
+ ...docState.panelTabs,
1243
+ [panelId]: tabId
1244
+ }
1245
+ }
1246
+ }
1247
+ };
1248
+ }
1249
+ case OPEN_MODAL: {
1250
+ const { documentId, modalId } = action.payload;
1251
+ const docState = state.documents[documentId] || initialDocumentState;
1252
+ return {
1253
+ ...state,
1254
+ documents: {
1255
+ ...state.documents,
1256
+ [documentId]: {
1257
+ ...docState,
1258
+ activeModal: modalId,
1259
+ openMenus: {}
1260
+ // Close all menus when opening modal
1261
+ }
1262
+ }
1263
+ };
1264
+ }
1265
+ case CLOSE_MODAL: {
1266
+ const { documentId } = action.payload;
1267
+ const docState = state.documents[documentId];
1268
+ if (!docState) return state;
1269
+ return {
1270
+ ...state,
1271
+ documents: {
1272
+ ...state.documents,
1273
+ [documentId]: {
1274
+ ...docState,
1275
+ activeModal: null
1276
+ }
1277
+ }
1278
+ };
1279
+ }
1280
+ case OPEN_MENU: {
1281
+ const { documentId, menuState } = action.payload;
1282
+ const docState = state.documents[documentId] || initialDocumentState;
1283
+ return {
1284
+ ...state,
1285
+ documents: {
1286
+ ...state.documents,
1287
+ [documentId]: {
1288
+ ...docState,
1289
+ openMenus: {
1290
+ // Close other menus, open this one
1291
+ [menuState.menuId]: menuState
1292
+ }
1293
+ }
1294
+ }
1295
+ };
1296
+ }
1297
+ case CLOSE_MENU: {
1298
+ const { documentId, menuId } = action.payload;
1299
+ const docState = state.documents[documentId];
1300
+ if (!docState) return state;
1301
+ const { [menuId]: removed, ...remainingMenus } = docState.openMenus;
1302
+ return {
1303
+ ...state,
1304
+ documents: {
1305
+ ...state.documents,
1306
+ [documentId]: {
1307
+ ...docState,
1308
+ openMenus: remainingMenus
1309
+ }
1310
+ }
1311
+ };
1312
+ }
1313
+ case CLOSE_ALL_MENUS: {
1314
+ const { documentId } = action.payload;
1315
+ const docState = state.documents[documentId];
1316
+ if (!docState) return state;
1317
+ return {
1318
+ ...state,
1319
+ documents: {
1320
+ ...state.documents,
1321
+ [documentId]: {
1322
+ ...docState,
1323
+ openMenus: {}
1324
+ }
1325
+ }
1326
+ };
1327
+ }
1328
+ case SET_DISABLED_CATEGORIES: {
1329
+ return {
1330
+ ...state,
1331
+ disabledCategories: action.payload.categories
1332
+ };
1333
+ }
1334
+ default:
1335
+ return state;
1336
+ }
1337
+ };
1338
+ function selectUIState(plugins) {
1339
+ return plugins["ui"] ?? null;
711
1340
  }
712
- function isHeaderComponent(component) {
713
- return component.type === "header";
1341
+ function selectUIDocumentState(plugins, documentId) {
1342
+ const ui = selectUIState(plugins);
1343
+ return (ui == null ? void 0 : ui.documents[documentId]) ?? null;
714
1344
  }
715
- function isPanelComponent(component) {
716
- return component.type === "panel";
1345
+ function makeSlotKey(placement, slot) {
1346
+ return `${placement}-${slot}`;
717
1347
  }
718
- function isFloatingComponent(component) {
719
- return component.type === "floating";
1348
+ function selectToolbarSlot(plugins, documentId, placement, slot) {
1349
+ const doc = selectUIDocumentState(plugins, documentId);
1350
+ if (!doc) return null;
1351
+ return doc.activeToolbars[makeSlotKey(placement, slot)] ?? null;
720
1352
  }
721
- function isCommandMenuComponent(component) {
722
- return component.type === "commandMenu";
1353
+ function isToolbarOpen(plugins, documentId, placement, slot, toolbarId) {
1354
+ const slotState = selectToolbarSlot(plugins, documentId, placement, slot);
1355
+ if (!slotState || !slotState.isOpen) return false;
1356
+ return toolbarId ? slotState.toolbarId === toolbarId : true;
723
1357
  }
724
- function isCustomComponent(component) {
725
- return component.type === "custom";
1358
+ function selectPanelSlot(plugins, documentId, placement, slot) {
1359
+ const doc = selectUIDocumentState(plugins, documentId);
1360
+ if (!doc) return null;
1361
+ return doc.activePanels[makeSlotKey(placement, slot)] ?? null;
726
1362
  }
727
- const UI_PLUGIN_ID = "ui";
728
- const manifest = {
729
- id: UI_PLUGIN_ID,
730
- name: "UI Plugin",
731
- version: "1.0.0",
732
- provides: ["ui"],
733
- requires: [],
734
- optional: [],
735
- defaultConfig: {
736
- enabled: true,
737
- components: {}
738
- }
739
- };
740
- function hasActive(command) {
741
- return "active" in command;
1363
+ function isPanelOpen(plugins, documentId, placement, slot, panelId) {
1364
+ const slotState = selectPanelSlot(plugins, documentId, placement, slot);
1365
+ if (!slotState || !slotState.isOpen) return false;
1366
+ return panelId ? slotState.panelId === panelId : true;
742
1367
  }
743
1368
  const UIPluginPackage = {
744
1369
  manifest,
@@ -747,18 +1372,51 @@ const UIPluginPackage = {
747
1372
  initialState
748
1373
  };
749
1374
  export {
750
- UIComponent,
1375
+ CLEANUP_UI_STATE,
1376
+ CLOSE_ALL_MENUS,
1377
+ CLOSE_MENU,
1378
+ CLOSE_MODAL,
1379
+ CLOSE_PANEL_SLOT,
1380
+ CLOSE_TOOLBAR_SLOT,
1381
+ INIT_UI_STATE,
1382
+ OPEN_MENU,
1383
+ OPEN_MODAL,
1384
+ SET_ACTIVE_PANEL,
1385
+ SET_ACTIVE_TOOLBAR,
1386
+ SET_DISABLED_CATEGORIES,
1387
+ SET_PANEL_TAB,
751
1388
  UIPlugin,
752
1389
  UIPluginPackage,
1390
+ UI_ATTRIBUTES,
753
1391
  UI_PLUGIN_ID,
754
- createEventController,
755
- defineComponent,
756
- getIconProps,
757
- hasActive,
758
- isActive,
759
- isDisabled,
760
- isVisible,
1392
+ UI_SELECTORS,
1393
+ cleanupUIState,
1394
+ closeAllMenus,
1395
+ closeMenu,
1396
+ closeModal,
1397
+ closePanelSlot,
1398
+ closeToolbarSlot,
1399
+ extractCategories,
1400
+ generateUIStylesheet,
1401
+ getItemResponsiveMetadata,
1402
+ getStylesheetConfig,
1403
+ getUIItemProps,
1404
+ initUIState,
1405
+ isPanelOpen,
1406
+ isToolbarOpen,
761
1407
  manifest,
762
- resolveMenuItem
1408
+ mergeUISchema,
1409
+ openMenu,
1410
+ openModal,
1411
+ removeFromSchema,
1412
+ resolveResponsiveMetadata,
1413
+ selectPanelSlot,
1414
+ selectToolbarSlot,
1415
+ selectUIDocumentState,
1416
+ selectUIState,
1417
+ setActivePanel,
1418
+ setActiveToolbar,
1419
+ setDisabledCategories,
1420
+ setPanelTab
763
1421
  };
764
1422
  //# sourceMappingURL=index.js.map