flexdesk 0.18.0 → 0.18.1
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 +38 -0
- package/css/flexdesk.css +33 -5
- package/css/tokens.css +7 -0
- package/dist/charts.js +78 -12
- package/dist/charts.js.map +2 -2
- package/dist/{chunk-5W4EEKSK.js → chunk-3BLIVOFD.js} +3 -3
- package/dist/{chunk-P74F6I6P.js → chunk-4X4SY3CP.js} +4 -4
- package/dist/{chunk-P74F6I6P.js.map → chunk-4X4SY3CP.js.map} +2 -2
- package/dist/{chunk-ZL3VKINP.js → chunk-HL3IIHU3.js} +494 -3
- package/dist/{chunk-ZL3VKINP.js.map → chunk-HL3IIHU3.js.map} +4 -4
- package/dist/{chunk-RKVYIXDC.js → chunk-NFZJ6P42.js} +19 -500
- package/dist/chunk-NFZJ6P42.js.map +7 -0
- package/dist/{chunk-U5GKJUOC.js → chunk-QRBOIUU3.js} +5 -6
- package/dist/chunk-QRBOIUU3.js.map +7 -0
- package/dist/flexdesk.css +33 -5
- package/dist/flow.js +1 -1
- package/dist/git.js +3 -3
- package/dist/tiles.js +4 -4
- package/dist/tokens.css +7 -0
- package/dist/widgets.js +226 -58
- package/dist/widgets.js.map +2 -2
- package/dist/wm.js +2 -2
- package/docs/components/charts.md +40 -0
- package/docs/components/chat-shell.md +8 -2
- package/docs/components/filter-builder.md +52 -6
- package/docs/components/forms-and-dialogs.md +8 -0
- package/docs/components/input-fields.md +24 -0
- package/docs/components/menus.md +4 -0
- package/docs/components/shell.md +4 -0
- package/docs/components/workspace-picker.md +46 -2
- package/package.json +1 -1
- package/src/charts/plot_config_editor.js +92 -6
- package/src/charts/plot_grid.js +6 -1
- package/src/ui/components/chat_shell.js +7 -1
- package/src/ui/components/context_menu.js +8 -2
- package/src/ui/components/filter_builder.js +53 -28
- package/src/ui/components/filter_model.js +86 -18
- package/src/ui/components/inline_renamer.js +10 -5
- package/src/ui/components/modal.js +25 -8
- package/src/ui/components/workspace_picker.js +125 -27
- package/src/ui/utils/tooltip_service.js +81 -14
- package/dist/chunk-RKVYIXDC.js.map +0 -7
- package/dist/chunk-U5GKJUOC.js.map +0 -7
- /package/dist/{chunk-5W4EEKSK.js.map → chunk-3BLIVOFD.js.map} +0 -0
|
@@ -2,20 +2,17 @@ import {
|
|
|
2
2
|
ColorField,
|
|
3
3
|
ManagedWindow,
|
|
4
4
|
ToggleField,
|
|
5
|
+
createCombobox,
|
|
5
6
|
fireEvent,
|
|
6
7
|
isHexColor,
|
|
7
8
|
normalizeHex
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-HL3IIHU3.js";
|
|
9
10
|
import {
|
|
10
11
|
choiceList,
|
|
11
|
-
choiceSections,
|
|
12
|
-
filterSections,
|
|
13
12
|
findChoice,
|
|
14
13
|
flatChoices,
|
|
15
14
|
modalHost,
|
|
16
|
-
openAnchoredLayer,
|
|
17
15
|
selectBox,
|
|
18
|
-
shownValue,
|
|
19
16
|
toggleChips
|
|
20
17
|
} from "./chunk-Q7BALEPX.js";
|
|
21
18
|
import {
|
|
@@ -448,490 +445,6 @@ var ColorListField = class {
|
|
|
448
445
|
}
|
|
449
446
|
};
|
|
450
447
|
|
|
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
|
-
};
|
|
810
|
-
}
|
|
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
|
-
};
|
|
933
|
-
}
|
|
934
|
-
|
|
935
448
|
// src/ui/components/modal.js
|
|
936
449
|
var _modalSeq = 1;
|
|
937
450
|
function openForm({ title, fields = [], defaults = {}, submitLabel = "OK", onFieldChange = null } = {}) {
|
|
@@ -1278,7 +791,8 @@ function openModal({
|
|
|
1278
791
|
backdropOpacity = void 0,
|
|
1279
792
|
maximizable = false,
|
|
1280
793
|
onMaximizeChange = null,
|
|
1281
|
-
beforeClose = null
|
|
794
|
+
beforeClose = null,
|
|
795
|
+
dismissOnBackdrop = false
|
|
1282
796
|
} = {}) {
|
|
1283
797
|
return new Promise((resolve) => {
|
|
1284
798
|
const body = document.createElement("div");
|
|
@@ -1347,10 +861,10 @@ function openModal({
|
|
|
1347
861
|
defaultWidth: width,
|
|
1348
862
|
defaultHeight: height,
|
|
1349
863
|
beforeClose,
|
|
1350
|
-
// Esc, the X and
|
|
1351
|
-
// `close`, so the listener is dropped here as well as there —
|
|
1352
|
-
//
|
|
1353
|
-
//
|
|
864
|
+
// Esc, the X and an opted-in backdrop all land here without passing
|
|
865
|
+
// through `close`, so the listener is dropped here as well as there —
|
|
866
|
+
// the dismissals a user reaches for most are exactly the ones that
|
|
867
|
+
// would otherwise leak it.
|
|
1354
868
|
onClose: () => {
|
|
1355
869
|
_dropMaxListener();
|
|
1356
870
|
if (!_resolved) {
|
|
@@ -1360,6 +874,10 @@ function openModal({
|
|
|
1360
874
|
}
|
|
1361
875
|
});
|
|
1362
876
|
win.show();
|
|
877
|
+
if (dismissOnBackdrop) {
|
|
878
|
+
win.backdropElement.addEventListener("mousedown", (e) => e.preventDefault());
|
|
879
|
+
win.backdropElement.addEventListener("click", () => win.close());
|
|
880
|
+
}
|
|
1363
881
|
if (maximizable && typeof onMaximizeChange === "function") {
|
|
1364
882
|
_onMax = (e) => {
|
|
1365
883
|
if (e.detail?.id !== win.id) return;
|
|
@@ -1423,6 +941,7 @@ function openModal({
|
|
|
1423
941
|
});
|
|
1424
942
|
}
|
|
1425
943
|
requestAnimationFrame(() => {
|
|
944
|
+
if (bodyInner.contains(document.activeElement)) return;
|
|
1426
945
|
const field = bodyInner.querySelector(
|
|
1427
946
|
'input:not([type="hidden"]):not([disabled]):not([readonly]), textarea:not([disabled]):not([readonly]), select:not([disabled])'
|
|
1428
947
|
);
|
|
@@ -1781,6 +1300,7 @@ function _esc(s) {
|
|
|
1781
1300
|
// src/ui/components/context_menu.js
|
|
1782
1301
|
var _activeMenu = null;
|
|
1783
1302
|
var _returnFocusTo = null;
|
|
1303
|
+
var _armTimer = null;
|
|
1784
1304
|
function showContextMenu(x, y, items, onAction) {
|
|
1785
1305
|
hideContextMenu();
|
|
1786
1306
|
_returnFocusTo = document.activeElement;
|
|
@@ -1824,8 +1344,8 @@ function showContextMenu(x, y, items, onAction) {
|
|
|
1824
1344
|
menu.style.display = "block";
|
|
1825
1345
|
_activeMenu = menu;
|
|
1826
1346
|
placeMenuInViewport(menu, x, y);
|
|
1827
|
-
setTimeout(() => {
|
|
1828
|
-
document.addEventListener("mousedown", _outsideHandler, {
|
|
1347
|
+
_armTimer = setTimeout(() => {
|
|
1348
|
+
document.addEventListener("mousedown", _outsideHandler, { capture: true });
|
|
1829
1349
|
}, 0);
|
|
1830
1350
|
document.addEventListener("keydown", _keyHandler);
|
|
1831
1351
|
window.addEventListener("scroll", _scrollHandler, { capture: true });
|
|
@@ -1838,6 +1358,8 @@ function hideContextMenu() {
|
|
|
1838
1358
|
_activeMenu.remove();
|
|
1839
1359
|
_activeMenu = null;
|
|
1840
1360
|
_returnFocusTo = null;
|
|
1361
|
+
clearTimeout(_armTimer);
|
|
1362
|
+
document.removeEventListener("mousedown", _outsideHandler, { capture: true });
|
|
1841
1363
|
document.removeEventListener("keydown", _keyHandler);
|
|
1842
1364
|
window.removeEventListener("scroll", _scrollHandler, { capture: true });
|
|
1843
1365
|
if (held && returnTo?.isConnected) returnTo.focus?.({ preventScroll: true });
|
|
@@ -1874,13 +1396,10 @@ function escapeHtml(s) {
|
|
|
1874
1396
|
export {
|
|
1875
1397
|
SortableList,
|
|
1876
1398
|
ColorListField,
|
|
1877
|
-
createOptionList,
|
|
1878
|
-
attachInputOptions,
|
|
1879
|
-
createCombobox,
|
|
1880
1399
|
openForm,
|
|
1881
1400
|
openConfirm,
|
|
1882
1401
|
openModal,
|
|
1883
1402
|
showContextMenu,
|
|
1884
1403
|
hideContextMenu
|
|
1885
1404
|
};
|
|
1886
|
-
//# sourceMappingURL=chunk-
|
|
1405
|
+
//# sourceMappingURL=chunk-NFZJ6P42.js.map
|