@syware/jlib 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/dist/all.js +11 -4
  2. package/dist/atoms/jl-app-icon.css_raw.js +3 -0
  3. package/dist/atoms/jl-app-icon.d.ts +34 -0
  4. package/dist/atoms/jl-app-icon.js +86 -0
  5. package/dist/atoms/jl-avatar.js +1 -1
  6. package/dist/atoms/jl-badge.js +9 -2
  7. package/dist/atoms/jl-button.d.ts +5 -0
  8. package/dist/atoms/jl-button.js +5 -2
  9. package/dist/atoms/jl-chip.js +3 -2
  10. package/dist/atoms/jl-input.js +5 -4
  11. package/dist/atoms/jl-link.js +2 -2
  12. package/dist/atoms/jl-rating.js +4 -5
  13. package/dist/atoms/jl-select.css_raw.js +1 -1
  14. package/dist/atoms/jl-select.d.ts +3 -2
  15. package/dist/atoms/jl-select.js +6 -5
  16. package/dist/atoms/jl-spinner.js +2 -2
  17. package/dist/base/jl-form-control.js +2 -1
  18. package/dist/index.d.ts +10 -0
  19. package/dist/index.js +10 -3
  20. package/dist/lib/app-icon.d.ts +8 -0
  21. package/dist/lib/app-icon.js +37 -0
  22. package/dist/lib/dom.d.ts +2 -0
  23. package/dist/lib/dom.js +3 -1
  24. package/dist/lib/download.d.ts +10 -0
  25. package/dist/lib/download.js +62 -0
  26. package/dist/lib/icons.d.ts +4 -0
  27. package/dist/lib/icons.js +7 -4
  28. package/dist/lib/input-list.d.ts +12 -0
  29. package/dist/lib/input-list.js +14 -0
  30. package/dist/lib/internals.d.ts +5 -0
  31. package/dist/lib/internals.js +18 -0
  32. package/dist/lib/palette.js +6 -0
  33. package/dist/lib/resize.d.ts +2 -0
  34. package/dist/lib/resize.js +10 -0
  35. package/dist/lib/table.d.ts +6 -1
  36. package/dist/lib/table.js +16 -1
  37. package/dist/lib/text.d.ts +12 -0
  38. package/dist/lib/text.js +16 -1
  39. package/dist/lib/texts.d.ts +97 -0
  40. package/dist/lib/texts.js +102 -0
  41. package/dist/lib/time.d.ts +5 -3
  42. package/dist/lib/time.js +23 -15
  43. package/dist/lib/tree.d.ts +10 -1
  44. package/dist/lib/tree.js +37 -3
  45. package/dist/lib/trigger.js +1 -1
  46. package/dist/molecules/jl-account-button.css_raw.js +1 -1
  47. package/dist/molecules/jl-account-button.d.ts +8 -1
  48. package/dist/molecules/jl-account-button.js +9 -2
  49. package/dist/molecules/jl-alert.js +3 -2
  50. package/dist/molecules/jl-breadcrumb.js +3 -3
  51. package/dist/molecules/jl-calendar.js +5 -5
  52. package/dist/molecules/jl-chip-group.js +3 -3
  53. package/dist/molecules/jl-code-block.css_raw.js +1 -1
  54. package/dist/molecules/jl-code-block.d.ts +5 -0
  55. package/dist/molecules/jl-code-block.js +5 -2
  56. package/dist/molecules/jl-combobox.js +6 -7
  57. package/dist/molecules/jl-copy-button.js +5 -4
  58. package/dist/molecules/jl-date-picker.js +7 -8
  59. package/dist/molecules/jl-empty.js +2 -2
  60. package/dist/molecules/jl-facts.css_raw.js +3 -0
  61. package/dist/molecules/jl-facts.d.ts +35 -0
  62. package/dist/molecules/jl-facts.js +64 -0
  63. package/dist/molecules/jl-file-drop.d.ts +5 -0
  64. package/dist/molecules/jl-file-drop.js +18 -15
  65. package/dist/molecules/jl-input-list.css_raw.js +3 -0
  66. package/dist/molecules/jl-input-list.d.ts +79 -0
  67. package/dist/molecules/jl-input-list.js +177 -0
  68. package/dist/molecules/jl-list-item.js +1 -1
  69. package/dist/molecules/jl-load-more.css_raw.js +3 -0
  70. package/dist/molecules/jl-load-more.d.ts +41 -0
  71. package/dist/molecules/jl-load-more.js +62 -0
  72. package/dist/molecules/jl-load-state.d.ts +4 -1
  73. package/dist/molecules/jl-load-state.js +20 -12
  74. package/dist/molecules/jl-menu.js +1 -1
  75. package/dist/molecules/jl-number-input.js +5 -5
  76. package/dist/molecules/jl-pagination.js +5 -5
  77. package/dist/molecules/jl-radio-group.js +2 -2
  78. package/dist/molecules/jl-search-button.js +5 -5
  79. package/dist/molecules/jl-search.js +5 -5
  80. package/dist/molecules/jl-segmented.js +3 -2
  81. package/dist/molecules/jl-steps.css_raw.js +1 -1
  82. package/dist/molecules/jl-steps.d.ts +2 -0
  83. package/dist/molecules/jl-steps.js +30 -14
  84. package/dist/molecules/jl-tabs.js +3 -2
  85. package/dist/molecules/jl-tag-input.js +3 -3
  86. package/dist/molecules/jl-timeline.js +1 -1
  87. package/dist/molecules/jl-toast.css_raw.js +1 -1
  88. package/dist/molecules/jl-toast.d.ts +13 -1
  89. package/dist/molecules/jl-toast.js +23 -3
  90. package/dist/molecules/jl-tooltip.d.ts +4 -1
  91. package/dist/molecules/jl-tooltip.js +27 -15
  92. package/dist/organisms/jl-command-palette.css_raw.js +1 -1
  93. package/dist/organisms/jl-command-palette.d.ts +14 -0
  94. package/dist/organisms/jl-command-palette.js +40 -10
  95. package/dist/organisms/jl-confirm.d.ts +2 -1
  96. package/dist/organisms/jl-confirm.js +18 -12
  97. package/dist/organisms/jl-dialog.js +2 -1
  98. package/dist/organisms/jl-drawer.js +2 -1
  99. package/dist/organisms/jl-flow.js +13 -13
  100. package/dist/organisms/jl-navbar.js +6 -6
  101. package/dist/organisms/jl-side-nav.css_raw.js +1 -1
  102. package/dist/organisms/jl-side-nav.d.ts +33 -1
  103. package/dist/organisms/jl-side-nav.js +71 -14
  104. package/dist/organisms/jl-tab-bar.js +3 -3
  105. package/dist/organisms/jl-table.css_raw.js +1 -1
  106. package/dist/organisms/jl-table.d.ts +9 -0
  107. package/dist/organisms/jl-table.js +36 -6
  108. package/dist/organisms/jl-toaster.css_raw.js +1 -1
  109. package/dist/organisms/jl-toaster.d.ts +9 -1
  110. package/dist/organisms/jl-toaster.js +148 -7
  111. package/dist/organisms/jl-tree.css_raw.js +1 -1
  112. package/dist/organisms/jl-tree.d.ts +71 -4
  113. package/dist/organisms/jl-tree.js +303 -40
  114. package/dist/styles/tokens.css_raw.js +1 -1
  115. package/dist/templates/jl-app-shell.css_raw.js +1 -1
  116. package/dist/templates/jl-app-shell.d.ts +16 -1
  117. package/dist/templates/jl-app-shell.js +42 -3
  118. package/dist/templates/jl-auth-layout.css_raw.js +3 -0
  119. package/dist/templates/jl-auth-layout.d.ts +35 -0
  120. package/dist/templates/jl-auth-layout.js +139 -0
  121. package/dist/templates/jl-page.css_raw.js +1 -1
  122. package/dist/templates/jl-page.js +3 -3
  123. package/dist/templates/jl-split-view.css_raw.js +1 -1
  124. package/dist/templates/jl-split-view.d.ts +21 -1
  125. package/dist/templates/jl-split-view.js +37 -4
  126. package/dist/tokens.css +2 -0
  127. package/dist/types/jl-app-icon.types.d.ts +5 -0
  128. package/dist/types/jl-command-palette.types.d.ts +7 -0
  129. package/dist/types/jl-facts.types.d.ts +17 -0
  130. package/dist/types/jl-side-nav.types.d.ts +2 -0
  131. package/dist/types/jl-split-view.types.d.ts +2 -0
  132. package/dist/types/jl-steps.types.d.ts +2 -0
  133. package/dist/types/jl-table.types.d.ts +9 -2
  134. package/dist/types/jl-toaster.types.d.ts +42 -1
  135. package/dist/types/jl-tree.types.d.ts +26 -0
  136. package/dist/types/texts.types.d.ts +5 -0
  137. package/package.json +3 -3
