flexdesk 0.17.18 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +1 -0
  3. package/css/flexdesk.css +7544 -5916
  4. package/css/reset.css +1 -1
  5. package/css/tokens.css +23 -1
  6. package/dist/charts.js +10 -6
  7. package/dist/charts.js.map +1 -1
  8. package/dist/chunk-5W4EEKSK.js +778 -0
  9. package/dist/chunk-5W4EEKSK.js.map +7 -0
  10. package/dist/chunk-DNJ3N4T4.js +82 -0
  11. package/dist/chunk-DNJ3N4T4.js.map +7 -0
  12. package/dist/{chunk-REJ5HLIM.js → chunk-HYJYYEPK.js} +2 -2
  13. package/dist/{chunk-7U4IB65H.js → chunk-LLBJ2SJH.js} +5 -3
  14. package/dist/{chunk-7U4IB65H.js.map → chunk-LLBJ2SJH.js.map} +1 -1
  15. package/dist/{chunk-7OL6F2AM.js → chunk-P74F6I6P.js} +6 -4
  16. package/dist/{chunk-7OL6F2AM.js.map → chunk-P74F6I6P.js.map} +1 -1
  17. package/dist/chunk-PAI2ZUWV.js +260 -0
  18. package/dist/chunk-PAI2ZUWV.js.map +7 -0
  19. package/dist/chunk-Q7BALEPX.js +527 -0
  20. package/dist/chunk-Q7BALEPX.js.map +7 -0
  21. package/dist/chunk-QXAOTGCC.js +40 -0
  22. package/dist/chunk-QXAOTGCC.js.map +7 -0
  23. package/dist/{chunk-XJNADRGR.js → chunk-RKVYIXDC.js} +664 -91
  24. package/dist/chunk-RKVYIXDC.js.map +7 -0
  25. package/dist/{chunk-2Z2ENHR3.js → chunk-U5GKJUOC.js} +3 -3
  26. package/dist/{chunk-2Z2ENHR3.js.map → chunk-U5GKJUOC.js.map} +1 -1
  27. package/dist/chunk-UKP22RUG.js +219 -0
  28. package/dist/chunk-UKP22RUG.js.map +7 -0
  29. package/dist/chunk-VLOAI3HS.js +43 -0
  30. package/dist/chunk-VLOAI3HS.js.map +7 -0
  31. package/dist/{chunk-WUFLGETN.js → chunk-WVFGV5FT.js} +2 -39
  32. package/dist/{chunk-WUFLGETN.js.map → chunk-WVFGV5FT.js.map} +3 -3
  33. package/dist/{chunk-PGZ4EHSE.js → chunk-ZL3VKINP.js} +41 -390
  34. package/dist/chunk-ZL3VKINP.js.map +7 -0
  35. package/dist/editor.js +3 -2
  36. package/dist/editor.js.map +1 -1
  37. package/dist/flexdesk.css +7544 -5916
  38. package/dist/flow.js +29 -97
  39. package/dist/flow.js.map +3 -3
  40. package/dist/git.js +1356 -0
  41. package/dist/git.js.map +7 -0
  42. package/dist/reset.css +1 -1
  43. package/dist/tiles.js +20 -15
  44. package/dist/tiles.js.map +2 -2
  45. package/dist/tokens.css +23 -1
  46. package/dist/widgets.js +3967 -1188
  47. package/dist/widgets.js.map +4 -4
  48. package/dist/wm.js +120 -32
  49. package/dist/wm.js.map +2 -2
  50. package/docs/README.md +9 -4
  51. package/docs/best-practices.md +2 -2
  52. package/docs/components/chat-shell.md +198 -0
  53. package/docs/components/conversation.md +10 -0
  54. package/docs/components/filter-builder.md +246 -0
  55. package/docs/components/forms-and-dialogs.md +22 -0
  56. package/docs/components/git.md +355 -0
  57. package/docs/components/input-fields.md +167 -2
  58. package/docs/components/keymap.md +8 -0
  59. package/docs/components/menus.md +82 -2
  60. package/docs/components/panels-and-desktops.md +16 -5
  61. package/docs/components/shell.md +104 -1
  62. package/docs/components/workspace-picker.md +149 -0
  63. package/docs/concepts/building-and-packaging.md +2 -2
  64. package/docs/concepts/packages-and-entry-points.md +14 -13
  65. package/docs/concepts/state-guard.md +3 -3
  66. package/docs/quickstart.md +1 -1
  67. package/git.js +13 -0
  68. package/package.json +6 -1
  69. package/src/flow/kit/dom.js +28 -119
  70. package/src/flow/kit/step_picker.js +1 -1
  71. package/src/flow/kit/value_picker.js +1 -1
  72. package/src/flow/outline/menu.js +1 -1
  73. package/src/git/diff_viewer.js +212 -0
  74. package/src/git/diff_window.js +51 -0
  75. package/src/git/git_graph.js +401 -0
  76. package/src/git/git_ops.js +61 -0
  77. package/src/git/git_panel.js +755 -0
  78. package/src/git/index.js +9 -0
  79. package/src/git/line_diff.js +202 -0
  80. package/src/tiles/tile_base.js +5 -4
  81. package/src/tiling/desktops.js +10 -8
  82. package/src/tiling/keymap.js +6 -5
  83. package/src/tiling/kind_taxonomy.js +15 -2
  84. package/src/tiling/shell.js +124 -17
  85. package/src/tiling/tile_renderer.js +2 -3
  86. package/src/ui/components/action_dropdown.js +4 -78
  87. package/src/ui/components/anchored_layer.js +255 -0
  88. package/src/ui/components/chat_shell.js +920 -0
  89. package/src/ui/components/chat_state_machine.js +223 -0
  90. package/src/ui/components/choice_controls.js +45 -2
  91. package/src/ui/components/color_field.js +3 -2
  92. package/src/ui/components/color_list_field.js +3 -2
  93. package/src/ui/components/combobox.js +422 -0
  94. package/src/ui/components/conversation_view.js +17 -2
  95. package/src/ui/components/data_table.js +2 -2
  96. package/src/ui/components/filter_builder.js +766 -0
  97. package/src/ui/components/filter_model.js +614 -0
  98. package/src/ui/components/managed_window.js +24 -47
  99. package/src/ui/components/modal.js +79 -109
  100. package/src/ui/components/option_list.js +160 -0
  101. package/src/ui/components/popover.js +94 -0
  102. package/src/ui/components/recent_items.js +169 -0
  103. package/src/ui/components/select.js +322 -0
  104. package/src/ui/components/tag_input.js +77 -8
  105. package/src/ui/components/workspace_picker.js +1155 -0
  106. package/src/ui/utils/fire_event.js +17 -0
  107. package/src/ui/utils/focusable.js +25 -0
  108. package/src/ui/utils/modal_host.js +27 -0
  109. package/widgets.js +12 -2
  110. package/css/base.css +0 -10547
  111. package/css/overrides.css +0 -2596
  112. package/dist/chunk-7JI7HHSY.js +0 -107
  113. package/dist/chunk-7JI7HHSY.js.map +0 -7
  114. package/dist/chunk-MIW7D6WC.js +0 -526
  115. package/dist/chunk-MIW7D6WC.js.map +0 -7
  116. package/dist/chunk-PGZ4EHSE.js.map +0 -7
  117. package/dist/chunk-XJNADRGR.js.map +0 -7
  118. /package/dist/{chunk-REJ5HLIM.js.map → chunk-HYJYYEPK.js.map} +0 -0
