@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/dist/lib/flow.js CHANGED
@@ -1,7 +1,11 @@
1
1
  // src/lib/flow.ts
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
  var FLOW_NODE = {
4
7
  height: 64,
8
+ heightWithDetail: 96,
5
9
  width: 240
6
10
  };
7
11
  var FLOW_ZOOM = {
@@ -20,6 +24,7 @@ var REVEAL_MARGIN = 48;
20
24
  var ROW_GAP = 72;
21
25
  var COLUMN_GAP = 32;
22
26
  var portsOf = (node) => node.ports ?? [DEFAULT_PORT];
27
+ var nodeHeight = (node) => node.detail ? FLOW_NODE.heightWithDetail : FLOW_NODE.height;
23
28
  /** The exit an edge leaves by: its own, or its node's first. */
24
29
  var portOfEdge = (node, edge) => edge.port ?? portsOf(node)[0]?.id ?? "";
25
30
  var nodeById = (graph, id) => graph.nodes.find((node) => node.id === id);
@@ -36,7 +41,7 @@ function outputAnchor(node, portId) {
36
41
  if (index < 0) return;
37
42
  return {
38
43
  x: node.position.x + portX(index, ports.length),
39
- y: node.position.y + FLOW_NODE.height + PORT_DROP
44
+ y: node.position.y + nodeHeight(node) + PORT_DROP
40
45
  };
41
46
  }
42
47
  var inputAnchor = (node) => ({
@@ -152,7 +157,18 @@ function edgeEnds(graph, edge) {
152
157
  tone
153
158
  };
154
159
  }
155
- var isInside = (point, node) => point.x >= node.position.x && point.x <= node.position.x + FLOW_NODE.width && point.y >= node.position.y && point.y <= node.position.y + FLOW_NODE.height;
160
+ var isInside = (point, node) => point.x >= node.position.x && point.x <= node.position.x + FLOW_NODE.width && point.y >= node.position.y && point.y <= node.position.y + nodeHeight(node);
161
+ /** A run went this way: its node left by the edge's exit, and the node it leads to ran too. */
162
+ function isEdgeTaken(graph, edge) {
163
+ const source = nodeById(graph, edge.from);
164
+ const target = nodeById(graph, edge.to);
165
+ return source?.taken !== void 0 && source.taken === portOfEdge(source, edge) && target?.status !== void 0;
166
+ }
167
+ /** The position of a node centred on a point: where something dropped there lands. */
168
+ var centeredAt = (point) => snap({
169
+ x: point.x - FLOW_NODE.width / 2,
170
+ y: point.y - FLOW_NODE.height / 2
171
+ });
156
172
  /** The node under a point, other than `except`. */
157
173
  var nodeAt = (graph, point, except = "") => graph.nodes.find((node) => node.id !== except && isInside(point, node));
158
174
  var isPortFree = (graph, node, portId) => !graph.edges.some((edge) => edge.from === node.id && portOfEdge(node, edge) === portId);
@@ -188,8 +204,8 @@ var removeEdge = (graph, id) => ({
188
204
  ...graph,
189
205
  edges: graph.edges.filter((edge) => edge.id !== id)
190
206
  });
191
- var ROW = FLOW_NODE.height + ROW_GAP;
192
207
  var COLUMN = FLOW_NODE.width + COLUMN_GAP;
208
+ var rowOfFlow = (graph) => Math.max(FLOW_NODE.height, ...graph.nodes.map(nodeHeight)) + ROW_GAP;
193
209
  /** How far across a node hangs from its parent's exit: the exits share the row out, side by side, around it. */
194
210
  function portShift(node, portId) {
195
211
  const ports = portsOf(node);
@@ -204,7 +220,7 @@ function positionAfter(graph, from, portId) {
204
220
  };
205
221
  return snap({
206
222
  x: source.position.x + portShift(source, portId),
207
- y: source.position.y + ROW
223
+ y: source.position.y + nodeHeight(source) + ROW_GAP
208
224
  });
209
225
  }
210
226
  /** The edges that close a loop, found walking from the starts: the layout leaves them out, or a loop has no top. */
@@ -267,6 +283,7 @@ function layoutFlow(graph) {
267
283
  const loops = loopEdges(graph);
268
284
  const edges = graph.edges.filter((edge) => !loops.has(edge.id) && ids.has(edge.from) && ids.has(edge.to));
269
285
  const rows = rowsOf(graph, edges);
286
+ const row = rowOfFlow(graph);
270
287
  const placed = /* @__PURE__ */ new Map();
271
288
  for (const row of [...new Set(rows.values())].sort((a, b) => a - b)) placeRow(graph.nodes.filter((node) => rows.get(node.id) === row).map((node) => ({
272
289
  id: node.id,
@@ -278,7 +295,7 @@ function layoutFlow(graph) {
278
295
  ...node,
279
296
  position: snap({
280
297
  x: placed.get(node.id) ?? 0,
281
- y: (rows.get(node.id) ?? 0) * ROW
298
+ y: (rows.get(node.id) ?? 0) * row
282
299
  })
283
300
  }))
284
301
  };
@@ -309,7 +326,7 @@ function fitView(nodes, width, height) {
309
326
  const left = Math.min(...nodes.map((node) => node.position.x));
310
327
  const top = Math.min(...nodes.map((node) => node.position.y));
311
328
  const right = Math.max(...nodes.map((node) => node.position.x + FLOW_NODE.width));
312
- const bottom = Math.max(...nodes.map((node) => node.position.y + FLOW_NODE.height + PORT_DROP));
329
+ const bottom = Math.max(...nodes.map((node) => node.position.y + nodeHeight(node) + PORT_DROP));
313
330
  const zoom = clampZoom(Math.min(1, (width - 96) / (right - left), (height - 96) / (bottom - top)));
314
331
  return {
315
332
  x: width / 2 - (left + right) / 2 * zoom,
@@ -329,8 +346,8 @@ function revealIn(viewport, node, width, height) {
329
346
  const top = viewport.y + node.position.y * zoom;
330
347
  return {
331
348
  x: viewport.x + shiftInto(left, left + FLOW_NODE.width * zoom, width),
332
- y: viewport.y + shiftInto(top, top + (FLOW_NODE.height + PORT_DROP) * zoom, height),
349
+ y: viewport.y + shiftInto(top, top + (nodeHeight(node) + PORT_DROP) * zoom, height),
333
350
  zoom
334
351
  };
335
352
  }
336
- export { DEFAULT_PORT, FLOW_NODE, FLOW_ZOOM, connect, edgeEnds, edgeMidpoint, edgePath, edgeRoute, fitView, inputAnchor, isPortFree, layoutFlow, moveNode, nodeAt, outputAnchor, portOfEdge, portX, portsOf, positionAfter, removeEdge, removeNode, revealIn, roundedPath, snap, toWorld, zoomAt };
353
+ export { DEFAULT_PORT, FLOW_NODE, FLOW_ZOOM, centeredAt, connect, edgeEnds, edgeMidpoint, edgePath, edgeRoute, fitView, inputAnchor, isEdgeTaken, isPortFree, layoutFlow, moveNode, nodeAt, nodeHeight, outputAnchor, portOfEdge, portX, portsOf, positionAfter, removeEdge, removeNode, revealIn, roundedPath, snap, toWorld, zoomAt };
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * Gives the document the design tokens, once. They live in the `jlib` cascade layer, so the page overrides any of
3
- * them with a plain rule: `:root { --jl-accent: #7c3aed; }`.
3
+ * them with a plain rule: `:root { --jl-accent: #7c3aed; }`. Where the document takes no constructed stylesheet, a
4
+ * style element in its head holds them instead.
4
5
  */
5
6
  export declare function adoptTokens(): void;
@@ -1,15 +1,24 @@
1
1
  import { TOKEN_STYLES } from "../styles/shared.js";
2
2
  // src/lib/tokens.ts
3
- var adopted = { sheet: null };
3
+ var adopted = { done: false };
4
+ /** Whether the document takes constructed stylesheets: jsdom, which runs many apps' unit tests, does not. */
5
+ var takesSheets = () => typeof CSSStyleSheet !== "undefined" && typeof CSSStyleSheet.prototype.replaceSync === "function" && Array.isArray(document.adoptedStyleSheets);
4
6
  /**
5
7
  * Gives the document the design tokens, once. They live in the `jlib` cascade layer, so the page overrides any of
6
- * them with a plain rule: `:root { --jl-accent: #7c3aed; }`.
8
+ * them with a plain rule: `:root { --jl-accent: #7c3aed; }`. Where the document takes no constructed stylesheet, a
9
+ * style element in its head holds them instead.
7
10
  */
8
11
  function adoptTokens() {
9
- if (adopted.sheet !== null || typeof document === "undefined") return;
12
+ if (adopted.done || typeof document === "undefined") return;
13
+ adopted.done = true;
14
+ if (!takesSheets()) {
15
+ const style = document.createElement("style");
16
+ style.textContent = TOKEN_STYLES;
17
+ document.head.append(style);
18
+ return;
19
+ }
10
20
  const sheet = new CSSStyleSheet();
11
21
  sheet.replaceSync(TOKEN_STYLES);
12
22
  document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
13
- adopted.sheet = sheet;
14
23
  }
15
24
  export { adoptTokens };
@@ -1,3 +1,3 @@
1
1
  // src/molecules/jl-accordion-item.css?raw
2
- var jl_accordion_item_default = ":host {\n display: block;\n border-bottom: 1px solid var(--jl-line);\n /* Lets the content's height animate from 0 to auto. */\n interpolate-size: allow-keywords;\n --pad-x: 0px;\n}\n\nsummary {\n display: flex;\n align-items: center;\n gap: 10px;\n min-height: 48px;\n padding: 12px var(--pad-x);\n border-radius: var(--jl-radius-xs);\n color: var(--jl-text);\n font-size: var(--jl-font-md);\n font-weight: 600;\n line-height: 1.4;\n list-style: none;\n cursor: pointer;\n user-select: none;\n}\n\nsummary::-webkit-details-marker {\n display: none;\n}\n\n.heading {\n flex: 1;\n min-width: 0;\n}\n\n.icon {\n color: var(--jl-muted);\n}\n\n.chevron {\n color: var(--jl-faint);\n transition: rotate var(--jl-slow) var(--jl-ease);\n}\n\nsummary:hover .chevron {\n color: var(--jl-text);\n}\n\ndetails[open] .chevron {\n rotate: 180deg;\n}\n\nsummary:focus-visible {\n box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--jl-brand) 60%, transparent);\n}\n\n/*\n * The content box is a pseudo-element BASE_STYLES' \"* { box-sizing: inherit }\" does not reach: without this, what\n * it holds (and the slotted fields, which inherit through it) would fall back to content-box.\n */\ndetails::details-content {\n box-sizing: inherit;\n block-size: 0;\n overflow: clip;\n transition:\n block-size var(--jl-slow) var(--jl-ease),\n content-visibility var(--jl-slow) allow-discrete;\n}\n\ndetails[open]::details-content {\n block-size: auto;\n}\n\n.content {\n padding: 0 var(--pad-x) 16px;\n color: var(--jl-text-2);\n font-size: var(--jl-font-md);\n line-height: 1.6;\n}\n\n:host([disabled]) summary {\n color: var(--jl-faint);\n cursor: not-allowed;\n}\n\n:host([disabled]) summary:hover .chevron {\n color: var(--jl-faint);\n}\n";
2
+ var jl_accordion_item_default = ":host {\n display: block;\n border-bottom: 1px solid var(--jl-line);\n /* Lets the content's height animate from 0 to auto. */\n interpolate-size: allow-keywords;\n --pad-x: 0px;\n}\n\nsummary {\n display: flex;\n align-items: center;\n gap: 10px;\n min-height: 48px;\n padding: 12px var(--pad-x);\n border-radius: var(--jl-radius-xs);\n color: var(--jl-text);\n font-size: var(--jl-font-md);\n font-weight: 600;\n line-height: 1.4;\n list-style: none;\n cursor: pointer;\n user-select: none;\n}\n\nsummary::-webkit-details-marker {\n display: none;\n}\n\n.titles {\n display: grid;\n flex: 1;\n min-width: 0;\n}\n\n.description {\n overflow: hidden;\n color: var(--jl-muted);\n font-size: var(--jl-font-sm);\n font-weight: 400;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.icon {\n color: var(--jl-muted);\n}\n\n.chevron {\n color: var(--jl-faint);\n transition: rotate var(--jl-slow) var(--jl-ease);\n}\n\nsummary:hover .chevron {\n color: var(--jl-text);\n}\n\ndetails[open] .chevron {\n rotate: 180deg;\n}\n\nsummary:focus-visible {\n box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--jl-brand) 60%, transparent);\n}\n\n/*\n * The content box is a pseudo-element BASE_STYLES' \"* { box-sizing: inherit }\" does not reach: without this, what\n * it holds (and the slotted fields, which inherit through it) would fall back to content-box.\n */\ndetails::details-content {\n box-sizing: inherit;\n block-size: 0;\n overflow: clip;\n transition:\n block-size var(--jl-slow) var(--jl-ease),\n content-visibility var(--jl-slow) allow-discrete;\n}\n\ndetails[open]::details-content {\n block-size: auto;\n}\n\n.content {\n padding: 0 var(--pad-x) 16px;\n color: var(--jl-text-2);\n font-size: var(--jl-font-md);\n line-height: 1.6;\n}\n\n:host([disabled]) summary {\n color: var(--jl-faint);\n cursor: not-allowed;\n}\n\n:host([disabled]) summary:hover .chevron {\n color: var(--jl-faint);\n}\n";
3
3
  export { jl_accordion_item_default as default };
@@ -12,11 +12,16 @@ export declare class JlAccordionItem extends JlElement {
12
12
  get(): string;
13
13
  set(setter: string | ((prevState: string) => string)): void;
14
14
  }>>;
15
+ /** A quieter line under the heading, shown open or closed: what the section holds ("3 fields", "2 pages"). */
16
+ readonly description: Readonly<Readonly<{
17
+ get(): string;
18
+ set(setter: string | ((prevState: string) => string)): void;
19
+ }>>;
15
20
  readonly open: Readonly<Readonly<{
16
21
  get(): boolean;
17
22
  set(setter: boolean | ((prevState: boolean) => boolean)): void;
18
23
  }>>;
19
- /** An icon before the heading. */
24
+ /** An icon before the heading; the `icon` slot takes a richer one instead. */
20
25
  readonly icon: Readonly<Readonly<{
21
26
  get(): string;
22
27
  set(setter: string | ((prevState: string) => string)): void;
@@ -19,8 +19,13 @@ var JlAccordionItem = class extends JlElement {
19
19
  heading = this.useChange("", (value) => {
20
20
  this._refs.heading.textContent = value;
21
21
  }, { attribute: "heading" });
22
+ /** A quieter line under the heading, shown open or closed: what the section holds ("3 fields", "2 pages"). */
23
+ description = this.useChange("", (value) => {
24
+ this._refs.description.textContent = value;
25
+ this._refs.description.hidden = value === "";
26
+ }, { attribute: "description" });
22
27
  open = this.useChange(false, (value) => this.renderOpen(value), { attribute: "open" });
23
- /** An icon before the heading. */
28
+ /** An icon before the heading; the `icon` slot takes a richer one instead. */
24
29
  icon = this.useChange("", (name) => {
25
30
  this._refs.icon.name.set(name);
26
31
  this._refs.icon.hidden = name === "";
@@ -32,6 +37,7 @@ var JlAccordionItem = class extends JlElement {
32
37
  this._refs.summary.tabIndex = value ? -1 : 0;
33
38
  }, { attribute: "disabled" });
34
39
  _refs = this.useRefs((ref) => ({
40
+ description: ref(".description"),
35
41
  details: ref("details"),
36
42
  heading: ref(".heading"),
37
43
  icon: ref(".icon"),
@@ -41,12 +47,21 @@ var JlAccordionItem = class extends JlElement {
41
47
  return /* @__PURE__ */ jsxs("details", {
42
48
  attrs: { part: "base" },
43
49
  children: [/* @__PURE__ */ jsxs("summary", { children: [
44
- /* @__PURE__ */ jsx(JlIcon, {
45
- class: "icon",
46
- size: ICON_SIZE,
47
- hidden: true
50
+ /* @__PURE__ */ jsx("slot", {
51
+ name: "icon",
52
+ children: /* @__PURE__ */ jsx(JlIcon, {
53
+ class: "icon",
54
+ size: ICON_SIZE,
55
+ hidden: true
56
+ })
57
+ }),
58
+ /* @__PURE__ */ jsxs("span", {
59
+ class: "titles",
60
+ children: [/* @__PURE__ */ jsx("span", { class: "heading" }), /* @__PURE__ */ jsx("span", {
61
+ class: "description",
62
+ hidden: true
63
+ })]
48
64
  }),
49
- /* @__PURE__ */ jsx("span", { class: "heading" }),
50
65
  /* @__PURE__ */ jsx(JlIcon, {
51
66
  class: "chevron",
52
67
  name: "chevron-down",
@@ -1,8 +1,9 @@
1
1
  import { JlElement } from '../base/jl-element';
2
2
  import type { Tone } from '../types/options.types';
3
3
  /**
4
- * A message in the flow of the page, coloured by its tone: a tip, a success, a warning, an error. The text goes
5
- * in the default slot, buttons in `slot="actions"`. A `dismissible` alert has an × that hides it.
4
+ * A message in the flow of the page, coloured by its tone: a tip, a success, a warning, an error. The text is the
5
+ * `description` or goes in the default slot (for links and markup), buttons in `slot="actions"`. A `dismissible`
6
+ * alert has an × that hides it.
6
7
  */
7
8
  export declare class JlAlert extends JlElement {
8
9
  static readonly selector = "jl-alert";
@@ -17,6 +18,11 @@ export declare class JlAlert extends JlElement {
17
18
  get(): string;
18
19
  set(setter: string | ((prevState: string) => string)): void;
19
20
  }>>;
21
+ /** The message as plain text, for pages that set it from code; richer content goes in the default slot. */
22
+ readonly description: Readonly<Readonly<{
23
+ get(): string;
24
+ set(setter: string | ((prevState: string) => string)): void;
25
+ }>>;
20
26
  /** An icon name in place of the tone's own; `none` shows no icon. */
21
27
  readonly icon: Readonly<Readonly<{
22
28
  get(): string;
@@ -33,7 +39,8 @@ export declare class JlAlert extends JlElement {
33
39
  templateHtml(): Node;
34
40
  templateCss(): string;
35
41
  onConnect(): void;
36
- private renderActions;
42
+ /** Empty parts take no room: an alert with only a description has no blank line under it. */
43
+ private renderSlots;
37
44
  /** The tone's own icon is filled, as a toast's is; an icon of the page's stays as it is drawn. */
38
45
  private renderIcon;
39
46
  }
@@ -31,8 +31,9 @@ var TONE_ROLES = {
31
31
  warn: "alert"
32
32
  };
33
33
  /**
34
- * A message in the flow of the page, coloured by its tone: a tip, a success, a warning, an error. The text goes
35
- * in the default slot, buttons in `slot="actions"`. A `dismissible` alert has an × that hides it.
34
+ * A message in the flow of the page, coloured by its tone: a tip, a success, a warning, an error. The text is the
35
+ * `description` or goes in the default slot (for links and markup), buttons in `slot="actions"`. A `dismissible`
36
+ * alert has an × that hides it.
36
37
  */
37
38
  var JlAlert = class extends JlElement {
38
39
  static selector = "jl-alert";
@@ -49,6 +50,11 @@ var JlAlert = class extends JlElement {
49
50
  this._refs.heading.textContent = value;
50
51
  this._refs.heading.hidden = value === "";
51
52
  }, { attribute: "heading" });
53
+ /** The message as plain text, for pages that set it from code; richer content goes in the default slot. */
54
+ description = this.useChange("", (value) => {
55
+ this._refs.description.textContent = value;
56
+ this._refs.description.hidden = value === "";
57
+ }, { attribute: "description" });
52
58
  /** An icon name in place of the tone's own; `none` shows no icon. */
53
59
  icon = this.useChange("", () => this.renderIcon(), { attribute: "icon" });
54
60
  /** Shows an × that hides the alert and fires `dismiss`. */
@@ -61,8 +67,11 @@ var JlAlert = class extends JlElement {
61
67
  actionsSlot: ref("slot[name=\"actions\"]"),
62
68
  alert: ref(".alert"),
63
69
  close: ref(".close"),
70
+ description: ref(".description"),
64
71
  heading: ref(".heading"),
65
- icon: ref(".icon")
72
+ icon: ref(".icon"),
73
+ text: ref(".text"),
74
+ textSlot: ref("slot:not([name])")
66
75
  }));
67
76
  /** Hides the alert as its × does, and fires `dismiss`. */
68
77
  dismiss() {
@@ -86,8 +95,13 @@ var JlAlert = class extends JlElement {
86
95
  class: "heading",
87
96
  hidden: true
88
97
  }),
98
+ /* @__PURE__ */ jsx("p", {
99
+ class: "description",
100
+ hidden: true
101
+ }),
89
102
  /* @__PURE__ */ jsx("div", {
90
103
  class: "text",
104
+ hidden: true,
91
105
  children: /* @__PURE__ */ jsx("slot", {})
92
106
  }),
93
107
  /* @__PURE__ */ jsx("div", {
@@ -113,10 +127,13 @@ var JlAlert = class extends JlElement {
113
127
  }
114
128
  onConnect() {
115
129
  this._refs.close.events.register("press", () => this.dismiss());
116
- this.renderActions();
117
- this.on(this._refs.actionsSlot, "slotchange", () => this.renderActions());
130
+ this.renderSlots();
131
+ this.on(this._refs.actionsSlot, "slotchange", () => this.renderSlots());
132
+ this.on(this._refs.textSlot, "slotchange", () => this.renderSlots());
118
133
  }
119
- renderActions() {
134
+ /** Empty parts take no room: an alert with only a description has no blank line under it. */
135
+ renderSlots() {
136
+ this._refs.text.hidden = !hasSlotted(this._refs.textSlot);
120
137
  this._refs.actions.hidden = !hasSlotted(this._refs.actionsSlot);
121
138
  }
122
139
  /** The tone's own icon is filled, as a toast's is; an icon of the page's stays as it is drawn. */
@@ -0,0 +1,3 @@
1
+ // src/molecules/jl-chip-group.css?raw
2
+ var jl_chip_group_default = ":host {\n display: block;\n min-width: 0;\n --height: 28px;\n --pad: 10px;\n --font-size: var(--jl-font-sm);\n}\n\n:host([size=\"sm\"]) {\n --height: 24px;\n --pad: 8px;\n --font-size: var(--jl-font-xs);\n}\n\n/* Where a thumb presses it. */\n:host([size=\"lg\"]) {\n --height: 36px;\n --pad: 14px;\n --font-size: var(--jl-font-md);\n}\n\n/*\n * One row that scrolls sideways on a narrow screen. The padding keeps the focus ring inside what the scroller\n * clips; the negative margin keeps the chips aligned with the content around.\n */\n.group {\n display: flex;\n gap: 8px;\n min-width: 0;\n margin: -3px;\n padding: 3px;\n overflow-x: auto;\n scrollbar-width: none;\n contain: inline-size;\n}\n\n.group::-webkit-scrollbar {\n display: none;\n}\n\n:host([wrap]) .group {\n flex-wrap: wrap;\n overflow: visible;\n contain: none;\n}\n\n.chip {\n display: inline-flex;\n flex: none;\n align-items: center;\n gap: 6px;\n max-width: 100%;\n height: var(--height);\n padding: 0 var(--pad);\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 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.chip:hover:not(:disabled) {\n background: var(--jl-surface-2);\n}\n\n.chip.selected,\n.chip.selected:hover:not(:disabled) {\n border-color: var(--jl-accent-line);\n background: var(--jl-accent-soft);\n color: var(--jl-accent);\n}\n\n.chip:disabled {\n cursor: not-allowed;\n opacity: 0.5;\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.badge {\n min-width: 18px;\n margin-right: -4px;\n padding: 0 6px;\n border-radius: var(--jl-radius-pill);\n background: var(--jl-surface-3);\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n font-variant-numeric: tabular-nums;\n font-weight: 650;\n line-height: 18px;\n text-align: center;\n}\n\n.chip.selected .badge {\n background: color-mix(in srgb, var(--jl-accent) 14%, transparent);\n color: var(--jl-accent);\n}\n";
3
+ export { jl_chip_group_default as default };
@@ -0,0 +1,81 @@
1
+ import { JlFormControl } from '../base/jl-form-control';
2
+ import type { ChipGroupOption } from '../types/jl-chip-group.types';
3
+ import type { ControlSize } from '../types/options.types';
4
+ /**
5
+ * A row of chips to choose from: a status to filter by, a category, a reason. One choice by default, as radios
6
+ * (choosing the chosen chip again keeps it); `multiple` lets people turn each chip on and off. On a narrow screen
7
+ * the row scrolls sideways; `wrap` lets it go to the next line instead. `change` fires with the chosen values.
8
+ */
9
+ export declare class JlChipGroup extends JlFormControl {
10
+ static readonly selector = "jl-chip-group";
11
+ readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
12
+ change: string[];
13
+ }>>;
14
+ readonly options: Readonly<Readonly<{
15
+ get(): ChipGroupOption[];
16
+ set(setter: ChipGroupOption[] | ((prevState: ChipGroupOption[]) => ChipGroupOption[])): void;
17
+ }>>;
18
+ /** The chosen values, in the options' order; with one choice, a list of at most one. */
19
+ readonly value: Readonly<Readonly<{
20
+ get(): string[];
21
+ set(setter: string[] | ((prevState: string[]) => string[])): void;
22
+ }>>;
23
+ /** Several chips can be chosen, each turned on and off on its own. */
24
+ readonly multiple: Readonly<Readonly<{
25
+ get(): boolean;
26
+ set(setter: boolean | ((prevState: boolean) => boolean)): void;
27
+ }>>;
28
+ /** Goes to the next line when the chips do not fit, instead of scrolling sideways. */
29
+ readonly wrap: Readonly<Readonly<{
30
+ get(): boolean;
31
+ set(setter: boolean | ((prevState: boolean) => boolean)): void;
32
+ }>>;
33
+ readonly size: Readonly<Readonly<{
34
+ get(): ControlSize;
35
+ set(setter: ((prevState: ControlSize) => ControlSize) | ControlSize): void;
36
+ }>>;
37
+ /** The group's name for screen readers, when no visible label names it. */
38
+ readonly label: Readonly<Readonly<{
39
+ get(): string;
40
+ set(setter: string | ((prevState: string) => string)): void;
41
+ }>>;
42
+ /** A form refuses to submit while nothing is chosen. */
43
+ readonly required: Readonly<Readonly<{
44
+ get(): boolean;
45
+ set(setter: boolean | ((prevState: boolean) => boolean)): void;
46
+ }>>;
47
+ readonly disabled: Readonly<Readonly<{
48
+ get(): boolean;
49
+ set(setter: boolean | ((prevState: boolean) => boolean)): void;
50
+ }>>;
51
+ /** The chip that holds the Tab stop with several choices: the last one people reached. */
52
+ private readonly _state;
53
+ private readonly _nameWatch;
54
+ private readonly _refs;
55
+ /** Focuses the chip that holds the Tab stop. */
56
+ focus(options?: FocusOptions): void;
57
+ templateHtml(): Node;
58
+ templateCss(): string;
59
+ onConnect(): void;
60
+ onDisconnect(): void;
61
+ protected resetValue(): void;
62
+ /** The browser calls it for a disabled fieldset and for the element's own `disabled`, even before it renders. */
63
+ protected setDisabledByForm(disabled: boolean): void;
64
+ private chips;
65
+ /** What is chosen: with one choice, only the first value counts. */
66
+ private chosen;
67
+ private isDisabled;
68
+ /** One choice: the chip becomes it. Several: the chip turns on or off, the values keeping the options' order. */
69
+ private press;
70
+ private commit;
71
+ private onKeydown;
72
+ private renderOptions;
73
+ private chip;
74
+ /** Radios with one choice, toggle buttons with several; each chosen chip shows its icon or a check. */
75
+ private renderValue;
76
+ /** Tab reaches one chip: the chosen one with one choice, the last one reached with several, else the first. */
77
+ private renderTabStop;
78
+ /** One choice gives one entry; several give one entry each under the field's name, as checkboxes would. */
79
+ private renderFormValue;
80
+ private renderValidity;
81
+ }