@@ -1,3 +1,3 @@
1
1
  // src/organisms/jl-command-palette.css?raw
2
- var jl_command_palette_default = ":host {\n display: contents;\n}\n\ndialog {\n flex-direction: column;\n width: min(620px, calc(100vw - 24px));\n max-width: none;\n max-height: min(520px, calc(100dvh - 24vh));\n margin: 12vh auto auto;\n padding: 0;\n overflow: hidden;\n border: 1px solid var(--jl-line);\n border-radius: var(--jl-radius-lg);\n background: var(--jl-surface);\n color: var(--jl-text);\n font-size: var(--jl-font-md);\n box-shadow: var(--jl-shadow-lg);\n}\n\ndialog[open] {\n display: flex;\n animation: drop var(--jl-fast) var(--jl-ease);\n}\n\ndialog::backdrop {\n background: var(--jl-scrim);\n backdrop-filter: blur(2px);\n}\n\n.search {\n display: flex;\n flex: none;\n align-items: center;\n gap: 10px;\n height: 52px;\n padding: 0 14px 0 16px;\n border-bottom: 1px solid var(--jl-line);\n}\n\n.search-icon {\n color: var(--jl-faint);\n}\n\ninput {\n flex: 1;\n min-width: 0;\n height: 100%;\n padding: 0;\n border: none;\n outline: none;\n background: none;\n font-size: 15px;\n}\n\ninput::placeholder {\n color: var(--jl-faint);\n}\n\ninput:focus-visible {\n box-shadow: none;\n}\n\nkbd {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 20px;\n height: 20px;\n padding: 0 5px;\n border: 1px solid var(--jl-line-strong);\n border-bottom-width: 2px;\n border-radius: var(--jl-radius-xs);\n background: var(--jl-surface);\n color: var(--jl-muted);\n font-size: 11px;\n font-weight: 500;\n line-height: 1;\n}\n\n.results {\n flex: 1;\n min-height: 0;\n padding: 6px;\n overflow-y: auto;\n overscroll-behavior: contain;\n scroll-padding: 6px;\n}\n\n[role=\"group\"] + [role=\"group\"] {\n margin-top: 4px;\n padding-top: 4px;\n border-top: 1px solid var(--jl-line-soft);\n}\n\n.group-label {\n padding: 8px 10px 4px;\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n font-weight: 600;\n}\n\n.option {\n display: flex;\n align-items: center;\n gap: 10px;\n min-height: 38px;\n padding: 0 10px;\n border-radius: var(--jl-radius-sm);\n color: var(--jl-text-2);\n cursor: pointer;\n}\n\n.option[aria-selected=\"true\"] {\n background: var(--jl-accent-soft);\n color: var(--jl-text);\n}\n\n.option-icon {\n flex: none;\n width: 16px;\n color: var(--jl-muted);\n}\n\n.option[aria-selected=\"true\"] .option-icon {\n color: var(--jl-accent);\n}\n\n.option-label {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\nmark {\n background: none;\n color: var(--jl-accent);\n font-weight: 700;\n}\n\n.keys {\n display: inline-flex;\n flex: none;\n gap: 3px;\n}\n\n.empty {\n padding: 36px 16px;\n color: var(--jl-muted);\n text-align: center;\n}\n\nfooter {\n display: flex;\n flex: none;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px 16px;\n padding: 8px 16px;\n border-top: 1px solid var(--jl-line);\n background: var(--jl-surface-2);\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n}\n\n.hint,\n.hotkey-keys {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.hotkey-hint {\n margin-left: auto;\n}\n\n@media (width < 520px) {\n dialog {\n margin-top: 12px;\n max-height: calc(100dvh - 24px);\n }\n\n .hotkey-hint {\n display: none;\n }\n}\n\n@keyframes drop {\n from {\n opacity: 0;\n transform: translateY(-6px) scale(0.98);\n }\n}\n";
2
+ var jl_command_palette_default = ":host {\n display: contents;\n}\n\ndialog {\n flex-direction: column;\n width: min(620px, calc(100vw - 24px));\n max-width: none;\n max-height: min(520px, calc(100dvh - 24vh));\n margin: 12vh auto auto;\n padding: 0;\n overflow: hidden;\n border: 1px solid var(--jl-line);\n border-radius: var(--jl-radius-lg);\n background: var(--jl-surface);\n color: var(--jl-text);\n font-size: var(--jl-font-md);\n box-shadow: var(--jl-shadow-lg);\n}\n\ndialog[open] {\n display: flex;\n animation: drop var(--jl-fast) var(--jl-ease);\n}\n\ndialog::backdrop {\n background: var(--jl-scrim);\n backdrop-filter: blur(2px);\n}\n\n.search {\n display: flex;\n flex: none;\n align-items: center;\n gap: 10px;\n height: 52px;\n padding: 0 14px 0 16px;\n border-bottom: 1px solid var(--jl-line);\n}\n\n.search-icon,\n.search-spinner {\n flex: none;\n color: var(--jl-faint);\n}\n\ninput {\n flex: 1;\n min-width: 0;\n height: 100%;\n padding: 0;\n border: none;\n outline: none;\n background: none;\n /* At least the text of fields: 16px on touch screens, where phones zoom into smaller text. */\n font-size: max(15px, var(--jl-control-font));\n}\n\ninput::placeholder {\n color: var(--jl-faint);\n}\n\ninput:focus-visible {\n box-shadow: none;\n}\n\nkbd {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 20px;\n height: 20px;\n padding: 0 5px;\n border: 1px solid var(--jl-line-strong);\n border-bottom-width: 2px;\n border-radius: var(--jl-radius-xs);\n background: var(--jl-surface);\n color: var(--jl-muted);\n font-size: 11px;\n font-weight: 500;\n line-height: 1;\n}\n\n.results {\n flex: 1;\n min-height: 0;\n padding: 6px;\n overflow-y: auto;\n overscroll-behavior: contain;\n scroll-padding: 6px;\n}\n\n[role=\"group\"] + [role=\"group\"] {\n margin-top: 4px;\n padding-top: 4px;\n border-top: 1px solid var(--jl-line-soft);\n}\n\n.group-label {\n padding: 8px 10px 4px;\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n font-weight: 600;\n}\n\n.option {\n display: flex;\n align-items: center;\n gap: 10px;\n min-height: 38px;\n padding: 0 10px;\n border-radius: var(--jl-radius-sm);\n color: var(--jl-text-2);\n cursor: pointer;\n}\n\n.option[aria-selected=\"true\"] {\n background: var(--jl-accent-soft);\n color: var(--jl-text);\n}\n\n.option-icon {\n flex: none;\n width: 16px;\n color: var(--jl-muted);\n}\n\n.option[aria-selected=\"true\"] .option-icon {\n color: var(--jl-accent);\n}\n\n.option-label {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.option-description {\n flex: none;\n max-width: 40%;\n overflow: hidden;\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\nmark {\n background: none;\n color: var(--jl-accent);\n font-weight: 700;\n}\n\n.keys {\n display: inline-flex;\n flex: none;\n gap: 3px;\n}\n\n.empty {\n padding: 36px 16px;\n color: var(--jl-muted);\n text-align: center;\n}\n\nfooter {\n display: flex;\n flex: none;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px 16px;\n padding: 8px 16px;\n border-top: 1px solid var(--jl-line);\n background: var(--jl-surface-2);\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n}\n\n.hint,\n.hotkey-keys {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.hotkey-hint {\n margin-left: auto;\n}\n\n@media (width < 520px) {\n dialog {\n margin-top: 12px;\n max-height: calc(100dvh - 24px);\n }\n\n .hotkey-hint {\n display: none;\n }\n}\n\n@keyframes drop {\n from {\n opacity: 0;\n transform: translateY(-6px) scale(0.98);\n }\n}\n";
3
3
  export { jl_command_palette_default as default };
