@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
@@ -3,8 +3,9 @@ import { JlFormControl } from "../base/jl-form-control.js";
3
3
  import { oneOf } from "../lib/attributes.js";
4
4
  import { registerIcons } from "../lib/icons.js";
5
5
  import { CONTROL_SIZES } from "../lib/options.js";
6
- import { setOrRemove } from "../lib/dom.js";
6
+ import { jlText } from "../lib/texts.js";
7
7
  import { JlIcon } from "../atoms/jl-icon.js";
8
+ import { setOrRemove } from "../lib/dom.js";
8
9
  import { clamp, parseNumber, stepValue } from "../lib/numeric.js";
9
10
  import jl_number_input_default from "./jl-number-input.css_raw.js";
10
11
  import { css } from "@jadis/core";
@@ -18,7 +19,6 @@ registerIcons({
18
19
  var DEFAULT_STEP = 1;
19
20
  var PAGE_STEPS = 10;
20
21
  var ICON_SIZE = 14;
21
- var VALUE_MISSING = "Please fill in this field.";
22
22
  var positiveStep = (value) => {
23
23
  const parsed = parseNumber(value);
24
24
  return parsed === null || parsed <= 0 ? DEFAULT_STEP : parsed;
@@ -89,7 +89,7 @@ var JlNumberInput = class extends JlFormControl {
89
89
  class: "action step down",
90
90
  type: "button",
91
91
  tabIndex: -1,
92
- "aria-label": "Decrease",
92
+ "aria-label": jlText("decrease"),
93
93
  children: /* @__PURE__ */ jsx(JlIcon, {
94
94
  name: "minus",
95
95
  size: ICON_SIZE
@@ -106,7 +106,7 @@ var JlNumberInput = class extends JlFormControl {
106
106
  class: "action step up",
107
107
  type: "button",
108
108
  tabIndex: -1,
109
- "aria-label": "Increase",
109
+ "aria-label": jlText("increase"),
110
110
  children: /* @__PURE__ */ jsx(JlIcon, {
111
111
  name: "plus",
112
112
  size: ICON_SIZE
@@ -213,7 +213,7 @@ var JlNumberInput = class extends JlFormControl {
213
213
  this.internals.setValidity({});
214
214
  return;
215
215
  }
216
- this.internals.setValidity({ valueMissing: true }, VALUE_MISSING, this._refs.native);
216
+ this.internals.setValidity({ valueMissing: true }, jlText("fillField"), this._refs.native);
217
217
  }
218
218
  };
219
219
  JlNumberInput.register();
@@ -3,6 +3,7 @@ import { JlElement } from "../base/jl-element.js";
3
3
  import { clampedNumber, oneOf } from "../lib/attributes.js";
4
4
  import { registerIcons } from "../lib/icons.js";
5
5
  import { CONTROL_SIZES } from "../lib/options.js";
6
+ import { jlText } from "../lib/texts.js";
6
7
  import { JlIcon } from "../atoms/jl-icon.js";
7
8
  import { clampPage, pageItems } from "../lib/pagination.js";
8
9
  import jl_pagination_default from "./jl-pagination.css_raw.js";
@@ -19,7 +20,6 @@ var ICON_SIZES = {
19
20
  md: 16,
20
21
  sm: 14
21
22
  };
22
- var DEFAULT_LABEL = "Pagination";
23
23
  var MAX_PAGES = Number.MAX_SAFE_INTEGER;
24
24
  var MAX_NEIGHBOURS = 5;
25
25
  var STEP_BACK = -1;
@@ -61,8 +61,8 @@ var JlPagination = class extends JlElement {
61
61
  parse: oneOf(CONTROL_SIZES, "md")
62
62
  });
63
63
  /** The navigation's name for screen readers, when a page has more than one. */
64
- label = this.useChange(DEFAULT_LABEL, (value) => {
65
- this._refs.nav.setAttribute("aria-label", value || DEFAULT_LABEL);
64
+ label = this.useChange(jlText("pagination"), (value) => {
65
+ this._refs.nav.setAttribute("aria-label", value || jlText("pagination"));
66
66
  }, { attribute: "label" });
67
67
  _refs = this.useRefs((ref) => ({
68
68
  list: ref(".pages"),
@@ -80,7 +80,7 @@ var JlPagination = class extends JlElement {
80
80
  /* @__PURE__ */ jsx("button", {
81
81
  type: "button",
82
82
  class: "button previous",
83
- "aria-label": "Previous page",
83
+ "aria-label": jlText("previousPage"),
84
84
  children: /* @__PURE__ */ jsx(JlIcon, { name: "chevron-left" })
85
85
  }),
86
86
  /* @__PURE__ */ jsx("ol", { class: "pages" }),
@@ -91,7 +91,7 @@ var JlPagination = class extends JlElement {
91
91
  /* @__PURE__ */ jsx("button", {
92
92
  type: "button",
93
93
  class: "button next",
94
- "aria-label": "Next page",
94
+ "aria-label": jlText("nextPage"),
95
95
  children: /* @__PURE__ */ jsx(JlIcon, { name: "chevron-right" })
96
96
  })
97
97
  ]
@@ -1,6 +1,7 @@
1
1
  import { BASE_STYLES } from "../styles/shared.js";
2
2
  import { JlFormControl } from "../base/jl-form-control.js";
3
3
  import { jsonOf, oneOf } from "../lib/attributes.js";
4
+ import { jlText } from "../lib/texts.js";
4
5
  import { rovingIndex } from "../lib/roving-index.js";
5
6
  import jl_radio_group_default from "./jl-radio-group.css_raw.js";
6
7
  import { css } from "@jadis/core";
@@ -10,7 +11,6 @@ var RADIO_GROUP_ORIENTATIONS = ["vertical", "horizontal"];
10
11
  var RADIO_GROUP_VARIANTS = ["plain", "cards"];
11
12
  var NO_VALUE = "";
12
13
  var OPTION_SELECTOR = "[role=\"radio\"]";
13
- var VALUE_MISSING = "Please select one of these options.";
14
14
  /** The keys that move the choice, as with native radios: both arrow pairs whatever the orientation. */
15
15
  var MOVES = /* @__PURE__ */ new Map([
16
16
  ["ArrowDown", "next"],
@@ -195,7 +195,7 @@ var JlRadioGroup = class extends JlFormControl {
195
195
  this.internals.setValidity({});
196
196
  return;
197
197
  }
198
- this.internals.setValidity({ valueMissing: true }, VALUE_MISSING, this.radios()[0] ?? this._refs.group);
198
+ this.internals.setValidity({ valueMissing: true }, jlText("selectOption"), this.radios()[0] ?? this._refs.group);
199
199
  }
200
200
  };
201
201
  JlRadioGroup.register();
@@ -1,8 +1,9 @@
1
1
  import { BASE_STYLES } from "../styles/shared.js";
2
2
  import { JlElement } from "../base/jl-element.js";
3
3
  import { registerIcons } from "../lib/icons.js";
4
- import { setOrRemove } from "../lib/dom.js";
4
+ import { jlText } from "../lib/texts.js";
5
5
  import { JlIcon } from "../atoms/jl-icon.js";
6
+ import { setOrRemove } from "../lib/dom.js";
6
7
  import { JlKbd } from "../atoms/jl-kbd.js";
7
8
  import { parseShortcut, shortcutAria } from "../lib/key-shortcut.js";
8
9
  import jl_search_button_default from "./jl-search-button.css_raw.js";
@@ -11,7 +12,6 @@ import { Search } from "lucide";
11
12
  import { jsx, jsxs } from "@jadis/core/jsx-runtime";
12
13
  // src/molecules/jl-search-button.tsx
13
14
  registerIcons({ search: Search });
14
- var DEFAULT_LABEL = "Search";
15
15
  var DEFAULT_HOTKEY = "mod+k";
16
16
  var NO_HOTKEY = "none";
17
17
  var ICON_SIZE = 15;
@@ -26,14 +26,14 @@ var JlSearchButton = class extends JlElement {
26
26
  static selector = "jl-search-button";
27
27
  events = this.useEvents();
28
28
  /** The text, and the button's name: "Search". */
29
- label = this.useChange(DEFAULT_LABEL, () => this.renderLabel(), { attribute: "label" });
29
+ label = this.useChange(jlText("search"), () => this.renderLabel(), { attribute: "label" });
30
30
  /** The keys that open the palette, as `jl-kbd` writes them: `mod+k`; `none` for no keys. */
31
31
  hotkey = this.useChange(DEFAULT_HOTKEY, () => this.renderHotkey(), { attribute: "hotkey" });
32
32
  /** The icon alone, the label as its tooltip. */
33
33
  compact = this.useChange(false, (value) => this.reflect("compact", value), { attribute: "compact" });
34
34
  _refs = this.useRefs((ref) => ({
35
35
  button: ref("button"),
36
- keys: ref("jl-kbd"),
36
+ keys: ref(JlKbd),
37
37
  text: ref(".text")
38
38
  }));
39
39
  focus(options) {
@@ -63,7 +63,7 @@ var JlSearchButton = class extends JlElement {
63
63
  this.on(this._refs.button, "click", () => this.events.emit("press"));
64
64
  }
65
65
  renderLabel() {
66
- const label = this.label.get() || DEFAULT_LABEL;
66
+ const label = this.label.get() || jlText("search");
67
67
  this._refs.text.textContent = label;
68
68
  this._refs.button.setAttribute("aria-label", label);
69
69
  this._refs.button.title = label;
@@ -3,8 +3,9 @@ import { JlFormControl } from "../base/jl-form-control.js";
3
3
  import { oneOf } from "../lib/attributes.js";
4
4
  import { registerIcons } from "../lib/icons.js";
5
5
  import { CONTROL_SIZES } from "../lib/options.js";
6
- import { setOrRemove } from "../lib/dom.js";
6
+ import { jlText } from "../lib/texts.js";
7
7
  import { JlIcon } from "../atoms/jl-icon.js";
8
+ import { setOrRemove } from "../lib/dom.js";
8
9
  import { JlSpinner } from "../atoms/jl-spinner.js";
9
10
  import { hasCommandModifier, matchesShortcut, parseShortcut, shortcutAria, shortcutLabels } from "../lib/key-shortcut.js";
10
11
  import jl_search_default from "./jl-search.css_raw.js";
@@ -19,7 +20,6 @@ registerIcons({
19
20
  var DEFAULT_DEBOUNCE_MS = 250;
20
21
  var ICON_SIZE = 16;
21
22
  var ACTION_ICON_SIZE = 14;
22
- var DEFAULT_LABEL = "Search";
23
23
  var MAC_PATTERN = /Mac|iPhone|iPad|iPod/;
24
24
  var TYPING_TARGETS = "input, textarea, select, [contenteditable]";
25
25
  var isMac = () => MAC_PATTERN.test(navigator.userAgent);
@@ -38,7 +38,7 @@ var JlSearch = class extends JlFormControl {
38
38
  value = this.useChange("", (value) => this.renderValue(value), { attribute: "value" });
39
39
  placeholder = this.useChange("", (value) => setOrRemove(this._refs.native, "placeholder", value), { attribute: "placeholder" });
40
40
  /** The field's name for screen readers; "Search" when nothing else names it. */
41
- label = this.useChange("", (value) => this._refs.native.setAttribute("aria-label", value || DEFAULT_LABEL), { attribute: "label" });
41
+ label = this.useChange("", (value) => this._refs.native.setAttribute("aria-label", value || jlText("search")), { attribute: "label" });
42
42
  /** How long typing must pause, in milliseconds, before `search` fires. */
43
43
  debounce = this.useChange(DEFAULT_DEBOUNCE_MS, () => void 0, { attribute: "debounce" });
44
44
  /** Keys that focus the field from anywhere: "/" or "mod+k" (⌘ on a Mac, Ctrl elsewhere). */
@@ -68,7 +68,7 @@ var JlSearch = class extends JlFormControl {
68
68
  icon: ref(".icon"),
69
69
  keys: ref(".keys"),
70
70
  native: ref("input"),
71
- spinner: ref("jl-spinner")
71
+ spinner: ref(JlSpinner)
72
72
  }));
73
73
  focus(options) {
74
74
  this._refs.native.focus(options);
@@ -108,7 +108,7 @@ var JlSearch = class extends JlFormControl {
108
108
  /* @__PURE__ */ jsx("button", {
109
109
  class: "action clear",
110
110
  type: "button",
111
- "aria-label": "Clear the search",
111
+ "aria-label": jlText("clearSearch"),
112
112
  tabIndex: -1,
113
113
  hidden: true,
114
114
  children: /* @__PURE__ */ jsx(JlIcon, {
@@ -2,10 +2,11 @@ import { BASE_STYLES } from "../styles/shared.js";
2
2
  import { JlFormControl } from "../base/jl-form-control.js";
3
3
  import { jsonOf, oneOf } from "../lib/attributes.js";
4
4
  import { CONTROL_SIZES } from "../lib/options.js";
5
- import { setOrRemove } from "../lib/dom.js";
6
5
  import { JlIcon } from "../atoms/jl-icon.js";
6
+ import { setOrRemove } from "../lib/dom.js";
7
7
  import { firstIndex, lastIndex, stepIndex } from "../lib/roving.js";
8
8
  import { moveIndicator } from "../lib/indicator.js";
9
+ import { watchSizes } from "../lib/resize.js";
9
10
  import jl_segmented_default from "./jl-segmented.css_raw.js";
10
11
  import { css } from "@jadis/core";
11
12
  import { jsx, jsxs } from "@jadis/core/jsx-runtime";
@@ -55,7 +56,7 @@ var JlSegmented = class extends JlFormControl {
55
56
  /** The group's name for screen readers, when no visible label names it. */
56
57
  label = this.useChange("", (value) => setOrRemove(this._refs.group, "aria-label", value), { attribute: "label" });
57
58
  _state = { disabledByForm: false };
58
- _resize = new ResizeObserver(() => this.placeThumb());
59
+ _resize = watchSizes(() => this.placeThumb());
59
60
  _refs = this.useRefs((ref) => ({
60
61
  group: ref(".group"),
61
62
  thumb: ref(".thumb")
@@ -1,3 +1,3 @@
1
1
  // src/molecules/jl-steps.css?raw
2
- var jl_steps_default = ":host {\n display: block;\n container-type: inline-size;\n --marker: 28px;\n}\n\nol {\n display: flex;\n padding: 0;\n list-style: none;\n}\n\n.step {\n display: flex;\n flex: 1;\n min-width: 0;\n}\n\n.step:last-child {\n flex: none;\n}\n\n.head {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n padding: 0;\n border: none;\n border-radius: var(--jl-radius-xs);\n background: none;\n text-align: start;\n}\n\nbutton.head {\n cursor: pointer;\n}\n\n.marker {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: var(--marker);\n height: var(--marker);\n border: 1px solid var(--jl-line-strong);\n border-radius: 50%;\n background: var(--jl-surface);\n color: var(--jl-muted);\n font-size: var(--jl-font-sm);\n font-weight: 650;\n font-variant-numeric: tabular-nums;\n}\n\n.text {\n display: grid;\n gap: 2px;\n min-width: 0;\n padding-top: 4px;\n}\n\n.label {\n color: var(--jl-muted);\n font-size: var(--jl-font-md);\n font-weight: 600;\n line-height: 20px;\n}\n\n.description {\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n line-height: 1.45;\n}\n\n.step[data-status=\"done\"] .marker {\n border-color: var(--jl-accent-line);\n background: var(--jl-accent-soft);\n color: var(--jl-accent);\n}\n\n.step[data-status=\"done\"] .label {\n color: var(--jl-text);\n}\n\n.step[data-status=\"current\"] .marker {\n border-color: var(--jl-accent);\n background: var(--jl-accent);\n color: var(--jl-on-accent);\n box-shadow: 0 0 0 4px var(--jl-accent-soft);\n}\n\n.step[data-status=\"current\"] .label {\n color: var(--jl-accent);\n font-weight: 700;\n}\n\nbutton.head:hover .label {\n text-decoration: underline;\n text-underline-offset: 3px;\n}\n\n.line {\n flex: 1;\n min-width: 24px;\n height: 1px;\n /* Further from the label before it than from the next marker: a label reads as its own step's. */\n margin: calc(var(--marker) / 2) 12px 0 20px;\n background: var(--jl-line-strong);\n}\n\n.step[data-status=\"done\"] .line {\n background: var(--jl-accent);\n}\n\n.step:last-child .line {\n display: none;\n}\n\n/* Vertical: the markers in a column joined by a rule, the text beside them. */\n:host([orientation=\"vertical\"]) ol {\n flex-direction: column;\n}\n\n:host([orientation=\"vertical\"]) .step {\n position: relative;\n flex: none;\n padding-bottom: 24px;\n}\n\n:host([orientation=\"vertical\"]) .step:last-child {\n padding-bottom: 0;\n}\n\n:host([orientation=\"vertical\"]) .line {\n position: absolute;\n top: calc(var(--marker) + 4px);\n bottom: 4px;\n left: calc(var(--marker) / 2 - 0.5px);\n width: 1px;\n height: auto;\n min-width: 0;\n margin: 0;\n}\n\n/* Narrow, the horizontal steps keep their markers and only the current step's text. */\n@container (width < 560px) {\n /* Out of sight, still read: the same rules as .sr-only. */\n :host(:not([orientation=\"vertical\"])) .step:not([data-status=\"current\"]) .text {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n }\n\n :host(:not([orientation=\"vertical\"])) .description {\n display: none;\n }\n\n :host(:not([orientation=\"vertical\"])) .line {\n min-width: 8px;\n margin: calc(var(--marker) / 2) 8px 0;\n }\n\n /* The current step takes three shares of the room, the others one, never less than their marker and line: the\n label never runs into the next step, and takes an ellipsis only when there is no room at all. */\n :host(:not([orientation=\"vertical\"])) .step:not([data-status=\"current\"]) {\n min-width: min-content;\n }\n\n :host(:not([orientation=\"vertical\"])) .step[data-status=\"current\"] {\n flex: 3 1 0;\n }\n\n :host(:not([orientation=\"vertical\"])) .step[data-status=\"current\"] .label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n :host(:not([orientation=\"vertical\"])) .step[data-status=\"current\"] .line {\n min-width: 24px;\n margin-left: 16px;\n }\n}\n";
2
+ var jl_steps_default = ":host {\n display: block;\n container-type: inline-size;\n --marker: 28px;\n}\n\nol {\n display: flex;\n padding: 0;\n list-style: none;\n}\n\n.step {\n display: flex;\n flex: 1;\n min-width: 0;\n}\n\n.step:last-child {\n flex: none;\n}\n\n.head {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n padding: 0;\n border: none;\n border-radius: var(--jl-radius-xs);\n background: none;\n text-align: start;\n}\n\nbutton.head {\n cursor: pointer;\n}\n\n.marker {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: var(--marker);\n height: var(--marker);\n border: 1px solid var(--jl-line-strong);\n border-radius: 50%;\n background: var(--jl-surface);\n color: var(--jl-muted);\n font-size: var(--jl-font-sm);\n font-weight: 650;\n font-variant-numeric: tabular-nums;\n}\n\n.text {\n display: grid;\n gap: 2px;\n min-width: 0;\n padding-top: 4px;\n}\n\n.label {\n color: var(--jl-muted);\n font-size: var(--jl-font-md);\n font-weight: 600;\n line-height: 20px;\n}\n\n.description {\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n line-height: 1.45;\n}\n\n.step[data-status=\"done\"] .marker {\n border-color: var(--jl-accent-line);\n background: var(--jl-accent-soft);\n color: var(--jl-accent);\n}\n\n.step[data-status=\"done\"] .label {\n color: var(--jl-text);\n}\n\n.step[data-status=\"current\"] .marker {\n border-color: var(--jl-accent);\n background: var(--jl-accent);\n color: var(--jl-on-accent);\n box-shadow: 0 0 0 4px var(--jl-accent-soft);\n}\n\n.step[data-status=\"current\"] .label {\n color: var(--jl-accent);\n font-weight: 700;\n}\n\n.step[data-failed] .marker {\n border-color: color-mix(in srgb, var(--jl-danger) 40%, var(--jl-line));\n background: var(--jl-danger-soft);\n color: var(--jl-danger);\n box-shadow: none;\n}\n\n.step[data-failed] .label {\n color: var(--jl-danger);\n}\n\nbutton.head:hover .label {\n text-decoration: underline;\n text-underline-offset: 3px;\n}\n\n.line {\n flex: 1;\n min-width: 24px;\n height: 1px;\n /* Further from the label before it than from the next marker: a label reads as its own step's. */\n margin: calc(var(--marker) / 2) 12px 0 20px;\n background: var(--jl-line-strong);\n}\n\n.step[data-status=\"done\"] .line {\n background: var(--jl-accent);\n}\n\n.step:last-child .line {\n display: none;\n}\n\n/* Vertical: the markers in a column joined by a rule, the text beside them. */\n:host([orientation=\"vertical\"]) ol {\n flex-direction: column;\n}\n\n:host([orientation=\"vertical\"]) .step {\n position: relative;\n flex: none;\n padding-bottom: 24px;\n}\n\n:host([orientation=\"vertical\"]) .step:last-child {\n padding-bottom: 0;\n}\n\n:host([orientation=\"vertical\"]) .line {\n position: absolute;\n top: calc(var(--marker) + 4px);\n bottom: 4px;\n left: calc(var(--marker) / 2 - 0.5px);\n width: 1px;\n height: auto;\n min-width: 0;\n margin: 0;\n}\n\n/* Narrow, the horizontal steps keep their markers and only the current step's text. */\n@container (width < 560px) {\n /* Out of sight, still read: the same rules as .sr-only. */\n :host(:not([orientation=\"vertical\"])) .step:not([data-status=\"current\"]) .text {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n }\n\n :host(:not([orientation=\"vertical\"])) .description {\n display: none;\n }\n\n :host(:not([orientation=\"vertical\"])) .line {\n min-width: 8px;\n margin: calc(var(--marker) / 2) 8px 0;\n }\n\n /* The current step takes three shares of the room, the others one, never less than their marker and line: the\n label never runs into the next step, and takes an ellipsis only when there is no room at all. */\n :host(:not([orientation=\"vertical\"])) .step:not([data-status=\"current\"]) {\n min-width: min-content;\n }\n\n :host(:not([orientation=\"vertical\"])) .step[data-status=\"current\"] {\n flex: 3 1 0;\n }\n\n :host(:not([orientation=\"vertical\"])) .step[data-status=\"current\"] .label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n :host(:not([orientation=\"vertical\"])) .step[data-status=\"current\"] .line {\n min-width: 24px;\n margin-left: 16px;\n }\n}\n";
3
3
  export { jl_steps_default as default };
@@ -38,6 +38,8 @@ export declare class JlSteps extends JlElement {
38
38
  templateCss(): string;
39
39
  onConnect(): void;
40
40
  private render;
41
+ /** A failed step's ×, a done one's check, or the step's number. */
42
+ private markerOf;
41
43
  private step;
42
44
  /** Goes back to a done step people picked, and says so. */
43
45
  private goBackTo;
@@ -2,21 +2,28 @@ import { BASE_STYLES } from "../styles/shared.js";
2
2
  import { JlElement } from "../base/jl-element.js";
3
3
  import { clampedNumber, jsonOf, oneOf } 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_steps_default from "./jl-steps.css_raw.js";
7
8
  import { css } from "@jadis/core";
8
- import { Check } from "lucide";
9
+ import { Check, X } from "lucide";
9
10
  import { jsx, jsxs } from "@jadis/core/jsx-runtime";
10
11
  // src/molecules/jl-steps.tsx
11
- registerIcons({ check: Check });
12
+ registerIcons({
13
+ check: Check,
14
+ x: X
15
+ });
12
16
  var STEPS_ORIENTATIONS = ["horizontal", "vertical"];
13
17
  var CHECK_SIZE = 14;
14
18
  var MAX_STEPS = 100;
15
- var DEFAULT_LABEL = "Progress";
16
- var STATUS_TEXT = {
17
- current: ", current step",
18
- done: ", completed",
19
- upcoming: ""
19
+ var STATUS_TEXTS = {
20
+ current: "stepCurrent",
21
+ done: "stepDone",
22
+ upcoming: null
23
+ };
24
+ var statusText = (status) => {
25
+ const key = STATUS_TEXTS[status];
26
+ return key === null ? "" : jlText(key);
20
27
  };
21
28
  var statusOf = (index, current) => {
22
29
  if (index < current) return "done";
@@ -45,8 +52,8 @@ var JlSteps = class extends JlElement {
45
52
  /** Done steps become buttons that go back to them. */
46
53
  clickable = this.useChange(false, () => this.render(), { attribute: "clickable" });
47
54
  /** The list's name for screen readers. */
48
- label = this.useChange(DEFAULT_LABEL, (value) => {
49
- this._refs.list.setAttribute("aria-label", value || DEFAULT_LABEL);
55
+ label = this.useChange(jlText("progress"), (value) => {
56
+ this._refs.list.setAttribute("aria-label", value || jlText("progress"));
50
57
  }, { attribute: "label" });
51
58
  _refs = this.useRefs((ref) => ({ list: ref("ol") }));
52
59
  templateHtml() {
@@ -65,21 +72,29 @@ var JlSteps = class extends JlElement {
65
72
  const current = this.current.get();
66
73
  this._refs.list.replaceChildren(...this.items.get().map((item, index) => this.step(item, index, statusOf(index, current))));
67
74
  }
75
+ /** A failed step's ×, a done one's check, or the step's number. */
76
+ markerOf(item, index, status) {
77
+ if (item.failed) return /* @__PURE__ */ jsx(JlIcon, {
78
+ name: "x",
79
+ size: CHECK_SIZE
80
+ });
81
+ return status === "done" ? /* @__PURE__ */ jsx(JlIcon, {
82
+ name: "check",
83
+ size: CHECK_SIZE
84
+ }) : String(index + 1);
85
+ }
68
86
  step(item, index, status) {
69
87
  const content = [/* @__PURE__ */ jsx("span", {
70
88
  class: "marker",
71
89
  "aria-hidden": "true",
72
- children: status === "done" ? /* @__PURE__ */ jsx(JlIcon, {
73
- name: "check",
74
- size: CHECK_SIZE
75
- }) : String(index + 1)
90
+ children: this.markerOf(item, index, status)
76
91
  }), /* @__PURE__ */ jsxs("span", {
77
92
  class: "text",
78
93
  children: [/* @__PURE__ */ jsxs("span", {
79
94
  class: "label",
80
95
  children: [item.label, /* @__PURE__ */ jsx("span", {
81
96
  class: "sr-only",
82
- children: STATUS_TEXT[status]
97
+ children: item.failed ? jlText("failedStep") : statusText(status)
83
98
  })]
84
99
  }), item.description === void 0 ? "" : /* @__PURE__ */ jsx("span", {
85
100
  class: "description",
@@ -90,6 +105,7 @@ var JlSteps = class extends JlElement {
90
105
  return /* @__PURE__ */ jsxs("li", {
91
106
  class: "step",
92
107
  "data-status": status,
108
+ "data-failed": item.failed ? "" : void 0,
93
109
  "aria-current": status === "current" ? "step" : void 0,
94
110
  children: [canGoBack ? /* @__PURE__ */ jsx("button", {
95
111
  type: "button",
@@ -2,10 +2,11 @@ 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 { setOrRemove, uniqueId } from "../lib/dom.js";
6
5
  import { JlIcon } from "../atoms/jl-icon.js";
6
+ import { setOrRemove, uniqueId } from "../lib/dom.js";
7
7
  import { firstIndex, lastIndex, stepIndex } from "../lib/roving.js";
8
8
  import { moveIndicator } from "../lib/indicator.js";
9
+ import { watchSizes } from "../lib/resize.js";
9
10
  import { JlTabPanel } from "./jl-tab-panel.js";
10
11
  import jl_tabs_default from "./jl-tabs.css_raw.js";
11
12
  import { css } from "@jadis/core";
@@ -55,7 +56,7 @@ var JlTabs = class extends JlElement {
55
56
  label = this.useChange("", (value) => setOrRemove(this._refs.list, "aria-label", value), { attribute: "label" });
56
57
  _observers = {
57
58
  mutations: new MutationObserver((records) => this.onMutations(records)),
58
- resize: new ResizeObserver(() => this.placeIndicator())
59
+ resize: watchSizes(() => this.placeIndicator())
59
60
  };
60
61
  _refs = this.useRefs((ref) => ({
61
62
  indicator: ref(".indicator"),
@@ -4,8 +4,9 @@ import { clampedNumber, jsonOf, oneOf } from "../lib/attributes.js";
4
4
  import { followAnchor } from "../lib/floating.js";
5
5
  import { registerIcons } from "../lib/icons.js";
6
6
  import { CONTROL_SIZES } from "../lib/options.js";
7
- import { setOrRemove, uniqueId } from "../lib/dom.js";
7
+ import { jlText } from "../lib/texts.js";
8
8
  import { JlIcon } from "../atoms/jl-icon.js";
9
+ import { setOrRemove, uniqueId } from "../lib/dom.js";
9
10
  import { keepInView } from "../lib/keep-in-view.js";
10
11
  import { filterOptions, matchParts, nextEnabledIndex } from "../lib/option-filter.js";
11
12
  import { addTags, hasTag, hasTagSeparator, splitTags } from "../lib/tags.js";
@@ -18,7 +19,6 @@ registerIcons({ x: X });
18
19
  var REMOVE_ICON_SIZE = 12;
19
20
  var NONE = -1;
20
21
  var PLACEMENT = "bottom-start";
21
- var VALUE_MISSING = "Add at least one tag.";
22
22
  var NO_LIMIT = 0;
23
23
  var COMMA = ",";
24
24
  var MAX_SUGGESTIONS = 8;
@@ -413,7 +413,7 @@ var JlTagInput = class extends JlFormControl {
413
413
  }
414
414
  renderValidity() {
415
415
  if (this.required.get() && this.value.get().length === 0) {
416
- this.internals.setValidity({ valueMissing: true }, VALUE_MISSING, this._refs.native);
416
+ this.internals.setValidity({ valueMissing: true }, jlText("addTag"), this._refs.native);
417
417
  return;
418
418
  }
419
419
  this.internals.setValidity({});
@@ -1,8 +1,8 @@
1
1
  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
- import { setOrRemove } from "../lib/dom.js";
5
4
  import { JlIcon } from "../atoms/jl-icon.js";
5
+ import { setOrRemove } from "../lib/dom.js";
6
6
  import jl_timeline_default from "./jl-timeline.css_raw.js";
7
7
  import { css } from "@jadis/core";
8
8
  import { jsx, jsxs } from "@jadis/core/jsx-runtime";
@@ -1,3 +1,3 @@
1
1
  // src/molecules/jl-toast.css?raw
2
- var jl_toast_default = ":host {\n display: block;\n /* Coloured as jl-alert is, from the same tokens: a toned toast is tinted, its border and icon in its tone. */\n --fg: var(--jl-text-2);\n --bg: var(--jl-surface);\n --border: var(--jl-line);\n --bar: var(--jl-brand);\n}\n\n:host([tone=\"ok\"]),\n:host([tone=\"warn\"]),\n:host([tone=\"danger\"]),\n:host([tone=\"info\"]) {\n --border: color-mix(in srgb, var(--fg) 24%, var(--bg));\n --bar: var(--fg);\n}\n\n:host([tone=\"ok\"]) {\n --fg: var(--jl-ok);\n --bg: var(--jl-ok-soft);\n}\n\n:host([tone=\"warn\"]) {\n --fg: var(--jl-warn);\n --bg: var(--jl-warn-soft);\n}\n\n:host([tone=\"danger\"]) {\n --fg: var(--jl-danger);\n --bg: var(--jl-danger-soft);\n}\n\n:host([tone=\"info\"]) {\n --fg: var(--jl-info);\n --bg: var(--jl-info-soft);\n}\n\n.toast {\n position: relative;\n display: flex;\n align-items: flex-start;\n gap: 12px;\n padding: 14px 12px 14px 16px;\n overflow: hidden;\n border: 1px solid var(--border);\n border-radius: var(--jl-radius-lg);\n background: var(--bg);\n color: var(--jl-text);\n font-size: var(--jl-font-md);\n line-height: 20px;\n box-shadow: var(--jl-shadow-lg);\n}\n\n.icon {\n margin-top: 1px;\n color: var(--fg);\n}\n\n.content {\n display: grid;\n flex: 1;\n gap: 2px;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n\n.heading {\n font-weight: 650;\n}\n\n.text {\n color: var(--jl-muted);\n font-size: var(--jl-font-sm);\n}\n\n.heading[hidden] + .text {\n color: var(--jl-text-2);\n font-size: var(--jl-font-md);\n}\n\n.buttons {\n display: flex;\n flex: none;\n align-items: center;\n gap: 2px;\n margin: -5px -4px -5px 0;\n}\n\n.buttons:not(:has(jl-button:not([hidden]))) {\n display: none;\n}\n\n.close {\n --jl-accent: var(--fg);\n --jl-accent-soft: color-mix(in srgb, var(--fg) 12%, transparent);\n}\n\n.progress {\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n height: 2px;\n}\n\n.bar {\n height: 100%;\n background: var(--bar);\n transform-origin: left center;\n}\n\n:host([paused]) .bar {\n opacity: 0.55;\n}\n\n/* The line still counts the time, but does not move for people who asked for less motion. */\n@media (prefers-reduced-motion: reduce) {\n .progress {\n visibility: hidden;\n }\n}\n";
2
+ var jl_toast_default = ":host {\n display: block;\n /* Coloured as jl-alert is, from the same tokens: a toned toast is tinted, its border and icon in its tone. */\n --fg: var(--jl-text-2);\n --bg: var(--jl-surface);\n --border: var(--jl-line);\n --bar: var(--jl-brand);\n}\n\n:host([tone=\"ok\"]),\n:host([tone=\"warn\"]),\n:host([tone=\"danger\"]),\n:host([tone=\"info\"]) {\n --border: color-mix(in srgb, var(--fg) 24%, var(--bg));\n --bar: var(--fg);\n}\n\n:host([tone=\"ok\"]) {\n --fg: var(--jl-ok);\n --bg: var(--jl-ok-soft);\n}\n\n:host([tone=\"warn\"]) {\n --fg: var(--jl-warn);\n --bg: var(--jl-warn-soft);\n}\n\n:host([tone=\"danger\"]) {\n --fg: var(--jl-danger);\n --bg: var(--jl-danger-soft);\n}\n\n:host([tone=\"info\"]) {\n --fg: var(--jl-info);\n --bg: var(--jl-info-soft);\n}\n\n.toast {\n position: relative;\n display: flex;\n align-items: flex-start;\n gap: 12px;\n padding: 14px 12px 14px 16px;\n overflow: hidden;\n border: 1px solid var(--border);\n border-radius: var(--jl-radius-lg);\n background: var(--bg);\n color: var(--jl-text);\n font-size: var(--jl-font-md);\n line-height: 20px;\n box-shadow: var(--jl-shadow-lg);\n}\n\n.icon {\n margin-top: 1px;\n color: var(--fg);\n}\n\n.content {\n display: grid;\n flex: 1;\n gap: 2px;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n\n.heading {\n font-weight: 650;\n}\n\n.text {\n color: var(--jl-muted);\n font-size: var(--jl-font-sm);\n}\n\n.heading[hidden] + .text {\n color: var(--jl-text-2);\n font-size: var(--jl-font-md);\n}\n\n.buttons {\n display: flex;\n flex: none;\n align-items: center;\n gap: 2px;\n margin: -5px -4px -5px 0;\n}\n\n.buttons:not(:has(jl-button:not([hidden]))) {\n display: none;\n}\n\n.close {\n --jl-accent: var(--fg);\n --jl-accent-soft: color-mix(in srgb, var(--fg) 12%, transparent);\n}\n\n.work {\n margin-top: 8px;\n}\n\n.progress {\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n height: 2px;\n}\n\n.bar {\n height: 100%;\n background: var(--bar);\n transform-origin: left center;\n}\n\n:host([paused]) .bar {\n opacity: 0.55;\n}\n\n/* The line still counts the time, but does not move for people who asked for less motion. */\n@media (prefers-reduced-motion: reduce) {\n .progress {\n visibility: hidden;\n }\n}\n";
3
3
  export { jl_toast_default as default };
@@ -4,7 +4,9 @@ export declare const TOAST_TONES: readonly ToastTone[];
4
4
  /**
5
5
  * One notification: a heading, a line of text, an icon in its tone's colour, and maybe an action such as "Undo".
6
6
  * With a `duration` it closes by itself, a thin line showing the time left; hovering or focusing it pauses the
7
- * line. It emits `dismiss` once it has closed. Most pages call `toast()` rather than writing it.
7
+ * line. While `working`, it shows the progress of the work it tells about (a download): `progress` percent, or a
8
+ * bar running back and forth without it. It emits `dismiss` once it has closed. Most pages call `toast()` rather
9
+ * than writing it.
8
10
  */
9
11
  export declare class JlToast extends JlElement {
10
12
  static readonly selector = "jl-toast";
@@ -44,6 +46,16 @@ export declare class JlToast extends JlElement {
44
46
  get(): number;
45
47
  set(setter: number | ((prevState: number) => number)): void;
46
48
  }>>;
49
+ /** Shows a progress bar under the text, while the work it tells about runs. */
50
+ readonly working: Readonly<Readonly<{
51
+ get(): boolean;
52
+ set(setter: boolean | ((prevState: boolean) => boolean)): void;
53
+ }>>;
54
+ /** How far the work is, in percent; `null` (no attribute) for work of unknown length. */
55
+ readonly progress: Readonly<Readonly<{
56
+ get(): number | null;
57
+ set(setter: number | ((prevState: number | null) => number | null) | null): void;
58
+ }>>;
47
59
  private readonly _timer;
48
60
  private readonly _hold;
49
61
  private readonly _refs;
@@ -3,8 +3,11 @@ import { JlElement } from "../base/jl-element.js";
3
3
  import { oneOf } from "../lib/attributes.js";
4
4
  import { registerIcons } from "../lib/icons.js";
5
5
  import { TONE_ICONS, TONE_ICON_FILLED } from "../lib/options.js";
6
+ import { jlText } from "../lib/texts.js";
6
7
  import { JlIcon } from "../atoms/jl-icon.js";
7
8
  import { JlButton } from "../atoms/jl-button.js";
9
+ import { optionalNumber } from "../lib/progress.js";
10
+ import { JlProgress } from "../atoms/jl-progress.js";
8
11
  import jl_toast_default from "./jl-toast.css_raw.js";
9
12
  import { css } from "@jadis/core";
10
13
  import { CircleCheck, CircleX, Info, Sparkles, TriangleAlert, X } from "lucide";
@@ -45,7 +48,9 @@ var prefersReducedMotion = () => window.matchMedia(REDUCED_MOTION).matches;
45
48
  /**
46
49
  * One notification: a heading, a line of text, an icon in its tone's colour, and maybe an action such as "Undo".
47
50
  * With a `duration` it closes by itself, a thin line showing the time left; hovering or focusing it pauses the
48
- * line. It emits `dismiss` once it has closed. Most pages call `toast()` rather than writing it.
51
+ * line. While `working`, it shows the progress of the work it tells about (a download): `progress` percent, or a
52
+ * bar running back and forth without it. It emits `dismiss` once it has closed. Most pages call `toast()` rather
53
+ * than writing it.
49
54
  */
50
55
  var JlToast = class extends JlElement {
51
56
  static selector = "jl-toast";
@@ -79,6 +84,15 @@ var JlToast = class extends JlElement {
79
84
  }, { attribute: "action-label" });
80
85
  /** How long it stays, in milliseconds; 0 keeps it until it is closed. */
81
86
  duration = this.useChange(0, () => this.startTimer(), { attribute: "duration" });
87
+ /** Shows a progress bar under the text, while the work it tells about runs. */
88
+ working = this.useChange(false, (value) => {
89
+ this._refs.work.hidden = !value;
90
+ }, { attribute: "working" });
91
+ /** How far the work is, in percent; `null` (no attribute) for work of unknown length. */
92
+ progress = this.useChange(null, (value) => this._refs.work.value.set(value), {
93
+ attribute: "progress",
94
+ parse: optionalNumber
95
+ });
82
96
  _timer = {
83
97
  animation: null,
84
98
  entered: false
@@ -95,7 +109,8 @@ var JlToast = class extends JlElement {
95
109
  icon: ref(".icon"),
96
110
  progress: ref(".progress"),
97
111
  text: ref(".text"),
98
- toast: ref(".toast")
112
+ toast: ref(".toast"),
113
+ work: ref(JlProgress)
99
114
  }));
100
115
  /** Closes the toast: it leaves, hides, then emits `dismiss`. */
101
116
  dismiss() {
@@ -146,6 +161,11 @@ var JlToast = class extends JlElement {
146
161
  class: "text",
147
162
  hidden: true
148
163
  }),
164
+ /* @__PURE__ */ jsx(JlProgress, {
165
+ class: "work",
166
+ size: "sm",
167
+ hidden: true
168
+ }),
149
169
  /* @__PURE__ */ jsx("slot", {})
150
170
  ]
151
171
  }),
@@ -161,7 +181,7 @@ var JlToast = class extends JlElement {
161
181
  variant: "ghost",
162
182
  size: "sm",
163
183
  icon: "x",
164
- hint: "Close",
184
+ hint: jlText("close"),
165
185
  hidden: true
166
186
  })]
167
187
  }),
@@ -41,10 +41,13 @@ export declare class JlTooltip extends JlElement {
41
41
  private isInteractive;
42
42
  private pressStart;
43
43
  private pressMove;
44
- /** The finger lifted, or the page took it to scroll: a bubble the press showed stays a moment, then goes. */
44
+ /** The finger lifted. On a trigger that does nothing else on a tap, a tap (not a scroll) shows or hides the bubble. */
45
+ private pressUp;
46
+ /** The finger is gone: a bubble the long press showed stays a moment, then goes. */
45
47
  private pressEnd;
46
48
  /** The system's own long press: the bubble shows now, and the menu it would open does not, but over a link. */
47
49
  private pressHeld;
50
+ /** The long press shows the bubble, and says whether there is one: without a text, the press stays a press. */
48
51
  private openFromPress;
49
52
  private swallowLongPress;
50
53
  private hideBubble;
@@ -51,6 +51,7 @@ var JlTooltip = class extends JlElement {
51
51
  hideTimer: 0,
52
52
  press: {
53
53
  down: false,
54
+ moved: false,
54
55
  opened: false,
55
56
  timer: 0,
56
57
  x: 0,
@@ -94,7 +95,7 @@ var JlTooltip = class extends JlElement {
94
95
  this.hideBubble();
95
96
  });
96
97
  this.on(this, "pointermove", (event) => this.pressMove(event));
97
- this.on(this, "pointerup", () => this.pressEnd());
98
+ this.on(this, "pointerup", () => this.pressUp());
98
99
  this.on(this, "pointercancel", () => this.pressEnd());
99
100
  this.on(this, "contextmenu", (event) => this.pressHeld(event));
100
101
  this.addEventListener("click", (event) => this.swallowLongPress(event), {
@@ -131,26 +132,34 @@ var JlTooltip = class extends JlElement {
131
132
  pressStart(event) {
132
133
  const press = this._state.press;
133
134
  window.clearTimeout(press.timer);
134
- press.opened = false;
135
- if (!this.isInteractive()) {
136
- if (this.isOpen()) {
137
- this.close();
138
- return;
139
- }
140
- this.open();
141
- return;
142
- }
143
135
  press.down = true;
136
+ press.moved = false;
137
+ press.opened = false;
144
138
  press.x = event.clientX;
145
139
  press.y = event.clientY;
146
- press.timer = window.setTimeout(() => this.openFromPress(), LONG_PRESS_MS);
140
+ if (this.isInteractive()) press.timer = window.setTimeout(() => this.openFromPress(), LONG_PRESS_MS);
147
141
  }
148
142
  pressMove(event) {
149
143
  const press = this._state.press;
150
144
  const moved = Math.hypot(event.clientX - press.x, event.clientY - press.y);
151
- if (press.down && moved > MOVE_TOLERANCE_PX) window.clearTimeout(press.timer);
145
+ if (press.down && moved > MOVE_TOLERANCE_PX) {
146
+ press.moved = true;
147
+ window.clearTimeout(press.timer);
148
+ }
152
149
  }
153
- /** The finger lifted, or the page took it to scroll: a bubble the press showed stays a moment, then goes. */
150
+ /** The finger lifted. On a trigger that does nothing else on a tap, a tap (not a scroll) shows or hides the bubble. */
151
+ pressUp() {
152
+ const press = this._state.press;
153
+ const tapped = press.down && !press.moved;
154
+ this.pressEnd();
155
+ if (!tapped || this.isInteractive()) return;
156
+ if (this.isOpen()) {
157
+ this.close();
158
+ return;
159
+ }
160
+ this.open();
161
+ }
162
+ /** The finger is gone: a bubble the long press showed stays a moment, then goes. */
154
163
  pressEnd() {
155
164
  const press = this._state.press;
156
165
  window.clearTimeout(press.timer);
@@ -162,13 +171,15 @@ var JlTooltip = class extends JlElement {
162
171
  /** The system's own long press: the bubble shows now, and the menu it would open does not, but over a link. */
163
172
  pressHeld(event) {
164
173
  if (!this._state.press.down || !this.isInteractive()) return;
165
- this.openFromPress();
166
- if (!accessibleTarget(this.trigger()).matches("a[href]")) event.preventDefault();
174
+ if (this.openFromPress() && !accessibleTarget(this.trigger()).matches("a[href]")) event.preventDefault();
167
175
  }
176
+ /** The long press shows the bubble, and says whether there is one: without a text, the press stays a press. */
168
177
  openFromPress() {
169
178
  window.clearTimeout(this._state.press.timer);
179
+ if (this.text.get() === "") return false;
170
180
  this._state.press.opened = true;
171
181
  this.open();
182
+ return true;
172
183
  }
173
184
  swallowLongPress(event) {
174
185
  const press = this._state.press;
@@ -178,6 +189,7 @@ var JlTooltip = class extends JlElement {
178
189
  event.stopPropagation();
179
190
  }
180
191
  hideBubble() {
192
+ window.clearTimeout(this._state.press.timer);
181
193
  window.clearTimeout(this._state.showTimer);
182
194
  window.clearTimeout(this._state.hideTimer);
183
195
  this._state.following.abort();