flexdesk 0.17.17 → 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.
- package/CHANGELOG.md +70 -0
- package/LICENSE +0 -0
- package/README.md +1 -0
- package/THIRD_PARTY_LICENSES.md +0 -0
- package/canvas.js +0 -0
- package/charts.js +0 -0
- package/core.js +0 -0
- package/css/flexdesk.css +7499 -5851
- package/css/reset.css +1 -1
- package/css/tokens.css +23 -1
- package/dist/canvas.js +0 -0
- package/dist/canvas.js.map +0 -0
- package/dist/charts.js +10 -6
- package/dist/charts.js.map +1 -1
- package/dist/chunk-3PHCPZHT.js +0 -0
- package/dist/chunk-3PHCPZHT.js.map +0 -0
- package/dist/chunk-5W4EEKSK.js +778 -0
- package/dist/chunk-5W4EEKSK.js.map +7 -0
- package/dist/chunk-DNJ3N4T4.js +82 -0
- package/dist/chunk-DNJ3N4T4.js.map +7 -0
- package/dist/chunk-FOOS3T5L.js +0 -0
- package/dist/chunk-FOOS3T5L.js.map +0 -0
- package/dist/chunk-GOQLR5WR.js +0 -0
- package/dist/chunk-GOQLR5WR.js.map +0 -0
- package/dist/{chunk-JKZLNKG6.js → chunk-HKPB6OFA.js} +90 -25
- package/dist/{chunk-JKZLNKG6.js.map → chunk-HKPB6OFA.js.map} +2 -2
- package/dist/chunk-HNU6MM2B.js +0 -0
- package/dist/chunk-HNU6MM2B.js.map +0 -0
- package/dist/{chunk-REJ5HLIM.js → chunk-HYJYYEPK.js} +2 -2
- package/dist/chunk-JYWURG5T.js +0 -0
- package/dist/chunk-JYWURG5T.js.map +0 -0
- package/dist/{chunk-7U4IB65H.js → chunk-LLBJ2SJH.js} +5 -3
- package/dist/{chunk-7U4IB65H.js.map → chunk-LLBJ2SJH.js.map} +1 -1
- package/dist/chunk-N7HC3ADV.js +0 -0
- package/dist/chunk-N7HC3ADV.js.map +0 -0
- package/dist/{chunk-7OL6F2AM.js → chunk-P74F6I6P.js} +6 -4
- package/dist/{chunk-7OL6F2AM.js.map → chunk-P74F6I6P.js.map} +1 -1
- package/dist/chunk-PAI2ZUWV.js +260 -0
- package/dist/chunk-PAI2ZUWV.js.map +7 -0
- package/dist/chunk-Q7BALEPX.js +527 -0
- package/dist/chunk-Q7BALEPX.js.map +7 -0
- package/dist/chunk-QXAOTGCC.js +40 -0
- package/dist/chunk-QXAOTGCC.js.map +7 -0
- package/dist/{chunk-XJNADRGR.js → chunk-RKVYIXDC.js} +664 -91
- package/dist/chunk-RKVYIXDC.js.map +7 -0
- package/dist/{chunk-2Z2ENHR3.js → chunk-U5GKJUOC.js} +3 -3
- package/dist/{chunk-2Z2ENHR3.js.map → chunk-U5GKJUOC.js.map} +1 -1
- package/dist/chunk-UKP22RUG.js +219 -0
- package/dist/chunk-UKP22RUG.js.map +7 -0
- package/dist/chunk-VLOAI3HS.js +43 -0
- package/dist/chunk-VLOAI3HS.js.map +7 -0
- package/dist/{chunk-WUFLGETN.js → chunk-WVFGV5FT.js} +2 -39
- package/dist/{chunk-WUFLGETN.js.map → chunk-WVFGV5FT.js.map} +3 -3
- package/dist/{chunk-PGZ4EHSE.js → chunk-ZL3VKINP.js} +41 -390
- package/dist/chunk-ZL3VKINP.js.map +7 -0
- package/dist/editor.js +3 -2
- package/dist/editor.js.map +1 -1
- package/dist/flexdesk.css +7499 -5851
- package/dist/flow.js +29 -97
- package/dist/flow.js.map +3 -3
- package/dist/git.js +1356 -0
- package/dist/git.js.map +7 -0
- package/dist/host.js +0 -0
- package/dist/host.js.map +0 -0
- package/dist/reset.css +1 -1
- package/dist/tile_registry-6WZPTUZV.js +0 -0
- package/dist/tile_registry-6WZPTUZV.js.map +0 -0
- package/dist/tiles.js +20 -15
- package/dist/tiles.js.map +2 -2
- package/dist/tokens.css +23 -1
- package/dist/widgets.js +3969 -1189
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +121 -33
- package/dist/wm.js.map +2 -2
- package/docs/README.md +9 -4
- package/docs/best-practices.md +2 -2
- package/docs/components/chat-shell.md +198 -0
- package/docs/components/command-palette.md +0 -0
- package/docs/components/conversation.md +10 -0
- package/docs/components/data-table-grid.md +0 -0
- package/docs/components/data-table.md +0 -0
- package/docs/components/editor.md +0 -0
- package/docs/components/filter-builder.md +246 -0
- package/docs/components/flow-canvas-editor.md +0 -0
- package/docs/components/flow-editors.md +0 -0
- package/docs/components/flow-lane-editor.md +0 -0
- package/docs/components/flow-outline-editor.md +0 -0
- package/docs/components/forms-and-dialogs.md +22 -0
- package/docs/components/git.md +355 -0
- package/docs/components/input-fields.md +167 -2
- package/docs/components/keymap.md +8 -0
- package/docs/components/markdown-and-outline.md +0 -0
- package/docs/components/menus.md +120 -3
- package/docs/components/panels-and-desktops.md +16 -5
- package/docs/components/record-list-and-fact-table.md +0 -0
- package/docs/components/settings-view.md +0 -0
- package/docs/components/shell.md +104 -1
- package/docs/components/wizard.md +0 -0
- package/docs/components/workspace-picker.md +149 -0
- package/docs/concepts/building-and-packaging.md +2 -2
- package/docs/concepts/domain-neutrality.md +0 -0
- package/docs/concepts/host-port.md +0 -0
- package/docs/concepts/packages-and-entry-points.md +14 -13
- package/docs/concepts/peer-dependencies.md +0 -0
- package/docs/concepts/state-guard.md +3 -3
- package/docs/quickstart.md +1 -1
- package/editor.js +0 -0
- package/flow.js +0 -0
- package/git.js +13 -0
- package/host.js +0 -0
- package/package.json +6 -1
- package/src/canvas/canvas_adapter.js +0 -0
- package/src/canvas/edge_router.js +0 -0
- package/src/canvas/graph/connector_router.js +0 -0
- package/src/canvas/graph/history_service.js +0 -0
- package/src/canvas/graph/node_platform.js +0 -0
- package/src/canvas/index.js +0 -0
- package/src/canvas/layered_layout.js +0 -0
- package/src/charts/data_series_plot_window.js +0 -0
- package/src/charts/downsample.js +0 -0
- package/src/charts/plot_config.js +0 -0
- package/src/charts/plotly_wrapper.js +0 -0
- package/src/core/event_bus.js +0 -0
- package/src/core/logging.js +0 -0
- package/src/core/settings.js +0 -0
- package/src/core/state_guard.js +0 -0
- package/src/core/state_machine.js +0 -0
- package/src/editor/editor_pane.js +0 -0
- package/src/editor/monaco_editor_factory.js +0 -0
- package/src/editor/monaco_loader.js +0 -0
- package/src/editor/notebook_search.js +0 -0
- package/src/editor/notebook_undo_manager.js +0 -0
- package/src/editor/split_pane_container.js +0 -0
- package/src/flow/canvas_editor/connections.js +0 -0
- package/src/flow/canvas_editor/editor.js +0 -0
- package/src/flow/canvas_editor/index.js +0 -0
- package/src/flow/canvas_editor/layout.js +0 -0
- package/src/flow/canvas_editor/strings.js +0 -0
- package/src/flow/kit/always_before.js +0 -0
- package/src/flow/kit/catalogue.js +0 -0
- package/src/flow/kit/chip_input.js +0 -0
- package/src/flow/kit/dom.js +28 -119
- package/src/flow/kit/findings.js +0 -0
- package/src/flow/kit/history.js +0 -0
- package/src/flow/kit/index.js +0 -0
- package/src/flow/kit/keys.js +0 -0
- package/src/flow/kit/logic_graph.js +0 -0
- package/src/flow/kit/references.js +0 -0
- package/src/flow/kit/settings_panel.js +0 -0
- package/src/flow/kit/settings_schema.js +0 -0
- package/src/flow/kit/step_picker.js +1 -1
- package/src/flow/kit/strings.js +0 -0
- package/src/flow/kit/value_picker.js +1 -1
- package/src/flow/kit/widgets.js +0 -0
- package/src/flow/lanes/editor.js +0 -0
- package/src/flow/lanes/index.js +0 -0
- package/src/flow/lanes/layout.js +0 -0
- package/src/flow/lanes/pipeline.js +0 -0
- package/src/flow/lanes/preview.js +0 -0
- package/src/flow/lanes/strings.js +0 -0
- package/src/flow/outline/editor.js +0 -0
- package/src/flow/outline/index.js +0 -0
- package/src/flow/outline/mapping.js +0 -0
- package/src/flow/outline/menu.js +1 -1
- package/src/flow/outline/operations.js +0 -0
- package/src/flow/outline/recognise.js +0 -0
- package/src/flow/outline/strings.js +0 -0
- package/src/git/diff_viewer.js +212 -0
- package/src/git/diff_window.js +51 -0
- package/src/git/git_graph.js +401 -0
- package/src/git/git_ops.js +61 -0
- package/src/git/git_panel.js +755 -0
- package/src/git/index.js +9 -0
- package/src/git/line_diff.js +202 -0
- package/src/help/help_modal.js +0 -0
- package/src/help/help_registry.js +0 -0
- package/src/host/external_links.js +0 -0
- package/src/host/file_picker.js +0 -0
- package/src/host/host.js +0 -0
- package/src/host/photino_host.js +0 -0
- package/src/host/photino_host.md +0 -0
- package/src/host/pywebview_host.js +0 -0
- package/src/tiles/chart_tiles.js +0 -0
- package/src/tiles/config_schema.js +0 -0
- package/src/tiles/figure_tile.js +0 -0
- package/src/tiles/layout_persistence.js +0 -0
- package/src/tiles/list_tiles.js +0 -0
- package/src/tiles/scope_picker.js +0 -0
- package/src/tiles/tile_base.js +5 -4
- package/src/tiles/tile_config_form.js +0 -0
- package/src/tiles/tile_registry.js +0 -0
- package/src/tiling/bottom_panel_tabs.js +0 -0
- package/src/tiling/command_palette.js +0 -0
- package/src/tiling/content_registry.js +0 -0
- package/src/tiling/desktops.js +10 -8
- package/src/tiling/entity_sources.js +0 -0
- package/src/tiling/keymap.js +6 -5
- package/src/tiling/kind_taxonomy.js +15 -2
- package/src/tiling/landing_table.js +0 -0
- package/src/tiling/loading_overlay.js +0 -0
- package/src/tiling/nav_panel.js +0 -0
- package/src/tiling/page_factory.js +0 -0
- package/src/tiling/panel_availability.js +0 -0
- package/src/tiling/panel_keys.js +0 -0
- package/src/tiling/shell.js +124 -17
- package/src/tiling/tab_strip.js +0 -0
- package/src/tiling/tile_renderer.js +2 -3
- package/src/tiling/tile_tab_menu.js +0 -0
- package/src/tiling/zoom.js +0 -0
- package/src/ui/base/component_base.js +0 -0
- package/src/ui/base/controller_base.js +0 -0
- package/src/ui/base/page_base.js +0 -0
- package/src/ui/components/about_dialog.js +0 -0
- package/src/ui/components/action_dropdown.js +4 -78
- package/src/ui/components/alert_log.js +0 -0
- package/src/ui/components/alerts_pane.js +0 -0
- package/src/ui/components/anchored_layer.js +255 -0
- package/src/ui/components/attribute_list_editor.js +0 -0
- package/src/ui/components/autocomplete_field.js +0 -0
- package/src/ui/components/cell_editor_host.js +0 -0
- package/src/ui/components/cell_editors.js +0 -0
- package/src/ui/components/cell_grid.js +0 -0
- package/src/ui/components/cell_paste.js +0 -0
- package/src/ui/components/cell_selection.js +0 -0
- package/src/ui/components/chat_shell.js +920 -0
- package/src/ui/components/chat_state_machine.js +223 -0
- package/src/ui/components/choice_controls.js +45 -2
- package/src/ui/components/color_field.js +3 -2
- package/src/ui/components/color_list_field.js +3 -2
- package/src/ui/components/column_fit.js +0 -0
- package/src/ui/components/column_groups.js +0 -0
- package/src/ui/components/column_order.js +0 -0
- package/src/ui/components/combobox.js +422 -0
- package/src/ui/components/conversation_view.js +17 -2
- package/src/ui/components/crosstab.js +0 -0
- package/src/ui/components/data_table.js +2 -2
- package/src/ui/components/detail_header.js +0 -0
- package/src/ui/components/document_outline.js +0 -0
- package/src/ui/components/drag_reorder.js +0 -0
- package/src/ui/components/fact_table.js +0 -0
- package/src/ui/components/fill_handle.js +0 -0
- package/src/ui/components/filter_builder.js +766 -0
- package/src/ui/components/filter_model.js +614 -0
- package/src/ui/components/gallery_picker.js +0 -0
- package/src/ui/components/header_actions.js +0 -0
- package/src/ui/components/inline_renamer.js +0 -0
- package/src/ui/components/job_progress.js +0 -0
- package/src/ui/components/managed_window.js +24 -47
- package/src/ui/components/menu_bar.js +101 -24
- package/src/ui/components/modal.js +79 -109
- package/src/ui/components/option_list.js +160 -0
- package/src/ui/components/popover.js +94 -0
- package/src/ui/components/progress.js +0 -0
- package/src/ui/components/recent_items.js +169 -0
- package/src/ui/components/record_list.js +0 -0
- package/src/ui/components/row_activation.js +0 -0
- package/src/ui/components/section_stack.js +0 -0
- package/src/ui/components/select.js +322 -0
- package/src/ui/components/series.js +0 -0
- package/src/ui/components/settings_view.js +0 -0
- package/src/ui/components/slide_out_panel.js +0 -0
- package/src/ui/components/slider_field.js +0 -0
- package/src/ui/components/sortable_list.js +0 -0
- package/src/ui/components/staged_edits.js +0 -0
- package/src/ui/components/staged_history.js +0 -0
- package/src/ui/components/table_filter_chips.js +0 -0
- package/src/ui/components/table_state_store.js +0 -0
- package/src/ui/components/table_structure.js +0 -0
- package/src/ui/components/table_values.js +0 -0
- package/src/ui/components/tabs.js +0 -0
- package/src/ui/components/tag_input.js +77 -8
- package/src/ui/components/toast.js +0 -0
- package/src/ui/components/toggle_field.js +0 -0
- package/src/ui/components/tsv.js +0 -0
- package/src/ui/components/window_taskbar.js +0 -0
- package/src/ui/components/workspace_picker.js +1155 -0
- package/src/ui/controllers/window_chrome_controller.js +4 -1
- package/src/ui/utils/auto_scrollbars.js +0 -0
- package/src/ui/utils/fire_event.js +17 -0
- package/src/ui/utils/focusable.js +25 -0
- package/src/ui/utils/markdown.js +0 -0
- package/src/ui/utils/modal_host.js +27 -0
- package/src/ui/utils/native_context_menu.js +0 -0
- package/src/ui/utils/overlay_scrollbar.js +0 -0
- package/src/ui/utils/raf_resize_observer.js +0 -0
- package/src/ui/utils/scroll_lock.js +0 -0
- package/src/ui/utils/strip_scroll.js +0 -0
- package/src/ui/utils/tooltip_service.js +0 -0
- package/tiles.js +0 -0
- package/widgets.js +12 -2
- package/wm.js +0 -0
- package/css/base.css +0 -10527
- package/css/overrides.css +0 -2596
- package/dist/chunk-7JI7HHSY.js +0 -107
- package/dist/chunk-7JI7HHSY.js.map +0 -7
- package/dist/chunk-MIW7D6WC.js +0 -526
- package/dist/chunk-MIW7D6WC.js.map +0 -7
- package/dist/chunk-PGZ4EHSE.js.map +0 -7
- package/dist/chunk-XJNADRGR.js.map +0 -7
- /package/dist/{chunk-REJ5HLIM.js.map → chunk-HYJYYEPK.js.map} +0 -0
|
@@ -2,17 +2,30 @@ import {
|
|
|
2
2
|
ColorField,
|
|
3
3
|
ManagedWindow,
|
|
4
4
|
ToggleField,
|
|
5
|
+
fireEvent,
|
|
6
|
+
isHexColor,
|
|
7
|
+
normalizeHex
|
|
8
|
+
} from "./chunk-ZL3VKINP.js";
|
|
9
|
+
import {
|
|
5
10
|
choiceList,
|
|
11
|
+
choiceSections,
|
|
12
|
+
filterSections,
|
|
6
13
|
findChoice,
|
|
7
14
|
flatChoices,
|
|
8
|
-
|
|
9
|
-
|
|
15
|
+
modalHost,
|
|
16
|
+
openAnchoredLayer,
|
|
10
17
|
selectBox,
|
|
18
|
+
shownValue,
|
|
11
19
|
toggleChips
|
|
12
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-Q7BALEPX.js";
|
|
13
21
|
import {
|
|
14
22
|
DragReorder
|
|
15
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-WVFGV5FT.js";
|
|
24
|
+
import {
|
|
25
|
+
focusMenuItem,
|
|
26
|
+
moveMenuFocus,
|
|
27
|
+
placeMenuInViewport
|
|
28
|
+
} from "./chunk-QXAOTGCC.js";
|
|
16
29
|
|
|
17
30
|
// src/ui/components/sortable_list.js
|
|
18
31
|
var ROW_CLASS = "twm-sortable-list__row";
|
|
@@ -428,21 +441,498 @@ var ColorListField = class {
|
|
|
428
441
|
#emit(commit) {
|
|
429
442
|
this.#writeInput();
|
|
430
443
|
if (this.input) {
|
|
431
|
-
this.input
|
|
432
|
-
if (commit) this.input
|
|
444
|
+
fireEvent(this.input, "input", { bubbles: true });
|
|
445
|
+
if (commit) fireEvent(this.input, "change", { bubbles: true });
|
|
433
446
|
}
|
|
434
447
|
this.onChange?.(this.getValue(), { commit });
|
|
435
448
|
}
|
|
436
449
|
};
|
|
437
450
|
|
|
438
|
-
// src/ui/components/
|
|
439
|
-
var
|
|
440
|
-
function
|
|
441
|
-
|
|
451
|
+
// src/ui/components/option_list.js
|
|
452
|
+
var SEQ = 0;
|
|
453
|
+
function node(tag, className, text) {
|
|
454
|
+
const n = document.createElement(tag);
|
|
455
|
+
if (className) n.className = className;
|
|
456
|
+
if (text !== void 0 && text !== null) n.textContent = String(text);
|
|
457
|
+
return n;
|
|
458
|
+
}
|
|
459
|
+
function marked(target, text, needle) {
|
|
460
|
+
const at = needle ? text.toLowerCase().indexOf(needle.toLowerCase()) : -1;
|
|
461
|
+
if (at < 0) {
|
|
462
|
+
target.textContent = text;
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
target.append(
|
|
466
|
+
text.slice(0, at),
|
|
467
|
+
node("mark", "twm-option-list__match", text.slice(at, at + needle.length)),
|
|
468
|
+
text.slice(at + needle.length)
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
function createOptionList({ onPick, onActive = null }) {
|
|
472
|
+
const id = `twm-option-list-${++SEQ}`;
|
|
473
|
+
const el = node("div", "twm-option-list");
|
|
474
|
+
el.id = id;
|
|
475
|
+
el.setAttribute("role", "listbox");
|
|
476
|
+
let rows = [];
|
|
477
|
+
let active = -1;
|
|
478
|
+
const paintActive = () => {
|
|
479
|
+
rows.forEach(({ row: row2 }, i) => row2.classList.toggle("twm-option-list__row--active", i === active));
|
|
480
|
+
const row = rows[active]?.row;
|
|
481
|
+
row?.scrollIntoView?.({ block: "nearest" });
|
|
482
|
+
onActive?.(row ? row.id : null);
|
|
483
|
+
};
|
|
484
|
+
const rowFor = (option, { glyphs, mark, isCurrent }) => {
|
|
485
|
+
const row = node("div", "twm-option-list__row");
|
|
486
|
+
row.id = `${id}-${rows.length}`;
|
|
487
|
+
row.setAttribute("role", "option");
|
|
488
|
+
row.setAttribute("aria-selected", String(!!isCurrent?.(option)));
|
|
489
|
+
if (option.disabled) row.setAttribute("aria-disabled", "true");
|
|
490
|
+
row.title = option.description ? `${option.label} \u2014 ${option.description}` : option.label;
|
|
491
|
+
if (glyphs) {
|
|
492
|
+
const glyph = node("span", "twm-option-list__glyph material-symbols-outlined", option.icon || "");
|
|
493
|
+
glyph.setAttribute("aria-hidden", "true");
|
|
494
|
+
row.appendChild(glyph);
|
|
495
|
+
}
|
|
496
|
+
const words = node("span", "twm-option-list__words");
|
|
497
|
+
const label = node("span", "twm-option-list__label");
|
|
498
|
+
marked(label, option.label, mark);
|
|
499
|
+
words.appendChild(label);
|
|
500
|
+
if (option.description) words.appendChild(node("span", "twm-option-list__description", option.description));
|
|
501
|
+
if (option.disabled && option.reason) words.appendChild(node("span", "twm-choice__reason", option.reason));
|
|
502
|
+
row.appendChild(words);
|
|
503
|
+
if (option.hint) row.appendChild(node("span", "twm-option-list__hint", option.hint));
|
|
504
|
+
return row;
|
|
505
|
+
};
|
|
506
|
+
const enabled = () => rows.map((r, i) => r.option.disabled ? -1 : i).filter((i) => i >= 0);
|
|
507
|
+
el.addEventListener("mousedown", (ev) => ev.preventDefault());
|
|
508
|
+
el.addEventListener("click", (ev) => {
|
|
509
|
+
const row = ev.target.closest?.('[role="option"]');
|
|
510
|
+
const hit = rows.find((r) => r.row === row);
|
|
511
|
+
if (hit && !hit.option.disabled) onPick(hit.option);
|
|
512
|
+
});
|
|
513
|
+
return {
|
|
514
|
+
el,
|
|
515
|
+
id,
|
|
516
|
+
/**
|
|
517
|
+
* Draw `sections` (`[{ heading, items }]`, as `choiceSections` gives them).
|
|
518
|
+
* @param {object} [opts]
|
|
519
|
+
* @param {string} [opts.mark] The text to mark in each label.
|
|
520
|
+
* @param {(option: object) => boolean} [opts.isCurrent] Is this the current value?
|
|
521
|
+
*/
|
|
522
|
+
setRows(sections, { mark = "", isCurrent = null } = {}) {
|
|
523
|
+
rows = [];
|
|
524
|
+
active = -1;
|
|
525
|
+
el.replaceChildren();
|
|
526
|
+
const glyphs = sections.some((s) => s.items.some((o) => o.icon));
|
|
527
|
+
for (const { heading, items } of sections) {
|
|
528
|
+
let parent = el;
|
|
529
|
+
if (heading != null) {
|
|
530
|
+
parent = node("div", "twm-option-list__section");
|
|
531
|
+
parent.setAttribute("role", "group");
|
|
532
|
+
parent.setAttribute("aria-label", heading);
|
|
533
|
+
const title = node("div", "twm-choice__group", heading);
|
|
534
|
+
title.setAttribute("aria-hidden", "true");
|
|
535
|
+
parent.appendChild(title);
|
|
536
|
+
el.appendChild(parent);
|
|
537
|
+
}
|
|
538
|
+
for (const option of items) {
|
|
539
|
+
const row = rowFor(option, { glyphs, mark, isCurrent });
|
|
540
|
+
rows.push({ option, row });
|
|
541
|
+
parent.appendChild(row);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
paintActive();
|
|
545
|
+
},
|
|
546
|
+
/** One line of text in place of the rows: nothing matched, still loading, a failure. */
|
|
547
|
+
setMessage(text) {
|
|
548
|
+
rows = [];
|
|
549
|
+
active = -1;
|
|
550
|
+
el.replaceChildren(node("div", "twm-option-list__message", text));
|
|
551
|
+
paintActive();
|
|
552
|
+
},
|
|
553
|
+
/** How many rows are drawn. */
|
|
554
|
+
get size() {
|
|
555
|
+
return rows.length;
|
|
556
|
+
},
|
|
557
|
+
/** The drawn options, in order. */
|
|
558
|
+
options: () => rows.map((r) => r.option),
|
|
559
|
+
/** The active row's option, or null. */
|
|
560
|
+
activeOption: () => rows[active]?.option ?? null,
|
|
561
|
+
/** Make row `index` active; -1, or a row that is disabled, makes none active. */
|
|
562
|
+
setActive(index) {
|
|
563
|
+
active = rows[index] && !rows[index].option.disabled ? index : -1;
|
|
564
|
+
paintActive();
|
|
565
|
+
},
|
|
566
|
+
/** The next row that may be chosen, by `step`, wrapping. From none: the first or the last. */
|
|
567
|
+
move(step) {
|
|
568
|
+
const stops = enabled();
|
|
569
|
+
if (!stops.length) return;
|
|
570
|
+
const at = stops.indexOf(active);
|
|
571
|
+
const next = at < 0 ? step > 0 ? 0 : stops.length - 1 : (at + step + stops.length) % stops.length;
|
|
572
|
+
active = stops[next];
|
|
573
|
+
paintActive();
|
|
574
|
+
},
|
|
575
|
+
/** The first (`step` 1) or last (`step` -1) row that may be chosen. */
|
|
576
|
+
moveToEnd(step) {
|
|
577
|
+
const stops = enabled();
|
|
578
|
+
active = stops.length ? stops[step > 0 ? 0 : stops.length - 1] : -1;
|
|
579
|
+
paintActive();
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// src/ui/components/combobox.js
|
|
585
|
+
var DEBOUNCE_MS = 150;
|
|
586
|
+
var SEQ2 = 0;
|
|
587
|
+
function attachInputOptions(input, {
|
|
588
|
+
options = [],
|
|
589
|
+
onPick,
|
|
590
|
+
anchor = null,
|
|
591
|
+
isCurrent = null,
|
|
592
|
+
autoHighlight = false,
|
|
593
|
+
isCandidate = null,
|
|
594
|
+
normalise = (text) => text,
|
|
595
|
+
openOnFocus = false,
|
|
596
|
+
passEnter = false,
|
|
597
|
+
loadingText = "Loading\u2026",
|
|
598
|
+
errorText = "Could not load the options.",
|
|
599
|
+
debounce = DEBOUNCE_MS,
|
|
600
|
+
panelClassName = "",
|
|
601
|
+
onSettle = null
|
|
602
|
+
} = {}) {
|
|
603
|
+
let source = options;
|
|
604
|
+
let layer = null;
|
|
605
|
+
let wanted = false;
|
|
606
|
+
let query = null;
|
|
607
|
+
let asked = 0;
|
|
608
|
+
let timer = null;
|
|
609
|
+
let controller = null;
|
|
610
|
+
let picking = false;
|
|
611
|
+
const list = createOptionList({
|
|
612
|
+
onPick: (option) => take(option),
|
|
613
|
+
onActive: (id) => {
|
|
614
|
+
if (id) input.setAttribute("aria-activedescendant", id);
|
|
615
|
+
else input.removeAttribute("aria-activedescendant");
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
const panel = document.createElement("div");
|
|
619
|
+
panel.className = `twm-option-panel${panelClassName ? ` ${panelClassName}` : ""}`;
|
|
620
|
+
panel.appendChild(list.el);
|
|
621
|
+
input.setAttribute("autocomplete", "off");
|
|
622
|
+
input.setAttribute("role", "combobox");
|
|
623
|
+
input.setAttribute("aria-autocomplete", "list");
|
|
624
|
+
input.setAttribute("aria-expanded", "false");
|
|
625
|
+
input.setAttribute("aria-controls", list.id);
|
|
626
|
+
const show = () => {
|
|
627
|
+
if (layer) {
|
|
628
|
+
layer.place();
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
layer = openAnchoredLayer({
|
|
632
|
+
anchor: anchor || input,
|
|
633
|
+
el: panel,
|
|
634
|
+
gap: 4,
|
|
635
|
+
matchWidth: true,
|
|
636
|
+
returnFocus: false,
|
|
637
|
+
onClose() {
|
|
638
|
+
layer = null;
|
|
639
|
+
wanted = false;
|
|
640
|
+
input.setAttribute("aria-expanded", "false");
|
|
641
|
+
input.removeAttribute("aria-activedescendant");
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
if (!layer) {
|
|
645
|
+
wanted = false;
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
input.setAttribute("aria-expanded", "true");
|
|
649
|
+
};
|
|
650
|
+
function forget() {
|
|
651
|
+
clearTimeout(timer);
|
|
652
|
+
asked += 1;
|
|
653
|
+
controller?.abort();
|
|
654
|
+
controller = null;
|
|
655
|
+
}
|
|
656
|
+
function close(reason = "closed") {
|
|
657
|
+
wanted = false;
|
|
658
|
+
forget();
|
|
659
|
+
layer?.close(reason);
|
|
660
|
+
}
|
|
661
|
+
function highlight(typed) {
|
|
662
|
+
const text = normalise(typed.trim());
|
|
663
|
+
if (!autoHighlight || !text) {
|
|
664
|
+
list.setActive(-1);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const exact = list.options().findIndex((o) => normalise(shownValue(o.value)) === text);
|
|
668
|
+
if (exact >= 0) list.setActive(exact);
|
|
669
|
+
else if (isCandidate?.(text)) list.setActive(-1);
|
|
670
|
+
else list.moveToEnd(1);
|
|
671
|
+
}
|
|
672
|
+
function drawRows(sections) {
|
|
673
|
+
if (!wanted) return;
|
|
674
|
+
if (!sections.length) {
|
|
675
|
+
layer?.close("empty");
|
|
676
|
+
wanted = true;
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
const text = query ?? "";
|
|
680
|
+
list.setRows(sections, { mark: normalise(text.trim()), isCurrent });
|
|
681
|
+
show();
|
|
682
|
+
highlight(text);
|
|
683
|
+
}
|
|
684
|
+
function ask(text) {
|
|
685
|
+
controller = new AbortController();
|
|
686
|
+
const token = asked;
|
|
687
|
+
const failed = (err) => {
|
|
688
|
+
if (token !== asked) return;
|
|
689
|
+
console.warn("[createCombobox] options failed", err);
|
|
690
|
+
if (!wanted) return;
|
|
691
|
+
list.setMessage(errorText);
|
|
692
|
+
show();
|
|
693
|
+
};
|
|
694
|
+
let answer;
|
|
695
|
+
try {
|
|
696
|
+
answer = source(text, { signal: controller.signal });
|
|
697
|
+
} catch (err) {
|
|
698
|
+
failed(err);
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
if (typeof answer?.then !== "function") {
|
|
702
|
+
drawRows(choiceSections(answer));
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
if (!list.size && wanted) {
|
|
706
|
+
list.setMessage(loadingText);
|
|
707
|
+
show();
|
|
708
|
+
}
|
|
709
|
+
answer.then((rows) => {
|
|
710
|
+
if (token === asked) drawRows(choiceSections(rows));
|
|
711
|
+
}, failed);
|
|
712
|
+
}
|
|
713
|
+
function refresh() {
|
|
714
|
+
forget();
|
|
715
|
+
if (typeof source !== "function") {
|
|
716
|
+
const all = choiceSections(source);
|
|
717
|
+
drawRows(query === null ? all : filterSections(all, query));
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
const text = query ?? "";
|
|
721
|
+
if (query === null || debounce <= 0) {
|
|
722
|
+
ask(text);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
const token = asked;
|
|
726
|
+
timer = setTimeout(() => {
|
|
727
|
+
if (token === asked) ask(text);
|
|
728
|
+
}, debounce);
|
|
729
|
+
}
|
|
730
|
+
function take(option) {
|
|
731
|
+
close("pick");
|
|
732
|
+
picking = true;
|
|
733
|
+
onPick?.(option);
|
|
734
|
+
picking = false;
|
|
735
|
+
}
|
|
736
|
+
function takeActive() {
|
|
737
|
+
const option = layer && list.activeOption();
|
|
738
|
+
if (!option) return false;
|
|
739
|
+
take(option);
|
|
740
|
+
return true;
|
|
741
|
+
}
|
|
742
|
+
const openFor = (text) => {
|
|
743
|
+
query = text;
|
|
744
|
+
wanted = true;
|
|
745
|
+
refresh();
|
|
746
|
+
};
|
|
747
|
+
const onInput = () => openFor(input.value);
|
|
748
|
+
const onClick = () => openFor(null);
|
|
749
|
+
const onFocus = () => {
|
|
750
|
+
if (openOnFocus && !layer && !picking) openFor(input.value || null);
|
|
751
|
+
};
|
|
752
|
+
const onBlur = () => close("blur");
|
|
753
|
+
const onKey = (ev) => {
|
|
754
|
+
if (ev.isComposing) return;
|
|
755
|
+
if (ev.key === "ArrowDown" || ev.key === "ArrowUp") {
|
|
756
|
+
ev.preventDefault();
|
|
757
|
+
ev.stopPropagation();
|
|
758
|
+
if (!layer) openFor(null);
|
|
759
|
+
list.move(ev.key === "ArrowDown" ? 1 : -1);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
if (ev.key === "Enter") {
|
|
763
|
+
if (!layer) return;
|
|
764
|
+
if (takeActive()) {
|
|
765
|
+
ev.preventDefault();
|
|
766
|
+
ev.stopImmediatePropagation();
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
close();
|
|
770
|
+
if (passEnter) return;
|
|
771
|
+
ev.preventDefault();
|
|
772
|
+
ev.stopPropagation();
|
|
773
|
+
onSettle?.();
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (ev.key === "Tab") {
|
|
777
|
+
close("blur");
|
|
778
|
+
onSettle?.();
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
input.addEventListener("input", onInput);
|
|
782
|
+
input.addEventListener("click", onClick);
|
|
783
|
+
input.addEventListener("focus", onFocus);
|
|
784
|
+
input.addEventListener("blur", onBlur);
|
|
785
|
+
input.addEventListener("keydown", onKey);
|
|
786
|
+
return {
|
|
787
|
+
get isOpen() {
|
|
788
|
+
return !!layer;
|
|
789
|
+
},
|
|
790
|
+
/** Open it showing everything, or for `text`. */
|
|
791
|
+
open(text = null) {
|
|
792
|
+
openFor(text);
|
|
793
|
+
},
|
|
794
|
+
close,
|
|
795
|
+
takeActive,
|
|
796
|
+
/** Replace the options; an open list redraws. */
|
|
797
|
+
setOptions(next) {
|
|
798
|
+
source = next;
|
|
799
|
+
if (wanted) refresh();
|
|
800
|
+
},
|
|
801
|
+
destroy() {
|
|
802
|
+
close();
|
|
803
|
+
input.removeEventListener("input", onInput);
|
|
804
|
+
input.removeEventListener("click", onClick);
|
|
805
|
+
input.removeEventListener("focus", onFocus);
|
|
806
|
+
input.removeEventListener("blur", onBlur);
|
|
807
|
+
input.removeEventListener("keydown", onKey);
|
|
808
|
+
}
|
|
809
|
+
};
|
|
442
810
|
}
|
|
443
|
-
function
|
|
444
|
-
|
|
811
|
+
function createCombobox({
|
|
812
|
+
options = [],
|
|
813
|
+
value = "",
|
|
814
|
+
onChange = null,
|
|
815
|
+
className = "",
|
|
816
|
+
panelClassName = "",
|
|
817
|
+
placeholder = "",
|
|
818
|
+
title = "",
|
|
819
|
+
ariaLabel = "",
|
|
820
|
+
name = null,
|
|
821
|
+
disabled = false,
|
|
822
|
+
normalise = (text) => text,
|
|
823
|
+
autoHighlight = true,
|
|
824
|
+
isCandidate = null,
|
|
825
|
+
validate = null,
|
|
826
|
+
loadingText,
|
|
827
|
+
errorText,
|
|
828
|
+
debounce
|
|
829
|
+
} = {}) {
|
|
830
|
+
const handed = String(value ?? "");
|
|
831
|
+
let current = normalise(handed);
|
|
832
|
+
const el = document.createElement("div");
|
|
833
|
+
el.className = `twm-combobox${className ? ` ${className}` : ""}`;
|
|
834
|
+
const input = document.createElement("input");
|
|
835
|
+
input.type = "text";
|
|
836
|
+
input.className = "twm-combobox__input";
|
|
837
|
+
input.spellcheck = false;
|
|
838
|
+
input.value = current;
|
|
839
|
+
input.disabled = !!disabled;
|
|
840
|
+
if (name) input.name = name;
|
|
841
|
+
if (placeholder) input.placeholder = placeholder;
|
|
842
|
+
if (title) input.title = title;
|
|
843
|
+
if (ariaLabel) input.setAttribute("aria-label", ariaLabel);
|
|
844
|
+
const error = document.createElement("p");
|
|
845
|
+
error.className = "twm-combobox__error";
|
|
846
|
+
error.hidden = true;
|
|
847
|
+
error.id = `twm-combobox-error-${++SEQ2}`;
|
|
848
|
+
el.append(input, error);
|
|
849
|
+
function report(next, option) {
|
|
850
|
+
const settled = normalise(String(next ?? ""));
|
|
851
|
+
if (settled === current) return;
|
|
852
|
+
current = settled;
|
|
853
|
+
onChange?.(current, option ?? null);
|
|
854
|
+
}
|
|
855
|
+
const clearValidity = () => {
|
|
856
|
+
error.hidden = true;
|
|
857
|
+
error.textContent = "";
|
|
858
|
+
input.removeAttribute("aria-invalid");
|
|
859
|
+
input.removeAttribute("aria-describedby");
|
|
860
|
+
};
|
|
861
|
+
function paintValidity() {
|
|
862
|
+
const message = validate && current ? validate(current) || null : null;
|
|
863
|
+
if (!message) {
|
|
864
|
+
clearValidity();
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
error.textContent = message;
|
|
868
|
+
error.hidden = false;
|
|
869
|
+
input.setAttribute("aria-invalid", "true");
|
|
870
|
+
input.setAttribute("aria-describedby", error.id);
|
|
871
|
+
}
|
|
872
|
+
function settle() {
|
|
873
|
+
report(input.value, null);
|
|
874
|
+
if (input.value !== current) input.value = current;
|
|
875
|
+
paintValidity();
|
|
876
|
+
}
|
|
877
|
+
input.addEventListener("input", () => {
|
|
878
|
+
report(input.value, null);
|
|
879
|
+
clearValidity();
|
|
880
|
+
});
|
|
881
|
+
const menu = attachInputOptions(input, {
|
|
882
|
+
options,
|
|
883
|
+
autoHighlight,
|
|
884
|
+
isCandidate,
|
|
885
|
+
normalise,
|
|
886
|
+
panelClassName,
|
|
887
|
+
loadingText,
|
|
888
|
+
errorText,
|
|
889
|
+
debounce,
|
|
890
|
+
isCurrent: (o) => normalise(shownValue(o.value)) === current,
|
|
891
|
+
onSettle: settle,
|
|
892
|
+
onPick(option) {
|
|
893
|
+
input.value = shownValue(option.value);
|
|
894
|
+
report(option.value, option.source);
|
|
895
|
+
settle();
|
|
896
|
+
input.focus();
|
|
897
|
+
fireEvent(input, "change", { bubbles: true });
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
input.addEventListener("blur", settle);
|
|
901
|
+
paintValidity();
|
|
902
|
+
if (onChange && current !== handed) onChange(current, null);
|
|
903
|
+
return {
|
|
904
|
+
el,
|
|
905
|
+
input,
|
|
906
|
+
get value() {
|
|
907
|
+
return current;
|
|
908
|
+
},
|
|
909
|
+
set value(next) {
|
|
910
|
+
current = normalise(String(next ?? ""));
|
|
911
|
+
input.value = current;
|
|
912
|
+
paintValidity();
|
|
913
|
+
},
|
|
914
|
+
/** The complaint on screen, or `null`. A caller that wants to refuse Save has this. */
|
|
915
|
+
get error() {
|
|
916
|
+
return error.hidden ? null : error.textContent;
|
|
917
|
+
},
|
|
918
|
+
get isOpen() {
|
|
919
|
+
return menu.isOpen;
|
|
920
|
+
},
|
|
921
|
+
setOptions: menu.setOptions,
|
|
922
|
+
/** Open the list showing everything, or for `text`. */
|
|
923
|
+
open: menu.open,
|
|
924
|
+
close: () => menu.close(),
|
|
925
|
+
focus() {
|
|
926
|
+
input.focus();
|
|
927
|
+
},
|
|
928
|
+
destroy() {
|
|
929
|
+
menu.destroy();
|
|
930
|
+
el.remove();
|
|
931
|
+
}
|
|
932
|
+
};
|
|
445
933
|
}
|
|
934
|
+
|
|
935
|
+
// src/ui/components/modal.js
|
|
446
936
|
var _modalSeq = 1;
|
|
447
937
|
function openForm({ title, fields = [], defaults = {}, submitLabel = "OK", onFieldChange = null } = {}) {
|
|
448
938
|
return new Promise((resolve) => {
|
|
@@ -461,7 +951,7 @@ function openForm({ title, fields = [], defaults = {}, submitLabel = "OK", onFie
|
|
|
461
951
|
</form>
|
|
462
952
|
`;
|
|
463
953
|
const win = new ManagedWindow({
|
|
464
|
-
container:
|
|
954
|
+
container: modalHost(),
|
|
465
955
|
id: `twm-modal-${_modalSeq++}`,
|
|
466
956
|
title: title || "Dialog",
|
|
467
957
|
icon: "edit_note",
|
|
@@ -501,12 +991,7 @@ function openForm({ title, fields = [], defaults = {}, submitLabel = "OK", onFie
|
|
|
501
991
|
body.querySelector('[data-action="cancel"]')?.addEventListener("click", () => close(null));
|
|
502
992
|
const formFields = _formFields(fields);
|
|
503
993
|
const optionsOf = new Map(formFields.map((f) => [f.name, f.options]));
|
|
504
|
-
const comboHints = /* @__PURE__ */ new Map();
|
|
505
994
|
for (const f of formFields) {
|
|
506
|
-
if (f.type === "select" && f.create) {
|
|
507
|
-
const update = _wireComboboxHint(body, f, () => optionsOf.get(f.name));
|
|
508
|
-
if (update) comboHints.set(f.name, update);
|
|
509
|
-
}
|
|
510
995
|
if (f.type === "picker") _wirePicker(body, f);
|
|
511
996
|
}
|
|
512
997
|
const rowHidden = (name) => !!body.querySelector(`[data-field-row="${name}"]`)?.hidden;
|
|
@@ -540,13 +1025,7 @@ function openForm({ title, fields = [], defaults = {}, submitLabel = "OK", onFie
|
|
|
540
1025
|
}
|
|
541
1026
|
win.show();
|
|
542
1027
|
const comboOption = (f, text) => findChoice(flatChoices(optionsOf.get(f.name)), text, true);
|
|
543
|
-
const unavailable = (f
|
|
544
|
-
const c = controls.get(f.name);
|
|
545
|
-
if (c) return c.unavailable();
|
|
546
|
-
if (f.type !== "select" || !f.create || value == null || value === "") return null;
|
|
547
|
-
const o = comboOption(f, value);
|
|
548
|
-
return o?.disabled ? o.reason || "" : null;
|
|
549
|
-
};
|
|
1028
|
+
const unavailable = (f) => controls.get(f.name)?.unavailable() ?? null;
|
|
550
1029
|
for (const f of formFields) {
|
|
551
1030
|
const el = body.querySelector(`[name="${f.name}"]`);
|
|
552
1031
|
el?.addEventListener("input", () => clearFieldError(f.name));
|
|
@@ -592,11 +1071,6 @@ function openForm({ title, fields = [], defaults = {}, submitLabel = "OK", onFie
|
|
|
592
1071
|
const el = body.querySelector(`[name="${name}"]`);
|
|
593
1072
|
if (!el) return void 0;
|
|
594
1073
|
if (el.type === "checkbox") return !!el.checked;
|
|
595
|
-
const f = fieldOf(name);
|
|
596
|
-
if (f?.type === "select" && f.create) {
|
|
597
|
-
const match = comboOption(f, el.value);
|
|
598
|
-
return match ? match.value : el.value;
|
|
599
|
-
}
|
|
600
1074
|
return el.value;
|
|
601
1075
|
},
|
|
602
1076
|
set(name, value) {
|
|
@@ -611,28 +1085,17 @@ function openForm({ title, fields = [], defaults = {}, submitLabel = "OK", onFie
|
|
|
611
1085
|
return;
|
|
612
1086
|
}
|
|
613
1087
|
const f = fieldOf(name);
|
|
614
|
-
|
|
615
|
-
el.value = value == null || shut ? "" : String(value);
|
|
616
|
-
comboHints.get(name)?.();
|
|
1088
|
+
el.value = value == null ? "" : String(value);
|
|
617
1089
|
if (f?.type === "picker") {
|
|
618
1090
|
const display = body.querySelector(`[data-picker-display="${name}"]`);
|
|
619
1091
|
if (display) display.value = _pickerLabel(f, el.value);
|
|
620
1092
|
}
|
|
621
1093
|
},
|
|
622
1094
|
setOptions(name, options) {
|
|
623
|
-
const f = fieldOf(name);
|
|
624
|
-
if (!f) return;
|
|
625
1095
|
const c = controls.get(name);
|
|
626
|
-
if (c?.setOptions)
|
|
627
|
-
optionsOf.set(name, options);
|
|
628
|
-
c.setOptions(options);
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
if (f.type !== "select" || !f.create) return;
|
|
1096
|
+
if (!c?.setOptions) return;
|
|
632
1097
|
optionsOf.set(name, options);
|
|
633
|
-
|
|
634
|
-
if (list) list.innerHTML = _datalistOptions(options);
|
|
635
|
-
comboHints.get(name)?.();
|
|
1098
|
+
c.setOptions(options);
|
|
636
1099
|
}
|
|
637
1100
|
};
|
|
638
1101
|
const fire = (name) => {
|
|
@@ -701,14 +1164,14 @@ function openForm({ title, fields = [], defaults = {}, submitLabel = "OK", onFie
|
|
|
701
1164
|
for (const f of formFields) {
|
|
702
1165
|
clearFieldError(f.name);
|
|
703
1166
|
if (rowHidden(f.name)) {
|
|
704
|
-
if (unavailable(f
|
|
1167
|
+
if (unavailable(f) != null) data[f.name] = f.type === "radio" ? null : "";
|
|
705
1168
|
continue;
|
|
706
1169
|
}
|
|
707
1170
|
const v = data[f.name];
|
|
708
1171
|
let msg = f.required && _missing(f, v) ? "Required." : null;
|
|
709
1172
|
if (!msg && _outOfRange(f, v)) msg = _rangeMessage(f);
|
|
710
1173
|
if (!msg) {
|
|
711
|
-
const reason = unavailable(f
|
|
1174
|
+
const reason = unavailable(f);
|
|
712
1175
|
if (reason != null) msg = reason ? `Not available: ${reason}` : "Not available.";
|
|
713
1176
|
}
|
|
714
1177
|
if (!msg) msg = controls.get(f.name)?.error?.() ?? null;
|
|
@@ -767,7 +1230,7 @@ function openConfirm({
|
|
|
767
1230
|
body.append(messageEl, actionsEl);
|
|
768
1231
|
let _resolved = false;
|
|
769
1232
|
const win = new ManagedWindow({
|
|
770
|
-
container:
|
|
1233
|
+
container: modalHost(),
|
|
771
1234
|
id: `twm-confirm-${_modalSeq++}`,
|
|
772
1235
|
title: title || "Confirm",
|
|
773
1236
|
icon: icon || (danger ? "warning" : "help"),
|
|
@@ -814,7 +1277,8 @@ function openModal({
|
|
|
814
1277
|
backdropBlur = void 0,
|
|
815
1278
|
backdropOpacity = void 0,
|
|
816
1279
|
maximizable = false,
|
|
817
|
-
onMaximizeChange = null
|
|
1280
|
+
onMaximizeChange = null,
|
|
1281
|
+
beforeClose = null
|
|
818
1282
|
} = {}) {
|
|
819
1283
|
return new Promise((resolve) => {
|
|
820
1284
|
const body = document.createElement("div");
|
|
@@ -861,7 +1325,7 @@ function openModal({
|
|
|
861
1325
|
_onMax = null;
|
|
862
1326
|
};
|
|
863
1327
|
const win = new ManagedWindow({
|
|
864
|
-
container:
|
|
1328
|
+
container: modalHost(),
|
|
865
1329
|
id: `twm-modal-${_modalSeq++}`,
|
|
866
1330
|
title,
|
|
867
1331
|
icon,
|
|
@@ -882,6 +1346,7 @@ function openModal({
|
|
|
882
1346
|
canDrag: true,
|
|
883
1347
|
defaultWidth: width,
|
|
884
1348
|
defaultHeight: height,
|
|
1349
|
+
beforeClose,
|
|
885
1350
|
// Esc, the X and the backdrop all land here without passing through
|
|
886
1351
|
// `close`, so the listener is dropped here as well as there — the
|
|
887
1352
|
// three dismissals a user reaches for most are exactly the ones
|
|
@@ -1023,8 +1488,54 @@ function _rangeMessage(f) {
|
|
|
1023
1488
|
function _offered(options) {
|
|
1024
1489
|
return flatChoices(options).filter((o) => !o.disabled);
|
|
1025
1490
|
}
|
|
1026
|
-
function
|
|
1027
|
-
|
|
1491
|
+
function _mountCombobox(slot, f, value) {
|
|
1492
|
+
let options = f.options;
|
|
1493
|
+
const named = (text) => findChoice(flatChoices(options), text, true);
|
|
1494
|
+
const shown = (v) => v == null || named(v)?.disabled ? "" : String(v);
|
|
1495
|
+
const hint = document.createElement("span");
|
|
1496
|
+
hint.className = "twm-combobox__hint";
|
|
1497
|
+
hint.hidden = true;
|
|
1498
|
+
const noun = f.create === true ? null : _createSpec(f).hint || "new entry";
|
|
1499
|
+
const updateHint = () => {
|
|
1500
|
+
const v = c.value.trim();
|
|
1501
|
+
const isNew = noun && v && !flatChoices(options).some((o) => String(o.value) === v);
|
|
1502
|
+
if (isNew) hint.textContent = `\u21B3 will create ${noun} \u201C${v}\u201D`;
|
|
1503
|
+
hint.hidden = !isNew;
|
|
1504
|
+
};
|
|
1505
|
+
const c = createCombobox({
|
|
1506
|
+
options: _offered(options),
|
|
1507
|
+
value: shown(value),
|
|
1508
|
+
name: f.name,
|
|
1509
|
+
placeholder: f.placeholder ?? "type to pick or create\u2026",
|
|
1510
|
+
// Enter keeps what was typed: this field exists to accept a value nobody listed.
|
|
1511
|
+
autoHighlight: false,
|
|
1512
|
+
onChange: () => updateHint()
|
|
1513
|
+
});
|
|
1514
|
+
if (f.required) c.input.required = true;
|
|
1515
|
+
c.el.appendChild(hint);
|
|
1516
|
+
slot.replaceWith(c.el);
|
|
1517
|
+
updateHint();
|
|
1518
|
+
return {
|
|
1519
|
+
read() {
|
|
1520
|
+
const o = named(c.value);
|
|
1521
|
+
return o ? o.value : c.value;
|
|
1522
|
+
},
|
|
1523
|
+
write(v) {
|
|
1524
|
+
c.value = shown(v);
|
|
1525
|
+
updateHint();
|
|
1526
|
+
},
|
|
1527
|
+
focus: c.focus,
|
|
1528
|
+
unavailable() {
|
|
1529
|
+
const o = c.value === "" ? void 0 : named(c.value);
|
|
1530
|
+
return o?.disabled ? o.reason || "" : null;
|
|
1531
|
+
},
|
|
1532
|
+
setOptions(next) {
|
|
1533
|
+
options = next;
|
|
1534
|
+
c.setOptions(_offered(next));
|
|
1535
|
+
updateHint();
|
|
1536
|
+
},
|
|
1537
|
+
dispose: c.destroy
|
|
1538
|
+
};
|
|
1028
1539
|
}
|
|
1029
1540
|
function _mountControl(host, f, value, onChange) {
|
|
1030
1541
|
const slot = host.querySelector(`[data-control-slot="${f.name}"]`);
|
|
@@ -1067,6 +1578,7 @@ function _mountControl(host, f, value, onChange) {
|
|
|
1067
1578
|
dispose: () => c2.dispose()
|
|
1068
1579
|
};
|
|
1069
1580
|
}
|
|
1581
|
+
if (f.type === "select" && f.create) return _mountCombobox(slot, f, value);
|
|
1070
1582
|
const common = { keepMissing: false, loose: true };
|
|
1071
1583
|
const disabledReason = (entry) => entry?.disabled ? entry.reason || "" : null;
|
|
1072
1584
|
if (f.type === "select") {
|
|
@@ -1173,31 +1685,11 @@ function _wirePicker(host, f) {
|
|
|
1173
1685
|
const isPair = typeof picked === "object";
|
|
1174
1686
|
hidden.value = String((isPair ? picked.value : picked) ?? "");
|
|
1175
1687
|
display.value = isPair && picked.label != null ? String(picked.label) : _pickerLabel(f, hidden.value);
|
|
1176
|
-
hidden
|
|
1688
|
+
fireEvent(hidden, "change", { bubbles: true });
|
|
1177
1689
|
};
|
|
1178
1690
|
btn.addEventListener("click", open);
|
|
1179
1691
|
display.addEventListener("click", open);
|
|
1180
1692
|
}
|
|
1181
|
-
function _wireComboboxHint(host, f, optionsOf) {
|
|
1182
|
-
const input = host.querySelector(`input[name="${f.name}"]`);
|
|
1183
|
-
const hintEl = host.querySelector(`.twm-combobox__hint[data-for="${f.name}"]`);
|
|
1184
|
-
if (!input || !hintEl) return;
|
|
1185
|
-
if (f.create === true) return;
|
|
1186
|
-
const label = _createSpec(f).hint || "new entry";
|
|
1187
|
-
const update = () => {
|
|
1188
|
-
const v = input.value.trim();
|
|
1189
|
-
const known = new Set(flatChoices(optionsOf()).map((o) => String(o.value)));
|
|
1190
|
-
if (v && !known.has(v)) {
|
|
1191
|
-
hintEl.textContent = `\u21B3 will create ${label} \u201C${v}\u201D`;
|
|
1192
|
-
hintEl.hidden = false;
|
|
1193
|
-
} else {
|
|
1194
|
-
hintEl.hidden = true;
|
|
1195
|
-
}
|
|
1196
|
-
};
|
|
1197
|
-
input.addEventListener("input", update);
|
|
1198
|
-
update();
|
|
1199
|
-
return update;
|
|
1200
|
-
}
|
|
1201
1693
|
function _renderEntry(f, defaults) {
|
|
1202
1694
|
if (f && f.section != null) {
|
|
1203
1695
|
const attr = f.name ? ` data-field-row="${f.name}"` : "";
|
|
@@ -1273,21 +1765,6 @@ function _renderField(f, value) {
|
|
|
1273
1765
|
</div>
|
|
1274
1766
|
`);
|
|
1275
1767
|
}
|
|
1276
|
-
if (f.type === "select" && f.create) {
|
|
1277
|
-
const shown = findChoice(flatChoices(f.options), value, true)?.disabled ? "" : v;
|
|
1278
|
-
const dlId = `ea-dl-${f.name}-${_modalSeq}`;
|
|
1279
|
-
const opts = _datalistOptions(f.options);
|
|
1280
|
-
const placeholder2 = f.placeholder ? `placeholder="${_esc(f.placeholder)}"` : 'placeholder="type to pick or create\u2026"';
|
|
1281
|
-
return wrap(`
|
|
1282
|
-
<div class="twm-combobox">
|
|
1283
|
-
<input name="${f.name}" type="text" list="${dlId}"
|
|
1284
|
-
value="${_esc(shown)}" ${placeholder2} ${required}
|
|
1285
|
-
autocomplete="off">
|
|
1286
|
-
<datalist id="${dlId}" data-datalist-for="${f.name}">${opts}</datalist>
|
|
1287
|
-
<span class="twm-combobox__hint" data-for="${f.name}" hidden></span>
|
|
1288
|
-
</div>
|
|
1289
|
-
`);
|
|
1290
|
-
}
|
|
1291
1768
|
if (f.type === "select") return wrap(slot);
|
|
1292
1769
|
const step = f.step != null ? `step="${f.step}"` : "";
|
|
1293
1770
|
const placeholder = f.placeholder ? `placeholder="${_esc(f.placeholder)}"` : "";
|
|
@@ -1301,13 +1778,109 @@ function _esc(s) {
|
|
|
1301
1778
|
return String(s == null ? "" : s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1302
1779
|
}
|
|
1303
1780
|
|
|
1781
|
+
// src/ui/components/context_menu.js
|
|
1782
|
+
var _activeMenu = null;
|
|
1783
|
+
var _returnFocusTo = null;
|
|
1784
|
+
function showContextMenu(x, y, items, onAction) {
|
|
1785
|
+
hideContextMenu();
|
|
1786
|
+
_returnFocusTo = document.activeElement;
|
|
1787
|
+
const menu = document.createElement("div");
|
|
1788
|
+
menu.className = "twm-context-menu ea-context-menu";
|
|
1789
|
+
menu.setAttribute("role", "menu");
|
|
1790
|
+
for (const it of items) {
|
|
1791
|
+
if (it.separator) {
|
|
1792
|
+
const sep = document.createElement("div");
|
|
1793
|
+
sep.className = "twm-context-menu__separator";
|
|
1794
|
+
sep.setAttribute("role", "separator");
|
|
1795
|
+
menu.appendChild(sep);
|
|
1796
|
+
continue;
|
|
1797
|
+
}
|
|
1798
|
+
const row = document.createElement("button");
|
|
1799
|
+
row.type = "button";
|
|
1800
|
+
row.setAttribute("role", "menuitem");
|
|
1801
|
+
let cls = "twm-context-menu-item";
|
|
1802
|
+
if (it.danger) cls += " twm-delete-node";
|
|
1803
|
+
if (it.disabled) cls += " disabled";
|
|
1804
|
+
row.className = cls;
|
|
1805
|
+
if (it.disabled) {
|
|
1806
|
+
row.disabled = true;
|
|
1807
|
+
row.setAttribute("aria-disabled", "true");
|
|
1808
|
+
}
|
|
1809
|
+
if (it.title) row.title = it.title;
|
|
1810
|
+
row.innerHTML = `
|
|
1811
|
+
<span class="material-symbols-outlined">${it.icon || ""}</span>
|
|
1812
|
+
<span>${escapeHtml(it.label)}</span>
|
|
1813
|
+
`;
|
|
1814
|
+
if (!it.disabled) {
|
|
1815
|
+
row.addEventListener("click", (e) => {
|
|
1816
|
+
e.stopPropagation();
|
|
1817
|
+
hideContextMenu();
|
|
1818
|
+
onAction?.(it.action);
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
menu.appendChild(row);
|
|
1822
|
+
}
|
|
1823
|
+
(modalHost() || document.body).appendChild(menu);
|
|
1824
|
+
menu.style.display = "block";
|
|
1825
|
+
_activeMenu = menu;
|
|
1826
|
+
placeMenuInViewport(menu, x, y);
|
|
1827
|
+
setTimeout(() => {
|
|
1828
|
+
document.addEventListener("mousedown", _outsideHandler, { once: true, capture: true });
|
|
1829
|
+
}, 0);
|
|
1830
|
+
document.addEventListener("keydown", _keyHandler);
|
|
1831
|
+
window.addEventListener("scroll", _scrollHandler, { capture: true });
|
|
1832
|
+
focusMenuItem(_enabledItems(menu)[0]);
|
|
1833
|
+
}
|
|
1834
|
+
function hideContextMenu() {
|
|
1835
|
+
if (!_activeMenu) return;
|
|
1836
|
+
const returnTo = _returnFocusTo;
|
|
1837
|
+
const held = _activeMenu.contains(document.activeElement);
|
|
1838
|
+
_activeMenu.remove();
|
|
1839
|
+
_activeMenu = null;
|
|
1840
|
+
_returnFocusTo = null;
|
|
1841
|
+
document.removeEventListener("keydown", _keyHandler);
|
|
1842
|
+
window.removeEventListener("scroll", _scrollHandler, { capture: true });
|
|
1843
|
+
if (held && returnTo?.isConnected) returnTo.focus?.({ preventScroll: true });
|
|
1844
|
+
}
|
|
1845
|
+
function _enabledItems(menu) {
|
|
1846
|
+
return [...menu.querySelectorAll(".twm-context-menu-item:not(.disabled)")];
|
|
1847
|
+
}
|
|
1848
|
+
function _keyHandler(e) {
|
|
1849
|
+
if (!_activeMenu) return;
|
|
1850
|
+
if (e.key === "Escape") {
|
|
1851
|
+
e.preventDefault();
|
|
1852
|
+
hideContextMenu();
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
moveMenuFocus(e, _enabledItems(_activeMenu));
|
|
1856
|
+
}
|
|
1857
|
+
function _scrollHandler(e) {
|
|
1858
|
+
if (_activeMenu && e.target?.nodeType && _activeMenu.contains(e.target)) return;
|
|
1859
|
+
hideContextMenu();
|
|
1860
|
+
}
|
|
1861
|
+
function _outsideHandler(e) {
|
|
1862
|
+
if (_activeMenu && !_activeMenu.contains(e.target)) hideContextMenu();
|
|
1863
|
+
}
|
|
1864
|
+
function escapeHtml(s) {
|
|
1865
|
+
return String(s).replace(/[&<>"']/g, (c) => ({
|
|
1866
|
+
"&": "&",
|
|
1867
|
+
"<": "<",
|
|
1868
|
+
">": ">",
|
|
1869
|
+
'"': """,
|
|
1870
|
+
"'": "'"
|
|
1871
|
+
})[c]);
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1304
1874
|
export {
|
|
1305
1875
|
SortableList,
|
|
1306
1876
|
ColorListField,
|
|
1307
|
-
|
|
1308
|
-
|
|
1877
|
+
createOptionList,
|
|
1878
|
+
attachInputOptions,
|
|
1879
|
+
createCombobox,
|
|
1309
1880
|
openForm,
|
|
1310
1881
|
openConfirm,
|
|
1311
|
-
openModal
|
|
1882
|
+
openModal,
|
|
1883
|
+
showContextMenu,
|
|
1884
|
+
hideContextMenu
|
|
1312
1885
|
};
|
|
1313
|
-
//# sourceMappingURL=chunk-
|
|
1886
|
+
//# sourceMappingURL=chunk-RKVYIXDC.js.map
|