@@ -7,10 +7,12 @@ import type { PaletteCommand } from '../types/jl-command-palette.types';
7
7
  */
8
8
  export declare class JlCommandPalette extends JlElement {
9
9
  static readonly selector = "jl-command-palette";
10
+ /** `search`: what was typed, once typing pauses (and empty when it opens), for a page that answers it. */
10
11
  readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
11
12
  select: string;
12
13
  open: undefined;
13
14
  close: undefined;
15
+ search: string;
14
16
  }>>;
15
17
  readonly commands: Readonly<Readonly<{
16
18
  get(): PaletteCommand[];
@@ -34,6 +36,16 @@ export declare class JlCommandPalette extends JlElement {
34
36
  get(): string;
35
37
  set(setter: string | ((prevState: string) => string)): void;
36
38
  }>>;
39
+ /** How long typing pauses, in milliseconds, before `search` fires. */
40
+ readonly debounce: Readonly<Readonly<{
41
+ get(): number;
42
+ set(setter: number | ((prevState: number) => number)): void;
43
+ }>>;
44
+ /** Shows a spinner in place of the search icon, while the page looks for what was typed. */
45
+ readonly loading: Readonly<Readonly<{
46
+ get(): boolean;
47
+ set(setter: boolean | ((prevState: boolean) => boolean)): void;
48
+ }>>;
37
49
  /** The dialog's name for screen readers. */
38
50
  readonly label: Readonly<Readonly<{
39
51
  get(): string;
@@ -52,6 +64,8 @@ export declare class JlCommandPalette extends JlElement {
52
64
  private move;
53
65
  private choose;
54
66
  private renderOpen;
67
+ private scheduleSearch;
68
+ private search;
55
69
  private renderHotkey;
56
70
  private renderResults;
57
71
  private groupNode;
@@ -2,8 +2,10 @@ import { BASE_STYLES } from "../styles/shared.js";
2
2
  import { JlElement } from "../base/jl-element.js";
3
3
  import { jsonOf } from "../lib/attributes.js";
4
4
  import { registerIcons } from "../lib/icons.js";
5
- import { setOrRemove } from "../lib/dom.js";
5
+ import { jlText } from "../lib/texts.js";
6
6
  import { JlIcon } from "../atoms/jl-icon.js";
7
+ import { setOrRemove } from "../lib/dom.js";
8
+ import { JlSpinner } from "../atoms/jl-spinner.js";
7
9
  import { highlightSegments } from "../lib/fuzzy.js";
8
10
  import { hotkeyLabels, isApplePlatform, matchesHotkey, parseHotkey } from "../lib/hotkey.js";
9
11
  import { groupMatches, matchCommands } from "../lib/palette.js";
@@ -15,10 +17,8 @@ import { jsx, jsxs } from "@jadis/core/jsx-runtime";
15
17
  registerIcons({ search: Search });
16
18
  var DEFAULT_HOTKEY = "mod+k";
17
19
  var NO_HOTKEY = "none";
18
- var DEFAULT_PLACEHOLDER = "Type a command or search…";
19
- var DEFAULT_EMPTY_TEXT = "No matching commands";
20
- var DEFAULT_LABEL = "Command palette";
21
20
  var SEARCH_ICON_SIZE = 18;
21
+ var DEFAULT_DEBOUNCE_MS = 250;
22
22
  var OPTION_ICON_SIZE = 16;
23
23
  /** How far each key moves the active command. */
24
24
  var MOVES = /* @__PURE__ */ new Map([["ArrowDown", 1], ["ArrowUp", -1]]);
@@ -37,26 +37,35 @@ function highlighted(label, positions) {
37
37
  */
38
38
  var JlCommandPalette = class extends JlElement {
39
39
  static selector = "jl-command-palette";
40
+ /** `search`: what was typed, once typing pauses (and empty when it opens), for a page that answers it. */
40
41
  events = this.useEvents();
41
42
  commands = this.useChange([], () => this.renderResults(), {
42
43
  attribute: "commands",
43
44
  parse: jsonOf([])
44
45
  });
45
46
  open = this.useChange(false, (value) => this.renderOpen(value), { attribute: "open" });
46
- placeholder = this.useChange(DEFAULT_PLACEHOLDER, (value) => {
47
+ placeholder = this.useChange(jlText("commandPlaceholder"), (value) => {
47
48
  this._refs.input.placeholder = value;
48
49
  }, { attribute: "placeholder" });
49
50
  /** The keys that open and close it from anywhere on the page; `none` for no keys. `mod` is ⌘ or Ctrl. */
50
51
  hotkey = this.useChange(DEFAULT_HOTKEY, () => this.renderHotkey(), { attribute: "hotkey" });
51
52
  /** What it says when nothing matches. */
52
- emptyText = this.useChange(DEFAULT_EMPTY_TEXT, (value) => {
53
+ emptyText = this.useChange(jlText("noCommands"), (value) => {
53
54
  this._refs.empty.textContent = value;
54
55
  }, { attribute: "empty-text" });
56
+ /** How long typing pauses, in milliseconds, before `search` fires. */
57
+ debounce = this.useChange(DEFAULT_DEBOUNCE_MS, () => void 0, { attribute: "debounce" });
58
+ /** Shows a spinner in place of the search icon, while the page looks for what was typed. */
59
+ loading = this.useChange(false, (value) => {
60
+ this._refs.searchIcon.hidden = value;
61
+ this._refs.spinner.hidden = !value;
62
+ }, { attribute: "loading" });
55
63
  /** The dialog's name for screen readers. */
56
- label = this.useChange(DEFAULT_LABEL, (value) => this._refs.dialog.setAttribute("aria-label", value), { attribute: "label" });
64
+ label = this.useChange(jlText("commandPalette"), (value) => this._refs.dialog.setAttribute("aria-label", value), { attribute: "label" });
57
65
  _state = {
58
66
  active: 0,
59
- matches: []
67
+ matches: [],
68
+ timer: 0
60
69
  };
61
70
  _refs = this.useRefs((ref) => ({
62
71
  dialog: ref("dialog"),
@@ -64,7 +73,9 @@ var JlCommandPalette = class extends JlElement {
64
73
  hotkeyHint: ref(".hotkey-hint"),
65
74
  hotkeyKeys: ref(".hotkey-keys"),
66
75
  input: ref("input"),
67
- results: ref(".results")
76
+ results: ref(".results"),
77
+ searchIcon: ref(".search-icon"),
78
+ spinner: ref(JlSpinner)
68
79
  }));
69
80
  show() {
70
81
  this.open.set(true);
@@ -84,6 +95,11 @@ var JlCommandPalette = class extends JlElement {
84
95
  name: "search",
85
96
  size: SEARCH_ICON_SIZE
86
97
  }),
98
+ /* @__PURE__ */ jsx(JlSpinner, {
99
+ class: "search-spinner",
100
+ size: SEARCH_ICON_SIZE,
101
+ hidden: true
102
+ }),
87
103
  /* @__PURE__ */ jsx("input", {
88
104
  type: "text",
89
105
  role: "combobox",
@@ -103,7 +119,7 @@ var JlCommandPalette = class extends JlElement {
103
119
  class: "results",
104
120
  id: "results",
105
121
  role: "listbox",
106
- "aria-label": "Commands"
122
+ "aria-label": jlText("commands")
107
123
  }),
108
124
  /* @__PURE__ */ jsx("p", {
109
125
  class: "empty",
@@ -144,6 +160,7 @@ var JlCommandPalette = class extends JlElement {
144
160
  this.on(input, "input", (event) => {
145
161
  event.stopPropagation();
146
162
  this.renderResults();
163
+ this.scheduleSearch();
147
164
  });
148
165
  this.on(input, "keydown", (event) => this.navigate(event));
149
166
  this.on(this._refs.results, "pointermove", (event) => {
@@ -208,11 +225,20 @@ var JlCommandPalette = class extends JlElement {
208
225
  }
209
226
  this._refs.input.value = "";
210
227
  this.renderResults();
228
+ this.search("");
211
229
  dialog.showModal();
212
230
  this._refs.input.focus();
213
231
  this.reflect("open", true);
214
232
  this.events.emit("open");
215
233
  }
234
+ scheduleSearch() {
235
+ window.clearTimeout(this._state.timer);
236
+ this._state.timer = window.setTimeout(() => this.search(this._refs.input.value), this.debounce.get());
237
+ }
238
+ search(query) {
239
+ window.clearTimeout(this._state.timer);
240
+ this.events.emit("search", query);
241
+ }
216
242
  renderHotkey() {
217
243
  const hotkey = this.hotkey.get();
218
244
  const none = hotkey === "" || hotkey === NO_HOTKEY;
@@ -265,6 +291,10 @@ var JlCommandPalette = class extends JlElement {
265
291
  class: "option-label",
266
292
  children: highlighted(command.label, match.positions)
267
293
  }),
294
+ command.description ? /* @__PURE__ */ jsx("span", {
295
+ class: "option-description",
296
+ children: command.description
297
+ }) : null,
268
298
  command.shortcut === void 0 ? null : /* @__PURE__ */ jsx("span", {
269
299
  class: "keys",
270
300
  children: keycaps(command.shortcut)
@@ -57,6 +57,7 @@ export declare function confirmDialog(options: ConfirmOptions): Promise<boolean>
57
57
  /**
58
58
  * Guards a form or an editor against leaving with changes not saved. Closing or reloading the tab asks the
59
59
  * browser's own question; the function it returns asks in the page, for the app's router to call before it moves
60
- * on: `if (!(await canLeave())) return;`. It resolves `true` at once when nothing would be lost.
60
+ * on: `if (!(await canLeave())) return;`. It resolves `true` at once when nothing would be lost, or once `signal`
61
+ * ended the guard.
61
62
  */
62
63
  export declare function guardLeave(options: LeaveGuardOptions): () => Promise<boolean>;
@@ -2,6 +2,7 @@ import { BASE_STYLES } from "../styles/shared.js";
2
2
  import { JlElement } from "../base/jl-element.js";
3
3
  import { oneOf } from "../lib/attributes.js";
4
4
  import { define } from "../lib/define.js";
5
+ import { jlText } from "../lib/texts.js";
5
6
  import { JlButton } from "../atoms/jl-button.js";
6
7
  import jl_confirm_default from "./jl-confirm.css_raw.js";
7
8
  import { JlDialog } from "./jl-dialog.js";
@@ -46,7 +47,7 @@ var JlConfirm = class extends JlElement {
46
47
  _refs = this.useRefs((ref) => ({
47
48
  cancel: ref(".cancel"),
48
49
  confirm: ref(".confirm"),
49
- dialog: ref("jl-dialog"),
50
+ dialog: ref(JlDialog),
50
51
  text: ref(".text")
51
52
  }));
52
53
  /** Opens the question; resolves `true` when confirmed, `false` otherwise. Asking again while open waits for the same answer. */
@@ -130,24 +131,29 @@ function confirmDialog(options) {
130
131
  return element.ask().finally(() => element.remove());
131
132
  }
132
133
  var LEAVE_HEADING = "Leave without saving?";
133
- var LEAVE_TEXT = "Your changes are lost.";
134
- var LEAVE_LABEL = "Leave";
135
- var STAY_LABEL = "Keep editing";
136
134
  /**
137
135
  * Guards a form or an editor against leaving with changes not saved. Closing or reloading the tab asks the
138
136
  * browser's own question; the function it returns asks in the page, for the app's router to call before it moves
139
- * on: `if (!(await canLeave())) return;`. It resolves `true` at once when nothing would be lost.
137
+ * on: `if (!(await canLeave())) return;`. It resolves `true` at once when nothing would be lost, or once `signal`
138
+ * ended the guard.
140
139
  */
141
140
  function guardLeave(options) {
142
141
  window.addEventListener("beforeunload", (event) => {
143
142
  if (options.isDirty()) event.preventDefault();
144
143
  }, { signal: options.signal });
145
- return async () => !options.isDirty() || confirmDialog({
146
- cancelLabel: options.stayLabel ?? STAY_LABEL,
147
- confirmLabel: options.leaveLabel ?? LEAVE_LABEL,
148
- heading: options.heading ?? LEAVE_HEADING,
149
- text: options.text ?? LEAVE_TEXT,
150
- tone: "danger"
151
- });
144
+ const asking = { pending: null };
145
+ return () => {
146
+ if (options.signal.aborted || !options.isDirty()) return Promise.resolve(true);
147
+ asking.pending ??= confirmDialog({
148
+ cancelLabel: options.stayLabel ?? jlText("stay"),
149
+ confirmLabel: options.leaveLabel ?? jlText("leave"),
150
+ heading: options.heading ?? LEAVE_HEADING,
151
+ text: options.text ?? jlText("leaveText"),
152
+ tone: "danger"
153
+ }).finally(() => {
154
+ asking.pending = null;
155
+ });
156
+ return asking.pending;
157
+ };
152
158
  }
153
159
  export { CONFIRM_TONES, JlConfirm, confirmDialog, guardLeave };
@@ -2,6 +2,7 @@ import { BASE_STYLES } from "../styles/shared.js";
2
2
  import { JlElement } from "../base/jl-element.js";
3
3
  import { oneOf } from "../lib/attributes.js";
4
4
  import { registerIcons } from "../lib/icons.js";
5
+ import { jlText } from "../lib/texts.js";
5
6
  import { JlButton } from "../atoms/jl-button.js";
6
7
  import jl_dialog_default from "./jl-dialog.css_raw.js";
7
8
  import { css } from "@jadis/core";
@@ -67,7 +68,7 @@ var JlDialog = class extends JlElement {
67
68
  class: "close",
68
69
  variant: "ghost",
69
70
  icon: "x",
70
- hint: "Close",
71
+ hint: jlText("close"),
71
72
  size: "sm"
72
73
  })] }),
73
74
  /* @__PURE__ */ jsx("div", {
@@ -2,6 +2,7 @@ import { BASE_STYLES } from "../styles/shared.js";
2
2
  import { JlElement } from "../base/jl-element.js";
3
3
  import { oneOf } from "../lib/attributes.js";
4
4
  import { registerIcons } from "../lib/icons.js";
5
+ import { jlText } from "../lib/texts.js";
5
6
  import { JlButton } from "../atoms/jl-button.js";
6
7
  import jl_drawer_default from "./jl-drawer.css_raw.js";
7
8
  import { css } from "@jadis/core";
@@ -85,7 +86,7 @@ var JlDrawer = class extends JlElement {
85
86
  class: "close",
86
87
  variant: "ghost",
87
88
  icon: "x",
88
- hint: "Close",
89
+ hint: jlText("close"),
89
90
  size: "sm"
90
91
  })]
91
92
  })] }),
@@ -2,8 +2,9 @@ import { BASE_STYLES } from "../styles/shared.js";
2
2
  import { JlElement } from "../base/jl-element.js";
3
3
  import { jsonOf } from "../lib/attributes.js";
4
4
  import { registerIcons } from "../lib/icons.js";
5
- import { setOrRemove, svgElement } from "../lib/dom.js";
5
+ import { jlText } from "../lib/texts.js";
6
6
  import { JlIcon } from "../atoms/jl-icon.js";
7
+ import { setOrRemove, svgElement } from "../lib/dom.js";
7
8
  import { FLOW_NODE, FLOW_ZOOM, centeredAt, connect, edgeEnds, edgeMidpoint, edgePath, fitView, isEdgeTaken, isPortFree, layoutFlow, moveNode, nodeAt, nodeHeight, outputAnchor, portX, portsOf, positionAfter, removeEdge, removeNode, revealIn, snap, toWorld, zoomAt } from "../lib/flow.js";
8
9
  import jl_flow_default from "./jl-flow.css_raw.js";
9
10
  import { css } from "@jadis/core";
@@ -26,7 +27,6 @@ var EMPTY = {
26
27
  nodes: []
27
28
  };
28
29
  var IDLE = { kind: "idle" };
29
- var DEFAULT_LABEL = "Flow";
30
30
  var DOT_SPACING = 20;
31
31
  var WHEEL_ZOOM_SPEED = .0018;
32
32
  var CLICK_TOLERANCE = 4;
@@ -116,7 +116,7 @@ var JlFlow = class extends JlElement {
116
116
  */
117
117
  dropType = this.useChange("", () => void 0, { attribute: "drop-type" });
118
118
  /** The canvas's name for screen readers. */
119
- label = this.useChange("", (value) => this._refs.viewport.setAttribute("aria-label", value || DEFAULT_LABEL), { attribute: "label" });
119
+ label = this.useChange("", (value) => this._refs.viewport.setAttribute("aria-label", value || jlText("flow")), { attribute: "label" });
120
120
  _state = {
121
121
  edge: "",
122
122
  fitted: false,
@@ -203,7 +203,7 @@ var JlFlow = class extends JlElement {
203
203
  class: "remove-edge",
204
204
  "data-control": "",
205
205
  hidden: true,
206
- "aria-label": "Remove this edge",
206
+ "aria-label": jlText("removeEdge"),
207
207
  children: /* @__PURE__ */ jsx(JlIcon, {
208
208
  name: "x",
209
209
  size: ADD_ICON_SIZE
@@ -227,8 +227,8 @@ var JlFlow = class extends JlElement {
227
227
  /* @__PURE__ */ jsx("button", {
228
228
  type: "button",
229
229
  class: "control zoom-out",
230
- title: "Zoom out",
231
- "aria-label": "Zoom out",
230
+ title: jlText("zoomOut"),
231
+ "aria-label": jlText("zoomOut"),
232
232
  children: /* @__PURE__ */ jsx(JlIcon, {
233
233
  name: "minus",
234
234
  size: CONTROL_ICON_SIZE
@@ -238,8 +238,8 @@ var JlFlow = class extends JlElement {
238
238
  /* @__PURE__ */ jsx("button", {
239
239
  type: "button",
240
240
  class: "control zoom-in",
241
- title: "Zoom in",
242
- "aria-label": "Zoom in",
241
+ title: jlText("zoomIn"),
242
+ "aria-label": jlText("zoomIn"),
243
243
  children: /* @__PURE__ */ jsx(JlIcon, {
244
244
  name: "plus",
245
245
  size: CONTROL_ICON_SIZE
@@ -248,8 +248,8 @@ var JlFlow = class extends JlElement {
248
248
  /* @__PURE__ */ jsx("button", {
249
249
  type: "button",
250
250
  class: "control fit",
251
- title: "Show every node",
252
- "aria-label": "Show every node",
251
+ title: jlText("fitView"),
252
+ "aria-label": jlText("fitView"),
253
253
  children: /* @__PURE__ */ jsx(JlIcon, {
254
254
  name: "maximize",
255
255
  size: CONTROL_ICON_SIZE
@@ -258,8 +258,8 @@ var JlFlow = class extends JlElement {
258
258
  /* @__PURE__ */ jsx("button", {
259
259
  type: "button",
260
260
  class: "control tidy",
261
- title: "Tidy up the layout",
262
- "aria-label": "Tidy up the layout",
261
+ title: jlText("tidyLayout"),
262
+ "aria-label": jlText("tidyLayout"),
263
263
  children: /* @__PURE__ */ jsx(JlIcon, {
264
264
  name: "wand-sparkles",
265
265
  size: CONTROL_ICON_SIZE
@@ -671,7 +671,7 @@ var JlFlow = class extends JlElement {
671
671
  const element = /* @__PURE__ */ jsxs("span", {
672
672
  class: "port",
673
673
  "data-port": port.id,
674
- title: "Drag to another node to connect",
674
+ title: jlText("dragToConnect"),
675
675
  attrs: { part: "port" },
676
676
  children: [
677
677
  /* @__PURE__ */ jsx("span", { class: "dot" }),
@@ -2,6 +2,7 @@ import { BASE_STYLES } from "../styles/shared.js";
2
2
  import { JlElement } from "../base/jl-element.js";
3
3
  import { jsonOf } from "../lib/attributes.js";
4
4
  import { registerIcons } from "../lib/icons.js";
5
+ import { jlText } from "../lib/texts.js";
5
6
  import { JlIcon } from "../atoms/jl-icon.js";
6
7
  import jl_navbar_default from "./jl-navbar.css_raw.js";
7
8
  import { css } from "@jadis/core";
@@ -13,7 +14,6 @@ registerIcons({
13
14
  x: X
14
15
  });
15
16
  var ICON_SIZE = 18;
16
- var DEFAULT_LABEL = "Main";
17
17
  var MENU_ICON = "menu";
18
18
  var CLOSE_ICON = "x";
19
19
  /**
@@ -30,9 +30,9 @@ var JlNavbar = class extends JlElement {
30
30
  /** Stays at the top of the window while the page scrolls. */
31
31
  sticky = this.useChange(false, (value) => this.reflect("sticky", value), { attribute: "sticky" });
32
32
  /** The navigation's name for screen readers. */
33
- label = this.useChange(DEFAULT_LABEL, (value) => {
34
- this._refs.links.setAttribute("aria-label", value || DEFAULT_LABEL);
35
- this._refs.drawer.setAttribute("aria-label", value || DEFAULT_LABEL);
33
+ label = this.useChange(jlText("mainNavigation"), (value) => {
34
+ this._refs.links.setAttribute("aria-label", value || jlText("mainNavigation"));
35
+ this._refs.drawer.setAttribute("aria-label", value || jlText("mainNavigation"));
36
36
  }, { attribute: "label" });
37
37
  _refs = this.useRefs((ref) => ({
38
38
  drawer: ref(".drawer"),
@@ -65,7 +65,7 @@ var JlNavbar = class extends JlElement {
65
65
  class: "toggle",
66
66
  "aria-expanded": "false",
67
67
  "aria-controls": "drawer",
68
- "aria-label": "Menu",
68
+ "aria-label": jlText("menu"),
69
69
  children: /* @__PURE__ */ jsx(JlIcon, {
70
70
  name: MENU_ICON,
71
71
  size: ICON_SIZE
@@ -112,7 +112,7 @@ var JlNavbar = class extends JlElement {
112
112
  setDrawer(open) {
113
113
  this._refs.drawer.hidden = !open;
114
114
  this._refs.toggle.setAttribute("aria-expanded", String(open));
115
- this._refs.toggle.setAttribute("aria-label", open ? "Close the menu" : "Menu");
115
+ this._refs.toggle.setAttribute("aria-label", open ? jlText("closeMenu") : jlText("menu"));
116
116
  this._refs.toggleIcon.name.set(open ? CLOSE_ICON : MENU_ICON);
117
117
  this.reflect("expanded", open);
118
118
  }
@@ -1,3 +1,3 @@
1
1
  // src/organisms/jl-side-nav.css?raw
2
- var jl_side_nav_default = ":host {\n display: block;\n width: var(--width, 248px);\n height: 100%;\n min-height: 0;\n transition: width var(--jl-slow) var(--jl-ease);\n}\n\n:host([collapsed]) {\n width: var(--rail-width, 60px);\n}\n\nnav {\n display: flex;\n flex-direction: column;\n gap: 8px;\n height: 100%;\n padding: 12px 8px;\n overflow-x: hidden;\n overflow-y: auto;\n color: var(--jl-text-2);\n font-size: var(--jl-font-md);\n}\n\n::slotted([slot=\"footer\"]) {\n margin-top: auto;\n}\n\n.sections {\n display: grid;\n gap: 14px;\n}\n\n.section {\n display: grid;\n gap: 2px;\n}\n\n.section-label {\n padding: 4px 10px 6px;\n color: var(--jl-muted);\n font-size: 11px;\n font-weight: 650;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n white-space: nowrap;\n}\n\n.items,\n.children {\n display: grid;\n gap: 2px;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.children {\n margin-top: 2px;\n}\n\n.entry {\n position: relative;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n min-height: 34px;\n padding: 0 10px;\n border: none;\n border-radius: var(--jl-radius-sm);\n background: none;\n color: inherit;\n font: inherit;\n font-weight: 550;\n text-align: start;\n text-decoration: none;\n white-space: nowrap;\n cursor: pointer;\n transition:\n background var(--jl-fast) var(--jl-ease),\n color var(--jl-fast) var(--jl-ease);\n}\n\n.item[data-depth=\"1\"] {\n padding-left: 36px;\n}\n\n.item[data-depth=\"2\"] {\n padding-left: 50px;\n}\n\n.item[data-depth=\"3\"] {\n padding-left: 64px;\n}\n\n.item:hover {\n background: var(--jl-surface-3);\n color: var(--jl-text);\n}\n\n.item[aria-current=\"page\"] {\n background: var(--jl-accent-soft);\n color: var(--jl-accent);\n}\n\n.item.holds-active {\n color: var(--jl-text);\n}\n\n.item-icon {\n color: var(--jl-faint);\n}\n\n.item:hover .item-icon,\n.item.holds-active .item-icon {\n color: var(--jl-text-2);\n}\n\n.item[aria-current=\"page\"] .item-icon {\n color: var(--jl-accent);\n}\n\n.item-initial {\n display: none;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: var(--jl-radius-xs);\n background: var(--jl-surface-3);\n color: var(--jl-text-2);\n font-size: 11px;\n font-weight: 700;\n}\n\n/* Nested items without an icon line their labels up under their parent's label. */\n.item:not([data-depth=\"0\"]) .item-initial {\n display: none;\n}\n.item-label {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.item-badge {\n flex: none;\n min-width: 20px;\n padding: 2px 6px;\n border-radius: var(--jl-radius-pill);\n background: var(--jl-surface-3);\n color: var(--jl-text-2);\n font-size: 11px;\n font-weight: 650;\n line-height: 14px;\n text-align: center;\n}\n\n.item[aria-current=\"page\"] .item-badge {\n background: var(--jl-accent);\n color: var(--jl-on-accent);\n}\n\n.expander jl-icon {\n transition: transform var(--jl-fast) var(--jl-ease);\n}\n\n.chevron {\n color: var(--jl-faint);\n transition: transform var(--jl-fast) var(--jl-ease);\n}\n\n.entry.open > .item .chevron,\n.entry.open > .expander jl-icon {\n transform: rotate(90deg);\n}\n\n/* The chevron of a parent that is also a link: its own button, over the end of the row. */\n.expander {\n position: absolute;\n top: 5px;\n right: 6px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n padding: 0;\n border: none;\n border-radius: var(--jl-radius-xs);\n background: none;\n color: var(--jl-faint);\n cursor: pointer;\n}\n\n.expander:hover {\n background: var(--jl-surface-3);\n color: var(--jl-text);\n}\n\n.entry:has(> .expander) > .item {\n padding-right: 36px;\n}\n\n/* The rail: icons only, centred; labels stay for screen readers, and show as tooltips. */\n:host([collapsed]) .section-label {\n height: 1px;\n margin: 4px 10px;\n padding: 0;\n overflow: hidden;\n background: var(--jl-line);\n color: transparent;\n}\n\n:host([collapsed]) .section:first-child .section-label {\n display: none;\n}\n\n:host([collapsed]) .item {\n justify-content: center;\n padding: 0;\n}\n\n:host([collapsed]) .item-label {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n}\n\n:host([collapsed]) .item-initial {\n display: inline-flex;\n}\n\n:host([collapsed]) .item-badge {\n position: absolute;\n top: 5px;\n right: 9px;\n min-width: 0;\n width: 8px;\n height: 8px;\n padding: 0;\n overflow: hidden;\n background: var(--jl-accent);\n color: transparent;\n}\n\n:host([collapsed]) .children {\n display: none;\n}\n";
2
+ var jl_side_nav_default = ":host {\n display: block;\n width: var(--width, 248px);\n height: 100%;\n min-height: 0;\n transition: width var(--jl-slow) var(--jl-ease);\n}\n\n:host([rail]) {\n width: var(--rail-width, 60px);\n}\n\nnav {\n display: flex;\n flex-direction: column;\n gap: 8px;\n height: 100%;\n padding: 12px 8px;\n overflow-x: hidden;\n overflow-y: auto;\n color: var(--jl-text-2);\n font-size: var(--jl-font-md);\n}\n\n/* The footer and the fold button keep to the bottom; the button is centred in the rail. */\n.bottom {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: auto;\n}\n\n.fold {\n align-self: flex-start;\n}\n\n:host([rail]) .fold {\n align-self: center;\n}\n\n.sections {\n display: grid;\n gap: 14px;\n}\n\n.section {\n display: grid;\n gap: 2px;\n}\n\n.section-label {\n padding: 4px 10px 6px;\n color: var(--jl-muted);\n font-size: 11px;\n font-weight: 650;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n white-space: nowrap;\n}\n\n.items,\n.children {\n display: grid;\n gap: 2px;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.children {\n margin-top: 2px;\n}\n\n.entry {\n position: relative;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n min-height: 34px;\n padding: 0 10px;\n border: none;\n border-radius: var(--jl-radius-sm);\n background: none;\n color: inherit;\n font: inherit;\n font-weight: 550;\n text-align: start;\n text-decoration: none;\n white-space: nowrap;\n cursor: pointer;\n transition:\n background var(--jl-fast) var(--jl-ease),\n color var(--jl-fast) var(--jl-ease);\n}\n\n.item[data-depth=\"1\"] {\n padding-left: 36px;\n}\n\n.item[data-depth=\"2\"] {\n padding-left: 50px;\n}\n\n.item[data-depth=\"3\"] {\n padding-left: 64px;\n}\n\n.item:hover {\n background: var(--jl-surface-3);\n color: var(--jl-text);\n}\n\n.item[aria-current=\"page\"] {\n background: var(--jl-accent-soft);\n color: var(--jl-accent);\n}\n\n.item.holds-active {\n color: var(--jl-text);\n}\n\n.item-icon {\n color: var(--jl-faint);\n}\n\n.item:hover .item-icon,\n.item.holds-active .item-icon {\n color: var(--jl-text-2);\n}\n\n.item[aria-current=\"page\"] .item-icon {\n color: var(--jl-accent);\n}\n\n.item-initial {\n display: none;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: var(--jl-radius-xs);\n background: var(--jl-surface-3);\n color: var(--jl-text-2);\n font-size: 11px;\n font-weight: 700;\n}\n\n/* Nested items without an icon line their labels up under their parent's label. */\n.item:not([data-depth=\"0\"]) .item-initial {\n display: none;\n}\n.item-label {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.item-badge {\n flex: none;\n min-width: 20px;\n padding: 2px 6px;\n border-radius: var(--jl-radius-pill);\n background: var(--jl-surface-3);\n color: var(--jl-text-2);\n font-size: 11px;\n font-weight: 650;\n line-height: 14px;\n text-align: center;\n}\n\n.item[aria-current=\"page\"] .item-badge {\n background: var(--jl-accent);\n color: var(--jl-on-accent);\n}\n\n.expander jl-icon {\n transition: transform var(--jl-fast) var(--jl-ease);\n}\n\n.chevron {\n color: var(--jl-faint);\n transition: transform var(--jl-fast) var(--jl-ease);\n}\n\n.entry.open > .item .chevron,\n.entry.open > .expander jl-icon {\n transform: rotate(90deg);\n}\n\n/* The chevron of a parent that is also a link: its own button, over the end of the row. */\n.expander {\n position: absolute;\n top: 5px;\n right: 6px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n padding: 0;\n border: none;\n border-radius: var(--jl-radius-xs);\n background: none;\n color: var(--jl-faint);\n cursor: pointer;\n}\n\n.expander:hover {\n background: var(--jl-surface-3);\n color: var(--jl-text);\n}\n\n.entry:has(> .expander) > .item {\n padding-right: 36px;\n}\n\n/* The rail: icons only, centred; labels stay for screen readers, and show as tooltips. */\n:host([rail]) .section-label {\n height: 1px;\n margin: 4px 10px;\n padding: 0;\n overflow: hidden;\n background: var(--jl-line);\n color: transparent;\n}\n\n:host([rail]) .section:first-child .section-label {\n display: none;\n}\n\n:host([rail]) .item {\n justify-content: center;\n padding: 0;\n}\n\n:host([rail]) .item-label {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n}\n\n:host([rail]) .item-initial {\n display: inline-flex;\n}\n\n:host([rail]) .item-badge {\n position: absolute;\n top: 5px;\n right: 9px;\n min-width: 0;\n width: 8px;\n height: 8px;\n padding: 0;\n overflow: hidden;\n background: var(--jl-accent);\n color: transparent;\n}\n\n:host([rail]) .children {\n display: none;\n}\n";
3
3
  export { jl_side_nav_default as default };
@@ -3,12 +3,15 @@ import type { SideNavSection } from '../types/jl-side-nav.types';
3
3
  /**
4
4
  * The navigation of an app, down its side: sections of items with icons, badges and nested items that open with
5
5
  * a chevron. `value` is the active item (its parents open to show it); `collapsed` makes it a rail of icons, with
6
- * the labels as tooltips. Items with an `href` are links; the others fire `select`.
6
+ * the labels as tooltips, and `collapsible` lets people fold and unfold it with a button at its bottom. Items with
7
+ * an `href` are links; the others fire `select`.
7
8
  */
8
9
  export declare class JlSideNav extends JlElement {
9
10
  static readonly selector = "jl-side-nav";
11
+ /** `collapse`: people folded the side nav into its rail (true) or unfolded it (false), for the app to remember. */
10
12
  readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
11
13
  select: string;
14
+ collapse: boolean;
12
15
  }>>;
13
16
  readonly sections: Readonly<Readonly<{
14
17
  get(): SideNavSection[];
@@ -24,6 +27,29 @@ export declare class JlSideNav extends JlElement {
24
27
  get(): boolean;
25
28
  set(setter: boolean | ((prevState: boolean) => boolean)): void;
26
29
  }>>;
30
+ /** Adds a button at the bottom that folds the side nav into its rail and unfolds it. */
31
+ readonly collapsible: Readonly<Readonly<{
32
+ get(): boolean;
33
+ set(setter: boolean | ((prevState: boolean) => boolean)): void;
34
+ }>>;
35
+ /** The fold button's text while the side nav is open. */
36
+ readonly collapseLabel: Readonly<Readonly<{
37
+ get(): string;
38
+ set(setter: string | ((prevState: string) => string)): void;
39
+ }>>;
40
+ /** Its hint while the side nav is a rail. */
41
+ readonly expandLabel: Readonly<Readonly<{
42
+ get(): string;
43
+ set(setter: string | ((prevState: string) => string)): void;
44
+ }>>;
45
+ /**
46
+ * Set by `jl-app-shell` while the side nav is its drawer, on a narrow screen: the drawer shows it whole, and its
47
+ * fold button hides, `collapsed` waiting for a wide screen again.
48
+ */
49
+ readonly inDrawer: Readonly<Readonly<{
50
+ get(): boolean;
51
+ set(setter: boolean | ((prevState: boolean) => boolean)): void;
52
+ }>>;
27
53
  /** The name of the navigation for screen readers. */
28
54
  readonly label: Readonly<Readonly<{
29
55
  get(): string;
@@ -39,6 +65,12 @@ export declare class JlSideNav extends JlElement {
39
65
  /** Opens or closes a parent item. */
40
66
  toggleItem(id: string, force?: boolean): void;
41
67
  private onClick;
68
+ /** Whether it shows as the rail now: folded, and not in a drawer. */
69
+ private isRail;
70
+ private fold;
71
+ /** The rail, or the whole side nav: drawn again, said on the host (`rail`) for the styles and the page. */
72
+ private renderRail;
73
+ private renderFold;
42
74
  private choose;
43
75
  /** Up and Down move between the rows people can see, Right opens a parent, Left closes it or goes up to it. */
44
76
  private onKey;