@@ -0,0 +1,260 @@
1
+ import {
2
+ positionAnchored
3
+ } from "./chunk-Q7BALEPX.js";
4
+
5
+ // src/ui/components/action_dropdown.js
6
+ var ActionDropdown = class _ActionDropdown {
7
+ /**
8
+ * @param {Object} config
9
+ * @param {HTMLElement} config.trigger - Button element that triggers the dropdown
10
+ * @param {Array} config.options - Array of option objects with { type, label, icon, description? },
11
+ * or `{ separator: true }` for a divider between them
12
+ * @param {Function} config.onSelect - Callback when an option is selected
13
+ * @param {string} [config.className=''] - Additional CSS class for the dropdown
14
+ * @param {string} [config.menuId] - Optional ID for the menu element
15
+ */
16
+ constructor(config) {
17
+ this.trigger = config.trigger;
18
+ this.options = config.options || [];
19
+ this.onSelect = config.onSelect;
20
+ this.className = config.className || "";
21
+ this.menuId = config.menuId;
22
+ this.isOpen = false;
23
+ this.menuEl = null;
24
+ this._boundHandleDocumentClick = this._handleDocumentClick.bind(this);
25
+ this._boundHandleKeydown = this._handleKeydown.bind(this);
26
+ this._boundHandleTriggerClick = this._handleTriggerClick.bind(this);
27
+ this._init();
28
+ }
29
+ /**
30
+ * Initialize the dropdown.
31
+ * @private
32
+ */
33
+ _init() {
34
+ if (!this.trigger) {
35
+ console.warn("[ActionDropdown] No trigger element provided");
36
+ return;
37
+ }
38
+ this._createMenu();
39
+ this.trigger.addEventListener("click", this._boundHandleTriggerClick);
40
+ }
41
+ /**
42
+ * Create the dropdown menu element.
43
+ * @private
44
+ */
45
+ _createMenu() {
46
+ const menu = document.createElement("div");
47
+ menu.className = `twm-action-dropdown-menu ${this.className}`.trim();
48
+ menu.setAttribute("role", "menu");
49
+ menu.hidden = true;
50
+ if (this.menuId) {
51
+ menu.id = this.menuId;
52
+ }
53
+ this._renderOptions(menu);
54
+ document.body.appendChild(menu);
55
+ this.menuEl = menu;
56
+ }
57
+ /**
58
+ * Render option buttons, and dividers for separator entries, into a
59
+ * container element. `data-index` stays the entry's index in `options`.
60
+ * @param {HTMLElement} container
61
+ * @private
62
+ */
63
+ _renderOptions(container) {
64
+ container.innerHTML = "";
65
+ let pendingSeparator = false;
66
+ this.options.forEach((opt, index) => {
67
+ if (opt?.separator) {
68
+ pendingSeparator = container.childElementCount > 0;
69
+ return;
70
+ }
71
+ if (pendingSeparator) {
72
+ const sep = document.createElement("div");
73
+ sep.className = "twm-action-dropdown-separator";
74
+ sep.setAttribute("role", "separator");
75
+ container.appendChild(sep);
76
+ pendingSeparator = false;
77
+ }
78
+ const optionBtn = document.createElement("button");
79
+ optionBtn.type = "button";
80
+ optionBtn.className = "twm-action-dropdown-option";
81
+ optionBtn.dataset.index = index;
82
+ optionBtn.dataset.type = opt.type;
83
+ optionBtn.setAttribute("role", "menuitem");
84
+ const iconHtml = opt.icon ? `<span class="material-symbols-outlined twm-option-icon">${opt.icon}</span>` : "";
85
+ const descHtml = opt.description ? `<span class="twm-option-desc">${opt.description}</span>` : "";
86
+ optionBtn.innerHTML = `
87
+ ${iconHtml}
88
+ <span class="option-text">
89
+ <span class="twm-option-label">${opt.label}</span>
90
+ ${descHtml}
91
+ </span>
92
+ `;
93
+ optionBtn.addEventListener("click", (e) => {
94
+ e.preventDefault();
95
+ e.stopPropagation();
96
+ this._selectOption(opt);
97
+ });
98
+ container.appendChild(optionBtn);
99
+ });
100
+ }
101
+ /**
102
+ * Handle trigger button click.
103
+ * @param {MouseEvent} e
104
+ * @private
105
+ */
106
+ _handleTriggerClick(e) {
107
+ e.preventDefault();
108
+ e.stopPropagation();
109
+ this.toggle();
110
+ }
111
+ /**
112
+ * Toggle the dropdown open/closed.
113
+ * @param {boolean} [forceState] - Optional forced state
114
+ */
115
+ toggle(forceState) {
116
+ const shouldOpen = typeof forceState === "boolean" ? forceState : !this.isOpen;
117
+ if (shouldOpen) {
118
+ this.open();
119
+ } else {
120
+ this.close();
121
+ }
122
+ }
123
+ /**
124
+ * Open the dropdown with auto-positioning.
125
+ */
126
+ open() {
127
+ if (this.isOpen || !this.menuEl) return;
128
+ this.isOpen = true;
129
+ this.menuEl.hidden = false;
130
+ _ActionDropdown.position(this.trigger, this.menuEl);
131
+ requestAnimationFrame(() => {
132
+ this.menuEl?.classList.add("visible");
133
+ });
134
+ this.trigger?.classList.add("twm-is-open");
135
+ if (this.trigger?.hasAttribute("aria-expanded")) {
136
+ this.trigger.setAttribute("aria-expanded", "true");
137
+ }
138
+ document.addEventListener("click", this._boundHandleDocumentClick, true);
139
+ document.addEventListener("keydown", this._boundHandleKeydown);
140
+ const firstOption = this.menuEl.querySelector(".twm-action-dropdown-option");
141
+ firstOption?.focus();
142
+ }
143
+ /**
144
+ * Close the dropdown.
145
+ */
146
+ close() {
147
+ if (!this.isOpen) return;
148
+ this.isOpen = false;
149
+ if (this.menuEl) {
150
+ this.menuEl.classList.remove("visible");
151
+ this.menuEl.hidden = true;
152
+ }
153
+ this.trigger?.classList.remove("twm-is-open");
154
+ if (this.trigger?.hasAttribute("aria-expanded")) {
155
+ this.trigger.setAttribute("aria-expanded", "false");
156
+ }
157
+ document.removeEventListener("click", this._boundHandleDocumentClick, true);
158
+ document.removeEventListener("keydown", this._boundHandleKeydown);
159
+ }
160
+ /**
161
+ * Handle document click (close on outside click).
162
+ * @param {MouseEvent} e
163
+ * @private
164
+ */
165
+ _handleDocumentClick(e) {
166
+ if (!this.isOpen) return;
167
+ if (this.menuEl?.contains(e.target) || this.trigger?.contains(e.target)) {
168
+ return;
169
+ }
170
+ this.close();
171
+ }
172
+ /**
173
+ * Handle keyboard events.
174
+ * @param {KeyboardEvent} e
175
+ * @private
176
+ */
177
+ _handleKeydown(e) {
178
+ if (e.key === "Escape") {
179
+ e.preventDefault();
180
+ e.stopPropagation();
181
+ this.close();
182
+ this.trigger?.focus();
183
+ return;
184
+ }
185
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
186
+ e.preventDefault();
187
+ this._navigateOptions(e.key === "ArrowDown" ? 1 : -1);
188
+ }
189
+ }
190
+ /**
191
+ * Navigate options with arrow keys.
192
+ * @param {number} direction - 1 for down, -1 for up
193
+ * @private
194
+ */
195
+ _navigateOptions(direction) {
196
+ const options = this.menuEl?.querySelectorAll(".twm-action-dropdown-option");
197
+ if (!options || options.length === 0) return;
198
+ const currentIndex = Array.from(options).findIndex((opt) => opt === document.activeElement);
199
+ let nextIndex = currentIndex + direction;
200
+ if (nextIndex < 0) nextIndex = options.length - 1;
201
+ if (nextIndex >= options.length) nextIndex = 0;
202
+ options[nextIndex]?.focus();
203
+ }
204
+ /**
205
+ * Handle option selection.
206
+ * @param {Object} option - The selected option
207
+ * @private
208
+ */
209
+ _selectOption(option) {
210
+ this.close();
211
+ if (this.onSelect) {
212
+ this.onSelect(option);
213
+ }
214
+ }
215
+ /**
216
+ * Update the options dynamically.
217
+ * @param {Array} newOptions - New options array
218
+ */
219
+ setOptions(newOptions) {
220
+ this.options = newOptions;
221
+ if (this.menuEl) {
222
+ this._renderOptions(this.menuEl);
223
+ }
224
+ }
225
+ /**
226
+ * Destroy the dropdown and clean up.
227
+ */
228
+ destroy() {
229
+ this.close();
230
+ this.trigger?.removeEventListener("click", this._boundHandleTriggerClick);
231
+ this.menuEl?.remove();
232
+ this.menuEl = null;
233
+ this.trigger = null;
234
+ this.options = [];
235
+ this.onSelect = null;
236
+ }
237
+ // =========================================================================
238
+ // STATIC — shared auto-positioning for any trigger + menu pair
239
+ // =========================================================================
240
+ /**
241
+ * Position a dropdown menu relative to a trigger element: `positionAnchored`
242
+ * (`anchored_layer.js`), the one placement every floating panel uses.
243
+ *
244
+ * @param {HTMLElement} trigger - The element the dropdown opens from
245
+ * @param {HTMLElement} menu - The dropdown menu element to position
246
+ * @param {Object} [opts]
247
+ * @param {'start'|'end'} [opts.align='start'] - Which trigger edge the menu
248
+ * lines up with first: `'start'` its left edge, `'end'` its right edge.
249
+ * Either flips to the other edge when it would leave the window.
250
+ */
251
+ static position(trigger, menu, { align = "start" } = {}) {
252
+ if (!trigger || !menu) return;
253
+ positionAnchored(trigger, menu, { align });
254
+ }
255
+ };
256
+
257
+ export {
258
+ ActionDropdown
259
+ };
260
+ //# sourceMappingURL=chunk-PAI2ZUWV.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/ui/components/action_dropdown.js"],
4
+ "sourcesContent": ["/**\n * action_dropdown.js\n *\n * Reusable dropdown component for action buttons (Add Scenario, Add Widget, etc).\n * Handles opening, closing, auto-positioning, keyboard navigation, and outside clicks.\n *\n * Auto-positioning: measures available viewport space around the trigger element\n * and picks the best direction (above/below) and alignment (left/right).\n * Adds scrolling when the menu would exceed available vertical space.\n */\n\nimport { positionAnchored } from './anchored_layer.js';\n\n/**\n * ActionDropdown - A reusable dropdown component with smart auto-positioning.\n *\n * Usage:\n * ```javascript\n * const dropdown = new ActionDropdown({\n * trigger: buttonElement,\n * options: [\n * { type: 'option1', label: 'Option 1', icon: 'icon_name', description: 'Description' },\n * { separator: true },\n * { type: 'option2', label: 'Option 2', icon: 'icon_name', description: 'Description' }\n * ],\n * onSelect: (option) => console.log('Selected:', option.type),\n * });\n *\n * // Control programmatically\n * dropdown.open();\n * dropdown.close();\n * dropdown.toggle();\n * dropdown.destroy();\n * ```\n *\n * \u2500\u2500 Separators \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n * An entry `{ separator: true }` draws a divider instead of a button: a\n * `<div role=\"separator\" class=\"twm-action-dropdown-separator\">`, which takes\n * no focus, is never reached by the arrow keys (they move between the option\n * buttons only) and never reaches `onSelect` (it has no click handler). A\n * separator only separates: one at the start or the end of the list, or one\n * right after another, is dropped, so a list assembled from groups that may\n * come out empty never draws a stray or doubled rule.\n */\nexport class ActionDropdown {\n /**\n * @param {Object} config\n * @param {HTMLElement} config.trigger - Button element that triggers the dropdown\n * @param {Array} config.options - Array of option objects with { type, label, icon, description? },\n * or `{ separator: true }` for a divider between them\n * @param {Function} config.onSelect - Callback when an option is selected\n * @param {string} [config.className=''] - Additional CSS class for the dropdown\n * @param {string} [config.menuId] - Optional ID for the menu element\n */\n constructor(config) {\n this.trigger = config.trigger;\n this.options = config.options || [];\n this.onSelect = config.onSelect;\n this.className = config.className || '';\n this.menuId = config.menuId;\n\n this.isOpen = false;\n this.menuEl = null;\n\n this._boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n this._boundHandleKeydown = this._handleKeydown.bind(this);\n this._boundHandleTriggerClick = this._handleTriggerClick.bind(this);\n\n this._init();\n }\n\n /**\n * Initialize the dropdown.\n * @private\n */\n _init() {\n if (!this.trigger) {\n console.warn('[ActionDropdown] No trigger element provided');\n return;\n }\n\n this._createMenu();\n this.trigger.addEventListener('click', this._boundHandleTriggerClick);\n }\n\n /**\n * Create the dropdown menu element.\n * @private\n */\n _createMenu() {\n const menu = document.createElement('div');\n menu.className = `twm-action-dropdown-menu ${this.className}`.trim();\n menu.setAttribute('role', 'menu');\n menu.hidden = true;\n\n if (this.menuId) {\n menu.id = this.menuId;\n }\n\n this._renderOptions(menu);\n\n // Append to body so position:fixed works without clipping.\n document.body.appendChild(menu);\n\n this.menuEl = menu;\n }\n\n /**\n * Render option buttons, and dividers for separator entries, into a\n * container element. `data-index` stays the entry's index in `options`.\n * @param {HTMLElement} container\n * @private\n */\n _renderOptions(container) {\n container.innerHTML = '';\n let pendingSeparator = false;\n this.options.forEach((opt, index) => {\n if (opt?.separator) {\n // Drawn only once an option follows it, and only if an option\n // came before it: leading, trailing and doubled separators\n // collapse.\n pendingSeparator = container.childElementCount > 0;\n return;\n }\n if (pendingSeparator) {\n const sep = document.createElement('div');\n sep.className = 'twm-action-dropdown-separator';\n sep.setAttribute('role', 'separator');\n container.appendChild(sep);\n pendingSeparator = false;\n }\n\n const optionBtn = document.createElement('button');\n optionBtn.type = 'button';\n optionBtn.className = 'twm-action-dropdown-option';\n optionBtn.dataset.index = index;\n optionBtn.dataset.type = opt.type;\n optionBtn.setAttribute('role', 'menuitem');\n\n const iconHtml = opt.icon\n ? `<span class=\"material-symbols-outlined twm-option-icon\">${opt.icon}</span>`\n : '';\n\n const descHtml = opt.description\n ? `<span class=\"twm-option-desc\">${opt.description}</span>`\n : '';\n\n optionBtn.innerHTML = `\n ${iconHtml}\n <span class=\"option-text\">\n <span class=\"twm-option-label\">${opt.label}</span>\n ${descHtml}\n </span>\n `;\n\n optionBtn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this._selectOption(opt);\n });\n\n container.appendChild(optionBtn);\n });\n }\n\n /**\n * Handle trigger button click.\n * @param {MouseEvent} e\n * @private\n */\n _handleTriggerClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.toggle();\n }\n\n /**\n * Toggle the dropdown open/closed.\n * @param {boolean} [forceState] - Optional forced state\n */\n toggle(forceState) {\n const shouldOpen = typeof forceState === 'boolean' ? forceState : !this.isOpen;\n if (shouldOpen) {\n this.open();\n } else {\n this.close();\n }\n }\n\n /**\n * Open the dropdown with auto-positioning.\n */\n open() {\n if (this.isOpen || !this.menuEl) return;\n\n this.isOpen = true;\n this.menuEl.hidden = false;\n\n // Position against trigger\n ActionDropdown.position(this.trigger, this.menuEl);\n\n // Animate in.\n //\n // OPTIONAL CHAINING, AND IT IS LOAD-BEARING. This callback runs a frame\n // after `open()` returned, and `destroy()` sets `this.menuEl = null`\n // (see below) \u2014 so a dropdown that is opened and then destroyed inside\n // one frame threw an uncaught `TypeError: Cannot read properties of\n // null` out of an animation-frame callback, where no caller has a stack\n // to catch it. That is not a hypothetical: it is what a user does every\n // time they open a picker and then click something that unmounts the\n // pane around it, and it was reproduced eighteen times in one run of a\n // consumer's settings suite. Nothing is lost by skipping the class \u2014 the\n // element it would have been added to no longer exists.\n requestAnimationFrame(() => {\n this.menuEl?.classList.add('visible');\n });\n\n // Update trigger state\n this.trigger?.classList.add('twm-is-open');\n if (this.trigger?.hasAttribute('aria-expanded')) {\n this.trigger.setAttribute('aria-expanded', 'true');\n }\n\n // Add document listeners\n document.addEventListener('click', this._boundHandleDocumentClick, true);\n document.addEventListener('keydown', this._boundHandleKeydown);\n\n // Focus first option for keyboard accessibility\n const firstOption = this.menuEl.querySelector('.twm-action-dropdown-option');\n firstOption?.focus();\n }\n\n /**\n * Close the dropdown.\n */\n close() {\n if (!this.isOpen) return;\n\n this.isOpen = false;\n\n if (this.menuEl) {\n this.menuEl.classList.remove('visible');\n this.menuEl.hidden = true;\n }\n\n // Update trigger state. `aria-expanded` belongs on the TRIGGER and has\n // to be written on every close, not only on the ones a click caused \u2014\n // an embedder that synced it from its own click handler was announcing\n // an expanded menu to a screen reader every time Escape or an outside\n // click dismissed one. The component knows when it closed; nothing else\n // reliably does.\n this.trigger?.classList.remove('twm-is-open');\n if (this.trigger?.hasAttribute('aria-expanded')) {\n this.trigger.setAttribute('aria-expanded', 'false');\n }\n\n // Remove document listeners\n document.removeEventListener('click', this._boundHandleDocumentClick, true);\n document.removeEventListener('keydown', this._boundHandleKeydown);\n }\n\n /**\n * Handle document click (close on outside click).\n * @param {MouseEvent} e\n * @private\n */\n _handleDocumentClick(e) {\n if (!this.isOpen) return;\n\n // Don't close if clicking inside dropdown or trigger\n if (this.menuEl?.contains(e.target) || this.trigger?.contains(e.target)) {\n return;\n }\n\n this.close();\n }\n\n /**\n * Handle keyboard events.\n * @param {KeyboardEvent} e\n * @private\n */\n _handleKeydown(e) {\n if (e.key === 'Escape') {\n // AND NOBODY ELSE GETS IT. An open dropdown is the innermost thing\n // on screen, so Escape means \"close this\" and nothing further \u2014\n // but the event was left to bubble, and inside a `ManagedWindow`\n // (which binds its own Escape to dismiss) that meant one keystroke\n // closed the dropdown AND the dialog around it. The user loses a\n // form they were filling in because they changed their mind about\n // one field.\n e.preventDefault();\n e.stopPropagation();\n this.close();\n this.trigger?.focus();\n return;\n }\n\n // Arrow key navigation\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {\n e.preventDefault();\n this._navigateOptions(e.key === 'ArrowDown' ? 1 : -1);\n }\n }\n\n /**\n * Navigate options with arrow keys.\n * @param {number} direction - 1 for down, -1 for up\n * @private\n */\n _navigateOptions(direction) {\n const options = this.menuEl?.querySelectorAll('.twm-action-dropdown-option');\n if (!options || options.length === 0) return;\n\n const currentIndex = Array.from(options).findIndex(opt => opt === document.activeElement);\n let nextIndex = currentIndex + direction;\n\n if (nextIndex < 0) nextIndex = options.length - 1;\n if (nextIndex >= options.length) nextIndex = 0;\n\n options[nextIndex]?.focus();\n }\n\n /**\n * Handle option selection.\n * @param {Object} option - The selected option\n * @private\n */\n _selectOption(option) {\n this.close();\n\n if (this.onSelect) {\n this.onSelect(option);\n }\n }\n\n /**\n * Update the options dynamically.\n * @param {Array} newOptions - New options array\n */\n setOptions(newOptions) {\n this.options = newOptions;\n if (this.menuEl) {\n this._renderOptions(this.menuEl);\n }\n }\n\n /**\n * Destroy the dropdown and clean up.\n */\n destroy() {\n this.close();\n\n // Remove trigger listener\n this.trigger?.removeEventListener('click', this._boundHandleTriggerClick);\n\n // Remove menu element\n this.menuEl?.remove();\n this.menuEl = null;\n\n this.trigger = null;\n this.options = [];\n this.onSelect = null;\n }\n\n // =========================================================================\n // STATIC \u2014 shared auto-positioning for any trigger + menu pair\n // =========================================================================\n\n /**\n * Position a dropdown menu relative to a trigger element: `positionAnchored`\n * (`anchored_layer.js`), the one placement every floating panel uses.\n *\n * @param {HTMLElement} trigger - The element the dropdown opens from\n * @param {HTMLElement} menu - The dropdown menu element to position\n * @param {Object} [opts]\n * @param {'start'|'end'} [opts.align='start'] - Which trigger edge the menu\n * lines up with first: `'start'` its left edge, `'end'` its right edge.\n * Either flips to the other edge when it would leave the window.\n */\n static position(trigger, menu, { align = 'start' } = {}) {\n if (!trigger || !menu) return;\n positionAnchored(trigger, menu, { align });\n }\n}\n\nexport default ActionDropdown;\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }