pith-ui 0.1.0 → 0.1.4

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 (124) hide show
  1. package/AI.md +20 -1
  2. package/API.md +834 -272
  3. package/README.md +3 -1
  4. package/dist/composer.d.ts.map +1 -1
  5. package/dist/composer.js +48 -29
  6. package/dist/composer.js.map +1 -1
  7. package/dist/content/conversation-reader.d.ts.map +1 -1
  8. package/dist/content/conversation-reader.js +8 -1
  9. package/dist/content/conversation-reader.js.map +1 -1
  10. package/dist/content/conversation-view.d.ts +21 -4
  11. package/dist/content/conversation-view.d.ts.map +1 -1
  12. package/dist/content/conversation-view.js +72 -13
  13. package/dist/content/conversation-view.js.map +1 -1
  14. package/dist/content/image-viewer.d.ts +4 -2
  15. package/dist/content/image-viewer.d.ts.map +1 -1
  16. package/dist/content/image-viewer.js +36 -15
  17. package/dist/content/image-viewer.js.map +1 -1
  18. package/dist/content/paged-preview.d.ts +1 -1
  19. package/dist/content/paged-preview.d.ts.map +1 -1
  20. package/dist/content/paged-preview.js +10 -5
  21. package/dist/content/paged-preview.js.map +1 -1
  22. package/dist/content/views.d.ts +3 -0
  23. package/dist/content/views.d.ts.map +1 -1
  24. package/dist/content/views.js +110 -41
  25. package/dist/content/views.js.map +1 -1
  26. package/dist/content/welcome.d.ts.map +1 -1
  27. package/dist/content/welcome.js +19 -7
  28. package/dist/content/welcome.js.map +1 -1
  29. package/dist/dom.js +1 -1
  30. package/dist/dom.js.map +1 -1
  31. package/dist/files/panels.d.ts +8 -1
  32. package/dist/files/panels.d.ts.map +1 -1
  33. package/dist/files/panels.js +235 -120
  34. package/dist/files/panels.js.map +1 -1
  35. package/dist/files/systems.d.ts.map +1 -1
  36. package/dist/files/systems.js +81 -91
  37. package/dist/files/systems.js.map +1 -1
  38. package/dist/forms/actions.d.ts +28 -7
  39. package/dist/forms/actions.d.ts.map +1 -1
  40. package/dist/forms/actions.js +59 -11
  41. package/dist/forms/actions.js.map +1 -1
  42. package/dist/forms/custom-connection.d.ts +2 -1
  43. package/dist/forms/custom-connection.d.ts.map +1 -1
  44. package/dist/forms/custom-connection.js +36 -28
  45. package/dist/forms/custom-connection.js.map +1 -1
  46. package/dist/forms/dialog-frame.d.ts +2 -0
  47. package/dist/forms/dialog-frame.d.ts.map +1 -1
  48. package/dist/forms/dialog-frame.js +46 -26
  49. package/dist/forms/dialog-frame.js.map +1 -1
  50. package/dist/forms/history.d.ts +1 -1
  51. package/dist/forms/history.d.ts.map +1 -1
  52. package/dist/forms/history.js +78 -15
  53. package/dist/forms/history.js.map +1 -1
  54. package/dist/forms/identity.d.ts +12 -0
  55. package/dist/forms/identity.d.ts.map +1 -1
  56. package/dist/forms/identity.js +16 -0
  57. package/dist/forms/identity.js.map +1 -1
  58. package/dist/forms/model-editor.d.ts +8 -3
  59. package/dist/forms/model-editor.d.ts.map +1 -1
  60. package/dist/forms/model-editor.js +73 -62
  61. package/dist/forms/model-editor.js.map +1 -1
  62. package/dist/forms/panels.d.ts +103 -7
  63. package/dist/forms/panels.d.ts.map +1 -1
  64. package/dist/forms/panels.js +183 -77
  65. package/dist/forms/panels.js.map +1 -1
  66. package/dist/forms/provider-login.d.ts.map +1 -1
  67. package/dist/forms/provider-login.js +25 -39
  68. package/dist/forms/provider-login.js.map +1 -1
  69. package/dist/forms/resource-editor.d.ts +9 -5
  70. package/dist/forms/resource-editor.d.ts.map +1 -1
  71. package/dist/forms/resource-editor.js +20 -9
  72. package/dist/forms/resource-editor.js.map +1 -1
  73. package/dist/foundations.d.ts +95 -0
  74. package/dist/foundations.d.ts.map +1 -0
  75. package/dist/foundations.js +299 -0
  76. package/dist/foundations.js.map +1 -0
  77. package/dist/navigation/context-menu.d.ts +8 -1
  78. package/dist/navigation/context-menu.d.ts.map +1 -1
  79. package/dist/navigation/context-menu.js +67 -50
  80. package/dist/navigation/context-menu.js.map +1 -1
  81. package/dist/navigation/navigation.d.ts +55 -7
  82. package/dist/navigation/navigation.d.ts.map +1 -1
  83. package/dist/navigation/navigation.js +167 -18
  84. package/dist/navigation/navigation.js.map +1 -1
  85. package/dist/operation.d.ts +12 -0
  86. package/dist/operation.d.ts.map +1 -0
  87. package/dist/operation.js +49 -0
  88. package/dist/operation.js.map +1 -0
  89. package/dist/primitives.d.ts +11 -1
  90. package/dist/primitives.d.ts.map +1 -1
  91. package/dist/primitives.js +164 -301
  92. package/dist/primitives.js.map +1 -1
  93. package/dist/reconcile.d.ts +11 -0
  94. package/dist/reconcile.d.ts.map +1 -0
  95. package/dist/reconcile.js +36 -0
  96. package/dist/reconcile.js.map +1 -0
  97. package/dist/select.d.ts +14 -0
  98. package/dist/select.d.ts.map +1 -0
  99. package/dist/select.js +304 -0
  100. package/dist/select.js.map +1 -0
  101. package/dist/styles.css +452 -382
  102. package/dist/surface.d.ts +60 -0
  103. package/dist/surface.d.ts.map +1 -0
  104. package/dist/surface.js +344 -0
  105. package/dist/surface.js.map +1 -0
  106. package/dist/tasks/approval.d.ts.map +1 -1
  107. package/dist/tasks/approval.js +86 -45
  108. package/dist/tasks/approval.js.map +1 -1
  109. package/dist/tasks/permission-element.d.ts +3 -0
  110. package/dist/tasks/permission-element.d.ts.map +1 -0
  111. package/dist/tasks/permission-element.js +10 -0
  112. package/dist/tasks/permission-element.js.map +1 -0
  113. package/dist/tasks/permission.d.ts +5 -3
  114. package/dist/tasks/permission.d.ts.map +1 -1
  115. package/dist/tasks/permission.js +47 -22
  116. package/dist/tasks/permission.js.map +1 -1
  117. package/dist/tasks/runtime.d.ts.map +1 -1
  118. package/dist/tasks/runtime.js +30 -13
  119. package/dist/tasks/runtime.js.map +1 -1
  120. package/dist/workspace.d.ts +58 -4
  121. package/dist/workspace.d.ts.map +1 -1
  122. package/dist/workspace.js +202 -44
  123. package/dist/workspace.js.map +1 -1
  124. package/package.json +1 -1
