@syware/jlib 0.2.0 → 0.4.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 +1 -0
  2. package/dist/all.js +5 -3
  3. package/dist/atoms/jl-button.css_raw.js +1 -1
  4. package/dist/atoms/jl-button.d.ts +19 -0
  5. package/dist/atoms/jl-button.js +75 -23
  6. package/dist/atoms/jl-chip.css_raw.js +1 -1
  7. package/dist/atoms/jl-input.d.ts +6 -0
  8. package/dist/atoms/jl-input.js +4 -0
  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 +5 -1
  14. package/dist/index.js +4 -2
  15. package/dist/lib/attributes.d.ts +5 -0
  16. package/dist/lib/attributes.js +13 -1
  17. package/dist/lib/flow.d.ts +10 -1
  18. package/dist/lib/flow.js +26 -9
  19. package/dist/lib/tokens.d.ts +2 -1
  20. package/dist/lib/tokens.js +13 -4
  21. package/dist/molecules/jl-accordion-item.css_raw.js +1 -1
  22. package/dist/molecules/jl-accordion-item.d.ts +6 -1
  23. package/dist/molecules/jl-accordion-item.js +21 -6
  24. package/dist/molecules/jl-alert.d.ts +10 -3
  25. package/dist/molecules/jl-alert.js +23 -6
  26. package/dist/molecules/jl-chip-group.css_raw.js +3 -0
  27. package/dist/molecules/jl-chip-group.d.ts +81 -0
  28. package/dist/molecules/jl-chip-group.js +259 -0
  29. package/dist/molecules/jl-search.d.ts +3 -1
  30. package/dist/molecules/jl-search.js +8 -6
  31. package/dist/molecules/jl-segmented.css_raw.js +1 -1
  32. package/dist/molecules/jl-segmented.js +14 -7
  33. package/dist/molecules/jl-stat.css_raw.js +1 -1
  34. package/dist/molecules/jl-stat.d.ts +6 -0
  35. package/dist/molecules/jl-stat.js +6 -0
  36. package/dist/molecules/jl-tag-input.css_raw.js +1 -1
  37. package/dist/organisms/jl-flow.css_raw.js +1 -1
  38. package/dist/organisms/jl-flow.d.ts +28 -5
  39. package/dist/organisms/jl-flow.js +138 -33
  40. package/dist/organisms/jl-tab-bar.css_raw.js +3 -0
  41. package/dist/organisms/jl-tab-bar.d.ts +43 -0
  42. package/dist/organisms/jl-tab-bar.js +123 -0
  43. package/dist/templates/jl-app-shell.css_raw.js +1 -1
  44. package/dist/templates/jl-app-shell.d.ts +8 -2
  45. package/dist/templates/jl-app-shell.js +32 -5
  46. package/dist/types/jl-chip-group.types.d.ts +10 -0
  47. package/dist/types/jl-flow.types.d.ts +17 -0
  48. package/dist/types/jl-segmented.types.d.ts +2 -0
  49. package/dist/types/jl-tab-bar.types.d.ts +11 -0
  50. package/package.json +1 -1
package/README.md CHANGED
@@ -107,6 +107,7 @@ git push --follow-tags
107
107
 
108
108
  The tag stages the version on npm and creates the GitHub release; a maintainer then approves it with
109
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).
110
111
 
111
112
  ## Made by SyWare
112
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";
@@ -34,6 +34,7 @@ import { JlAvatarGroup } from "./molecules/jl-avatar-group.js";
34
34
  import { JlBreadcrumb } from "./molecules/jl-breadcrumb.js";
35
35
  import { JlCalendar } from "./molecules/jl-calendar.js";
36
36
  import { CARD_PADDINGS, JlCard } from "./molecules/jl-card.js";
37
+ import { JlChipGroup } from "./molecules/jl-chip-group.js";
37
38
  import { JlCopyButton } from "./molecules/jl-copy-button.js";
38
39
  import { CODE_LANGUAGES, JlCodeBlock } from "./molecules/jl-code-block.js";
39
40
  import { JlCombobox } from "./molecules/jl-combobox.js";
@@ -65,6 +66,7 @@ import { JlFlow } from "./organisms/jl-flow.js";
65
66
  import { JlNavbar } from "./organisms/jl-navbar.js";
66
67
  import { JlPageHeader } from "./organisms/jl-page-header.js";
67
68
  import { JlSideNav } from "./organisms/jl-side-nav.js";
