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.
Files changed (44) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/css/flexdesk.css +33 -5
  3. package/css/tokens.css +7 -0
  4. package/dist/charts.js +78 -12
  5. package/dist/charts.js.map +2 -2
  6. package/dist/{chunk-5W4EEKSK.js → chunk-3BLIVOFD.js} +3 -3
  7. package/dist/{chunk-P74F6I6P.js → chunk-4X4SY3CP.js} +4 -4
  8. package/dist/{chunk-P74F6I6P.js.map → chunk-4X4SY3CP.js.map} +2 -2
  9. package/dist/{chunk-ZL3VKINP.js → chunk-HL3IIHU3.js} +494 -3
  10. package/dist/{chunk-ZL3VKINP.js.map → chunk-HL3IIHU3.js.map} +4 -4
  11. package/dist/{chunk-RKVYIXDC.js → chunk-NFZJ6P42.js} +19 -500
  12. package/dist/chunk-NFZJ6P42.js.map +7 -0
  13. package/dist/{chunk-U5GKJUOC.js → chunk-QRBOIUU3.js} +5 -6
  14. package/dist/chunk-QRBOIUU3.js.map +7 -0
  15. package/dist/flexdesk.css +33 -5
  16. package/dist/flow.js +1 -1
  17. package/dist/git.js +3 -3
  18. package/dist/tiles.js +4 -4
  19. package/dist/tokens.css +7 -0
  20. package/dist/widgets.js +226 -58
  21. package/dist/widgets.js.map +2 -2
  22. package/dist/wm.js +2 -2
  23. package/docs/components/charts.md +40 -0
  24. package/docs/components/chat-shell.md +8 -2
  25. package/docs/components/filter-builder.md +52 -6
  26. package/docs/components/forms-and-dialogs.md +8 -0
  27. package/docs/components/input-fields.md +24 -0
  28. package/docs/components/menus.md +4 -0
  29. package/docs/components/shell.md +4 -0
  30. package/docs/components/workspace-picker.md +46 -2
  31. package/package.json +1 -1
  32. package/src/charts/plot_config_editor.js +92 -6
  33. package/src/charts/plot_grid.js +6 -1
  34. package/src/ui/components/chat_shell.js +7 -1
  35. package/src/ui/components/context_menu.js +8 -2
  36. package/src/ui/components/filter_builder.js +53 -28
  37. package/src/ui/components/filter_model.js +86 -18
  38. package/src/ui/components/inline_renamer.js +10 -5
  39. package/src/ui/components/modal.js +25 -8
  40. package/src/ui/components/workspace_picker.js +125 -27
  41. package/src/ui/utils/tooltip_service.js +81 -14
  42. package/dist/chunk-RKVYIXDC.js.map +0 -7
  43. package/dist/chunk-U5GKJUOC.js.map +0 -7
  44. /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-ZL3VKINP.js";
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 the backdrop all land here without passing through
1351
- // `close`, so the listener is dropped here as well as there — the
1352
- // three dismissals a user reaches for most are exactly the ones
1353
- // that would otherwise leak it.
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, { once: true, capture: true });
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-RKVYIXDC.js.map
1405
+ //# sourceMappingURL=chunk-NFZJ6P42.js.map