@syware/jlib 0.1.3 → 0.3.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 (50) hide show
  1. package/README.md +3 -17
  2. package/dist/all.js +5 -4
  3. package/dist/atoms/jl-icon.css_raw.js +1 -1
  4. package/dist/atoms/jl-icon.d.ts +7 -1
  5. package/dist/atoms/jl-icon.js +4 -1
  6. package/dist/atoms/jl-input.d.ts +1 -0
  7. package/dist/atoms/jl-input.js +2 -0
  8. package/dist/atoms/jl-progress-ring.js +1 -6
  9. package/dist/atoms/jl-select.d.ts +6 -0
  10. package/dist/atoms/jl-select.js +18 -1
  11. package/dist/atoms/jl-switch.d.ts +12 -1
  12. package/dist/atoms/jl-switch.js +11 -1
  13. package/dist/index.d.ts +4 -2
  14. package/dist/index.js +4 -3
  15. package/dist/lib/calendar.d.ts +15 -1
  16. package/dist/lib/calendar.js +72 -1
  17. package/dist/lib/dom.d.ts +2 -0
  18. package/dist/lib/dom.js +8 -1
  19. package/dist/lib/flow.d.ts +70 -0
  20. package/dist/lib/flow.js +353 -0
  21. package/dist/lib/options.d.ts +5 -0
  22. package/dist/lib/options.js +13 -1
  23. package/dist/molecules/jl-accordion-item.css_raw.js +1 -1
  24. package/dist/molecules/jl-accordion-item.d.ts +6 -1
  25. package/dist/molecules/jl-accordion-item.js +21 -6
  26. package/dist/molecules/jl-alert.d.ts +1 -0
  27. package/dist/molecules/jl-alert.js +3 -1
  28. package/dist/molecules/jl-date-picker.d.ts +10 -4
  29. package/dist/molecules/jl-date-picker.js +21 -8
  30. package/dist/molecules/jl-search.d.ts +3 -1
  31. package/dist/molecules/jl-search.js +8 -6
  32. package/dist/molecules/jl-segmented.css_raw.js +1 -1
  33. package/dist/molecules/jl-segmented.js +14 -7
  34. package/dist/molecules/jl-stat.css_raw.js +1 -1
  35. package/dist/molecules/jl-stat.d.ts +6 -0
  36. package/dist/molecules/jl-stat.js +6 -0
  37. package/dist/molecules/jl-steps.css_raw.js +1 -1
  38. package/dist/molecules/jl-timeline.css_raw.js +1 -1
  39. package/dist/molecules/jl-timeline.d.ts +2 -1
  40. package/dist/molecules/jl-timeline.js +18 -16
  41. package/dist/molecules/jl-toast.css_raw.js +1 -1
  42. package/dist/molecules/jl-toast.d.ts +1 -0
  43. package/dist/molecules/jl-toast.js +3 -1
  44. package/dist/organisms/jl-flow.css_raw.js +3 -0
  45. package/dist/organisms/jl-flow.d.ts +109 -0
  46. package/dist/organisms/jl-flow.js +755 -0
  47. package/dist/types/calendar.types.d.ts +2 -0
  48. package/dist/types/jl-flow.types.d.ts +94 -0
  49. package/dist/types/jl-segmented.types.d.ts +2 -0
  50. package/package.json +1 -1
package/README.md CHANGED
@@ -97,11 +97,6 @@ component. Conventions for contributors are in [CLAUDE.md](CLAUDE.md).
97
97
 
98
98
  ## Releasing
99
99
 
100
- Pushing a version tag releases the package: the `Publish to npm` workflow checks and builds it, stages it on npm
101
- through trusted publishing (no token; the package gets a provenance statement), then creates the GitHub release with
102
- the package attached. The staged version becomes public once a maintainer approves it with their two-factor
103
- authentication.
104
-
105
100
  ```bash
106
101
  npm version patch
107
102
  ```
@@ -110,18 +105,9 @@ npm version patch
110
105
  git push --follow-tags