69
+ import { JlTabBar } from "./organisms/jl-tab-bar.js";
68
70
  import { JlTable, TABLE_SORT_DIRECTIONS } from "./organisms/jl-table.js";
69
71
  import { JlToaster, TOASTER_PLACEMENTS, toast } from "./organisms/jl-toaster.js";
70
72
  import { JlTree } from "./organisms/jl-tree.js";
@@ -72,5 +74,5 @@ import { JlAppShell } from "./templates/jl-app-shell.js";
72
74
  import { JlSplitView, SPLIT_ORIENTATIONS } from "./templates/jl-split-view.js";
73
75
  // src/all.ts
74
76
  registerIcons(COMMON_ICONS);
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, 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, 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 };
77
+ define(JlAccordion, JlAccordionItem, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlChipGroup, 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, JlTabBar, JlTable, JlTabPanel, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree);
78
+ 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, JlChipGroup, 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, JlTabBar, 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-button.css?raw
2
- var jl_button_default = ":host {\n display: inline-flex;\n vertical-align: middle;\n --height: var(--jl-control-md);\n --pad: 14px;\n --font-size: var(--jl-font-md);\n --radius: var(--jl-radius-sm);\n}\n\n:host([size=\"sm\"]) {\n --height: var(--jl-control-sm);\n --pad: 11px;\n --font-size: var(--jl-font-xs);\n}\n\n:host([size=\"lg\"]) {\n --height: var(--jl-control-lg);\n --pad: 18px;\n --font-size: 15px;\n --radius: var(--jl-radius);\n}\n\n:host([block]) {\n display: flex;\n}\n\nbutton {\n display: inline-flex;\n flex: 1;\n align-items: center;\n justify-content: center;\n gap: 8px;\n height: var(--height);\n padding: 0 var(--pad);\n border: 1px solid var(--jl-line-strong);\n border-radius: var(--radius);\n background: var(--jl-surface);\n color: var(--jl-text);\n font-size: var(--font-size);\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n cursor: pointer;\n transition:\n background var(--jl-fast) var(--jl-ease),\n border-color var(--jl-fast) var(--jl-ease),\n color var(--jl-fast) var(--jl-ease);\n}\n\n:host([icon-only]) button {\n width: var(--height);\n padding: 0;\n}\n\nbutton:hover {\n background: var(--jl-surface-2);\n}\n\nbutton:active {\n background: var(--jl-surface-3);\n}\n\n:host([variant=\"primary\"]) button {\n border-color: transparent;\n background: var(--jl-accent);\n color: var(--jl-on-accent);\n}\n\n:host([variant=\"primary\"]) button:hover {\n background: var(--jl-accent-strong);\n}\n\n:host([variant=\"ghost\"]) button {\n border-color: transparent;\n background: transparent;\n color: var(--jl-accent);\n}\n\n:host([variant=\"ghost\"]) button:hover {\n background: var(--jl-accent-soft);\n}\n\n:host([variant=\"danger\"]) button {\n border-color: transparent;\n background: var(--jl-danger);\n color: var(--jl-on-danger);\n}\n\n:host([variant=\"danger\"]) button:hover {\n background: color-mix(in srgb, var(--jl-danger) 86%, var(--jl-text));\n}\n\n:host([loading]) button {\n cursor: progress;\n}\n\nbutton:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n";
2
+ var jl_button_default = ":host {\n display: inline-flex;\n vertical-align: middle;\n --height: var(--jl-control-md);\n --pad: 14px;\n --font-size: var(--jl-font-md);\n --radius: var(--jl-radius-sm);\n}\n\n:host([size=\"sm\"]) {\n --height: var(--jl-control-sm);\n --pad: 11px;\n --font-size: var(--jl-font-xs);\n}\n\n:host([size=\"lg\"]) {\n --height: var(--jl-control-lg);\n --pad: 18px;\n --font-size: 15px;\n --radius: var(--jl-radius);\n}\n\n:host([block]) {\n display: flex;\n}\n\n/* Moves between the button and the link: its children are laid out by whichever holds it. */\n.content {\n display: contents;\n}\n\n.base {\n display: inline-flex;\n flex: 1;\n align-items: center;\n justify-content: center;\n gap: 8px;\n height: var(--height);\n padding: 0 var(--pad);\n border: 1px solid var(--jl-line-strong);\n border-radius: var(--radius);\n background: var(--jl-surface);\n color: var(--jl-text);\n font-size: var(--font-size);\n font-weight: 600;\n line-height: 1;\n text-decoration: none;\n white-space: nowrap;\n cursor: pointer;\n transition:\n background var(--jl-fast) var(--jl-ease),\n border-color var(--jl-fast) var(--jl-ease),\n color var(--jl-fast) var(--jl-ease);\n}\n\n:host([icon-only]) .base {\n width: var(--height);\n padding: 0;\n}\n\n.base:hover {\n background: var(--jl-surface-2);\n}\n\n.base:active {\n background: var(--jl-surface-3);\n}\n\n:host([variant=\"primary\"]) .base {\n border-color: transparent;\n background: var(--jl-accent);\n color: var(--jl-on-accent);\n}\n\n:host([variant=\"primary\"]) .base:hover {\n background: var(--jl-accent-strong);\n}\n\n:host([variant=\"ghost\"]) .base {\n border-color: transparent;\n background: transparent;\n color: var(--jl-accent);\n}\n\n:host([variant=\"ghost\"]) .base:hover {\n background: var(--jl-accent-soft);\n}\n\n:host([variant=\"danger\"]) .base {\n border-color: transparent;\n background: var(--jl-danger);\n color: var(--jl-on-danger);\n}\n\n:host([variant=\"danger\"]) .base:hover {\n background: color-mix(in srgb, var(--jl-danger) 86%, var(--jl-text));\n}\n\n:host([loading]) .base {\n cursor: progress;\n}\n\n.base:disabled,\n.base[aria-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: 0.5;\n}\n";
3
3
  export { jl_button_default as default };