@@ -0,0 +1,95 @@
1
+ import type { Density, Intent, UIAction } from './core.js';
2
+ /** Private controllers shared by factories and the retained product templates.
3
+ * Retained nodes keep their identity; single selects get one owned wrapper.
4
+ */
5
+ export interface ActionState {
6
+ label?: string;
7
+ intent?: Intent;
8
+ density?: Density;
9
+ disabled?: boolean;
10
+ pending?: boolean;
11
+ icon?: string;
12
+ title?: string;
13
+ appearance?: 'standard' | 'secondary';
14
+ }
15
+ export interface ActionController {
16
+ readonly element: HTMLButtonElement;
17
+ update(state: Readonly<ActionState>): void;
18
+ destroy(): void;
19
+ }
20
+ export declare function adoptAction(node: HTMLButtonElement, options?: {
21
+ caption?: HTMLElement;
22
+ onAction?: UIAction;
23
+ onError?: (message: string) => void;
24
+ }): ActionController;
25
+ export type NativeControl = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
26
+ export interface NativeSelectOption {
27
+ value: string;
28
+ label: string;
29
+ disabled?: boolean;
30
+ }
31
+ /** Reconcile native options by their stable value without replacing the select.
32
+ * Callers decide the fallback when a preferred value has disappeared.
33
+ */
34
+ export declare function reconcileSelectOptions(control: HTMLSelectElement, entries: readonly NativeSelectOption[], preferred?: string): void;
35
+ export interface NativeControlState {
36
+ label?: string;
37
+ description?: string;
38
+ error?: string;
39
+ disabled?: boolean;
40
+ required?: boolean;
41
+ readOnly?: boolean;
42
+ placeholder?: string;
43
+ }
44
+ export interface NativeControlController {
45
+ readonly element: NativeControl;
46
+ update(state: Readonly<NativeControlState>): void;
47
+ getValue(): string;
48
+ getChecked(): boolean;
49
+ reset(value: string | boolean): void;
50
+ focus(): void;
51
+ destroy(): void;
52
+ }
53
+ export declare function adoptNativeControl(control: NativeControl, options?: {
54
+ label?: HTMLLabelElement;
55
+ description?: HTMLElement;
56
+ error?: HTMLElement;
57
+ onInput?: (value: string, event: Event) => void;
58
+ onChange?: (value: string, event: Event) => void;
59
+ }): NativeControlController;
60
+ export interface FeedbackState {
61
+ message: string;
62
+ tone?: 'info' | 'neutral' | 'success' | 'warning' | 'error';
63
+ }
64
+ export interface FeedbackController {
65
+ readonly element: HTMLElement;
66
+ update(state: Readonly<FeedbackState>): void;
67
+ destroy(): void;
68
+ }
69
+ export declare function adoptFeedback(node: HTMLElement, options: {
70
+ presentation: 'notice' | 'inline' | 'status';
71
+ autoHide?: boolean;
72
+ }): FeedbackController;
73
+ export interface ActionLayoutState {
74
+ align?: 'start' | 'end' | 'between';
75
+ density?: Density;
76
+ label?: string;
77
+ }
78
+ export declare function adoptActionLayout(node: HTMLElement, kind: 'actions' | 'toolbar'): {
79
+ readonly element: HTMLElement;
80
+ update(state: Readonly<ActionLayoutState>): void;
81
+ destroy(): void;
82
+ };
83
+ /** Owned controllers for existing product templates; not a renderer or observer.
84
+ * Controllers are lazy. Reconciliation prunes owned handles without rescanning
85
+ * long message histories or allocating a controller for every static button.
86
+ */
87
+ export declare function adoptProductParts(owner: HTMLElement): {
88
+ update(): void;
89
+ action(node: HTMLButtonElement): ActionController;
90
+ control(node: NativeControl): NativeControlController;
91
+ feedback(node: HTMLElement): FeedbackController;
92
+ layout(node: HTMLElement, kind: 'actions' | 'toolbar'): ReturnType<typeof adoptActionLayout>;
93
+ destroy(): void;
94
+ };
95
+ //# sourceMappingURL=foundations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foundations.d.ts","sourceRoot":"","sources":["../src/foundations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,299 @@
1
+ import { controlDisabled, Lifetime, setControlPending } from './core.js';
2
+ import { iconElement, setText, uniqueId } from './dom.js';
3
+ import { adoptSelect, refreshSelect } from './select.js';
4
+ import { createOperation } from './operation.js';
5
+ export function adoptAction(node, options = {}) {
6
+ const life = options.onAction ? new Lifetime() : undefined;
7
+ let state = { disabled: node.disabled };
8
+ let localPending = false;
9
+ let disposed = false;
10
+ let glyph = node.querySelector(':scope > svg') ?? undefined;
11
+ const caption = options.caption ?? [...node.children].find((child) => child instanceof HTMLElement && child.tagName === 'SPAN');
12
+ const iconOnly = !caption && !!glyph && ![...node.childNodes].some(child => child.nodeType === Node.TEXT_NODE && child.textContent?.trim());
13
+ let iconName;
14
+ const update = (next) => {
15
+ if (disposed)
16
+ return;
17
+ state = { ...state, ...next };
18
+ if (next.label != null) {
19
+ if (caption)
20
+ setText(caption, next.label);
21
+ else if (iconOnly)
22
+ node.setAttribute('aria-label', next.label);
23
+ else
24
+ setText(node, next.label);
25
+ }
26
+ if (next.intent != null) {
27
+ for (const intent of ['neutral', 'primary', 'danger'])
28
+ node.classList.toggle(`pui-${intent}`, next.intent === intent);
29
+ }
30
+ if (next.density != null) {
31
+ node.classList.toggle('pui-normal', next.density === 'normal');
32
+ node.classList.toggle('pui-compact', next.density === 'compact');
33
+ }
34
+ if (next.appearance != null)
35
+ node.classList.toggle('pui-secondary-button', next.appearance === 'secondary');
36
+ // Adopted nodes keep their host-owned attributes unless explicitly updated.
37
+ if (next.disabled != null || next.pending != null || localPending) {
38
+ // A retained product action can also be pending in the application's
39
+ // adapter or native-copy controller. Only clear pending that we own.
40
+ if (state.pending != null || options.onAction)
41
+ setControlPending(node, !!(state.pending || localPending));
42
+ node.disabled = controlDisabled(node, !!(state.disabled || state.pending || localPending));
43
+ node.setAttribute('aria-busy', String(!!(state.pending || localPending || node.dataset.controlPending || node.dataset.copyPending)));
44
+ }
45
+ if (next.title != null)
46
+ node.title = next.title;
47
+ if (next.icon != null && iconName !== next.icon) {
48
+ glyph?.remove();
49
+ iconName = next.icon;
50
+ glyph = next.icon ? iconElement(next.icon) : undefined;
51
+ if (glyph)
52
+ node.prepend(glyph);
53
+ }
54
+ };
55
+ const operation = options.onAction ? createOperation({
56
+ unavailable: () => node.disabled || disposed,
57
+ onPending(value) { localPending = value; update({ ...state, disabled: !!state.disabled, pending: !!state.pending }); },
58
+ onError: options.onError,
59
+ }) : undefined;
60
+ // A delegated product action has no local handler and needs no second listener.
61
+ if (options.onAction)
62
+ life.listen(node, 'click', (() => {
63
+ if (node.disabled || localPending || disposed)
64
+ return;
65
+ operation.run(options.onAction, (result) => { if (result && !result.ok)
66
+ options.onError?.(result.error); });
67
+ }));
68
+ return { element: node, update, destroy() { disposed = true; operation?.destroy(); life?.destroy(); } };
69
+ }
70
+ /** Reconcile native options by their stable value without replacing the select.
71
+ * Callers decide the fallback when a preferred value has disappeared.
72
+ */
73
+ export function reconcileSelectOptions(control, entries, preferred) {
74
+ const selected = new Set(preferred == null ? [...control.selectedOptions].map(option => option.value) : [preferred]);
75
+ const existing = new Map();
76
+ for (const option of control.options) {
77
+ const group = existing.get(option.value) ?? [];
78
+ group.push(option);
79
+ existing.set(option.value, group);
80
+ }
81
+ const keep = new Set();
82
+ entries.forEach((entry, index) => {
83
+ const option = existing.get(entry.value)?.shift() ?? document.createElement('option');
84
+ keep.add(option);
85
+ if (option.value !== entry.value)
86
+ option.value = entry.value;
87
+ if (option.textContent !== entry.label)
88
+ option.textContent = entry.label;
89
+ if (option.disabled !== !!entry.disabled)
90
+ option.disabled = !!entry.disabled;
91
+ if (control.options[index] !== option)
92
+ control.insertBefore(option, control.options[index] ?? null);
93
+ });
94
+ for (const option of [...control.options])
95
+ if (!keep.has(option))
96
+ option.remove();
97
+ if (control.multiple) {
98
+ for (const option of control.options)
99
+ if (option.selected !== selected.has(option.value))
100
+ option.selected = selected.has(option.value);
101
+ }
102
+ else {
103
+ const value = preferred ?? [...selected][0] ?? '';
104
+ if (control.value !== value || !selected.size)
105
+ control.value = value;
106
+ }
107
+ refreshSelect(control);
108
+ }
109
+ export function adoptNativeControl(control, options = {}) {
110
+ const life = options.onInput || options.onChange ? new Lifetime() : undefined;
111
+ let disposed = false;
112
+ const originalDescriptions = new Set((control.getAttribute('aria-describedby') ?? '').split(/\s+/).filter(Boolean));
113
+ const ownedDescriptions = new Set();
114
+ if (options.label) {
115
+ if (!control.id)
116
+ control.id = uniqueId('pui-input');
117
+ options.label.htmlFor = control.id;
118
+ }
119
+ for (const node of [options.description, options.error])
120
+ if (node) {
121
+ if (!node.id)
122
+ node.id = uniqueId('pui-field-message');
123
+ ownedDescriptions.add(node.id);
124
+ }
125
+ if (options.error)
126
+ options.error.setAttribute('role', 'alert');
127
+ const select = control instanceof HTMLSelectElement ? adoptSelect(control) : undefined;
128
+ const update = (state) => {
129
+ if (disposed)
130
+ return;
131
+ if (state.label != null && options.label)
132
+ setText(options.label, state.label);
133
+ if (state.disabled != null)
134
+ control.disabled = state.disabled;
135
+ if (state.required != null)
136
+ control.required = state.required;
137
+ if (state.readOnly != null && !(control instanceof HTMLSelectElement))
138
+ control.readOnly = state.readOnly;
139
+ if (state.placeholder != null && !(control instanceof HTMLSelectElement))
140
+ control.placeholder = state.placeholder;
141
+ if (state.description != null && options.description) {
142
+ setText(options.description, state.description);
143
+ options.description.hidden = !state.description;
144
+ }
145
+ if (state.error != null) {
146
+ control.setAttribute('aria-invalid', String(!!state.error));
147
+ if (options.error) {
148
+ setText(options.error, state.error);
149
+ options.error.hidden = !state.error;
150
+ }
151
+ }
152
+ if (options.description || options.error) {
153
+ // Preserve descriptions supplied by the host or another owned component.
154
+ const ids = new Set([...originalDescriptions, ...(control.getAttribute('aria-describedby') ?? '').split(/\s+/).filter(Boolean)]);
155
+ for (const id of ownedDescriptions)
156
+ ids.delete(id);
157
+ if (options.description && !options.description.hidden && options.description.textContent)
158
+ ids.add(options.description.id);
159
+ if (options.error && !options.error.hidden && options.error.textContent)
160
+ ids.add(options.error.id);
161
+ if (ids.size)
162
+ control.setAttribute('aria-describedby', [...ids].join(' '));
163
+ else
164
+ control.removeAttribute('aria-describedby');
165
+ }
166
+ select?.refresh();
167
+ };
168
+ if (options.onInput)
169
+ life.listen(control, 'input', (event => options.onInput(control.value, event)));
170
+ if (options.onChange)
171
+ life.listen(control, 'change', (event => options.onChange(control.value, event)));
172
+ return { element: control, update, getValue: () => control.value,
173
+ getChecked: () => control instanceof HTMLInputElement && control.checked,
174
+ reset(value) { if (disposed)
175
+ return; if (typeof value === 'boolean' && control instanceof HTMLInputElement)
176
+ control.checked = value;
177
+ else if (typeof value === 'string' && control.value !== value)
178
+ control.value = value; select?.refresh(); },
179
+ focus() { if (!disposed) {
180
+ if (select)
181
+ select.focus();
182
+ else
183
+ control.focus();
184
+ } }, destroy() { if (disposed)
185
+ return; disposed = true; life?.destroy(); select?.destroy(); } };
186
+ }
187
+ export function adoptFeedback(node, options) {
188
+ let disposed = false;
189
+ const update = (state) => {
190
+ if (disposed)
191
+ return;
192
+ const tone = state.tone ?? (options.presentation === 'status' ? 'neutral' : 'info');
193
+ if (options.presentation !== 'inline') {
194
+ const prefix = options.presentation === 'notice' ? 'pui-notice' : 'pui-status';
195
+ node.classList.add(prefix);
196
+ for (const value of ['info', 'neutral', 'success', 'warning', 'error'])
197
+ node.classList.toggle(`${prefix}-${value}`, tone === value);
198
+ }
199
+ if (options.presentation !== 'status')
200
+ node.setAttribute('role', tone === 'error' || tone === 'warning' ? 'alert' : 'status');
201
+ if (options.autoHide !== false)
202
+ node.hidden = !state.message;
203
+ setText(node, state.message);
204
+ };
205
+ return { element: node, update, destroy() { disposed = true; } };
206
+ }
207
+ export function adoptActionLayout(node, kind) {
208
+ let disposed = false;
209
+ return { element: node, update(state) {
210
+ if (disposed)
211
+ return;
212
+ if (state.align != null)
213
+ node.dataset.align = state.align;
214
+ if (state.density != null)
215
+ node.classList.toggle('pui-compact', state.density === 'compact');
216
+ if (state.label != null) {
217
+ node.setAttribute('role', 'group');
218
+ node.setAttribute('aria-label', state.label);
219
+ }
220
+ node.dataset.puiActionLayout = kind;
221
+ }, destroy() { disposed = true; } };
222
+ }
223
+ /** Owned controllers for existing product templates; not a renderer or observer.
224
+ * Controllers are lazy. Reconciliation prunes owned handles without rescanning
225
+ * long message histories or allocating a controller for every static button.
226
+ */
227
+ export function adoptProductParts(owner) {
228
+ const actions = new Map();
229
+ const controls = new Map();
230
+ const feedbacks = new Map();
231
+ const layouts = new Map();
232
+ let disposed = false;
233
+ const action = (node) => {
234
+ let handle = actions.get(node);
235
+ if (!handle) {
236
+ handle = adoptAction(node);
237
+ if (disposed)
238
+ handle.destroy();
239
+ else
240
+ actions.set(node, handle);
241
+ }
242
+ return handle;
243
+ };
244
+ const control = (node) => {
245
+ let handle = controls.get(node);
246
+ if (!handle) {
247
+ handle = adoptNativeControl(node);
248
+ if (disposed)
249
+ handle.destroy();
250
+ else
251
+ controls.set(node, handle);
252
+ }
253
+ return handle;
254
+ };
255
+ const feedback = (node) => {
256
+ let handle = feedbacks.get(node);
257
+ if (!handle) {
258
+ handle = adoptFeedback(node, { presentation: 'inline', autoHide: false });
259
+ if (disposed)
260
+ handle.destroy();
261
+ else
262
+ feedbacks.set(node, handle);
263
+ }
264
+ return handle;
265
+ };
266
+ const layout = (node, kind) => {
267
+ let handle = layouts.get(node);
268
+ if (!handle) {
269
+ handle = adoptActionLayout(node, kind);
270
+ if (disposed)
271
+ handle.destroy();
272
+ else
273
+ layouts.set(node, handle);
274
+ }
275
+ return handle;
276
+ };
277
+ const update = () => {
278
+ if (disposed)
279
+ return;
280
+ for (const map of [actions, controls, feedbacks, layouts])
281
+ for (const [node, handle] of map) {
282
+ if (!owner.contains(node) && node !== owner) {
283
+ handle.destroy();
284
+ map.delete(node);
285
+ }
286
+ }
287
+ };
288
+ return { update, action, control, feedback, layout, destroy() {
289
+ if (disposed)
290
+ return;
291
+ disposed = true;
292
+ for (const map of [actions, controls, feedbacks, layouts]) {
293
+ for (const handle of map.values())
294
+ handle.destroy();
295
+ map.clear();
296
+ }
297
+ } };
298
+ }
299
+ //# sourceMappingURL=foundations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foundations.js","sourceRoot":"","sources":["../src/foundations.ts"],"names":[],"mappings":"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","sourcesContent":["import { controlDisabled, Lifetime, setControlPending } from './core.js';\nimport type { ActionResult, Density, Intent, UIAction } from './core.js';\nimport { iconElement, setText, uniqueId } from './dom.js';\nimport { adoptSelect, refreshSelect } from './select.js';\nimport { createOperation } from './operation.js';\n\n/** Private controllers shared by factories and the retained product templates.\n * Retained nodes keep their identity; single selects get one owned wrapper.\n */\nexport interface ActionState {\n label?: string;\n intent?: Intent;\n density?: Density;\n disabled?: boolean;\n pending?: boolean;\n icon?: string;\n title?: string;\n appearance?: 'standard' | 'secondary';\n}\nexport interface ActionController {\n readonly element: HTMLButtonElement;\n update(state: Readonly<ActionState>): void;\n destroy(): void;\n}\nexport function adoptAction(node: HTMLButtonElement, options: {\n caption?: HTMLElement;\n onAction?: UIAction;\n onError?: (message: string) => void;\n} = {}): ActionController {\n const life = options.onAction ? new Lifetime() : undefined;\n let state: ActionState = { disabled: node.disabled };\n let localPending = false; let disposed = false;\n let glyph = node.querySelector<SVGSVGElement>(':scope > svg') ?? undefined;\n const caption = options.caption ?? [...node.children].find((child): child is HTMLElement => child instanceof HTMLElement && child.tagName === 'SPAN');\n const iconOnly = !caption && !!glyph && ![...node.childNodes].some(child => child.nodeType === Node.TEXT_NODE && child.textContent?.trim());\n let iconName: string | undefined;\n const update = (next: Readonly<ActionState>): void => {\n if (disposed) return;\n state = { ...state, ...next };\n if (next.label != null) {\n if (caption) setText(caption, next.label);\n else if (iconOnly) node.setAttribute('aria-label', next.label);\n else setText(node, next.label);\n }\n if (next.intent != null) {\n for (const intent of ['neutral', 'primary', 'danger']) node.classList.toggle(`pui-${intent}`, next.intent === intent);\n }\n if (next.density != null) {\n node.classList.toggle('pui-normal', next.density === 'normal');\n node.classList.toggle('pui-compact', next.density === 'compact');\n }\n if (next.appearance != null) node.classList.toggle('pui-secondary-button', next.appearance === 'secondary');\n // Adopted nodes keep their host-owned attributes unless explicitly updated.\n if (next.disabled != null || next.pending != null || localPending) {\n // A retained product action can also be pending in the application's\n // adapter or native-copy controller. Only clear pending that we own.\n if (state.pending != null || options.onAction) setControlPending(node, !!(state.pending || localPending));\n node.disabled = controlDisabled(node, !!(state.disabled || state.pending || localPending));\n node.setAttribute('aria-busy', String(!!(state.pending || localPending || node.dataset.controlPending || node.dataset.copyPending)));\n }\n if (next.title != null) node.title = next.title;\n if (next.icon != null && iconName !== next.icon) {\n glyph?.remove(); iconName = next.icon;\n glyph = next.icon ? iconElement(next.icon) : undefined;\n if (glyph) node.prepend(glyph);\n }\n };\n const operation = options.onAction ? createOperation({\n unavailable: () => node.disabled || disposed,\n onPending(value) { localPending = value; update({ ...state, disabled: !!state.disabled, pending: !!state.pending }); },\n onError: options.onError,\n }) : undefined;\n // A delegated product action has no local handler and needs no second listener.\n if (options.onAction) life!.listen(node, 'click', (() => {\n if (node.disabled || localPending || disposed) return;\n operation!.run(options.onAction!, (result: void | ActionResult) => { if (result && !result.ok) options.onError?.(result.error); });\n }) as EventListener);\n return { element: node, update, destroy() { disposed = true; operation?.destroy(); life?.destroy(); } };\n}\n\nexport type NativeControl = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;\nexport interface NativeSelectOption { value: string; label: string; disabled?: boolean; }\n/** Reconcile native options by their stable value without replacing the select.\n * Callers decide the fallback when a preferred value has disappeared.\n */\nexport function reconcileSelectOptions(control: HTMLSelectElement, entries: readonly NativeSelectOption[], preferred?: string): void {\n const selected = new Set(preferred == null ? [...control.selectedOptions].map(option => option.value) : [preferred]);\n const existing = new Map<string, HTMLOptionElement[]>();\n for (const option of control.options) { const group = existing.get(option.value) ?? []; group.push(option); existing.set(option.value, group); }\n const keep = new Set<HTMLOptionElement>();\n entries.forEach((entry, index) => {\n const option = existing.get(entry.value)?.shift() ?? document.createElement('option');\n keep.add(option);\n if (option.value !== entry.value) option.value = entry.value;\n if (option.textContent !== entry.label) option.textContent = entry.label;\n if (option.disabled !== !!entry.disabled) option.disabled = !!entry.disabled;\n if (control.options[index] !== option) control.insertBefore(option, control.options[index] ?? null);\n });\n for (const option of [...control.options]) if (!keep.has(option)) option.remove();\n if (control.multiple) {\n for (const option of control.options) if (option.selected !== selected.has(option.value)) option.selected = selected.has(option.value);\n } else {\n const value = preferred ?? [...selected][0] ?? '';\n if (control.value !== value || !selected.size) control.value = value;\n }\n refreshSelect(control);\n}\nexport interface NativeControlState {\n label?: string;\n description?: string;\n error?: string;\n disabled?: boolean;\n required?: boolean;\n readOnly?: boolean;\n placeholder?: string;\n}\nexport interface NativeControlController {\n readonly element: NativeControl;\n update(state: Readonly<NativeControlState>): void;\n getValue(): string;\n getChecked(): boolean;\n reset(value: string | boolean): void;\n focus(): void;\n destroy(): void;\n}\nexport function adoptNativeControl(control: NativeControl, options: {\n label?: HTMLLabelElement;\n description?: HTMLElement;\n error?: HTMLElement;\n onInput?: (value: string, event: Event) => void;\n onChange?: (value: string, event: Event) => void;\n} = {}): NativeControlController {\n const life = options.onInput || options.onChange ? new Lifetime() : undefined; let disposed = false;\n const originalDescriptions = new Set((control.getAttribute('aria-describedby') ?? '').split(/\\s+/).filter(Boolean));\n const ownedDescriptions = new Set<string>();\n if (options.label) {\n if (!control.id) control.id = uniqueId('pui-input');\n options.label.htmlFor = control.id;\n }\n for (const node of [options.description, options.error]) if (node) {\n if (!node.id) node.id = uniqueId('pui-field-message');\n ownedDescriptions.add(node.id);\n }\n if (options.error) options.error.setAttribute('role', 'alert');\n const select = control instanceof HTMLSelectElement ? adoptSelect(control) : undefined;\n const update = (state: Readonly<NativeControlState>): void => {\n if (disposed) return;\n if (state.label != null && options.label) setText(options.label, state.label);\n if (state.disabled != null) control.disabled = state.disabled;\n if (state.required != null) control.required = state.required;\n if (state.readOnly != null && !(control instanceof HTMLSelectElement)) control.readOnly = state.readOnly;\n if (state.placeholder != null && !(control instanceof HTMLSelectElement)) control.placeholder = state.placeholder;\n if (state.description != null && options.description) {\n setText(options.description, state.description); options.description.hidden = !state.description;\n }\n if (state.error != null) {\n control.setAttribute('aria-invalid', String(!!state.error));\n if (options.error) { setText(options.error, state.error); options.error.hidden = !state.error; }\n }\n if (options.description || options.error) {\n // Preserve descriptions supplied by the host or another owned component.\n const ids = new Set([...originalDescriptions, ...(control.getAttribute('aria-describedby') ?? '').split(/\\s+/).filter(Boolean)]);\n for (const id of ownedDescriptions) ids.delete(id);\n if (options.description && !options.description.hidden && options.description.textContent) ids.add(options.description.id);\n if (options.error && !options.error.hidden && options.error.textContent) ids.add(options.error.id);\n if (ids.size) control.setAttribute('aria-describedby', [...ids].join(' ')); else control.removeAttribute('aria-describedby');\n }\n select?.refresh();\n };\n if (options.onInput) life!.listen(control, 'input', (event => options.onInput!(control.value, event)) as EventListener);\n if (options.onChange) life!.listen(control, 'change', (event => options.onChange!(control.value, event)) as EventListener);\n return { element: control, update, getValue: () => control.value,\n getChecked: () => control instanceof HTMLInputElement && control.checked,\n reset(value) { if (disposed) return; if (typeof value === 'boolean' && control instanceof HTMLInputElement) control.checked = value; else if (typeof value === 'string' && control.value !== value) control.value = value; select?.refresh(); },\n focus() { if (!disposed) { if (select) select.focus(); else control.focus(); } }, destroy() { if (disposed) return; disposed = true; life?.destroy(); select?.destroy(); } };\n}\n\nexport interface FeedbackState { message: string; tone?: 'info' | 'neutral' | 'success' | 'warning' | 'error'; }\nexport interface FeedbackController {\n readonly element: HTMLElement;\n update(state: Readonly<FeedbackState>): void;\n destroy(): void;\n}\nexport function adoptFeedback(node: HTMLElement, options: {\n presentation: 'notice' | 'inline' | 'status';\n autoHide?: boolean;\n}): FeedbackController {\n let disposed = false;\n const update = (state: Readonly<FeedbackState>): void => {\n if (disposed) return;\n const tone = state.tone ?? (options.presentation === 'status' ? 'neutral' : 'info');\n if (options.presentation !== 'inline') {\n const prefix = options.presentation === 'notice' ? 'pui-notice' : 'pui-status';\n node.classList.add(prefix);\n for (const value of ['info', 'neutral', 'success', 'warning', 'error']) node.classList.toggle(`${prefix}-${value}`, tone === value);\n }\n if (options.presentation !== 'status') node.setAttribute('role', tone === 'error' || tone === 'warning' ? 'alert' : 'status');\n if (options.autoHide !== false) node.hidden = !state.message;\n setText(node, state.message);\n };\n return { element: node, update, destroy() { disposed = true; } };\n}\n\nexport interface ActionLayoutState { align?: 'start' | 'end' | 'between'; density?: Density; label?: string; }\nexport function adoptActionLayout(node: HTMLElement, kind: 'actions' | 'toolbar'): {\n readonly element: HTMLElement;\n update(state: Readonly<ActionLayoutState>): void;\n destroy(): void;\n} {\n let disposed = false;\n return { element: node, update(state) {\n if (disposed) return;\n if (state.align != null) node.dataset.align = state.align;\n if (state.density != null) node.classList.toggle('pui-compact', state.density === 'compact');\n if (state.label != null) { node.setAttribute('role', 'group'); node.setAttribute('aria-label', state.label); }\n node.dataset.puiActionLayout = kind;\n }, destroy() { disposed = true; } };\n}\n\n/** Owned controllers for existing product templates; not a renderer or observer.\n * Controllers are lazy. Reconciliation prunes owned handles without rescanning\n * long message histories or allocating a controller for every static button.\n */\nexport function adoptProductParts(owner: HTMLElement): {\n update(): void;\n action(node: HTMLButtonElement): ActionController;\n control(node: NativeControl): NativeControlController;\n feedback(node: HTMLElement): FeedbackController;\n layout(node: HTMLElement, kind: 'actions' | 'toolbar'): ReturnType<typeof adoptActionLayout>;\n destroy(): void;\n} {\n const actions = new Map<HTMLButtonElement, ActionController>();\n const controls = new Map<NativeControl, NativeControlController>();\n const feedbacks = new Map<HTMLElement, FeedbackController>();\n const layouts = new Map<HTMLElement, ReturnType<typeof adoptActionLayout>>();\n let disposed = false;\n const action = (node: HTMLButtonElement): ActionController => {\n let handle = actions.get(node); if (!handle) { handle = adoptAction(node); if (disposed) handle.destroy(); else actions.set(node, handle); } return handle;\n };\n const control = (node: NativeControl): NativeControlController => {\n let handle = controls.get(node); if (!handle) { handle = adoptNativeControl(node); if (disposed) handle.destroy(); else controls.set(node, handle); } return handle;\n };\n const feedback = (node: HTMLElement): FeedbackController => {\n let handle = feedbacks.get(node); if (!handle) { handle = adoptFeedback(node, { presentation: 'inline', autoHide: false }); if (disposed) handle.destroy(); else feedbacks.set(node, handle); } return handle;\n };\n const layout = (node: HTMLElement, kind: 'actions' | 'toolbar'): ReturnType<typeof adoptActionLayout> => {\n let handle = layouts.get(node); if (!handle) { handle = adoptActionLayout(node, kind); if (disposed) handle.destroy(); else layouts.set(node, handle); } return handle;\n };\n const update = (): void => {\n if (disposed) return;\n for (const map of [actions, controls, feedbacks, layouts]) for (const [node, handle] of map) {\n if (!owner.contains(node) && node !== owner) { handle.destroy(); map.delete(node as never); }\n }\n };\n return { update, action, control, feedback, layout, destroy() {\n if (disposed) return; disposed = true;\n for (const map of [actions, controls, feedbacks, layouts]) { for (const handle of map.values()) handle.destroy(); map.clear(); }\n } };\n}\n\n"]}
@@ -1,5 +1,6 @@
1
1
  import type { UIBlock } from '../core.js';
2
2
  export interface ContextMenuItem {
3
+ id?: string;
3
4
  label: string;
4
5
  action?: string;
5
6
  sdkAction?: string;
@@ -14,12 +15,18 @@ export interface ContextMenuState {
14
15
  label: string;
15
16
  items: readonly ContextMenuItem[];
16
17
  }
18
+ export interface ContextMenuOpenOptions {
19
+ point?: {
20
+ x: number;
21
+ y: number;
22
+ };
23
+ }
17
24
  export declare function createContextMenu(options?: {
18
25
  element?: HTMLElement;
19
26
  onDismiss?: (restoreFocus: boolean) => void;
20
27
  onAction?: (item: ContextMenuItem) => void;
21
28
  }): UIBlock<ContextMenuState> & {
22
- open(trigger: HTMLElement, state: ContextMenuState): void;
29
+ open(trigger: HTMLElement, state: ContextMenuState, options?: ContextMenuOpenOptions): void;
23
30
  close(restoreFocus?: boolean): void;
24
31
  };
25
32
  //# sourceMappingURL=context-menu.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context-menu.d.ts","sourceRoot":"","sources":["../../src/navigation/context-menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACzF,IAAI,CAAC,EAAE;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC;CACxD;AACD,MAAM,WAAW,gBAAgB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,eAAe,EAAE,CAAA;CAAC;AACpF,wBAAgB,iBAAiB,CAAC,OAAO,GAAE;IAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAAC,SAAS,CAAC,EAAE,CAAC,YAAY,EAAE,OAAO,KAAK,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAA;CAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,GAAG;IAAC,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CAAC,CA4B9R"}
1
+ {"version":3,"file":"context-menu.d.ts","sourceRoot":"","sources":["../../src/navigation/context-menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAI1C,MAAM,WAAW,eAAe;IAC9B,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACtG,IAAI,CAAC,EAAE;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC;CACxD;AACD,MAAM,WAAW,gBAAgB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,eAAe,EAAE,CAAA;CAAC;AACpF,MAAM,WAAW,sBAAsB;IAAE,KAAK,CAAC,EAAE;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAC,CAAA;CAAC;AACxE,wBAAgB,iBAAiB,CAAC,OAAO,GAAE;IAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAAC,SAAS,CAAC,EAAE,CAAC,YAAY,EAAE,OAAO,KAAK,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAA;CAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,GAAG;IAAC,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CAAC,CA4ChU"}
@@ -1,3 +1,6 @@
1
+ import { installAnchoredSurface, installMenuNavigation } from '../surface.js';
2
+ import { adoptProductParts } from '../foundations.js';
3
+ import { reconcileKeyed } from '../reconcile.js';
1
4
  export function createContextMenu(options = {}) {
2
5
  const element = options.element || document.createElement('div');
3
6
  if (!options.element) {
@@ -5,63 +8,77 @@ export function createContextMenu(options = {}) {
5
8
  element.setAttribute('role', 'menu');
6
9
  element.hidden = true;
7
10
  }
11
+ element.setAttribute('role', 'menu');
12
+ element.tabIndex = -1;
8
13
  const abort = new AbortController();
9
- let trigger = null;
10
- let destroyed = false;
11
- const close = (restoreFocus = false) => { element.hidden = true; trigger?.setAttribute('aria-expanded', 'false'); if (restoreFocus)
12
- trigger?.focus(); trigger = null; };
14
+ let destroyed = false, programmaticClose = false;
15
+ let surface;
16
+ const parts = adoptProductParts(element);
17
+ const rows = new Map();
18
+ const actions = new Map();
19
+ const close = (restoreFocus = false) => {
20
+ programmaticClose = true;
21
+ surface?.close(restoreFocus);
22
+ surface?.destroy();
23
+ surface = undefined;
24
+ element.hidden = true;
25
+ programmaticClose = false;
26
+ };
27
+ const dismiss = (restoreFocus) => { close(restoreFocus); options.onDismiss?.(restoreFocus); };
28
+ const navigation = installMenuNavigation(element, { onDismiss: dismiss, isOpen: () => surface?.isOpen() ?? false });
13
29
  const update = (state) => {
14
30
  if (destroyed)
15
31
  return;
16
32
  element.setAttribute('aria-label', state.label);
17
- element.replaceChildren();
18
- for (const item of state.items) {
19
- const button = document.createElement('button');
20
- button.type = 'button';
21
- button.setAttribute('role', 'menuitem');
22
- button.textContent = item.label;
23
- button.disabled = !!item.disabled;
24
- if (item.danger)
25
- button.className = 'pui-destructive-menu-item';
26
- if (item.action)
27
- button.dataset.action = item.action;
28
- if (item.sdkAction)
29
- button.dataset.sdk = item.sdkAction;
30
- if (item.idle) {
31
- button.dataset.idleAction = '';
32
- if (item.idle.conversationId)
33
- button.dataset.idleConversation = item.idle.conversationId;
34
- if (item.idle.workspaceId)
35
- button.dataset.idleWorkspace = item.idle.workspaceId;
36
- }
37
- if (options.onAction)
38
- button.addEventListener('click', () => options.onAction(item), { signal: abort.signal });
39
- element.append(button);
40
- }
33
+ const entries = state.items.map((item, index) => ({ item, key: item.id ?? `${item.action || ''}/${item.sdkAction || ''}/${index}` }));
34
+ reconcileKeyed(element, rows, entries, { key: entry => entry.key, element: button => button,
35
+ create() { const button = document.createElement('button'); button.type = 'button'; button.setAttribute('role', 'menuitem'); return button; },
36
+ update(button, { item }) {
37
+ parts.action(button).update({ label: item.label, disabled: !!item.disabled });
38
+ button.classList.toggle('pui-destructive-menu-item', !!item.danger);
39
+ if (item.action)
40
+ button.dataset.action = item.action;
41
+ else
42
+ delete button.dataset.action;
43
+ if (item.sdkAction)
44
+ button.dataset.sdk = item.sdkAction;
45
+ else
46
+ delete button.dataset.sdk;
47
+ for (const name of ['idleAction', 'idleConversation', 'idleWorkspace'])
48
+ delete button.dataset[name];
49
+ if (item.idle) {
50
+ button.dataset.idleAction = '';
51
+ if (item.idle.conversationId)
52
+ button.dataset.idleConversation = item.idle.conversationId;
53
+ if (item.idle.workspaceId)
54
+ button.dataset.idleWorkspace = item.idle.workspaceId;
55
+ }
56
+ actions.set(button, item);
57
+ }, destroy(button) { actions.delete(button); },
58
+ });
59
+ parts.update();
41
60
  };
42
- element.addEventListener('keydown', (event) => {
43
- if (event.key === 'Escape') {
44
- event.preventDefault();
45
- event.stopPropagation();
46
- options.onDismiss ? options.onDismiss(true) : close(true);
47
- return;
48
- }
49
- if (event.key === 'Tab') {
50
- options.onDismiss ? options.onDismiss(false) : close(false);
51
- return;
52
- }
53
- const buttons = [...element.querySelectorAll('button:not(:disabled)')];
54
- if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) {
55
- event.preventDefault();
61
+ if (options.onAction)
62
+ element.addEventListener('click', event => { const button = event.target.closest('button'); const item = button ? actions.get(button) : undefined; if (item && !button.disabled) {
56
63
  event.stopPropagation();
57
- const index = buttons.indexOf(document.activeElement);
58
- const next = event.key === 'Home' ? 0 : event.key === 'End' ? buttons.length - 1 : (index + (event.key === 'ArrowDown' ? 1 : -1) + buttons.length) % buttons.length;
59
- buttons[next]?.focus();
60
- }
61
- }, { signal: abort.signal });
62
- return { element, update, close, open(source, state) { if (destroyed)
63
- return; close(); trigger = source; trigger.setAttribute('aria-expanded', 'true'); update(state); element.hidden = false; const rect = source.getBoundingClientRect(); element.style.left = `${Math.max(8, Math.min(rect.right, window.innerWidth - element.offsetWidth - 8))}px`; element.style.top = `${Math.max(8, Math.min(rect.bottom + 4, window.innerHeight - element.offsetHeight - 8))}px`; element.querySelector('button:not(:disabled)')?.focus(); }, destroy() { if (destroyed)
64
- return; destroyed = true; close(); abort.abort(); if (!options.element)
64
+ options.onAction(item);
65
+ } }, { signal: abort.signal });
66
+ return { element, update, close, open(source, state, anchor = {}) {
67
+ if (destroyed)
68
+ return;
69
+ close();
70
+ update(state);
71
+ source.focus({ preventScroll: true });
72
+ surface = installAnchoredSurface({ trigger: source, panel: element, dismissOnMove: true,
73
+ ...(anchor.point ? { getAnchor: () => ({ left: anchor.point.x, right: anchor.point.x, top: anchor.point.y, bottom: anchor.point.y, width: 0, height: 0 }) } : {}),
74
+ onToggle(open, restoreFocus) { if (!open && !programmaticClose)
75
+ options.onDismiss?.(!!restoreFocus); }, });
76
+ surface.open();
77
+ navigation.focus();
78
+ if (surface.isOpen() && !element.contains(document.activeElement))
79
+ element.focus({ preventScroll: true });
80
+ }, destroy() { if (destroyed)
81
+ return; destroyed = true; close(); navigation.destroy(); abort.abort(); parts.destroy(); rows.clear(); actions.clear(); if (!options.element)
65
82
  element.remove(); } };
66
83
  }
67
84
  //# sourceMappingURL=context-menu.js.map