flexdesk 0.16.1 → 0.17.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 (252) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/LICENSE +0 -0
  3. package/README.md +2217 -42
  4. package/THIRD_PARTY_LICENSES.md +0 -0
  5. package/css/base.css +1834 -75
  6. package/css/flexdesk.css +3146 -164
  7. package/css/overrides.css +16 -0
  8. package/css/reset.css +0 -0
  9. package/css/tokens.css +0 -0
  10. package/dist/canvas.js +6 -239
  11. package/dist/canvas.js.map +3 -3
  12. package/dist/charts.js +709 -18
  13. package/dist/charts.js.map +4 -4
  14. package/dist/chunk-3PHCPZHT.js +0 -0
  15. package/dist/chunk-3PHCPZHT.js.map +0 -0
  16. package/dist/chunk-4A7SJC3T.js +503 -0
  17. package/dist/chunk-4A7SJC3T.js.map +7 -0
  18. package/dist/{chunk-6JSOVNID.js → chunk-CVMCZZX2.js} +635 -10
  19. package/dist/chunk-CVMCZZX2.js.map +7 -0
  20. package/dist/chunk-D3COLZZ2.js +1311 -0
  21. package/dist/chunk-D3COLZZ2.js.map +7 -0
  22. package/dist/chunk-FOOS3T5L.js +0 -0
  23. package/dist/chunk-FOOS3T5L.js.map +0 -0
  24. package/dist/{chunk-SN4LSOAR.js → chunk-GOQLR5WR.js} +452 -2
  25. package/dist/chunk-GOQLR5WR.js.map +7 -0
  26. package/dist/{chunk-FL5KFNQH.js → chunk-HNU6MM2B.js} +52 -17
  27. package/dist/chunk-HNU6MM2B.js.map +7 -0
  28. package/dist/chunk-HYJYYEPK.js +375 -0
  29. package/dist/chunk-HYJYYEPK.js.map +7 -0
  30. package/dist/chunk-JKZLNKG6.js +2041 -0
  31. package/dist/chunk-JKZLNKG6.js.map +7 -0
  32. package/dist/chunk-JYWURG5T.js +0 -0
  33. package/dist/chunk-JYWURG5T.js.map +0 -0
  34. package/dist/{chunk-RQM5Z2MU.js → chunk-MCIXFNVF.js} +633 -311
  35. package/dist/chunk-MCIXFNVF.js.map +7 -0
  36. package/dist/chunk-N7HC3ADV.js +30 -0
  37. package/dist/chunk-N7HC3ADV.js.map +7 -0
  38. package/dist/{chunk-FBGP5KN5.js → chunk-Q36EPT2R.js} +3268 -203
  39. package/dist/chunk-Q36EPT2R.js.map +7 -0
  40. package/dist/chunk-QWQJUDPD.js +610 -0
  41. package/dist/chunk-QWQJUDPD.js.map +7 -0
  42. package/dist/{chunk-NLTOY57M.js → chunk-UURZ37SD.js} +28 -48
  43. package/dist/chunk-UURZ37SD.js.map +7 -0
  44. package/dist/{chunk-B5Q53LAJ.js → chunk-UWO43BZU.js} +2 -2
  45. package/dist/chunk-UWO43BZU.js.map +7 -0
  46. package/dist/chunk-WVFGV5FT.js +0 -0
  47. package/dist/chunk-WVFGV5FT.js.map +0 -0
  48. package/dist/core.js +170 -118
  49. package/dist/core.js.map +2 -2
  50. package/dist/editor.js +156 -70
  51. package/dist/editor.js.map +2 -2
  52. package/dist/flexdesk.css +3146 -164
  53. package/dist/flow.js +2789 -306
  54. package/dist/flow.js.map +4 -4
  55. package/dist/host.js +109 -3
  56. package/dist/host.js.map +3 -3
  57. package/dist/reset.css +0 -0
  58. package/dist/tile_registry-6WZPTUZV.js +0 -0
  59. package/dist/tile_registry-6WZPTUZV.js.map +0 -0
  60. package/dist/tiles.js +657 -234
  61. package/dist/tiles.js.map +4 -4
  62. package/dist/tokens.css +0 -0
  63. package/dist/widgets.js +3252 -2124
  64. package/dist/widgets.js.map +4 -4
  65. package/dist/wm.js +1227 -347
  66. package/dist/wm.js.map +4 -4
  67. package/package.json +3 -1
  68. package/src/canvas/canvas_adapter.js +5 -0
  69. package/src/canvas/edge_router.js +0 -0
  70. package/src/canvas/graph/connector_router.js +0 -0
  71. package/src/canvas/graph/history_service.js +0 -0
  72. package/src/canvas/graph/node_platform.js +0 -0
  73. package/src/canvas/index.js +1 -0
  74. package/src/canvas/layered_layout.js +244 -0
  75. package/src/charts/chart_types.js +0 -0
  76. package/src/charts/data_series_plot_window.js +0 -0
  77. package/src/charts/downsample.js +2 -2
  78. package/src/charts/plot_config.js +53 -29
  79. package/src/charts/plot_config_editor.js +627 -0
  80. package/src/charts/plot_grid.js +449 -0
  81. package/src/charts/plot_popout_window.js +103 -317
  82. package/src/charts/plotly_wrapper.js +22 -2
  83. package/src/charts/series_transforms.js +206 -0
  84. package/src/core/event_bus.js +0 -0
  85. package/src/core/logging.js +1 -1
  86. package/src/core/settings.js +105 -19
  87. package/src/core/state_guard.js +93 -51
  88. package/src/core/state_guard_config.js +85 -74
  89. package/src/core/state_machine.js +0 -0
  90. package/src/editor/editor_pane.js +3 -3
  91. package/src/editor/monaco_editor_factory.js +124 -60
  92. package/src/editor/monaco_loader.js +103 -28
  93. package/src/editor/notebook_search.js +0 -0
  94. package/src/editor/notebook_tab_bar.js +38 -52
  95. package/src/editor/notebook_undo_manager.js +0 -0
  96. package/src/editor/split_pane_container.js +6 -3
  97. package/src/flow/canvas_editor/connections.js +90 -0
  98. package/src/flow/canvas_editor/editor.js +2351 -153
  99. package/src/flow/canvas_editor/index.js +2 -1
  100. package/src/flow/canvas_editor/layout.js +119 -12
  101. package/src/flow/canvas_editor/strings.js +80 -0
  102. package/src/flow/kit/always_before.js +0 -0
  103. package/src/flow/kit/catalogue.js +97 -3
  104. package/src/flow/kit/chip_input.js +0 -0
  105. package/src/flow/kit/dom.js +0 -0
  106. package/src/flow/kit/findings.js +0 -0
  107. package/src/flow/kit/history.js +0 -0
  108. package/src/flow/kit/index.js +3 -3
  109. package/src/flow/kit/keys.js +7 -1
  110. package/src/flow/kit/logic_graph.js +97 -15
  111. package/src/flow/kit/references.js +0 -0
  112. package/src/flow/kit/settings_panel.js +0 -0
  113. package/src/flow/kit/settings_schema.js +2 -1
  114. package/src/flow/kit/step_picker.js +0 -0
  115. package/src/flow/kit/strings.js +3 -1
  116. package/src/flow/kit/value_picker.js +0 -0
  117. package/src/flow/kit/widgets.js +86 -109
  118. package/src/flow/lanes/editor.js +136 -14
  119. package/src/flow/lanes/index.js +3 -1
  120. package/src/flow/lanes/layout.js +0 -0
  121. package/src/flow/lanes/pipeline.js +44 -2
  122. package/src/flow/lanes/preview.js +0 -0
  123. package/src/flow/lanes/strings.js +1 -0
  124. package/src/flow/outline/editor.js +0 -0
  125. package/src/flow/outline/index.js +0 -0
  126. package/src/flow/outline/mapping.js +0 -0
  127. package/src/flow/outline/menu.js +0 -0
  128. package/src/flow/outline/operations.js +0 -0
  129. package/src/flow/outline/recognise.js +0 -0
  130. package/src/flow/outline/strings.js +0 -0
  131. package/src/help/help_modal.js +168 -42
  132. package/src/help/help_registry.js +12 -1
  133. package/src/host/external_links.js +0 -0
  134. package/src/host/file_picker.js +117 -0
  135. package/src/host/host.js +10 -2
  136. package/src/host/photino_host.js +9 -0
  137. package/src/host/photino_host.md +21 -3
  138. package/src/host/pywebview_host.js +10 -0
  139. package/src/tiles/chart_tiles.js +53 -245
  140. package/src/tiles/config_schema.js +8 -0
  141. package/src/tiles/figure_tile.js +322 -0
  142. package/src/tiles/layout_persistence.js +3 -3
  143. package/src/tiles/list_tiles.js +7 -7
  144. package/src/tiles/scope_picker.js +0 -0
  145. package/src/tiles/tile_base.js +1 -1
  146. package/src/tiles/tile_config_form.js +442 -0
  147. package/src/tiles/tile_grid.js +2 -0
  148. package/src/tiles/tile_registry.js +0 -0
  149. package/src/tiling/bottom_panel_tabs.js +67 -99
  150. package/src/tiling/command_palette.js +64 -19
  151. package/src/tiling/content_registry.js +4 -1
  152. package/src/tiling/desktops.js +11 -1
  153. package/src/tiling/entity_sources.js +0 -0
  154. package/src/tiling/keymap.js +403 -128
  155. package/src/tiling/kind_taxonomy.js +78 -0
  156. package/src/tiling/landing_table.js +0 -0
  157. package/src/tiling/loading_overlay.js +0 -0
  158. package/src/tiling/nav_panel.js +59 -10
  159. package/src/tiling/page_factory.js +5 -5
  160. package/src/tiling/panel_availability.js +49 -0
  161. package/src/tiling/panel_keys.js +0 -0
  162. package/src/tiling/shell.js +114 -12
  163. package/src/tiling/tab_strip.js +0 -0
  164. package/src/tiling/tile_breadcrumb.js +0 -0
  165. package/src/tiling/tile_renderer.js +105 -9
  166. package/src/tiling/tile_tab_menu.js +0 -0
  167. package/src/tiling/tile_tree.js +0 -0
  168. package/src/tiling/wm.js +562 -30
  169. package/src/tiling/zoom.js +0 -0
  170. package/src/ui/base/component_base.js +0 -0
  171. package/src/ui/base/controller_base.js +0 -0
  172. package/src/ui/base/page_base.js +0 -0
  173. package/src/ui/components/about_dialog.js +0 -0
  174. package/src/ui/components/action_dropdown.js +30 -2
  175. package/src/ui/components/alert_log.js +0 -0
  176. package/src/ui/components/alerts_pane.js +0 -0
  177. package/src/ui/components/attribute_list_editor.js +0 -0
  178. package/src/ui/components/autocomplete_field.js +0 -0
  179. package/src/ui/components/cell_editor_host.js +211 -0
  180. package/src/ui/components/cell_grid.js +648 -0
  181. package/src/ui/components/cell_selection.js +220 -0
  182. package/src/ui/components/choice_controls.js +351 -0
  183. package/src/ui/components/color_field.js +211 -0
  184. package/src/ui/components/color_list_field.js +210 -0
  185. package/src/ui/components/column_fit.js +0 -0
  186. package/src/ui/components/column_groups.js +314 -0
  187. package/src/ui/components/column_order.js +142 -0
  188. package/src/ui/components/computing_status_window.js +98 -25
  189. package/src/ui/components/confirm_dialog.js +81 -38
  190. package/src/ui/components/context_menu.js +2 -2
  191. package/src/ui/components/conversation_view.js +440 -0
  192. package/src/ui/components/crosstab.js +289 -0
  193. package/src/ui/components/data_table.js +601 -62
  194. package/src/ui/components/detail_header.js +22 -11
  195. package/src/ui/components/document_outline.js +397 -0
  196. package/src/ui/components/drag_reorder.js +0 -0
  197. package/src/ui/components/fact_table.js +0 -0
  198. package/src/ui/components/fill_handle.js +322 -0
  199. package/src/ui/components/gallery_picker.js +19 -8
  200. package/src/ui/components/header_actions.js +55 -0
  201. package/src/ui/components/inline_renamer.js +0 -0
  202. package/src/ui/components/job_progress.js +51 -0
  203. package/src/ui/components/managed_window.js +27 -4
  204. package/src/ui/components/menu_bar.js +745 -0
  205. package/src/ui/components/modal.js +449 -75
  206. package/src/ui/components/notification_history.js +3 -3
  207. package/src/ui/components/progress.js +483 -0
  208. package/src/ui/components/record_list.js +0 -0
  209. package/src/ui/components/row_activation.js +0 -0
  210. package/src/ui/components/section_stack.js +298 -0
  211. package/src/ui/components/series.js +173 -0
  212. package/src/ui/components/settings_view.js +634 -0
  213. package/src/ui/components/slide_out_panel.js +43 -6
  214. package/src/ui/components/slider_field.js +275 -18
  215. package/src/ui/components/sortable_list.js +0 -0
  216. package/src/ui/components/staged_edits.js +343 -0
  217. package/src/ui/components/staged_history.js +172 -0
  218. package/src/ui/components/table_filter_chips.js +0 -0
  219. package/src/ui/components/table_state_store.js +0 -0
  220. package/src/ui/components/table_structure.js +182 -0
  221. package/src/ui/components/table_values.js +0 -0
  222. package/src/ui/components/tabs.js +497 -0
  223. package/src/ui/components/tag_input.js +215 -0
  224. package/src/ui/components/toast.js +25 -8
  225. package/src/ui/components/toggle_field.js +175 -0
  226. package/src/ui/components/tree_view.js +55 -34
  227. package/src/ui/components/window_taskbar.js +0 -0
  228. package/src/ui/components/wizard.js +563 -0
  229. package/src/ui/controllers/window_chrome_controller.js +2 -2
  230. package/src/ui/notification_center.js +375 -119
  231. package/src/ui/utils/auto_scrollbars.js +0 -0
  232. package/src/ui/utils/markdown.js +709 -0
  233. package/src/ui/utils/native_context_menu.js +0 -0
  234. package/src/ui/utils/overlay_scrollbar.js +16 -12
  235. package/src/ui/utils/raf_resize_observer.js +0 -0
  236. package/src/ui/utils/scroll_lock.js +0 -0
  237. package/src/ui/utils/strip_scroll.js +55 -0
  238. package/src/ui/utils/tooltip_service.js +0 -8
  239. package/dist/chunk-6JSOVNID.js.map +0 -7
  240. package/dist/chunk-B5Q53LAJ.js.map +0 -7
  241. package/dist/chunk-FBGP5KN5.js.map +0 -7
  242. package/dist/chunk-FL5KFNQH.js.map +0 -7
  243. package/dist/chunk-NLTOY57M.js.map +0 -7
  244. package/dist/chunk-NNFTETK3.js +0 -633
  245. package/dist/chunk-NNFTETK3.js.map +0 -7
  246. package/dist/chunk-O5OHMWBB.js +0 -284
  247. package/dist/chunk-O5OHMWBB.js.map +0 -7
  248. package/dist/chunk-RQM5Z2MU.js.map +0 -7
  249. package/dist/chunk-SN4LSOAR.js.map +0 -7
  250. package/dist/chunk-W6FBJLPW.js +0 -367
  251. package/dist/chunk-W6FBJLPW.js.map +0 -7
  252. package/src/ui/controllers/panel_state_machine.js +0 -634
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  getSetting
3
- } from "./chunk-FL5KFNQH.js";
3
+ } from "./chunk-HNU6MM2B.js";
4
4
 
5
5
  // src/ui/components/managed_window.js
6
- var STORAGE_KEY = "ecosim.managedWindows.v1";
6
+ var DEFAULT_WINDOW_STORAGE_KEY = "flexdesk.managedWindows.v1";
7
+ var _storageKey = DEFAULT_WINDOW_STORAGE_KEY;
7
8
  var BASE_Z_INDEX = 6e3;
8
9
  var MAX_Z_INDEX = 6999;
9
10
  var TOP_BAR_HEIGHT = 35;
@@ -22,10 +23,10 @@ function _collectFocusable(root) {
22
23
  "textarea:not([disabled])",
23
24
  '[tabindex]:not([tabindex="-1"])'
24
25
  ].join(",");
25
- return Array.from(root.querySelectorAll(sel)).filter((el) => {
26
- if (el.hidden) return false;
27
- if (el.closest("[hidden]")) return false;
28
- const r = el.getBoundingClientRect();
26
+ return Array.from(root.querySelectorAll(sel)).filter((el2) => {
27
+ if (el2.hidden) return false;
28
+ if (el2.closest("[hidden]")) return false;
29
+ const r = el2.getBoundingClientRect();
29
30
  return r.width > 0 || r.height > 0;
30
31
  });
31
32
  }
@@ -33,7 +34,7 @@ var _stateCache = null;
33
34
  function _loadState() {
34
35
  if (_stateCache) return _stateCache;
35
36
  try {
36
- const raw = localStorage.getItem(STORAGE_KEY);
37
+ const raw = localStorage.getItem(_storageKey);
37
38
  _stateCache = raw ? JSON.parse(raw) : {};
38
39
  } catch {
39
40
  _stateCache = {};
@@ -43,7 +44,7 @@ function _loadState() {
43
44
  function _saveState(state) {
44
45
  _stateCache = state;
45
46
  try {
46
- localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
47
+ localStorage.setItem(_storageKey, JSON.stringify(state));
47
48
  } catch (err) {
48
49
  console.warn("[ManagedWindow] Failed to save state:", err);
49
50
  }
@@ -639,7 +640,7 @@ var ManagedWindow = class {
639
640
  * x = max(minX, min(x, maxX)) = max(0, min(x, maxX)) ✓
640
641
  * y = max(minY, min(y, maxY)) = max(TOP, min(y, maxY)) ✓
641
642
  *
642
- * EcoAgent's and EcoSim's modals depend on that arithmetic; prove the
643
+ * Embedders' modals depend on that arithmetic; prove the
643
644
  * equivalence in review by substitution rather than by testing.
644
645
  */
645
646
  _bounds() {
@@ -1199,6 +1200,23 @@ var ManagedWindow = class {
1199
1200
  }
1200
1201
  }
1201
1202
  // ========== Static Methods ==========
1203
+ /**
1204
+ * Choose where window geometry is saved and restored. Call once at boot,
1205
+ * before the first window opens; from then on windows read and write only
1206
+ * `storageKey`. The geometry already read from another key is dropped, so
1207
+ * a window shown afterwards restores what `storageKey` holds.
1208
+ *
1209
+ * @param {object} [opts]
1210
+ * @param {string} [opts.storageKey=DEFAULT_WINDOW_STORAGE_KEY]
1211
+ */
1212
+ static configure({ storageKey = DEFAULT_WINDOW_STORAGE_KEY } = {}) {
1213
+ _storageKey = storageKey;
1214
+ _stateCache = null;
1215
+ }
1216
+ /** @returns {string} The localStorage key window geometry uses. */
1217
+ static get storageKey() {
1218
+ return _storageKey;
1219
+ }
1202
1220
  /**
1203
1221
  * Get a window by ID.
1204
1222
  */
@@ -1231,6 +1249,602 @@ var ManagedWindow = class {
1231
1249
  }
1232
1250
  };
1233
1251
 
1252
+ // src/ui/components/toggle_field.js
1253
+ var ToggleField = class {
1254
+ /**
1255
+ * @param {object} o
1256
+ * @param {boolean} [o.checked=false]
1257
+ * @param {string?} [o.label] text beside the switch, and its accessible name
1258
+ * @param {string?} [o.ariaLabel] accessible name when there is no visible label
1259
+ * @param {string?} [o.onText] state text shown while on
1260
+ * @param {string?} [o.offText] state text shown while off
1261
+ * @param {boolean} [o.disabled=false]
1262
+ * @param {boolean} [o.compact=false] a smaller track, for dense rows
1263
+ * @param {string?} [o.name] the input's name, for a surrounding form
1264
+ * @param {string?} [o.id] the input's id
1265
+ * @param {Function?}[o.onChange] `(checked: boolean)` after a user toggle
1266
+ */
1267
+ constructor({
1268
+ checked = false,
1269
+ label = null,
1270
+ ariaLabel = null,
1271
+ onText = null,
1272
+ offText = null,
1273
+ disabled = false,
1274
+ compact = false,
1275
+ name = null,
1276
+ id = null,
1277
+ onChange = null
1278
+ } = {}) {
1279
+ this._initial = !!checked;
1280
+ this._label = label == null ? null : String(label);
1281
+ this._ariaLabel = ariaLabel == null ? null : String(ariaLabel);
1282
+ this._onText = onText == null ? null : String(onText);
1283
+ this._offText = offText == null ? null : String(offText);
1284
+ this._disabled = !!disabled;
1285
+ this._compact = !!compact;
1286
+ this._name = name == null ? null : String(name);
1287
+ this._id = id == null ? null : String(id);
1288
+ this._onChange = typeof onChange === "function" ? onChange : null;
1289
+ this.root = null;
1290
+ this.input = null;
1291
+ this._stateEl = null;
1292
+ this._disposers = [];
1293
+ }
1294
+ /** Build the switch once and hand back its root; later calls return it. */
1295
+ render() {
1296
+ if (this.root) return this.root;
1297
+ const root = document.createElement("label");
1298
+ root.className = "twm-toggle";
1299
+ if (this._compact) root.classList.add("twm-toggle--compact");
1300
+ const control = document.createElement("span");
1301
+ control.className = "twm-toggle__control";
1302
+ const input = document.createElement("input");
1303
+ input.type = "checkbox";
1304
+ input.className = "twm-toggle__input";
1305
+ input.setAttribute("role", "switch");
1306
+ if (this._name) input.name = this._name;
1307
+ if (this._id) input.id = this._id;
1308
+ if (this._ariaLabel && !this._label) input.setAttribute("aria-label", this._ariaLabel);
1309
+ input.checked = this._initial;
1310
+ const track = document.createElement("span");
1311
+ track.className = "twm-toggle__track";
1312
+ track.setAttribute("aria-hidden", "true");
1313
+ const knob = document.createElement("span");
1314
+ knob.className = "twm-toggle__knob";
1315
+ track.appendChild(knob);
1316
+ control.append(input, track);
1317
+ root.appendChild(control);
1318
+ if (this._onText != null || this._offText != null) {
1319
+ this._stateEl = document.createElement("span");
1320
+ this._stateEl.className = "twm-toggle__state";
1321
+ this._stateEl.setAttribute("aria-hidden", "true");
1322
+ root.appendChild(this._stateEl);
1323
+ }
1324
+ if (this._label != null) {
1325
+ const labelEl = document.createElement("span");
1326
+ labelEl.className = "twm-toggle__label";
1327
+ labelEl.textContent = this._label;
1328
+ root.appendChild(labelEl);
1329
+ }
1330
+ this.root = root;
1331
+ this.input = input;
1332
+ this.setDisabled(this._disabled);
1333
+ this._sync();
1334
+ const onKeyDown = (e) => {
1335
+ if (e.key !== " " && e.key !== "Enter") return;
1336
+ e.preventDefault();
1337
+ if (e.repeat) return;
1338
+ input.click();
1339
+ };
1340
+ const onKeyUp = (e) => {
1341
+ if (e.key === " ") e.preventDefault();
1342
+ };
1343
+ const onChange = () => {
1344
+ this._sync();
1345
+ this._onChange?.(input.checked);
1346
+ };
1347
+ input.addEventListener("keydown", onKeyDown);
1348
+ input.addEventListener("keyup", onKeyUp);
1349
+ input.addEventListener("change", onChange);
1350
+ this._disposers.push(
1351
+ () => input.removeEventListener("keydown", onKeyDown),
1352
+ () => input.removeEventListener("keyup", onKeyUp),
1353
+ () => input.removeEventListener("change", onChange)
1354
+ );
1355
+ return root;
1356
+ }
1357
+ /** Whether the switch is on. */
1358
+ get() {
1359
+ return this.input ? this.input.checked : this._initial;
1360
+ }
1361
+ /** Turn it on or off. Does NOT call `onChange`. */
1362
+ set(checked) {
1363
+ if (!this.input) {
1364
+ this._initial = !!checked;
1365
+ return;
1366
+ }
1367
+ this.input.checked = !!checked;
1368
+ this._sync();
1369
+ }
1370
+ /** A disabled switch ignores clicks and keys alike. */
1371
+ setDisabled(disabled) {
1372
+ this._disabled = !!disabled;
1373
+ if (!this.input) return;
1374
+ this.input.disabled = this._disabled;
1375
+ this.root.classList.toggle("twm-toggle--disabled", this._disabled);
1376
+ }
1377
+ dispose() {
1378
+ for (const d of this._disposers) {
1379
+ try {
1380
+ d();
1381
+ } catch {
1382
+ }
1383
+ }
1384
+ this._disposers = [];
1385
+ this.root?.remove();
1386
+ this.root = null;
1387
+ this.input = null;
1388
+ this._stateEl = null;
1389
+ }
1390
+ _sync() {
1391
+ const on = this.input.checked;
1392
+ this.input.setAttribute("aria-checked", on ? "true" : "false");
1393
+ if (this._stateEl) this._stateEl.textContent = (on ? this._onText : this._offText) ?? "";
1394
+ }
1395
+ };
1396
+
1397
+ // src/ui/components/color_field.js
1398
+ var HEX_RE = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
1399
+ var RGB_RE = /^rgba?\(\s*([^)]*)\)$/i;
1400
+ function isHexColor(value) {
1401
+ return typeof value === "string" && HEX_RE.test(value.trim());
1402
+ }
1403
+ function normalizeHex(value) {
1404
+ if (typeof value !== "string") return null;
1405
+ const s = value.trim();
1406
+ if (HEX_RE.test(s)) {
1407
+ const h = s.slice(1).toLowerCase();
1408
+ return h.length === 3 ? `#${h[0]}${h[0]}${h[1]}${h[1]}${h[2]}${h[2]}` : `#${h}`;
1409
+ }
1410
+ const m = RGB_RE.exec(s);
1411
+ if (!m) return null;
1412
+ const parts = m[1].split(/\s*[,/]\s*|\s+/).filter(Boolean);
1413
+ if (parts.length < 3 || parts.length > 4) return null;
1414
+ const channels = [];
1415
+ for (const p of parts.slice(0, 3)) {
1416
+ const pct = p.endsWith("%");
1417
+ const n = Number(pct ? p.slice(0, -1) : p);
1418
+ if (!Number.isFinite(n)) return null;
1419
+ const v = pct ? n / 100 * 255 : n;
1420
+ if (v < 0 || v > 255) return null;
1421
+ channels.push(Math.round(v));
1422
+ }
1423
+ return `#${channels.map((c) => c.toString(16).padStart(2, "0")).join("")}`;
1424
+ }
1425
+ function displayText(value) {
1426
+ if (value == null) return "";
1427
+ return normalizeHex(String(value)) ?? String(value);
1428
+ }
1429
+ var ColorField = class {
1430
+ /**
1431
+ * @param {Object} [o]
1432
+ * @param {string} [o.value] initial colour (hex or rgb()); '' for none
1433
+ * @param {string[]} [o.presets] preset colours drawn as buttons; ones that
1434
+ * are not colours are left out
1435
+ * @param {string} [o.name] `name` of the hex box, for a host form
1436
+ * @param {string} [o.label] accessible name of the swatch and the box
1437
+ * @param {Function} [o.onChange] `(value, { commit })`, value as `getValue()`
1438
+ */
1439
+ constructor({ value = "", presets = [], name = null, label = null, onChange = null } = {}) {
1440
+ this.initial = displayText(value);
1441
+ this.presets = (Array.isArray(presets) ? presets : []).map(normalizeHex).filter(Boolean);
1442
+ this.name = name;
1443
+ this.label = label;
1444
+ this.onChange = typeof onChange === "function" ? onChange : null;
1445
+ this.root = null;
1446
+ this.swatch = null;
1447
+ this.hex = null;
1448
+ this._disposers = [];
1449
+ }
1450
+ render() {
1451
+ if (this.root) return this.root;
1452
+ const root = document.createElement("div");
1453
+ root.className = "twm-color-field";
1454
+ const swatch = document.createElement("input");
1455
+ swatch.type = "color";
1456
+ swatch.className = "twm-color-field__swatch";
1457
+ swatch.value = normalizeHex(this.initial) ?? "#000000";
1458
+ if (this.label) swatch.title = this.label;
1459
+ const hex = document.createElement("input");
1460
+ hex.type = "text";
1461
+ hex.className = "twm-color-field__hex";
1462
+ hex.value = this.initial;
1463
+ hex.placeholder = "#rrggbb";
1464
+ hex.spellcheck = false;
1465
+ hex.autocomplete = "off";
1466
+ hex.maxLength = 7;
1467
+ if (this.name) hex.name = this.name;
1468
+ if (this.label) hex.setAttribute("aria-label", this.label);
1469
+ root.append(swatch, hex);
1470
+ this.root = root;
1471
+ this.swatch = swatch;
1472
+ this.hex = hex;
1473
+ if (this.presets.length) {
1474
+ const strip = document.createElement("div");
1475
+ strip.className = "twm-color-field__presets";
1476
+ for (const color of this.presets) {
1477
+ const btn = document.createElement("button");
1478
+ btn.type = "button";
1479
+ btn.className = "twm-color-field__preset";
1480
+ btn.style.backgroundColor = color;
1481
+ btn.title = color;
1482
+ btn.setAttribute("aria-label", color);
1483
+ this.#listen(btn, "click", () => this.#writeBox(color, true));
1484
+ strip.appendChild(btn);
1485
+ }
1486
+ root.appendChild(strip);
1487
+ }
1488
+ this.#listen(swatch, "input", () => this.#writeBox(swatch.value, false));
1489
+ this.#listen(swatch, "change", () => this.#writeBox(swatch.value, true));
1490
+ this.#listen(hex, "input", () => this.#sync(false));
1491
+ this.#listen(hex, "change", () => {
1492
+ const v = this.getValue();
1493
+ if (v) hex.value = v;
1494
+ this.#sync(true);
1495
+ });
1496
+ this.#sync(null);
1497
+ return root;
1498
+ }
1499
+ /** `#rrggbb`, `''` when empty, or `null` when the box holds no hex colour. */
1500
+ getValue() {
1501
+ const text = this.getText().trim();
1502
+ if (text === "") return "";
1503
+ return isHexColor(text) ? normalizeHex(text) : null;
1504
+ }
1505
+ /** The box as it stands. */
1506
+ getText() {
1507
+ return this.hex ? this.hex.value : this.initial;
1508
+ }
1509
+ isValid() {
1510
+ return this.getValue() !== null;
1511
+ }
1512
+ /** Write a colour (hex or rgb()) without firing `onChange`. */
1513
+ setValue(value) {
1514
+ const text = displayText(value);
1515
+ if (!this.hex) {
1516
+ this.initial = text;
1517
+ return;
1518
+ }
1519
+ this.hex.value = text;
1520
+ this.#sync(null);
1521
+ }
1522
+ focus() {
1523
+ this.hex?.focus();
1524
+ }
1525
+ dispose() {
1526
+ for (const d of this._disposers) {
1527
+ try {
1528
+ d();
1529
+ } catch {
1530
+ }
1531
+ }
1532
+ this._disposers = [];
1533
+ this.root = null;
1534
+ this.swatch = null;
1535
+ this.hex = null;
1536
+ }
1537
+ #listen(el2, type, fn) {
1538
+ el2.addEventListener(type, fn);
1539
+ this._disposers.push(() => el2.removeEventListener(type, fn));
1540
+ }
1541
+ /** Put `text` in the box and fire the box's own events, as typing would. */
1542
+ #writeBox(text, commit) {
1543
+ this.hex.value = text;
1544
+ this.hex.dispatchEvent(new Event("input", { bubbles: true }));
1545
+ if (commit) this.hex.dispatchEvent(new Event("change", { bubbles: true }));
1546
+ }
1547
+ /** Swatch and invalid mark follow the box; `commit === null` is silent. */
1548
+ #sync(commit) {
1549
+ const v = this.getValue();
1550
+ if (v) this.swatch.value = v;
1551
+ this.root.classList.toggle("twm-color-field--invalid", v === null);
1552
+ if (commit !== null) this.onChange?.(v, { commit });
1553
+ }
1554
+ };
1555
+
1556
+ // src/ui/components/choice_controls.js
1557
+ var shownValue = (v) => v === void 0 ? "" : typeof v === "string" ? v : JSON.stringify(v);
1558
+ function el(tag, className, text) {
1559
+ const n = document.createElement(tag);
1560
+ if (className) n.className = className;
1561
+ if (text !== void 0 && text !== null) n.textContent = String(text);
1562
+ return n;
1563
+ }
1564
+ var isGroup = (o) => o !== null && typeof o === "object" && Array.isArray(o.options) && "group" in o;
1565
+ function choiceOption(o) {
1566
+ if (o === null || typeof o !== "object") {
1567
+ return { value: o, label: shownValue(o), description: null, disabled: false, reason: null };
1568
+ }
1569
+ return {
1570
+ value: o.value,
1571
+ label: o.label != null ? String(o.label) : shownValue(o.value),
1572
+ description: o.description != null ? String(o.description) : null,
1573
+ disabled: Boolean(o.disabled),
1574
+ reason: o.reason != null && o.reason !== "" ? String(o.reason) : null
1575
+ };
1576
+ }
1577
+ function flatChoices(options) {
1578
+ const out = [];
1579
+ for (const o of options || []) {
1580
+ if (isGroup(o)) for (const inner of o.options) out.push(choiceOption(inner));
1581
+ else out.push(choiceOption(o));
1582
+ }
1583
+ return out;
1584
+ }
1585
+ function groupHeads(options) {
1586
+ const heads = /* @__PURE__ */ new Map();
1587
+ let at = 0;
1588
+ for (const o of options || []) {
1589
+ if (!isGroup(o)) {
1590
+ at += 1;
1591
+ continue;
1592
+ }
1593
+ if (o.options.length) heads.set(at, String(o.group ?? ""));
1594
+ at += o.options.length;
1595
+ }
1596
+ return heads;
1597
+ }
1598
+ var groupHeading = (text) => el("div", "twm-choice__group", text);
1599
+ function sameChoice(v, value, loose = false) {
1600
+ if (v === value) return true;
1601
+ if (!loose || v === void 0 || value === void 0 || v === null || value === null) return false;
1602
+ return String(v) === String(value);
1603
+ }
1604
+ function choiceIndex(list, v, loose = false, from = 0) {
1605
+ for (let i = from; i < list.length; i += 1) if (list[i].value === v) return i;
1606
+ if (!loose) return -1;
1607
+ for (let i = from; i < list.length; i += 1) if (sameChoice(list[i].value, v, true)) return i;
1608
+ return -1;
1609
+ }
1610
+ function findChoice(list, v, loose = false) {
1611
+ const i = choiceIndex(list, v, loose);
1612
+ return i >= 0 ? list[i] : void 0;
1613
+ }
1614
+ var optionText = (o) => o.disabled && o.reason ? `${o.label} \u2014 ${o.reason}` : o.label;
1615
+ function selectBox(options, value, {
1616
+ empty = null,
1617
+ label = "",
1618
+ notFound = (v) => `${v} (not found)`,
1619
+ keepMissing = true,
1620
+ valueAttr = "index",
1621
+ loose = false,
1622
+ className = "twm-flow-input twm-flow-input--select"
1623
+ } = {}) {
1624
+ const box = el("select", className);
1625
+ if (label) box.setAttribute("aria-label", label);
1626
+ let entries = [];
1627
+ const add = (parent, o) => {
1628
+ const attr = valueAttr === "index" ? String(entries.length) : o.value === void 0 ? "" : String(o.value);
1629
+ const opt = el("option", "", optionText(o));
1630
+ opt.value = attr;
1631
+ if (o.disabled) {
1632
+ opt.disabled = true;
1633
+ if (o.reason) opt.title = o.reason;
1634
+ }
1635
+ parent.appendChild(opt);
1636
+ entries.push(o);
1637
+ };
1638
+ const firstEnabled = () => {
1639
+ const i = entries.findIndex((e) => !e.disabled);
1640
+ return i >= 0 ? i : 0;
1641
+ };
1642
+ const select = (v) => {
1643
+ const start = empty !== null ? 1 : 0;
1644
+ const at = choiceIndex(entries, v, loose, start);
1645
+ if (at < 0 || entries[at].disabled || entries[at].stray) return false;
1646
+ box.selectedIndex = at;
1647
+ return true;
1648
+ };
1649
+ const place = (v) => {
1650
+ if (select(v)) return;
1651
+ if (keepMissing && v !== void 0 && v !== null && v !== "") {
1652
+ add(box, {
1653
+ value: v,
1654
+ label: notFound(shownValue(v)),
1655
+ description: null,
1656
+ disabled: false,
1657
+ reason: null,
1658
+ stray: true
1659
+ });
1660
+ box.selectedIndex = entries.length - 1;
1661
+ return;
1662
+ }
1663
+ box.selectedIndex = empty !== null ? 0 : firstEnabled();
1664
+ };
1665
+ const fill = (opts, v) => {
1666
+ box.replaceChildren();
1667
+ entries = [];
1668
+ if (empty !== null) add(box, {
1669
+ value: void 0,
1670
+ label: String(empty),
1671
+ description: null,
1672
+ disabled: false,
1673
+ reason: null
1674
+ });
1675
+ for (const o of opts || []) {
1676
+ if (isGroup(o)) {
1677
+ const g = el("optgroup");
1678
+ g.label = String(o.group ?? "");
1679
+ for (const inner of o.options) add(g, choiceOption(inner));
1680
+ box.appendChild(g);
1681
+ } else {
1682
+ add(box, choiceOption(o));
1683
+ }
1684
+ }
1685
+ place(v);
1686
+ };
1687
+ const entry = () => box.selectedIndex < 0 ? null : entries[box.selectedIndex] ?? null;
1688
+ const valueOf = () => entry()?.value;
1689
+ fill(options, value);
1690
+ return {
1691
+ box,
1692
+ valueOf,
1693
+ entry,
1694
+ select,
1695
+ setOptions(opts, v = valueOf()) {
1696
+ fill(opts, v);
1697
+ }
1698
+ };
1699
+ }
1700
+ function toggleChips({
1701
+ options,
1702
+ value,
1703
+ label,
1704
+ readOnly,
1705
+ onChange,
1706
+ notFound = (v) => `${v} (not found)`,
1707
+ keepMissing = true,
1708
+ loose = false
1709
+ }) {
1710
+ const root = el("div", "twm-flow-togglechips");
1711
+ root.setAttribute("role", "group");
1712
+ if (label) root.setAttribute("aria-label", label);
1713
+ let shown = [];
1714
+ let list = [];
1715
+ let chosen = /* @__PURE__ */ new Set();
1716
+ let strays = [];
1717
+ const emit = () => {
1718
+ const out = valueOf();
1719
+ onChange?.(out.length ? out : void 0);
1720
+ };
1721
+ const chipFor = (text, pressed, toggle, { stray = false, reason = null, disabled = false } = {}) => {
1722
+ const b = el("button", `twm-flow-togglechip${stray ? " twm-flow-togglechip--missing" : ""}`, text);
1723
+ b.type = "button";
1724
+ b.setAttribute("aria-pressed", pressed ? "true" : "false");
1725
+ b.disabled = Boolean(readOnly) || disabled;
1726
+ if (reason) {
1727
+ b.title = reason;
1728
+ b.appendChild(el("span", "twm-choice__reason", reason));
1729
+ }
1730
+ b.addEventListener("click", () => {
1731
+ const on = toggle();
1732
+ b.setAttribute("aria-pressed", on ? "true" : "false");
1733
+ emit();
1734
+ });
1735
+ return b;
1736
+ };
1737
+ const draw = (opts, stored) => {
1738
+ shown = opts || [];
1739
+ list = flatChoices(shown);
1740
+ const heads = groupHeads(shown);
1741
+ const wanted = Array.isArray(stored) ? stored : [];
1742
+ const offered = (v) => {
1743
+ const o = findChoice(list, v, loose);
1744
+ return o && !o.disabled ? o : void 0;
1745
+ };
1746
+ chosen = new Set(wanted.map(offered).filter(Boolean).map((o) => o.value));
1747
+ strays = keepMissing ? wanted.filter((v) => !offered(v)) : [];
1748
+ root.replaceChildren();
1749
+ list.forEach((o, i) => {
1750
+ if (heads.has(i)) root.appendChild(groupHeading(heads.get(i)));
1751
+ root.appendChild(chipFor(o.label, chosen.has(o.value), () => {
1752
+ if (o.disabled) return false;
1753
+ if (chosen.has(o.value)) chosen.delete(o.value);
1754
+ else chosen.add(o.value);
1755
+ return chosen.has(o.value);
1756
+ }, { disabled: o.disabled, reason: o.disabled ? o.reason : null }));
1757
+ });
1758
+ for (const v of [...strays]) {
1759
+ root.appendChild(chipFor(notFound(shownValue(v)), true, () => {
1760
+ const i = strays.indexOf(v);
1761
+ if (i >= 0) {
1762
+ strays.splice(i, 1);
1763
+ return false;
1764
+ }
1765
+ strays.push(v);
1766
+ return true;
1767
+ }, { stray: true }));
1768
+ }
1769
+ };
1770
+ const valueOf = () => [...list.filter((o) => chosen.has(o.value)).map((o) => o.value), ...strays];
1771
+ draw(options, value);
1772
+ return {
1773
+ el: root,
1774
+ valueOf,
1775
+ setValue: (stored) => draw(shown, stored),
1776
+ setOptions: (opts) => draw(opts, valueOf())
1777
+ };
1778
+ }
1779
+ function choiceList({
1780
+ options,
1781
+ value,
1782
+ name,
1783
+ label,
1784
+ readOnly,
1785
+ onChange,
1786
+ notFound = (v) => `${v} (not found)`,
1787
+ keepMissing = true,
1788
+ loose = false
1789
+ }) {
1790
+ const root = el("div", "twm-flow-choices");
1791
+ root.setAttribute("role", "radiogroup");
1792
+ if (label) root.setAttribute("aria-label", label);
1793
+ let list = [];
1794
+ let radios = [];
1795
+ const draw = (opts, current) => {
1796
+ list = flatChoices(opts);
1797
+ const heads = groupHeads(opts);
1798
+ radios = [];
1799
+ root.replaceChildren();
1800
+ const named = choiceIndex(list, current, loose);
1801
+ const at = named >= 0 && !list[named].disabled ? named : -1;
1802
+ list.forEach((o, i) => {
1803
+ if (heads.has(i)) root.appendChild(groupHeading(heads.get(i)));
1804
+ const row = el("label", `twm-flow-choice${o.disabled ? " twm-choice--disabled" : ""}`);
1805
+ const radio = el("input", "twm-flow-choice__radio");
1806
+ radio.type = "radio";
1807
+ radio.name = name;
1808
+ radio.checked = i === at;
1809
+ radio.disabled = Boolean(readOnly) || o.disabled;
1810
+ radio.addEventListener("change", () => {
1811
+ if (radio.checked) onChange?.(o.value);
1812
+ });
1813
+ radios.push(radio);
1814
+ const words = el("span", "twm-flow-choice__words");
1815
+ words.appendChild(el("span", "twm-flow-choice__label", o.label));
1816
+ if (o.description !== null) words.appendChild(el("span", "twm-flow-choice__sub", o.description));
1817
+ if (o.disabled && o.reason) words.appendChild(el("span", "twm-choice__reason", o.reason));
1818
+ row.append(radio, words);
1819
+ root.appendChild(row);
1820
+ });
1821
+ if (keepMissing && current !== void 0 && named < 0) {
1822
+ root.appendChild(el("p", "twm-flow-field__note", notFound(shownValue(current))));
1823
+ }
1824
+ };
1825
+ const checkedAt = () => radios.findIndex((r) => r.checked);
1826
+ const entry = () => list[checkedAt()] ?? null;
1827
+ const api = {
1828
+ el: root,
1829
+ focus: () => (radios.find((r) => r.checked && !r.disabled) || radios.find((r) => !r.disabled))?.focus({ preventScroll: true }),
1830
+ entry,
1831
+ valueOf: () => entry()?.value,
1832
+ select(v) {
1833
+ const named = choiceIndex(list, v, loose);
1834
+ const at = named >= 0 && !list[named].disabled ? named : -1;
1835
+ radios.forEach((r, i) => {
1836
+ r.checked = i === at;
1837
+ });
1838
+ return at >= 0;
1839
+ },
1840
+ setOptions(opts) {
1841
+ draw(opts, api.valueOf());
1842
+ }
1843
+ };
1844
+ draw(options, value);
1845
+ return api;
1846
+ }
1847
+
1234
1848
  // src/ui/components/row_activation.js
1235
1849
  var ROW_CONTROL_SELECTOR = [
1236
1850
  "button",
@@ -1268,10 +1882,21 @@ function isRowActivation(ev, gesture = "click", { scope = null } = {}) {
1268
1882
  }
1269
1883
 
1270
1884
  export {
1885
+ DEFAULT_WINDOW_STORAGE_KEY,
1271
1886
  ManagedWindow,
1887
+ ToggleField,
1888
+ isHexColor,
1889
+ normalizeHex,
1890
+ ColorField,
1891
+ shownValue,
1892
+ flatChoices,
1893
+ findChoice,
1894
+ selectBox,
1895
+ toggleChips,
1896
+ choiceList,
1272
1897
  ROW_CONTROL_SELECTOR,
1273
1898
  isRowControl,
1274
1899
  endsTextSelection,
1275
1900
  isRowActivation
1276
1901
  };
1277
- //# sourceMappingURL=chunk-6JSOVNID.js.map
1902
+ //# sourceMappingURL=chunk-CVMCZZX2.js.map