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
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -23,6 +23,7 @@ const GAP = 8;
23
23
  * trigger: buttonElement,
24
24
  * options: [
25
25
  * { type: 'option1', label: 'Option 1', icon: 'icon_name', description: 'Description' },
26
+ * { separator: true },
26
27
  * { type: 'option2', label: 'Option 2', icon: 'icon_name', description: 'Description' }
27
28
  * ],
28
29
  * onSelect: (option) => console.log('Selected:', option.type),
@@ -34,12 +35,22 @@ const GAP = 8;
34
35
  * dropdown.toggle();
35
36
  * dropdown.destroy();
36
37
  * ```
38
+ *
39
+ * ── Separators ────────────────────────────────────────────────────────
40
+ * An entry `{ separator: true }` draws a divider instead of a button: a
41
+ * `<div role="separator" class="twm-action-dropdown-separator">`, which takes
42
+ * no focus, is never reached by the arrow keys (they move between the option
43
+ * buttons only) and never reaches `onSelect` (it has no click handler). A
44
+ * separator only separates: one at the start or the end of the list, or one
45
+ * right after another, is dropped, so a list assembled from groups that may
46
+ * come out empty never draws a stray or doubled rule.
37
47
  */
38
48
  export class ActionDropdown {
39
49
  /**
40
50
  * @param {Object} config
41
51
  * @param {HTMLElement} config.trigger - Button element that triggers the dropdown
42
- * @param {Array} config.options - Array of option objects with { type, label, icon, description? }
52
+ * @param {Array} config.options - Array of option objects with { type, label, icon, description? },
53
+ * or `{ separator: true }` for a divider between them
43
54
  * @param {Function} config.onSelect - Callback when an option is selected
44
55
  * @param {string} [config.className=''] - Additional CSS class for the dropdown
45
56
  * @param {string} [config.menuId] - Optional ID for the menu element
@@ -98,13 +109,30 @@ export class ActionDropdown {
98
109
  }
99
110
 
100
111
  /**
101
- * Render option buttons into a container element.
112
+ * Render option buttons, and dividers for separator entries, into a
113
+ * container element. `data-index` stays the entry's index in `options`.
102
114
  * @param {HTMLElement} container
103
115
  * @private
104
116
  */
105
117
  _renderOptions(container) {
106
118
  container.innerHTML = '';
119
+ let pendingSeparator = false;
107
120
  this.options.forEach((opt, index) => {
121
+ if (opt?.separator) {
122
+ // Drawn only once an option follows it, and only if an option
123
+ // came before it: leading, trailing and doubled separators
124
+ // collapse.
125
+ pendingSeparator = container.childElementCount > 0;
126
+ return;
127
+ }
128
+ if (pendingSeparator) {
129
+ const sep = document.createElement('div');
130
+ sep.className = 'twm-action-dropdown-separator';
131
+ sep.setAttribute('role', 'separator');
132
+ container.appendChild(sep);
133
+ pendingSeparator = false;
134
+ }
135
+
108
136
  const optionBtn = document.createElement('button');
109
137
  optionBtn.type = 'button';
110
138
  optionBtn.className = 'twm-action-dropdown-option';
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,211 @@
1
+ /**
2
+ * cell_editor_host.js — STORY-0003. ONE CELL BEING EDITED: open an editor in
3
+ * it, and close it exactly once — by a commit or by a cancel.
4
+ *
5
+ * The editor itself comes from a FACTORY, so a consumer can bring any editor
6
+ * it likes (DataTable's `editors` option); the plain text box below is the
7
+ * default. A factory is called with
8
+ *
9
+ * { value, row, rowIdx, colIdx, column: {index, header, type},
10
+ * initialText, commit(value), cancel() }
11
+ *
12
+ * and returns either an element, or `{ el, getValue?, focus?, destroy? }`.
13
+ * `initialText` is the character typed to open the editor (null when it was
14
+ * opened by F2, Enter or a double-click): the editor starts with it in place
15
+ * of the value, as a spreadsheet does.
16
+ *
17
+ * ══ THE KEYS, WHICH THE HOST OWNS ════════════════════════════════════════
18
+ *
19
+ * Enter commit, and the table moves the cursor down (Shift: up)
20
+ * Tab commit, and the cursor moves right (Shift: left)
21
+ * Escape cancel: the cell is drawn as it was, and nothing is called
22
+ * focus out commit, without moving — a click elsewhere keeps the edit
23
+ *
24
+ * Enter and Tab commit `getValue()` — an input, select or textarea's `value`
25
+ * when the factory returned the field itself; any other editor without a
26
+ * `getValue` calls `commit(value)` itself. Such an editor has no value the host
27
+ * can read: Enter, Tab and the focus leaving do nothing, and `commit()` from
28
+ * outside (its row leaving a virtual window) CANCELS it. Every key
29
+ * pressed in the editor stops there: the table's own keys (the arrows, Ctrl+A,
30
+ * Ctrl+C) are not the editor's.
31
+ *
32
+ * ══ EXACTLY ONCE ═════════════════════════════════════════════════════════
33
+ *
34
+ * A session closes once. Escape's own repaint takes the focused input out of
35
+ * the document, and a browser fires `focusout` for that — the commit listener.
36
+ * The session is marked closed BEFORE anything is taken out, so that late
37
+ * `focusout` (and a second Enter, and an editor that calls `commit` twice)
38
+ * finds nothing to do.
39
+ */
40
+
41
+ /**
42
+ * The default editor: a text box holding the value as text (or the character
43
+ * that opened it). Left unchanged, it commits the value it was given — not
44
+ * that value turned into a string — so opening and leaving a number cell is
45
+ * no edit at all.
46
+ */
47
+ export function createTextEditor({ value, initialText = null }) {
48
+ const input = document.createElement('input');
49
+ input.type = 'text';
50
+ input.className = 'twm-dt-editor';
51
+ input.spellcheck = false;
52
+ input.setAttribute('autocomplete', 'off');
53
+ const start = value == null ? '' : String(value);
54
+ input.value = initialText != null ? String(initialText) : start;
55
+ return {
56
+ el: input,
57
+ getValue: () => (initialText == null && input.value === start ? value : input.value),
58
+ focus: () => {
59
+ input.focus({ preventScroll: true });
60
+ const end = input.value.length;
61
+ try { input.setSelectionRange(end, end); } catch (_) { /* not a text type */ }
62
+ },
63
+ };
64
+ }
65
+
66
+ /** What a factory returned, as one shape — or null when it returned nothing. */
67
+ function normalise(made) {
68
+ if (!made) return null;
69
+ const isNode = typeof Node !== 'undefined' && made instanceof Node;
70
+ const el = isNode ? made : made.el;
71
+ if (!el || (typeof Node !== 'undefined' && !(el instanceof Node))) return null;
72
+ const own = isNode ? {} : made;
73
+ let getValue = typeof own.getValue === 'function' ? own.getValue : null;
74
+ // A form field's value is the editor's; any other element says for itself.
75
+ if (!getValue && el.matches?.('input, select, textarea')) getValue = () => el.value;
76
+ return {
77
+ el,
78
+ getValue,
79
+ focus: typeof own.focus === 'function' ? own.focus : null,
80
+ destroy: typeof own.destroy === 'function' ? own.destroy : null,
81
+ };
82
+ }
83
+
84
+ export class CellEditorHost {
85
+ /**
86
+ * @param {object} o
87
+ * @param {(session, value, move, reason) => void} o.onCommit after the
88
+ * cell is drawn as it was again; `move` is 'down', 'up', 'right', 'left'
89
+ * or null; `reason` is 'key', 'blur', 'editor' or 'scroll'.
90
+ * @param {(session, reason) => void} o.onCancel
91
+ */
92
+ constructor({ onCommit, onCancel }) {
93
+ this._onCommit = onCommit;
94
+ this._onCancel = onCancel;
95
+ /** The open session, or null. */
96
+ this.session = null;
97
+ }
98
+
99
+ /**
100
+ * Open an editor in `td`. Returns whether one opened (a factory that
101
+ * throws or returns nothing opens none, and the cell is untouched).
102
+ */
103
+ open({ td, factory, value, row, rowIdx, colIdx, column, initialText = null, ...extra }) {
104
+ if (this.session) return false;
105
+ const s = {
106
+ ...extra, td, value, row, rowIdx, colIdx, column,
107
+ previous: value, closed: false, offs: [], saved: null, editor: null,
108
+ };
109
+ let made = null;
110
+ try {
111
+ made = factory({
112
+ value, row, rowIdx, colIdx, column, initialText,
113
+ commit: (v) => this._finish(s, v, null, 'editor'),
114
+ cancel: () => this._cancel(s, 'editor'),
115
+ });
116
+ } catch (err) {
117
+ console.error('[DataTable] cell editor factory threw', err);
118
+ return false;
119
+ }
120
+ const editor = normalise(made);
121
+ if (!editor) return false;
122
+ s.editor = editor;
123
+ s.saved = [...td.childNodes];
124
+ s.savedTitle = td.getAttribute('title');
125
+ this.session = s;
126
+ td.classList.add('twm-dt-cell--editing');
127
+ td.replaceChildren(editor.el);
128
+
129
+ const on = (type, fn) => {
130
+ editor.el.addEventListener(type, fn);
131
+ s.offs.push(() => editor.el.removeEventListener(type, fn));
132
+ };
133
+ on('keydown', (ev) => {
134
+ if (ev.isComposing) return;
135
+ ev.stopPropagation();
136
+ if (ev.key === 'Escape') {
137
+ ev.preventDefault();
138
+ this._cancel(s, 'key');
139
+ } else if (ev.key === 'Enter' && !ev.altKey && !ev.ctrlKey && !ev.metaKey && editor.getValue) {
140
+ ev.preventDefault();
141
+ this._finish(s, editor.getValue(), ev.shiftKey ? 'up' : 'down', 'key');
142
+ } else if (ev.key === 'Tab' && !ev.altKey && !ev.ctrlKey && !ev.metaKey && editor.getValue) {
143
+ ev.preventDefault();
144
+ this._finish(s, editor.getValue(), ev.shiftKey ? 'left' : 'right', 'key');
145
+ }
146
+ });
147
+ // A press inside the editor is the editor's, not a click on the cell.
148
+ for (const type of ['click', 'dblclick', 'mousedown', 'contextmenu']) {
149
+ on(type, (ev) => ev.stopPropagation());
150
+ }
151
+ on('focusout', (ev) => {
152
+ if (s.closed || !editor.getValue) return;
153
+ const to = ev.relatedTarget;
154
+ if (to && editor.el.contains(to)) return;
155
+ // The window losing focus (another app) is not a click elsewhere.
156
+ const doc = editor.el.ownerDocument;
157
+ if (!to && doc && typeof doc.hasFocus === 'function' && !doc.hasFocus()) return;
158
+ this._finish(s, editor.getValue(), null, 'blur');
159
+ });
160
+
161
+ try {
162
+ if (editor.focus) editor.focus();
163
+ else editor.el.focus?.({ preventScroll: true });
164
+ } catch (_) { /* not focusable */ }
165
+ return true;
166
+ }
167
+
168
+ /** Commit the open session with what its editor holds. */
169
+ commit(move = null, reason = 'editor') {
170
+ const s = this.session;
171
+ if (!s) return false;
172
+ if (!s.editor.getValue) { this._cancel(s, reason); return false; }
173
+ return this._finish(s, s.editor.getValue(), move, reason);
174
+ }
175
+
176
+ /** Cancel the open session: the cell is drawn as it was. */
177
+ cancel(reason = 'editor') {
178
+ const s = this.session;
179
+ if (!s) return false;
180
+ return this._cancel(s, reason);
181
+ }
182
+
183
+ /** Take the editor out and put the cell's own content back. */
184
+ _close(s) {
185
+ s.closed = true;
186
+ if (this.session === s) this.session = null;
187
+ for (const off of s.offs) { try { off(); } catch (_) { /* gone */ } }
188
+ s.offs = [];
189
+ try { s.editor.destroy?.(); } catch (err) {
190
+ console.error('[DataTable] cell editor destroy threw', err);
191
+ }
192
+ s.td.classList.remove('twm-dt-cell--editing');
193
+ s.td.replaceChildren(...s.saved);
194
+ if (s.savedTitle == null) s.td.removeAttribute('title');
195
+ else s.td.setAttribute('title', s.savedTitle);
196
+ }
197
+
198
+ _finish(s, value, move, reason) {
199
+ if (s.closed) return false;
200
+ this._close(s);
201
+ this._onCommit?.(s, value, move, reason);
202
+ return true;
203
+ }
204
+
205
+ _cancel(s, reason) {
206
+ if (s.closed) return false;
207
+ this._close(s);
208
+ this._onCancel?.(s, reason);
209
+ return true;
210
+ }
211
+ }