@@ -6,6 +6,7 @@ export declare const BUTTON_TYPES: readonly ButtonType[];
6
6
  /**
7
7
  * A button. Its text is the `label` (or the slotted content); with only an `icon`, its `hint` names it.
8
8
  * Listen to `press`: it fires only while the button can be pressed. `type="submit"` submits the surrounding form.
9
+ * With an `href` it is a link that looks the same: it opens the address, and `press` still fires.
9
10
  */
10
11
  export declare class JlButton extends JlElement {
11
12
  static readonly selector = "jl-button";
@@ -56,12 +57,30 @@ export declare class JlButton extends JlElement {
56
57
  get(): boolean;
57
58
  set(setter: boolean | ((prevState: boolean) => boolean)): void;
58
59
  }>>;
60
+ /** An address to open: the button becomes a link, for navigation that should open in a new tab when asked. */
61
+ readonly href: Readonly<Readonly<{
62
+ get(): string;
63
+ set(setter: string | ((prevState: string) => string)): void;
64
+ }>>;
65
+ /** Where the link opens (`_blank` for a new tab), with an `href`. */
66
+ readonly target: Readonly<Readonly<{
67
+ get(): string;
68
+ set(setter: string | ((prevState: string) => string)): void;
69
+ }>>;
59
70
  private readonly _internals;
60
71
  private readonly _refs;
61
72
  focus(options?: FocusOptions): void;
62
73
  templateHtml(): Node;
63
74
  templateCss(): string;
64
75
  onConnect(): void;
76
+ private canPress;
77
+ /** The element that takes focus and clicks: the link with an `href`, the button otherwise. */
78
+ private control;
79
+ /**
80
+ * Moves the content into the link or back into the button. A disabled link loses its address, so it can be
81
+ * neither followed nor focused, and says it is disabled.
82
+ */
83
+ private renderLink;
65
84
  private actOnForm;
66
85
  private renderIcon;
67
86
  private renderSize;
@@ -2,11 +2,12 @@ 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 { CONTROL_SIZES } from "../lib/options.js";
5
+ import { setOrRemove } from "../lib/dom.js";
5
6
  import { JlIcon } from "./jl-icon.js";
6
7
  import jl_button_default from "./jl-button.css_raw.js";
7
8
  import { JlSpinner } from "./jl-spinner.js";
8
9
  import { css } from "@jadis/core";
9
- import { jsx, jsxs } from "@jadis/core/jsx-runtime";
10
+ import { Fragment, jsx, jsxs } from "@jadis/core/jsx-runtime";
10
11
  // src/atoms/jl-button.tsx
11
12
  var BUTTON_VARIANTS = [
12
13
  "secondary",
@@ -24,9 +25,13 @@ var ICON_SIZES = {
24
25
  md: 16,
25
26
  sm: 14
26
27
  };
28
+ var NEW_TAB = "_blank";
29
+ /** A page opened in a new tab gets no handle on this one. */
30
+ var SAFE_REL = "noopener noreferrer";
27
31
  /**
28
32
  * A button. Its text is the `label` (or the slotted content); with only an `icon`, its `hint` names it.
29
33
  * Listen to `press`: it fires only while the button can be pressed. `type="submit"` submits the surrounding form.
34
+ * With an `href` it is a link that looks the same: it opens the address, and `press` still fires.
30
35
  */
31
36
  var JlButton = class extends JlElement {
32
37
  static selector = "jl-button";
@@ -49,6 +54,7 @@ var JlButton = class extends JlElement {
49
54
  });
50
55
  disabled = this.useChange(false, (value) => {
51
56
  this._refs.button.disabled = value;
57
+ this.renderLink();
52
58
  this.reflect("disabled", value);
53
59
  }, { attribute: "disabled" });
54
60
  /** Shows a spinner in place of the icon and ignores presses, while the work it started runs. */
@@ -56,42 +62,57 @@ var JlButton = class extends JlElement {
56
62
  this._refs.spinner.hidden = !value;
57
63
  this._refs.start.hidden = value || this.icon.get() === "";
58
64
  this._refs.button.toggleAttribute("aria-busy", value);
65
+ this._refs.link.toggleAttribute("aria-busy", value);
59
66
  this.reflect("loading", value);
60
67
  }, { attribute: "loading" });
61
68
  /** The tooltip; also the button's name for screen readers when it only has an icon. */
62
69
  hint = this.useChange("", () => this.renderLabel(), { attribute: "hint" });
63
70
  /** Takes the whole width of its container. */
64
71
  block = this.useChange(false, (value) => this.reflect("block", value), { attribute: "block" });
72
+ /** An address to open: the button becomes a link, for navigation that should open in a new tab when asked. */
73
+ href = this.useChange("", () => this.renderLink(), { attribute: "href" });
74
+ /** Where the link opens (`_blank` for a new tab), with an `href`. */
75
+ target = this.useChange("", () => this.renderLink(), { attribute: "target" });
65
76
  _internals = this.attachInternals();
66
77
  _refs = this.useRefs((ref) => ({
67
78
  button: ref("button"),
79
+ content: ref(".content"),
68
80
  end: ref(".end"),
69
81
  label: ref(".text"),
82
+ link: ref("a"),
70
83
  slot: ref("slot"),
71
84
  spinner: ref("jl-spinner"),
72
85
  start: ref(".start")
73
86
  }));
74
87
  focus(options) {
75
- this._refs.button.focus(options);
88
+ this.control().focus(options);
76
89
  }
77
90
  templateHtml() {
78
- return /* @__PURE__ */ jsxs("button", {
91
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
92
+ class: "base",
79
93
  type: "button",
80
94
  attrs: { part: "base" },
81
- children: [
82
- /* @__PURE__ */ jsx(JlSpinner, { hidden: true }),
83
- /* @__PURE__ */ jsx(JlIcon, {
84
- class: "start",
85
- hidden: true
86
- }),
87
- /* @__PURE__ */ jsx("span", { class: "text" }),
88
- /* @__PURE__ */ jsx("slot", {}),
89
- /* @__PURE__ */ jsx(JlIcon, {
90
- class: "end",
91
- hidden: true
92
- })
93
- ]
94
- });
95
+ children: /* @__PURE__ */ jsxs("span", {
96
+ class: "content",
97
+ children: [
98
+ /* @__PURE__ */ jsx(JlSpinner, { hidden: true }),
99
+ /* @__PURE__ */ jsx(JlIcon, {
100
+ class: "start",
101
+ hidden: true
102
+ }),
103
+ /* @__PURE__ */ jsx("span", { class: "text" }),
104
+ /* @__PURE__ */ jsx("slot", {}),
105
+ /* @__PURE__ */ jsx(JlIcon, {
106
+ class: "end",
107
+ hidden: true
108
+ })
109
+ ]
110
+ })
111
+ }), /* @__PURE__ */ jsx("a", {
112
+ class: "base",
113
+ hidden: true,
114
+ attrs: { part: "base" }
115
+ })] });
95
116
  }
96
117
  templateCss() {
97
118
  return css`${BASE_STYLES}${jl_button_default}`;
@@ -99,10 +120,43 @@ var JlButton = class extends JlElement {
99
120
  onConnect() {
100
121
  this.on(this._refs.slot, "slotchange", () => this.renderLabel());
101
122
  this.on(this._refs.button, "click", () => {
102
- if (this.disabled.get() || this.loading.get()) return;
123
+ if (!this.canPress()) return;
103
124
  this.events.emit("press");
104
125
  this.actOnForm();
105
126
  });
127
+ this.on(this._refs.link, "click", (event) => {
128
+ if (!this.canPress()) {
129
+ event.preventDefault();
130
+ return;
131
+ }
132
+ this.events.emit("press");
133
+ });
134
+ }
135
+ canPress() {
136
+ return !(this.disabled.get() || this.loading.get());
137
+ }
138
+ /** The element that takes focus and clicks: the link with an `href`, the button otherwise. */
139
+ control() {
140
+ return this.href.get() === "" ? this._refs.button : this._refs.link;
141
+ }
142
+ /**
143
+ * Moves the content into the link or back into the button. A disabled link loses its address, so it can be
144
+ * neither followed nor focused, and says it is disabled.
145
+ */
146
+ renderLink() {
147
+ const { button, content, link } = this._refs;
148
+ const href = this.href.get();
149
+ const isLink = href !== "";
150
+ const target = this.target.get();
151
+ const off = isLink && this.disabled.get();
152
+ this.control().append(content);
153
+ button.hidden = isLink;
154
+ link.hidden = !isLink;
155
+ setOrRemove(link, "href", isLink && !off ? href : "");
156
+ setOrRemove(link, "target", target);
157
+ setOrRemove(link, "rel", target === NEW_TAB ? SAFE_REL : "");
158
+ setOrRemove(link, "role", off ? "link" : "");
159
+ setOrRemove(link, "aria-disabled", off ? "true" : "");
106
160
  }
107
161
  actOnForm() {
108
162
  const form = this._internals.form;
@@ -133,13 +187,11 @@ var JlButton = class extends JlElement {
133
187
  const iconOnly = label === "" && !this.hasSlottedContent();
134
188
  this._refs.label.textContent = label;
135
189
  this._refs.label.hidden = label === "";
136
- this._refs.button.title = hint;
137
190
  this.reflect("icon-only", iconOnly);
138
- if (iconOnly && hint !== "") {
139
- this._refs.button.setAttribute("aria-label", hint);
140
- return;
191
+ for (const control of [this._refs.button, this._refs.link]) {
192
+ setOrRemove(control, "title", hint);
193
+ setOrRemove(control, "aria-label", iconOnly ? hint : "");
141
194
  }
142
- this._refs.button.removeAttribute("aria-label");
143
195
  }
144
196
  };
145
197
  JlButton.register();
@@ -1,3 +1,3 @@
1
1
  // src/atoms/jl-chip.css?raw
2
- var jl_chip_default = ":host {\n display: inline-flex;\n max-width: 100%;\n vertical-align: middle;\n --height: 28px;\n --pad: 10px;\n --font-size: var(--jl-font-sm);\n --dismiss: 20px;\n}\n\n:host([size=\"sm\"]) {\n --height: 24px;\n --pad: 8px;\n --font-size: var(--jl-font-xs);\n --dismiss: 18px;\n}\n\n.chip {\n display: inline-flex;\n align-items: center;\n min-width: 0;\n height: var(--height);\n border: 1px solid var(--jl-line-strong);\n border-radius: var(--jl-radius-pill);\n background: var(--jl-surface);\n color: var(--jl-text-2);\n font-size: var(--font-size);\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n transition:\n background var(--jl-fast) var(--jl-ease),\n border-color var(--jl-fast) var(--jl-ease),\n color var(--jl-fast) var(--jl-ease);\n}\n\n.main {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n height: 100%;\n padding: 0 var(--pad);\n border-radius: inherit;\n}\n\n.label {\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.label:empty {\n display: none;\n}\n\n:host([removable]) .main {\n padding-right: 4px;\n}\n\n:host([selectable]) .main {\n cursor: pointer;\n}\n\n:host([selectable]:not([disabled])) .chip:hover {\n background: var(--jl-surface-2);\n}\n\n:host([selected]) .chip,\n:host([selected][selectable]:not([disabled])) .chip:hover {\n border-color: var(--jl-accent-line);\n background: var(--jl-accent-soft);\n color: var(--jl-accent);\n}\n\n/* The ring goes around the whole chip, which the main part fills. */\n.main:focus-visible {\n box-shadow: none;\n}\n\n.chip:has(.main:focus-visible) {\n box-shadow: var(--jl-focus-ring);\n}\n\n.dismiss {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: var(--dismiss);\n height: var(--dismiss);\n margin-right: 3px;\n padding: 0;\n border: 0;\n border-radius: 50%;\n background: transparent;\n color: var(--jl-muted);\n cursor: pointer;\n}\n\n.dismiss:hover {\n background: var(--jl-surface-3);\n color: var(--jl-text);\n}\n\n:host([selected]) .dismiss {\n color: var(--jl-accent);\n}\n\n:host([selected]) .dismiss:hover {\n background: color-mix(in srgb, var(--jl-accent) 14%, transparent);\n color: var(--jl-accent-strong);\n}\n\n:host([disabled]) .chip {\n opacity: 0.5;\n}\n\n:host([disabled]) .main,\n:host([disabled]) .dismiss {\n cursor: not-allowed;\n}\n\n:host([disabled]) .dismiss:hover {\n background: transparent;\n}\n";
2
+ var jl_chip_default = ":host {\n display: inline-flex;\n max-width: 100%;\n vertical-align: middle;\n --height: 28px;\n --pad: 10px;\n --font-size: var(--jl-font-sm);\n --dismiss: 20px;\n}\n\n:host([size=\"sm\"]) {\n --height: 24px;\n --pad: 8px;\n --font-size: var(--jl-font-xs);\n --dismiss: 18px;\n}\n\n.chip {\n display: inline-flex;\n align-items: center;\n min-width: 0;\n height: var(--height);\n border: 1px solid var(--jl-line-strong);\n border-radius: var(--jl-radius-pill);\n background: var(--jl-surface);\n color: var(--jl-text-2);\n font-size: var(--font-size);\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n transition:\n background var(--jl-fast) var(--jl-ease),\n border-color var(--jl-fast) var(--jl-ease),\n color var(--jl-fast) var(--jl-ease);\n}\n\n.main {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n height: 100%;\n padding: 0 var(--pad);\n border-radius: inherit;\n}\n\n/* A line box taller than the chip's own line-height of 1: the accents of É, Â, Ö stay inside what overflow clips. */\n.label {\n overflow: hidden;\n line-height: 1.4;\n text-overflow: ellipsis;\n}\n\n.label:empty {\n display: none;\n}\n\n:host([removable]) .main {\n padding-right: 4px;\n}\n\n:host([selectable]) .main {\n cursor: pointer;\n}\n\n:host([selectable]:not([disabled])) .chip:hover {\n background: var(--jl-surface-2);\n}\n\n:host([selected]) .chip,\n:host([selected][selectable]:not([disabled])) .chip:hover {\n border-color: var(--jl-accent-line);\n background: var(--jl-accent-soft);\n color: var(--jl-accent);\n}\n\n/* The ring goes around the whole chip, which the main part fills. */\n.main:focus-visible {\n box-shadow: none;\n}\n\n.chip:has(.main:focus-visible) {\n box-shadow: var(--jl-focus-ring);\n}\n\n.dismiss {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: var(--dismiss);\n height: var(--dismiss);\n margin-right: 3px;\n padding: 0;\n border: 0;\n border-radius: 50%;\n background: transparent;\n color: var(--jl-muted);\n cursor: pointer;\n}\n\n.dismiss:hover {\n background: var(--jl-surface-3);\n color: var(--jl-text);\n}\n\n:host([selected]) .dismiss {\n color: var(--jl-accent);\n}\n\n:host([selected]) .dismiss:hover {\n background: color-mix(in srgb, var(--jl-accent) 14%, transparent);\n color: var(--jl-accent-strong);\n}\n\n:host([disabled]) .chip {\n opacity: 0.5;\n}\n\n:host([disabled]) .main,\n:host([disabled]) .dismiss {\n cursor: not-allowed;\n}\n\n:host([disabled]) .dismiss:hover {\n background: transparent;\n}\n";
3
3
  export { jl_chip_default as default };
@@ -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 +34,11 @@ export declare class JlInput extends JlFormControl {
33
34
  get(): string;
34
35
  set(setter: string | ((prevState: string) => string)): void;
35
36
  }>>;
37
+ /** The keyboard a phone shows for the field (`numeric`, `decimal`, `tel`…), as a native input's `inputmode`. */
38
+ readonly inputmode: Readonly<Readonly<{
39
+ get(): string;
40
+ set(setter: string | ((prevState: string) => string)): void;
41
+ }>>;
36
42
  readonly min: Readonly<Readonly<{
37
43
  get(): string;
38
44
  set(setter: string | ((prevState: string) => string)): void;
@@ -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.
@@ -49,6 +50,8 @@ var JlInput = class extends JlFormControl {
49
50
  /** The field's name for screen readers, when no visible label names it. */
50
51
  label = this.useChange("", (value) => this.setNative("aria-label", value), { attribute: "label" });
51
52
  autocomplete = this.useChange("", (value) => this.setNative("autocomplete", value), { attribute: "autocomplete" });
53
+ /** The keyboard a phone shows for the field (`numeric`, `decimal`, `tel`…), as a native input's `inputmode`. */
54
+ inputmode = this.useChange("", (value) => this.setNative("inputmode", value), { attribute: "inputmode" });
52
55
  min = this.useChange("", (value) => this.setNative("min", value), { attribute: "min" });
53
56
  max = this.useChange("", (value) => this.setNative("max", value), { attribute: "max" });
54
57
  step = this.useChange("", (value) => this.setNative("step", value), { attribute: "step" });
@@ -137,6 +140,7 @@ var JlInput = class extends JlFormControl {
137
140
  this.events.emit("input", native.value);
138
141
  });
139
142
  this.on(native, "change", () => this.events.emit("change", native.value));
143
+ this.on(native, "keydown", (event) => event.key === ENTER && this.events.emit("enter", native.value));
140
144
  this.on(this._refs.clear, "click", () => this.clearValue());
141
145
  this.on(this._refs.reveal, "click", () => this.toggleReveal());
142
146
  }
@@ -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
@@ -7,7 +7,7 @@ export { COMMON_ICONS } from './lib/icon-set';
7
7
  export { hasIcon, iconElement, iconNames, registerIcons } from './lib/icons';
8
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';
@@ -19,6 +19,7 @@ export type * from './types/jl-avatar.types';
19
19
  export type * from './types/jl-breadcrumb.types';
20
20
  export type * from './types/jl-button.types';
21
21
  export type * from './types/jl-card.types';
22
+ export type * from './types/jl-chip-group.types';
22
23
  export type * from './types/jl-chip.types';
23
24
  export type * from './types/jl-combobox.types';
24
25
  export type * from './types/jl-command-palette.types';
@@ -44,6 +45,7 @@ export type * from './types/jl-split-view.types';
44
45
  export type * from './types/jl-stat.types';
45
46
  export type * from './types/jl-steps.types';
46
47
  export type * from './types/jl-switch.types';
48
+ export type * from './types/jl-tab-bar.types';
47
49
  export type * from './types/jl-table.types';
48
50
  export type * from './types/jl-tabs.types';
49
51
  export type * from './types/jl-timeline.types';
@@ -83,6 +85,7 @@ export * from './molecules/jl-avatar-group';
83
85
  export * from './molecules/jl-breadcrumb';
84
86
  export * from './molecules/jl-calendar';
85
87
  export * from './molecules/jl-card';
88
+ export * from './molecules/jl-chip-group';
86
89
  export * from './molecules/jl-code-block';
87
90
  export * from './molecules/jl-combobox';
88
91
  export * from './molecules/jl-copy-button';
@@ -114,6 +117,7 @@ export * from './organisms/jl-flow';
114
117
  export * from './organisms/jl-navbar';
115
118
  export * from './organisms/jl-page-header';
116
119
  export * from './organisms/jl-side-nav';
120
+ export * from './organisms/jl-tab-bar';
117
121
  export * from './organisms/jl-table';
118
122
  export * from './organisms/jl-toaster';
119
123
  export * from './organisms/jl-tree';
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";
@@ -34,6 +34,7 @@ import { JlAvatarGroup } from "./molecules/jl-avatar-group.js";
34
34
  import { JlBreadcrumb } from "./molecules/jl-breadcrumb.js";
35
35
  import { JlCalendar } from "./molecules/jl-calendar.js";
36
36
  import { CARD_PADDINGS, JlCard } from "./molecules/jl-card.js";
37
+ import { JlChipGroup } from "./molecules/jl-chip-group.js";
37
38
  import { JlCopyButton } from "./molecules/jl-copy-button.js";
38
39
  import { CODE_LANGUAGES, JlCodeBlock } from "./molecules/jl-code-block.js";
39
40
  import { JlCombobox } from "./molecules/jl-combobox.js";
@@ -65,9 +66,10 @@ import { JlFlow } from "./organisms/jl-flow.js";
65
66
  import { JlNavbar } from "./organisms/jl-navbar.js";
66
67
  import { JlPageHeader } from "./organisms/jl-page-header.js";
67
68
  import { JlSideNav } from "./organisms/jl-side-nav.js";
69
+ import { JlTabBar } from "./organisms/jl-tab-bar.js";
68
70
  import { JlTable, TABLE_SORT_DIRECTIONS } from "./organisms/jl-table.js";
69
71
  import { JlToaster, TOASTER_PLACEMENTS, toast } from "./organisms/jl-toaster.js";
70
72
  import { JlTree } from "./organisms/jl-tree.js";
71
73
  import { JlAppShell } from "./templates/jl-app-shell.js";
72
74
  import { JlSplitView, SPLIT_ORIENTATIONS } from "./templates/jl-split-view.js";
73
- 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, 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 };
75
+ 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, JlChipGroup, 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, JlTabBar, 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 };
@@ -7,3 +7,8 @@ export declare const oneOf: <Value extends string>(allowed: readonly Value[], fa
7
7
  export declare const jsonOf: <Value>(fallback: Value) => (value: string | null) => Value;
8
8
  /** A number attribute kept within bounds; a missing or unreadable one gives the fallback. */
9
9
  export declare const clampedNumber: (fallback: number, min: number, max: number) => (value: string | null) => number;
10
+ /**
11
+ * An attribute holding a list of strings, as HTML writes it: a JSON list (`'["a","b"]'`), or one plain value
12
+ * (`open`) for the common case of a single one. Empty or unreadable gives an empty list.
13
+ */
14
+ export declare const stringList: (value: string | null) => string[];
@@ -19,4 +19,16 @@ var clampedNumber = (fallback, min, max) => (value) => {
19
19
  if (Number.isNaN(parsed)) return fallback;
20
20
  return Math.min(max, Math.max(min, parsed));
21
21
  };
22
- export { clampedNumber, jsonOf, oneOf };
22
+ var LIST_START = "[";
23
+ /**
24
+ * An attribute holding a list of strings, as HTML writes it: a JSON list (`'["a","b"]'`), or one plain value
25
+ * (`open`) for the common case of a single one. Empty or unreadable gives an empty list.
26
+ */
27
+ var stringList = (value) => {
28
+ const text = value?.trim() ?? "";
29
+ if (text === "") return [];
30
+ if (!text.startsWith(LIST_START)) return [text];
31
+ const parsed = jsonOf([])(text);
32
+ return Array.isArray(parsed) ? parsed.map(String) : [];
33
+ };
34
+ export { clampedNumber, jsonOf, oneOf, stringList };
@@ -1,7 +1,11 @@
1
1
  import type { FlowEdge, FlowEdgeEnds, FlowGraph, FlowNode, FlowPoint, FlowPort, FlowViewport } from '../types/jl-flow.types';
2
- /** A node's box: fixed, so that every anchor and edge is computed without measuring the page. */
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
+ */
3
6
  export declare const FLOW_NODE: {
4
7
  readonly height: 64;
8
+ readonly heightWithDetail: 96;
5
9
  readonly width: 240;
6
10
  };
7
11
  export declare const FLOW_ZOOM: {
@@ -12,6 +16,7 @@ export declare const FLOW_ZOOM: {
12
16
  /** The exit of a node that names none. */
13
17
  export declare const DEFAULT_PORT: FlowPort;
14
18
  export declare const portsOf: (node: FlowNode) => FlowPort[];
19
+ export declare const nodeHeight: (node: FlowNode) => number;
15
20
  /** The exit an edge leaves by: its own, or its node's first. */
16
21
  export declare const portOfEdge: (node: FlowNode, edge: FlowEdge) => string;
17
22
  export declare const snap: (point: FlowPoint) => FlowPoint;
@@ -31,6 +36,10 @@ export declare const edgePath: (from: FlowPoint, to: FlowPoint) => string;
31
36
  export declare const edgeMidpoint: (from: FlowPoint, to: FlowPoint) => FlowPoint;
32
37
  /** Where an edge starts and ends, or nothing when one of its ends is gone. */
33
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;
34
43
  /** The node under a point, other than `except`. */
35
44
  export declare const nodeAt: (graph: FlowGraph, point: FlowPoint, except?: string) => FlowNode | undefined;
36
45
  export declare const isPortFree: (graph: FlowGraph, node: FlowNode, portId: string) => boolean;