111
106
  ```
112
107
 
113
- `npm version` (or `minor`, `major`) bumps package.json and the lockfile, commits and tags `vX.Y.Z`. The workflow
114
- refuses a tag that is not the version in package.json.
115
-
116
- When the run is done (its summary page shows what was staged), approve the version:
117
-
118
- ```bash
119
- npm stage list @syware/jlib
120
- ```
121
-
122
- ```bash
123
- npm stage approve <stage-id>
124
- ```
108
+ The tag stages the version on npm and creates the GitHub release; a maintainer then approves it with
109
+ `npm stage approve`. Every step, pre-releases and what to do when something fails: [RELEASING.md](RELEASING.md).
110
+ What changed in each version: [CHANGELOG.md](CHANGELOG.md).
125
111
 
126
112
  ## Made by SyWare
127
113
 
package/dist/all.js CHANGED
@@ -1,4 +1,4 @@
1
- import { TOKEN_STYLES } from "./styles/shared.js";
1
+ import { BASE_STYLES, CONTROL_STYLES, FLOATING_STYLES, TOKEN_STYLES } from "./styles/shared.js";
2
2
  import { adoptTokens } from "./lib/tokens.js";
3
3
  import { JlElement } from "./base/jl-element.js";
4
4
  import { JlFormControl } from "./base/jl-form-control.js";
@@ -7,7 +7,7 @@ import { define } from "./lib/define.js";
7
7
  import { PLACEMENTS, computePosition, followAnchor, placeFloating } from "./lib/floating.js";
8
8
  import { COMMON_ICONS } from "./lib/icon-set.js";
9
9
  import { hasIcon, iconElement, iconNames, registerIcons } from "./lib/icons.js";
10
- import { CONTROL_SIZES, TONES, TONE_ICONS } from "./lib/options.js";
10
+ import { CONTROL_SIZES, TONES, TONE_ICONS, TONE_ICON_FILLED } from "./lib/options.js";
11
11
  import { JlIcon } from "./atoms/jl-icon.js";
12
12
  import { AVATAR_SHAPES, AVATAR_SIZES, AVATAR_STATUSES, JlAvatar } from "./atoms/jl-avatar.js";
13
13
  import { JlBadge } from "./atoms/jl-badge.js";
@@ -61,6 +61,7 @@ import { JlCommandPalette } from "./organisms/jl-command-palette.js";
61
61
  import { DIALOG_SIZES, JlDialog } from "./organisms/jl-dialog.js";
62
62
  import { CONFIRM_TONES, JlConfirm, confirmDialog } from "./organisms/jl-confirm.js";
63
63
  import { DRAWER_PLACEMENTS, DRAWER_SIZES, JlDrawer } from "./organisms/jl-drawer.js";
64
+ import { JlFlow } from "./organisms/jl-flow.js";
64
65
  import { JlNavbar } from "./organisms/jl-navbar.js";
65
66
  import { JlPageHeader } from "./organisms/jl-page-header.js";
66
67
  import { JlSideNav } from "./organisms/jl-side-nav.js";
@@ -71,5 +72,5 @@ import { JlAppShell } from "./templates/jl-app-shell.js";
71
72
  import { JlSplitView, SPLIT_ORIENTATIONS } from "./templates/jl-split-view.js";
72
73
  // src/all.ts
73
74
  registerIcons(COMMON_ICONS);
74
- define(JlAccordion, JlAccordionItem, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlCodeBlock, JlCombobox, JlCommandPalette, JlConfirm, JlCopyButton, JlDatePicker, JlDialog, JlDivider, JlDrawer, JlEmpty, JlField, JlFileDrop, JlIcon, JlInput, JlKbd, JlLink, JlListItem, JlMenu, JlNavbar, JlNumberInput, JlPageHeader, JlPagination, JlPopover, JlProgress, JlProgressRing, JlRadioGroup, JlRating, JlSearch, JlSegmented, JlSelect, JlSideNav, JlSkeleton, JlSlider, JlSpinner, JlSplitView, JlStat, JlSteps, JlSwitch, JlTable, JlTabPanel, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree);
75
- export { ACCORDION_VARIANTS, AVATAR_SHAPES, AVATAR_SIZES, AVATAR_STATUSES, BUTTON_TYPES, BUTTON_VARIANTS, CARD_PADDINGS, CHIP_SIZES, CODE_LANGUAGES, COMMON_ICONS, CONFIRM_TONES, CONTROL_SIZES, DIALOG_SIZES, DIVIDER_ALIGNS, DRAWER_PLACEMENTS, DRAWER_SIZES, EMPTY_SIZES, FIELD_ORIENTATIONS, INPUT_TYPES, JlAccordion, JlAccordionItem, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlCodeBlock, JlCombobox, JlCommandPalette, JlConfirm, JlCopyButton, JlDatePicker, JlDialog, JlDivider, JlDrawer, JlElement, JlEmpty, JlField, JlFileDrop, JlFormControl, JlIcon, JlInput, JlKbd, JlLink, JlListItem, JlMenu, JlNavbar, JlNumberInput, JlPageHeader, JlPagination, JlPopover, JlProgress, JlProgressRing, JlRadioGroup, JlRating, JlSearch, JlSegmented, JlSelect, JlSideNav, JlSkeleton, JlSlider, JlSpinner, JlSplitView, JlStat, JlSteps, JlSwitch, JlTabPanel, JlTable, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree, LINK_VARIANTS, PLACEMENTS, PROGRESS_SIZES, PROGRESS_TONES, RADIO_GROUP_ORIENTATIONS, RADIO_GROUP_VARIANTS, SKELETON_SHAPES, SPLIT_ORIENTATIONS, STAT_TRENDS, STEPS_ORIENTATIONS, SWITCH_SIZES, TABLE_SORT_DIRECTIONS, TAB_VARIANTS, TOASTER_PLACEMENTS, TOAST_TONES, TOKEN_STYLES, TONES, TONE_ICONS, adoptTokens, clampedNumber, computePosition, confirmDialog, define, followAnchor, hasIcon, iconElement, iconNames, jsonOf, oneOf, placeFloating, registerIcons, toast };
75
+ define(JlAccordion, JlAccordionItem, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlCodeBlock, JlCombobox, JlCommandPalette, JlConfirm, JlCopyButton, JlDatePicker, JlDialog, JlDivider, JlDrawer, JlEmpty, JlField, JlFileDrop, JlFlow, JlIcon, JlInput, JlKbd, JlLink, JlListItem, JlMenu, JlNavbar, JlNumberInput, JlPageHeader, JlPagination, JlPopover, JlProgress, JlProgressRing, JlRadioGroup, JlRating, JlSearch, JlSegmented, JlSelect, JlSideNav, JlSkeleton, JlSlider, JlSpinner, JlSplitView, JlStat, JlSteps, JlSwitch, JlTable, JlTabPanel, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree);
76
+ export { ACCORDION_VARIANTS, AVATAR_SHAPES, AVATAR_SIZES, AVATAR_STATUSES, BASE_STYLES, BUTTON_TYPES, BUTTON_VARIANTS, CARD_PADDINGS, CHIP_SIZES, CODE_LANGUAGES, COMMON_ICONS, CONFIRM_TONES, CONTROL_SIZES, CONTROL_STYLES, DIALOG_SIZES, DIVIDER_ALIGNS, DRAWER_PLACEMENTS, DRAWER_SIZES, EMPTY_SIZES, FIELD_ORIENTATIONS, FLOATING_STYLES, INPUT_TYPES, JlAccordion, JlAccordionItem, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlCodeBlock, JlCombobox, JlCommandPalette, JlConfirm, JlCopyButton, JlDatePicker, JlDialog, JlDivider, JlDrawer, JlElement, JlEmpty, JlField, JlFileDrop, JlFlow, JlFormControl, JlIcon, JlInput, JlKbd, JlLink, JlListItem, JlMenu, JlNavbar, JlNumberInput, JlPageHeader, JlPagination, JlPopover, JlProgress, JlProgressRing, JlRadioGroup, JlRating, JlSearch, JlSegmented, JlSelect, JlSideNav, JlSkeleton, JlSlider, JlSpinner, JlSplitView, JlStat, JlSteps, JlSwitch, JlTabPanel, JlTable, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree, LINK_VARIANTS, PLACEMENTS, PROGRESS_SIZES, PROGRESS_TONES, RADIO_GROUP_ORIENTATIONS, RADIO_GROUP_VARIANTS, SKELETON_SHAPES, SPLIT_ORIENTATIONS, STAT_TRENDS, STEPS_ORIENTATIONS, SWITCH_SIZES, TABLE_SORT_DIRECTIONS, TAB_VARIANTS, TOASTER_PLACEMENTS, TOAST_TONES, TOKEN_STYLES, TONES, TONE_ICONS, TONE_ICON_FILLED, adoptTokens, clampedNumber, computePosition, confirmDialog, define, followAnchor, hasIcon, iconElement, iconNames, jsonOf, oneOf, placeFloating, registerIcons, toast };
@@ -1,3 +1,3 @@
1
1
  // src/atoms/jl-icon.css?raw
2
- var jl_icon_default = ":host {\n display: inline-flex;\n flex: none;\n line-height: 0;\n vertical-align: middle;\n}\n\n.icon {\n display: inline-flex;\n}\n";
2
+ var jl_icon_default = ":host {\n --cutout: var(--jl-surface);\n display: inline-flex;\n flex: none;\n line-height: 0;\n vertical-align: middle;\n}\n\n.icon {\n display: inline-flex;\n}\n\n/* Filled: lucide draws the shape around a sign first (the circle of circle-check, the triangle of triangle-alert).\n It takes the colour, and the sign takes the background's, as if cut out of it. */\n:host([filled]) svg > :first-child {\n fill: currentColor;\n}\n\n:host([filled]) svg > :not(:first-child) {\n stroke: var(--cutout);\n}\n";
3
3
  export { jl_icon_default as default };
@@ -1,7 +1,8 @@
1
1
  import { JlElement } from '../base/jl-element';
2
2
  /**
3
3
  * A lucide icon by name: `<jl-icon name="search">`. Decorative unless it has a `label`, which screen readers
4
- * read instead. More icons: `registerIcons({ rocket: Rocket })`.
4
+ * read instead. More icons: `registerIcons({ rocket: Rocket })`. `filled` draws a sign in a shape (`circle-check`,
5
+ * `triangle-alert`) as a solid badge.
5
6
  */
6
7
  export declare class JlIcon extends JlElement {
7
8
  static readonly selector = "jl-icon";
@@ -17,6 +18,11 @@ export declare class JlIcon extends JlElement {
17
18
  get(): string;
18
19
  set(setter: string | ((prevState: string) => string)): void;
19
20
  }>>;
21
+ /** The icon's first shape (the circle or triangle around a sign) in solid colour, the sign cut out of it. */
22
+ readonly filled: Readonly<Readonly<{
23
+ get(): boolean;
24
+ set(setter: boolean | ((prevState: boolean) => boolean)): void;
25
+ }>>;
20
26
  private readonly _refs;
21
27
  templateHtml(): Node;
22
28
  templateCss(): string;
@@ -8,13 +8,16 @@ import { jsx } from "@jadis/core/jsx-runtime";
8
8
  var DEFAULT_SIZE = 16;
9
9
  /**
10
10
  * A lucide icon by name: `<jl-icon name="search">`. Decorative unless it has a `label`, which screen readers
11
- * read instead. More icons: `registerIcons({ rocket: Rocket })`.
11
+ * read instead. More icons: `registerIcons({ rocket: Rocket })`. `filled` draws a sign in a shape (`circle-check`,
12
+ * `triangle-alert`) as a solid badge.
12
13
  */
13
14
  var JlIcon = class extends JlElement {
14
15
  static selector = "jl-icon";
15
16
  name = this.useChange("", () => this.draw(), { attribute: "name" });
16
17
  size = this.useChange(DEFAULT_SIZE, () => this.draw(), { attribute: "size" });
17
18
  label = this.useChange("", (value) => this.setAccessibleName(value), { attribute: "label" });
19
+ /** The icon's first shape (the circle or triangle around a sign) in solid colour, the sign cut out of it. */
20
+ filled = this.useChange(false, (value) => this.reflect("filled", value), { attribute: "filled" });
18
21
  _refs = this.useRefs((ref) => ({ icon: ref(".icon") }));
19
22
  templateHtml() {
20
23
  return /* @__PURE__ */ jsx("span", { class: "icon" });
@@ -11,6 +11,7 @@ export declare class JlInput extends JlFormControl {
11
11
  readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
12
12
  input: string;
13
13
  change: string;
14
+ enter: string;
14
15
  }>>;
15
16
  readonly value: Readonly<Readonly<{
16
17
  get(): string;
@@ -33,6 +33,7 @@ var PASSWORD = "password";
33
33
  var REVEALED = "text";
34
34
  var ICON_SIZE = 16;
35
35
  var ACTION_ICON_SIZE = 14;
36
+ var ENTER = "Enter";
36
37
  /**
37
38
  * A single-line text field that takes part in forms. `value` is the text; `input` fires as people type and
38
39
  * `change` when they commit. Slots `start` and `end` hold adornments such as a unit or a prefix.
@@ -137,6 +138,7 @@ var JlInput = class extends JlFormControl {
137
138
  this.events.emit("input", native.value);
138
139
  });
139
140
  this.on(native, "change", () => this.events.emit("change", native.value));
141
+ this.on(native, "keydown", (event) => event.key === ENTER && this.events.emit("enter", native.value));
140
142
  this.on(this._refs.clear, "click", () => this.clearValue());
141
143
  this.on(this._refs.reveal, "click", () => this.toggleReveal());
142
144
  }
@@ -1,23 +1,18 @@
1
1
  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
+ import { svgElement } from "../lib/dom.js";
4
5
  import { optionalNumber, percentLabel, percentOf, progressRatio, ringGeometry } from "../lib/progress.js";
5
6
  import { PROGRESS_TONES } from "./jl-progress.js";
6
7
  import jl_progress_ring_default from "./jl-progress-ring.css_raw.js";
7
8
  import { css } from "@jadis/core";
8
9
  import { jsx } from "@jadis/core/jsx-runtime";
9
10
  // src/atoms/jl-progress-ring.tsx
10
- var SVG_NS = "http://www.w3.org/2000/svg";
11
11
  var DEFAULT_MAX = 100;
12
12
  var DEFAULT_SIZE = 40;
13
13
  var DEFAULT_THICKNESS = 4;
14
14
  var PATH_LENGTH = 100;
15
15
  var DEFAULT_NAME = "Progress";
16
- function svgElement(tag, attributes) {
17
- const element = document.createElementNS(SVG_NS, tag);
18
- for (const [name, value] of Object.entries(attributes)) element.setAttribute(name, value);
19
- return element;
20
- }
21
16
  /**
22
17
  * How far a task has gone, as a ring: `value` out of `max`, the percentage in its centre with `show-value`.
23
18
  * Without a value, the ring turns: the work has no known end. Fits in buttons, cards and table cells.
@@ -15,6 +15,7 @@ export declare class JlSelect extends JlFormControl {
15
15
  get(): SelectOption[];
16
16
  set(setter: SelectOption[] | ((prevState: SelectOption[]) => SelectOption[])): void;
17
17
  }>>;
18
+ /** The chosen value. One that no option has is kept, as an option of its own, rather than replaced. */
18
19
  readonly value: Readonly<Readonly<{
19
20
  get(): string;
20
21
  set(setter: string | ((prevState: string) => string)): void;
@@ -60,6 +61,11 @@ export declare class JlSelect extends JlFormControl {
60
61
  private optionNodes;
61
62
  private placeholderNodes;
62
63
  private renderOptions;
64
+ /**
65
+ * A value no option has (a choice since removed, options still loading) shows as an option of its own, first, so
66
+ * the field shows what is saved and the form keeps it, rather than another choice.
67
+ */
68
+ private keepUnknownValue;
63
69
  /** Shows the chosen option; the form gets what is shown. */
64
70
  private renderValue;
65
71
  /**
@@ -40,7 +40,8 @@ var JlSelect = class extends JlFormControl {
40
40
  attribute: "options",
41
41
  parse: jsonOf([])
42
42
  });
43
- value = this.useChange(NO_VALUE, () => this.renderValue(), { attribute: "value" });
43
+ /** The chosen value. One that no option has is kept, as an option of its own, rather than replaced. */
44
+ value = this.useChange(NO_VALUE, () => this.renderOptions(), { attribute: "value" });
44
45
  /** Shown while nothing is chosen; it cannot be picked back from the list. */
45
46
  placeholder = this.useChange("", () => this.renderOptions(), { attribute: "placeholder" });
46
47
  size = this.useChange("md", (value) => this.reflect("size", value), {
@@ -120,8 +121,24 @@ var JlSelect = class extends JlFormControl {
120
121
  }
121
122
  renderOptions() {
122
123
  this._refs.native.replaceChildren(...this.placeholderNodes(), ...this.optionNodes());
124
+ this.keepUnknownValue();
123
125
  this.renderValue();
124
126
  }
127
+ /**
128
+ * A value no option has (a choice since removed, options still loading) shows as an option of its own, first, so
129
+ * the field shows what is saved and the form keeps it, rather than another choice.
130
+ */
131
+ keepUnknownValue() {
132
+ const native = this._refs.native;
133
+ const value = this.value.get();
134
+ const known = [...native.options].some((option) => option.value === value);
135
+ if (value === NO_VALUE || known) return;
136
+ const afterPlaceholder = this.placeholder.get() === "" ? 0 : 1;
137
+ native.add(/* @__PURE__ */ jsx("option", {
138
+ value,
139
+ children: value
140
+ }), afterPlaceholder);
141
+ }
125
142
  /** Shows the chosen option; the form gets what is shown. */
126
143
  renderValue() {
127
144
  const native = this._refs.native;
@@ -24,6 +24,14 @@ export declare class JlSwitch extends JlFormControl {
24
24
  get(): string;
25
25
  set(setter: string | ((prevState: string) => string)): void;
26
26
  }>>;
27
+ /**
28
+ * Keeps the label for screen readers only, for a switch whose meaning the page already shows beside it (an "On"
29
+ * or "Off" next to the track).
30
+ */
31
+ readonly hideLabel: Readonly<Readonly<{
32
+ get(): boolean;
33
+ set(setter: boolean | ((prevState: boolean) => boolean)): void;
34
+ }>>;
27
35
  readonly size: Readonly<Readonly<{
28
36
  get(): SwitchSize;
29
37
  set(setter: ((prevState: SwitchSize) => SwitchSize) | SwitchSize): void;
@@ -52,6 +60,9 @@ export declare class JlSwitch extends JlFormControl {
52
60
  private commit;
53
61
  private renderChecked;
54
62
  private renderFormValue;
55
- /** Hides the text column when there is nothing to show, so a lone switch has no gap beside it. */
63
+ /**
64
+ * Hides the text column when there is nothing to show, so a lone switch has no gap beside it; a hidden label stays
65
+ * for screen readers, out of the layout.
66
+ */
56
67
  private renderText;
57
68
  }
@@ -7,6 +7,7 @@ import { jsx, jsxs } from "@jadis/core/jsx-runtime";
7
7
  // src/atoms/jl-switch.tsx
8
8
  var SWITCH_SIZES = ["md", "sm"];
9
9
  var DEFAULT_VALUE = "on";
10
+ var SCREEN_READER_ONLY = "sr-only";
10
11
  /**
11
12
  * An on/off setting that applies at once, such as "Dark mode" or "Email me". `checked` is the state; the form gets
12
13
  * `value` while it is on. For a choice confirmed later with a button, prefer a checkbox.
@@ -27,6 +28,11 @@ var JlSwitch = class extends JlFormControl {
27
28
  this._refs.description.hidden = value === "";
28
29
  this.renderText();
29
30
  }, { attribute: "description" });
31
+ /**
32
+ * Keeps the label for screen readers only, for a switch whose meaning the page already shows beside it (an "On"
33
+ * or "Off" next to the track).
34
+ */
35
+ hideLabel = this.useChange(false, () => this.renderText(), { attribute: "hide-label" });
30
36
  size = this.useChange("md", (value) => this.reflect("size", value), {
31
37
  attribute: "size",
32
38
  parse: oneOf(SWITCH_SIZES, "md")
@@ -115,10 +121,14 @@ var JlSwitch = class extends JlFormControl {
115
121
  renderFormValue() {
116
122
  this.internals.setFormValue(this.checked.get() ? this.value.get() : null);
117
123
  }
118
- /** Hides the text column when there is nothing to show, so a lone switch has no gap beside it. */
124
+ /**
125
+ * Hides the text column when there is nothing to show, so a lone switch has no gap beside it; a hidden label stays
126
+ * for screen readers, out of the layout.
127
+ */
119
128
  renderText() {
120
129
  const slotted = this._refs.slot.assignedNodes().some((node) => node instanceof Element || node.textContent?.trim() !== "");
121
130
  this._refs.text.hidden = this.label.get() === "" && this.description.get() === "" && !slotted;
131
+ this._refs.text.classList.toggle(SCREEN_READER_ONLY, this.hideLabel.get());
122
132
  }
123
133
  };
124
134
  JlSwitch.register();
package/dist/index.d.ts CHANGED
@@ -5,9 +5,9 @@ export { define } from './lib/define';
5
5
  export { computePosition, followAnchor, PLACEMENTS, placeFloating } from './lib/floating';
6
6
  export { COMMON_ICONS } from './lib/icon-set';
7
7
  export { hasIcon, iconElement, iconNames, registerIcons } from './lib/icons';
8
- export { CONTROL_SIZES, TONE_ICONS, TONES } from './lib/options';
8
+ export { CONTROL_SIZES, TONE_ICON_FILLED, TONE_ICONS, TONES } from './lib/options';
9
9
  export { adoptTokens } from './lib/tokens';
10
- export { TOKEN_STYLES } from './styles/shared';
10
+ export { BASE_STYLES, CONTROL_STYLES, FLOATING_STYLES, TOKEN_STYLES } from './styles/shared';
11
11
  export type * from './types/calendar.types';
12
12
  export type * from './types/files.types';
13
13
  export type * from './types/floating.types';
@@ -28,6 +28,7 @@ export type * from './types/jl-divider.types';
28
28
  export type * from './types/jl-drawer.types';
29
29
  export type * from './types/jl-empty.types';
30
30
  export type * from './types/jl-field.types';
31
+ export type * from './types/jl-flow.types';
31
32
  export type * from './types/jl-input.types';
32
33
  export type * from './types/jl-link.types';
33
34
  export type * from './types/jl-menu.types';
@@ -109,6 +110,7 @@ export * from './organisms/jl-command-palette';
109
110
  export * from './organisms/jl-confirm';
110
111
  export * from './organisms/jl-dialog';
111
112
  export * from './organisms/jl-drawer';
113
+ export * from './organisms/jl-flow';
112
114
  export * from './organisms/jl-navbar';
113
115
  export * from './organisms/jl-page-header';
114
116
  export * from './organisms/jl-side-nav';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { TOKEN_STYLES } from "./styles/shared.js";
1
+ import { BASE_STYLES, CONTROL_STYLES, FLOATING_STYLES, TOKEN_STYLES } from "./styles/shared.js";
2
2
  import { adoptTokens } from "./lib/tokens.js";
3
3
  import { JlElement } from "./base/jl-element.js";
4
4
  import { JlFormControl } from "./base/jl-form-control.js";
@@ -7,7 +7,7 @@ import { define } from "./lib/define.js";
7
7
  import { PLACEMENTS, computePosition, followAnchor, placeFloating } from "./lib/floating.js";
8
8
  import { COMMON_ICONS } from "./lib/icon-set.js";
9
9
  import { hasIcon, iconElement, iconNames, registerIcons } from "./lib/icons.js";
10
- import { CONTROL_SIZES, TONES, TONE_ICONS } from "./lib/options.js";
10
+ import { CONTROL_SIZES, TONES, TONE_ICONS, TONE_ICON_FILLED } from "./lib/options.js";
11
11
  import { JlIcon } from "./atoms/jl-icon.js";
12
12
  import { AVATAR_SHAPES, AVATAR_SIZES, AVATAR_STATUSES, JlAvatar } from "./atoms/jl-avatar.js";
13
13
  import { JlBadge } from "./atoms/jl-badge.js";
@@ -61,6 +61,7 @@ import { JlCommandPalette } from "./organisms/jl-command-palette.js";
61
61
  import { DIALOG_SIZES, JlDialog } from "./organisms/jl-dialog.js";
62
62
  import { CONFIRM_TONES, JlConfirm, confirmDialog } from "./organisms/jl-confirm.js";
63
63
  import { DRAWER_PLACEMENTS, DRAWER_SIZES, JlDrawer } from "./organisms/jl-drawer.js";
64
+ import { JlFlow } from "./organisms/jl-flow.js";
64
65
  import { JlNavbar } from "./organisms/jl-navbar.js";
65
66
  import { JlPageHeader } from "./organisms/jl-page-header.js";
66
67
  import { JlSideNav } from "./organisms/jl-side-nav.js";
@@ -69,4 +70,4 @@ import { JlToaster, TOASTER_PLACEMENTS, toast } from "./organisms/jl-toaster.js"
69
70
  import { JlTree } from "./organisms/jl-tree.js";
70
71
  import { JlAppShell } from "./templates/jl-app-shell.js";
71
72
  import { JlSplitView, SPLIT_ORIENTATIONS } from "./templates/jl-split-view.js";
72
- export { ACCORDION_VARIANTS, AVATAR_SHAPES, AVATAR_SIZES, AVATAR_STATUSES, BUTTON_TYPES, BUTTON_VARIANTS, CARD_PADDINGS, CHIP_SIZES, CODE_LANGUAGES, COMMON_ICONS, CONFIRM_TONES, CONTROL_SIZES, DIALOG_SIZES, DIVIDER_ALIGNS, DRAWER_PLACEMENTS, DRAWER_SIZES, EMPTY_SIZES, FIELD_ORIENTATIONS, INPUT_TYPES, JlAccordion, JlAccordionItem, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlCodeBlock, JlCombobox, JlCommandPalette, JlConfirm, JlCopyButton, JlDatePicker, JlDialog, JlDivider, JlDrawer, JlElement, JlEmpty, JlField, JlFileDrop, JlFormControl, JlIcon, JlInput, JlKbd, JlLink, JlListItem, JlMenu, JlNavbar, JlNumberInput, JlPageHeader, JlPagination, JlPopover, JlProgress, JlProgressRing, JlRadioGroup, JlRating, JlSearch, JlSegmented, JlSelect, JlSideNav, JlSkeleton, JlSlider, JlSpinner, JlSplitView, JlStat, JlSteps, JlSwitch, JlTabPanel, JlTable, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree, LINK_VARIANTS, PLACEMENTS, PROGRESS_SIZES, PROGRESS_TONES, RADIO_GROUP_ORIENTATIONS, RADIO_GROUP_VARIANTS, SKELETON_SHAPES, SPLIT_ORIENTATIONS, STAT_TRENDS, STEPS_ORIENTATIONS, SWITCH_SIZES, TABLE_SORT_DIRECTIONS, TAB_VARIANTS, TOASTER_PLACEMENTS, TOAST_TONES, TOKEN_STYLES, TONES, TONE_ICONS, adoptTokens, clampedNumber, computePosition, confirmDialog, define, followAnchor, hasIcon, iconElement, iconNames, jsonOf, oneOf, placeFloating, registerIcons, toast };
73
+ export { ACCORDION_VARIANTS, AVATAR_SHAPES, AVATAR_SIZES, AVATAR_STATUSES, BASE_STYLES, BUTTON_TYPES, BUTTON_VARIANTS, CARD_PADDINGS, CHIP_SIZES, CODE_LANGUAGES, COMMON_ICONS, CONFIRM_TONES, CONTROL_SIZES, CONTROL_STYLES, DIALOG_SIZES, DIVIDER_ALIGNS, DRAWER_PLACEMENTS, DRAWER_SIZES, EMPTY_SIZES, FIELD_ORIENTATIONS, FLOATING_STYLES, INPUT_TYPES, JlAccordion, JlAccordionItem, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlCodeBlock, JlCombobox, JlCommandPalette, JlConfirm, JlCopyButton, JlDatePicker, JlDialog, JlDivider, JlDrawer, JlElement, JlEmpty, JlField, JlFileDrop, JlFlow, JlFormControl, JlIcon, JlInput, JlKbd, JlLink, JlListItem, JlMenu, JlNavbar, JlNumberInput, JlPageHeader, JlPagination, JlPopover, JlProgress, JlProgressRing, JlRadioGroup, JlRating, JlSearch, JlSegmented, JlSelect, JlSideNav, JlSkeleton, JlSlider, JlSpinner, JlSplitView, JlStat, JlSteps, JlSwitch, JlTabPanel, JlTable, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree, LINK_VARIANTS, PLACEMENTS, PROGRESS_SIZES, PROGRESS_TONES, RADIO_GROUP_ORIENTATIONS, RADIO_GROUP_VARIANTS, SKELETON_SHAPES, SPLIT_ORIENTATIONS, STAT_TRENDS, STEPS_ORIENTATIONS, SWITCH_SIZES, TABLE_SORT_DIRECTIONS, TAB_VARIANTS, TOASTER_PLACEMENTS, TOAST_TONES, TOKEN_STYLES, TONES, TONE_ICONS, TONE_ICON_FILLED, adoptTokens, clampedNumber, computePosition, confirmDialog, define, followAnchor, hasIcon, iconElement, iconNames, jsonOf, oneOf, placeFloating, registerIcons, toast };
@@ -1,4 +1,4 @@
1
- import type { CalendarDate, Weekday, WeekdayWidth } from '../types/calendar.types';
1
+ import type { CalendarDate, DatePart, Weekday, WeekdayWidth } from '../types/calendar.types';
2
2
  /** A month grid always has six weeks, so the calendar keeps its height from one month to the next. */
3
3
  export declare const WEEKS_PER_GRID = 6;
4
4
  export declare const isLeapYear: (year: number) => boolean;
@@ -46,3 +46,17 @@ export declare const formatDate: (date: CalendarDate, locale: string | undefined
46
46
  export declare const formatMonthTitle: (year: number, month: number, locale: string | undefined) => string;
47
47
  /** The names of the weekdays, in the order a week shows them from its first day. */
48
48
  export declare const weekdayNames: (locale: string | undefined, firstDay: Weekday, width: WeekdayWidth) => string[];
49
+ /** How a date field writes a date: in digits, in the locale's order, the year in full (`25.10.2026`, `10/25/2026`). */
50
+ export declare const NUMERIC_DATE: Intl.DateTimeFormatOptions;
51
+ /** The order a locale writes the day, the month and the year in: `['day', 'month', 'year']` in French. */
52
+ export declare const dateOrder: (locale: string | undefined) => DatePart[];
53
+ /** How a date is typed in a locale, for a placeholder: `dd.mm.yyyy`, `mm/dd/yyyy`. */
54
+ export declare const datePattern: (locale: string | undefined) => string;
55
+ /** Whether text could be part of a typed date: digits and separators, no letters. */
56
+ export declare const isDateText: (text: string) => boolean;
57
+ /**
58
+ * A date as people type it in a field: in the locale's order with any separators (`25.10.2026`, `25/10/2026`), its
59
+ * digits alone (`25102026`), or year first (`2026-10-25`, `2026/10/25`). Null for anything else, including days that
60
+ * do not exist.
61
+ */
62
+ export declare function parseTypedDate(text: string, order: readonly DatePart[]): CalendarDate | null;
@@ -163,4 +163,75 @@ var formatMonthTitle = (year, month, locale) => formatDate({
163
163
  });
164
164
  /** The names of the weekdays, in the order a week shows them from its first day. */
165
165
  var weekdayNames = (locale, firstDay, width) => weekdayOrder(firstDay).map((weekday) => formatDate(addDays(A_SUNDAY, weekday), locale, { weekday: width }));
166
- export { addDays, addMonths, addYears, calendarDateOf, clampDate, compareDates, dateAfterKey, dateFormatter, dayOfWeek, daysInMonth, endOfWeek, formatDate, formatMonthTitle, isLeapYear, isSameDate, isSameMonth, isWithin, monthGrid, monthHasDaysWithin, parseIsoDate, startOfWeek, toIsoDate, weekdayNames, weekdayOrder };
166
+ /** How a date field writes a date: in digits, in the locale's order, the year in full (`25.10.2026`, `10/25/2026`). */
167
+ var NUMERIC_DATE = {
168
+ day: "2-digit",
169
+ month: "2-digit",
170
+ numberingSystem: "latn",
171
+ year: "numeric"
172
+ };
173
+ var DATE_PARTS = [
174
+ "day",
175
+ "month",
176
+ "year"
177
+ ];
178
+ var ISO_ORDER = [
179
+ "year",
180
+ "month",
181
+ "day"
182
+ ];
183
+ var PATTERN_LETTERS = {
184
+ day: "dd",
185
+ month: "mm",
186
+ year: "yyyy"
187
+ };
188
+ var PART_DIGITS = {
189
+ day: 2,
190
+ month: 2,
191
+ year: 4
192
+ };
193
+ var DATE_TEXT = /^[\d\s./,-]*$/;
194
+ var NOT_DIGITS = /\D+/;
195
+ var isDatePart = (type) => DATE_PARTS.some((part) => part === type);
196
+ /** A date written in digits in a locale, cut in its parts: `25`, `.`, `10`, `.`, `2026`. */
197
+ var numericParts = (locale) => new Intl.DateTimeFormat(locale, {
198
+ ...NUMERIC_DATE,
199
+ timeZone: UTC
200
+ }).formatToParts(new Date(utcTime(A_SUNDAY)));
201
+ /** The order a locale writes the day, the month and the year in: `['day', 'month', 'year']` in French. */
202
+ var dateOrder = (locale) => numericParts(locale).flatMap((part) => isDatePart(part.type) ? [part.type] : []);
203
+ /** How a date is typed in a locale, for a placeholder: `dd.mm.yyyy`, `mm/dd/yyyy`. */
204
+ var datePattern = (locale) => numericParts(locale).map((part) => isDatePart(part.type) ? PATTERN_LETTERS[part.type] : part.value).join("");
205
+ /** Whether text could be part of a typed date: digits and separators, no letters. */
206
+ var isDateText = (text) => DATE_TEXT.test(text);
207
+ /** Digits typed without separators, cut in the locale's parts; null when there are not as many as a date has. */
208
+ function splitDigits(digits, order) {
209
+ const lengths = order.map((part) => PART_DIGITS[part]);
210
+ if (digits.length !== lengths.reduce((sum, length) => sum + length, 0)) return null;
211
+ return lengths.map((length, index) => {
212
+ const start = lengths.slice(0, index).reduce((sum, before) => sum + before, 0);
213
+ return digits.slice(start, start + length);
214
+ });
215
+ }
216
+ /** The typed groups of digits, as many as the date has parts; null otherwise. */
217
+ function typedGroups(text, order) {
218
+ const groups = text.split(NOT_DIGITS).filter((group) => group !== "");
219
+ const parts = groups.length === 1 ? splitDigits(groups[0] ?? "", order) : groups;
220
+ return parts !== null && parts.length === order.length ? parts : null;
221
+ }
222
+ /**
223
+ * A date as people type it in a field: in the locale's order with any separators (`25.10.2026`, `25/10/2026`), its
224
+ * digits alone (`25102026`), or year first (`2026-10-25`, `2026/10/25`). Null for anything else, including days that
225
+ * do not exist.
226
+ */
227
+ function parseTypedDate(text, order) {
228
+ const trimmed = text.trim();
229
+ const groups = isDateText(trimmed) ? typedGroups(trimmed, order) : null;
230
+ if (groups === null) return null;
231
+ const used = groups[0]?.length === YEAR_DIGITS ? ISO_ORDER : order;
232
+ const part = (name) => groups[used.indexOf(name)] ?? "";
233
+ if (!DATE_PARTS.every((name) => name === "year" ? part(name).length === PART_DIGITS.year : part(name).length <= PART_DIGITS[name])) return null;
234
+ const digits = (name) => pad(Number(part(name)), PART_DIGITS[name]);
235
+ return parseIsoDate(`${digits("year")}-${digits("month")}-${digits("day")}`);
236
+ }
237
+ export { NUMERIC_DATE, addDays, addMonths, addYears, calendarDateOf, clampDate, compareDates, dateAfterKey, dateFormatter, dateOrder, datePattern, dayOfWeek, daysInMonth, endOfWeek, formatDate, formatMonthTitle, isDateText, isLeapYear, isSameDate, isSameMonth, isWithin, monthGrid, monthHasDaysWithin, parseIsoDate, parseTypedDate, startOfWeek, toIsoDate, weekdayNames, weekdayOrder };
package/dist/lib/dom.d.ts CHANGED
@@ -2,3 +2,5 @@
2
2
  export declare function setOrRemove(element: Element, name: string, value: string): void;
3
3
  /** A unique id within the page, for wiring `aria-controls`, `aria-labelledby` and the like inside a shadow root. */
4
4
  export declare function uniqueId(prefix: string): string;
5
+ /** An SVG element with its attributes: JSX makes HTML elements only. */
6
+ export declare function svgElement<Tag extends keyof SVGElementTagNameMap>(tag: Tag, attributes: Record<string, string>): SVGElementTagNameMap[Tag];
package/dist/lib/dom.js CHANGED
@@ -13,4 +13,11 @@ function uniqueId(prefix) {
13
13
  sequence.next += 1;
14
14
  return `${prefix}-${sequence.next}`;
15
15
  }
16
- export { setOrRemove, uniqueId };
16
+ var SVG_NS = "http://www.w3.org/2000/svg";
17
+ /** An SVG element with its attributes: JSX makes HTML elements only. */
18
+ function svgElement(tag, attributes) {
19
+ const element = document.createElementNS(SVG_NS, tag);
20
+ for (const [name, value] of Object.entries(attributes)) element.setAttribute(name, value);
21
+ return element;
22
+ }
23
+ export { setOrRemove, svgElement, uniqueId };
@@ -0,0 +1,70 @@
1
+ import type { FlowEdge, FlowEdgeEnds, FlowGraph, FlowNode, FlowPoint, FlowPort, FlowViewport } from '../types/jl-flow.types';
2
+ /**
3
+ * A node's box: fixed, so that every anchor and edge is computed without measuring the page. A node with a detail
4
+ * line is taller.
5
+ */
6
+ export declare const FLOW_NODE: {
7
+ readonly height: 64;
8
+ readonly heightWithDetail: 96;
9
+ readonly width: 240;
10
+ };
11
+ export declare const FLOW_ZOOM: {
12
+ readonly max: 2;
13
+ readonly min: 0.25;
14
+ readonly step: 1.2;
15
+ };
16
+ /** The exit of a node that names none. */
17
+ export declare const DEFAULT_PORT: FlowPort;
18
+ export declare const portsOf: (node: FlowNode) => FlowPort[];
19
+ export declare const nodeHeight: (node: FlowNode) => number;
20
+ /** The exit an edge leaves by: its own, or its node's first. */
21
+ export declare const portOfEdge: (node: FlowNode, edge: FlowEdge) => string;
22
+ export declare const snap: (point: FlowPoint) => FlowPoint;
23
+ /** Where a node's n-th exit sits along its bottom edge: the edge cut in equal parts, each exit in the middle of one. */
24
+ export declare const portX: (index: number, count: number) => number;
25
+ export declare function outputAnchor(node: FlowNode, portId: string): FlowPoint | undefined;
26
+ export declare const inputAnchor: (node: FlowNode) => FlowPoint;
27
+ /**
28
+ * The corners an edge goes through, in straight runs: down from the exit, across halfway, down into the node. An
29
+ * edge that goes back up turns around the side, clear of the nodes.
30
+ */
31
+ export declare function edgeRoute(from: FlowPoint, to: FlowPoint): FlowPoint[];
32
+ /** An SVG path through the points, its corners rounded. */
33
+ export declare function roundedPath(points: readonly FlowPoint[], radius?: number): string;
34
+ export declare const edgePath: (from: FlowPoint, to: FlowPoint) => string;
35
+ /** The point halfway along `edgePath(from, to)`. */
36
+ export declare const edgeMidpoint: (from: FlowPoint, to: FlowPoint) => FlowPoint;
37
+ /** Where an edge starts and ends, or nothing when one of its ends is gone. */
38
+ export declare function edgeEnds(graph: FlowGraph, edge: FlowEdge): FlowEdgeEnds | undefined;
39
+ /** A run went this way: its node left by the edge's exit, and the node it leads to ran too. */
40
+ export declare function isEdgeTaken(graph: FlowGraph, edge: FlowEdge): boolean;
41
+ /** The position of a node centred on a point: where something dropped there lands. */
42
+ export declare const centeredAt: (point: FlowPoint) => FlowPoint;
43
+ /** The node under a point, other than `except`. */
44
+ export declare const nodeAt: (graph: FlowGraph, point: FlowPoint, except?: string) => FlowNode | undefined;
45
+ export declare const isPortFree: (graph: FlowGraph, node: FlowNode, portId: string) => boolean;
46
+ /**
47
+ * The graph with one more edge, or nothing when the edge cannot be: a node does not connect to itself, a start takes
48
+ * no incoming edge, and an exit joins a node once.
49
+ */
50
+ export declare function connect(graph: FlowGraph, edge: FlowEdge): FlowGraph | undefined;
51
+ export declare const moveNode: (graph: FlowGraph, id: string, position: FlowPoint) => FlowGraph;
52
+ /** The graph without the node, and without its edges. */
53
+ export declare const removeNode: (graph: FlowGraph, id: string) => FlowGraph;
54
+ export declare const removeEdge: (graph: FlowGraph, id: string) => FlowGraph;
55
+ /** Where a node added after an exit goes: a row below, under its exit, clear of the nodes after the other exits. */
56
+ export declare function positionAfter(graph: FlowGraph, from: string, portId: string): FlowPoint;
57
+ /**
58
+ * The nodes laid out again, top to bottom in the order they run: the starts and the nodes nothing leads to on the
59
+ * first row, each other node a row below the lowest node leading to it, under the exits it hangs from. Edges that
60
+ * loop back up do not count. Only the positions change.
61
+ */
62
+ export declare function layoutFlow(graph: FlowGraph): FlowGraph;
63
+ /** Where a point of the canvas (relative to its top left corner) is in the flow's own pixels. */
64
+ export declare const toWorld: (viewport: FlowViewport, screen: FlowPoint) => FlowPoint;
65
+ /** Zooms by `factor`, keeping the point under `screen` where it is: zooming follows the pointer. */
66
+ export declare function zoomAt(viewport: FlowViewport, screen: FlowPoint, factor: number): FlowViewport;
67
+ /** The view that shows every node, centred in a canvas of this size, never larger than life. */
68
+ export declare function fitView(nodes: readonly FlowNode[], width: number, height: number): FlowViewport;
69
+ /** The view moved just enough for a node to be seen whole, its exits included; unchanged when it already is. */
70
+ export declare function revealIn(viewport: FlowViewport, node: FlowNode, width: number, height: number): FlowViewport;