@syware/jlib 0.1.3 → 0.2.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.
- package/README.md +2 -17
- package/dist/all.js +4 -3
- package/dist/atoms/jl-icon.css_raw.js +1 -1
- package/dist/atoms/jl-icon.d.ts +7 -1
- package/dist/atoms/jl-icon.js +4 -1
- package/dist/atoms/jl-progress-ring.js +1 -6
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -2
- package/dist/lib/calendar.d.ts +15 -1
- package/dist/lib/calendar.js +72 -1
- package/dist/lib/dom.d.ts +2 -0
- package/dist/lib/dom.js +8 -1
- package/dist/lib/flow.d.ts +61 -0
- package/dist/lib/flow.js +336 -0
- package/dist/lib/options.d.ts +5 -0
- package/dist/lib/options.js +13 -1
- package/dist/molecules/jl-alert.d.ts +1 -0
- package/dist/molecules/jl-alert.js +3 -1
- package/dist/molecules/jl-date-picker.d.ts +10 -4
- package/dist/molecules/jl-date-picker.js +21 -8
- package/dist/molecules/jl-steps.css_raw.js +1 -1
- package/dist/molecules/jl-timeline.css_raw.js +1 -1
- package/dist/molecules/jl-timeline.d.ts +2 -1
- package/dist/molecules/jl-timeline.js +18 -16
- package/dist/molecules/jl-toast.css_raw.js +1 -1
- package/dist/molecules/jl-toast.d.ts +1 -0
- package/dist/molecules/jl-toast.js +3 -1
- package/dist/organisms/jl-flow.css_raw.js +3 -0
- package/dist/organisms/jl-flow.d.ts +86 -0
- package/dist/organisms/jl-flow.js +650 -0
- package/dist/types/calendar.types.d.ts +2 -0
- package/dist/types/jl-flow.types.d.ts +77 -0
- package/package.json +1 -1
package/dist/lib/flow.js
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// src/lib/flow.ts
|
|
2
|
+
/** A node's box: fixed, so that every anchor and edge is computed without measuring the page. */
|
|
3
|
+
var FLOW_NODE = {
|
|
4
|
+
height: 64,
|
|
5
|
+
width: 240
|
|
6
|
+
};
|
|
7
|
+
var FLOW_ZOOM = {
|
|
8
|
+
max: 2,
|
|
9
|
+
min: .25,
|
|
10
|
+
step: 1.2
|
|
11
|
+
};
|
|
12
|
+
/** The exit of a node that names none. */
|
|
13
|
+
var DEFAULT_PORT = { id: "out" };
|
|
14
|
+
var PORT_DROP = 12;
|
|
15
|
+
var GRID = 8;
|
|
16
|
+
var STUB = 24;
|
|
17
|
+
var CORNER_RADIUS = 12;
|
|
18
|
+
var FIT_PADDING = 48;
|
|
19
|
+
var REVEAL_MARGIN = 48;
|
|
20
|
+
var ROW_GAP = 72;
|
|
21
|
+
var COLUMN_GAP = 32;
|
|
22
|
+
var portsOf = (node) => node.ports ?? [DEFAULT_PORT];
|
|
23
|
+
/** The exit an edge leaves by: its own, or its node's first. */
|
|
24
|
+
var portOfEdge = (node, edge) => edge.port ?? portsOf(node)[0]?.id ?? "";
|
|
25
|
+
var nodeById = (graph, id) => graph.nodes.find((node) => node.id === id);
|
|
26
|
+
var toGrid = (value) => Math.round(value / GRID) * GRID + 0;
|
|
27
|
+
var snap = (point) => ({
|
|
28
|
+
x: toGrid(point.x),
|
|
29
|
+
y: toGrid(point.y)
|
|
30
|
+
});
|
|
31
|
+
/** 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. */
|
|
32
|
+
var portX = (index, count) => FLOW_NODE.width * (index + .5) / count;
|
|
33
|
+
function outputAnchor(node, portId) {
|
|
34
|
+
const ports = portsOf(node);
|
|
35
|
+
const index = ports.findIndex((port) => port.id === portId);
|
|
36
|
+
if (index < 0) return;
|
|
37
|
+
return {
|
|
38
|
+
x: node.position.x + portX(index, ports.length),
|
|
39
|
+
y: node.position.y + FLOW_NODE.height + PORT_DROP
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
var inputAnchor = (node) => ({
|
|
43
|
+
x: node.position.x + FLOW_NODE.width / 2,
|
|
44
|
+
y: node.position.y
|
|
45
|
+
});
|
|
46
|
+
var distance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
|
|
47
|
+
/** The point `length` away from `from`, towards `to`. */
|
|
48
|
+
function towards(from, to, length) {
|
|
49
|
+
const span = distance(from, to);
|
|
50
|
+
const ratio = span === 0 ? 0 : length / span;
|
|
51
|
+
return {
|
|
52
|
+
x: from.x + (to.x - from.x) * ratio,
|
|
53
|
+
y: from.y + (to.y - from.y) * ratio
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The corners an edge goes through, in straight runs: down from the exit, across halfway, down into the node. An
|
|
58
|
+
* edge that goes back up turns around the side, clear of the nodes.
|
|
59
|
+
*/
|
|
60
|
+
function edgeRoute(from, to) {
|
|
61
|
+
const forward = to.y - from.y >= 48;
|
|
62
|
+
if (forward && to.x === from.x) return [from, to];
|
|
63
|
+
if (forward) {
|
|
64
|
+
const middle = (from.y + to.y) / 2;
|
|
65
|
+
return [
|
|
66
|
+
from,
|
|
67
|
+
{
|
|
68
|
+
x: from.x,
|
|
69
|
+
y: middle
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
x: to.x,
|
|
73
|
+
y: middle
|
|
74
|
+
},
|
|
75
|
+
to
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
const below = from.y + STUB;
|
|
79
|
+
const above = to.y - STUB;
|
|
80
|
+
const side = Math.abs(to.x - from.x) > FLOW_NODE.width ? (from.x + to.x) / 2 : Math.max(from.x, to.x) + FLOW_NODE.width;
|
|
81
|
+
return [
|
|
82
|
+
from,
|
|
83
|
+
{
|
|
84
|
+
x: from.x,
|
|
85
|
+
y: below
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
x: side,
|
|
89
|
+
y: below
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
x: side,
|
|
93
|
+
y: above
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
x: to.x,
|
|
97
|
+
y: above
|
|
98
|
+
},
|
|
99
|
+
to
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
/** An SVG path through the points, its corners rounded. */
|
|
103
|
+
function roundedPath(points, radius = CORNER_RADIUS) {
|
|
104
|
+
const first = points[0];
|
|
105
|
+
const last = points.at(-1);
|
|
106
|
+
if (!first || !last) return "";
|
|
107
|
+
const corners = points.slice(1, -1).map((corner, index) => {
|
|
108
|
+
const before = points[index] ?? corner;
|
|
109
|
+
const after = points[index + 2] ?? corner;
|
|
110
|
+
const round = Math.min(radius, distance(before, corner) / 2, distance(corner, after) / 2);
|
|
111
|
+
const enter = towards(corner, before, round);
|
|
112
|
+
const leave = towards(corner, after, round);
|
|
113
|
+
return `L ${enter.x} ${enter.y} Q ${corner.x} ${corner.y} ${leave.x} ${leave.y}`;
|
|
114
|
+
});
|
|
115
|
+
return [
|
|
116
|
+
`M ${first.x} ${first.y}`,
|
|
117
|
+
...corners,
|
|
118
|
+
`L ${last.x} ${last.y}`
|
|
119
|
+
].join(" ");
|
|
120
|
+
}
|
|
121
|
+
/** The point halfway along a line through the points. */
|
|
122
|
+
function halfway(points) {
|
|
123
|
+
const segments = points.slice(1).map((end, index) => ({
|
|
124
|
+
end,
|
|
125
|
+
start: points[index] ?? end
|
|
126
|
+
}));
|
|
127
|
+
const walk = { left: segments.reduce((sum, { end, start }) => sum + distance(start, end), 0) / 2 };
|
|
128
|
+
for (const { end, start } of segments) {
|
|
129
|
+
const length = distance(start, end);
|
|
130
|
+
if (walk.left <= length) return towards(start, end, walk.left);
|
|
131
|
+
walk.left -= length;
|
|
132
|
+
}
|
|
133
|
+
return points.at(-1) ?? {
|
|
134
|
+
x: 0,
|
|
135
|
+
y: 0
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
var edgePath = (from, to) => roundedPath(edgeRoute(from, to));
|
|
139
|
+
/** The point halfway along `edgePath(from, to)`. */
|
|
140
|
+
var edgeMidpoint = (from, to) => halfway(edgeRoute(from, to));
|
|
141
|
+
/** Where an edge starts and ends, or nothing when one of its ends is gone. */
|
|
142
|
+
function edgeEnds(graph, edge) {
|
|
143
|
+
const source = nodeById(graph, edge.from);
|
|
144
|
+
const target = nodeById(graph, edge.to);
|
|
145
|
+
const portId = source ? portOfEdge(source, edge) : "";
|
|
146
|
+
const from = source ? outputAnchor(source, portId) : void 0;
|
|
147
|
+
if (!source || !target || !from) return;
|
|
148
|
+
const tone = portsOf(source).find((port) => port.id === portId)?.tone ?? "neutral";
|
|
149
|
+
return {
|
|
150
|
+
from,
|
|
151
|
+
to: inputAnchor(target),
|
|
152
|
+
tone
|
|
153
|
+
};
|
|
154
|
+
}
|
|
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;
|
|
156
|
+
/** The node under a point, other than `except`. */
|
|
157
|
+
var nodeAt = (graph, point, except = "") => graph.nodes.find((node) => node.id !== except && isInside(point, node));
|
|
158
|
+
var isPortFree = (graph, node, portId) => !graph.edges.some((edge) => edge.from === node.id && portOfEdge(node, edge) === portId);
|
|
159
|
+
/**
|
|
160
|
+
* The graph with one more edge, or nothing when the edge cannot be: a node does not connect to itself, a start takes
|
|
161
|
+
* no incoming edge, and an exit joins a node once.
|
|
162
|
+
*/
|
|
163
|
+
function connect(graph, edge) {
|
|
164
|
+
const source = nodeById(graph, edge.from);
|
|
165
|
+
const target = nodeById(graph, edge.to);
|
|
166
|
+
if (!source || !target || source.id === target.id || target.start === true) return;
|
|
167
|
+
const port = portOfEdge(source, edge);
|
|
168
|
+
const known = portsOf(source).some((candidate) => candidate.id === port);
|
|
169
|
+
const exists = graph.edges.some((candidate) => candidate.from === source.id && candidate.to === target.id && portOfEdge(source, candidate) === port);
|
|
170
|
+
return known && !exists ? {
|
|
171
|
+
...graph,
|
|
172
|
+
edges: [...graph.edges, edge]
|
|
173
|
+
} : void 0;
|
|
174
|
+
}
|
|
175
|
+
var moveNode = (graph, id, position) => ({
|
|
176
|
+
...graph,
|
|
177
|
+
nodes: graph.nodes.map((node) => node.id === id ? {
|
|
178
|
+
...node,
|
|
179
|
+
position
|
|
180
|
+
} : node)
|
|
181
|
+
});
|
|
182
|
+
/** The graph without the node, and without its edges. */
|
|
183
|
+
var removeNode = (graph, id) => ({
|
|
184
|
+
edges: graph.edges.filter((edge) => edge.from !== id && edge.to !== id),
|
|
185
|
+
nodes: graph.nodes.filter((node) => node.id !== id)
|
|
186
|
+
});
|
|
187
|
+
var removeEdge = (graph, id) => ({
|
|
188
|
+
...graph,
|
|
189
|
+
edges: graph.edges.filter((edge) => edge.id !== id)
|
|
190
|
+
});
|
|
191
|
+
var ROW = FLOW_NODE.height + ROW_GAP;
|
|
192
|
+
var COLUMN = FLOW_NODE.width + COLUMN_GAP;
|
|
193
|
+
/** How far across a node hangs from its parent's exit: the exits share the row out, side by side, around it. */
|
|
194
|
+
function portShift(node, portId) {
|
|
195
|
+
const ports = portsOf(node);
|
|
196
|
+
return (Math.max(0, ports.findIndex((port) => port.id === portId)) - (ports.length - 1) / 2) * COLUMN;
|
|
197
|
+
}
|
|
198
|
+
/** Where a node added after an exit goes: a row below, under its exit, clear of the nodes after the other exits. */
|
|
199
|
+
function positionAfter(graph, from, portId) {
|
|
200
|
+
const source = nodeById(graph, from);
|
|
201
|
+
if (!source) return {
|
|
202
|
+
x: 0,
|
|
203
|
+
y: 0
|
|
204
|
+
};
|
|
205
|
+
return snap({
|
|
206
|
+
x: source.position.x + portShift(source, portId),
|
|
207
|
+
y: source.position.y + ROW
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/** The edges that close a loop, found walking from the starts: the layout leaves them out, or a loop has no top. */
|
|
211
|
+
function loopEdges(graph) {
|
|
212
|
+
const loops = /* @__PURE__ */ new Set();
|
|
213
|
+
const done = /* @__PURE__ */ new Set();
|
|
214
|
+
const path = /* @__PURE__ */ new Set();
|
|
215
|
+
const visit = (id) => {
|
|
216
|
+
if (done.has(id)) return;
|
|
217
|
+
path.add(id);
|
|
218
|
+
for (const edge of graph.edges.filter((candidate) => candidate.from === id)) {
|
|
219
|
+
if (path.has(edge.to)) {
|
|
220
|
+
loops.add(edge.id);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
visit(edge.to);
|
|
224
|
+
}
|
|
225
|
+
path.delete(id);
|
|
226
|
+
done.add(id);
|
|
227
|
+
};
|
|
228
|
+
const starts = graph.nodes.filter((node) => node.start === true);
|
|
229
|
+
for (const node of [...starts, ...graph.nodes]) visit(node.id);
|
|
230
|
+
return loops;
|
|
231
|
+
}
|
|
232
|
+
/** Each node's row: one below the lowest node leading to it, so the flow reads from top to bottom. */
|
|
233
|
+
function rowsOf(graph, edges) {
|
|
234
|
+
const rows = new Map(graph.nodes.map((node) => [node.id, 0]));
|
|
235
|
+
const rowOf = (id) => rows.get(id) ?? 0;
|
|
236
|
+
for (const _round of graph.nodes) {
|
|
237
|
+
const deeper = edges.filter((edge) => rowOf(edge.to) <= rowOf(edge.from));
|
|
238
|
+
if (deeper.length === 0) break;
|
|
239
|
+
for (const edge of deeper) rows.set(edge.to, Math.max(rowOf(edge.to), rowOf(edge.from) + 1));
|
|
240
|
+
}
|
|
241
|
+
return rows;
|
|
242
|
+
}
|
|
243
|
+
/** Where a node wants to be across: under its parents, each by the exit it hangs from. */
|
|
244
|
+
function wantedX(graph, edges, id, placed) {
|
|
245
|
+
const parents = edges.flatMap((edge) => {
|
|
246
|
+
const source = nodeById(graph, edge.from);
|
|
247
|
+
const x = placed.get(edge.from);
|
|
248
|
+
return edge.to === id && source && x !== void 0 ? [x + portShift(source, portOfEdge(source, edge))] : [];
|
|
249
|
+
});
|
|
250
|
+
return parents.length === 0 ? 0 : parents.reduce((sum, x) => sum + x, 0) / parents.length;
|
|
251
|
+
}
|
|
252
|
+
/** A row's nodes where they want to be, pushed apart where they would overlap, the row kept centred on them. */
|
|
253
|
+
function placeRow(wanted, placed) {
|
|
254
|
+
const sorted = [...wanted].sort((a, b) => a.x - b.x);
|
|
255
|
+
const pushed = [];
|
|
256
|
+
for (const item of sorted) pushed.push(Math.max(item.x, (pushed.at(-1) ?? Number.NEGATIVE_INFINITY) + COLUMN));
|
|
257
|
+
const drift = pushed.reduce((sum, x, index) => sum + x - (sorted[index]?.x ?? x), 0) / Math.max(1, pushed.length);
|
|
258
|
+
for (const [index, item] of sorted.entries()) placed.set(item.id, (pushed[index] ?? item.x) - drift);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* The nodes laid out again, top to bottom in the order they run: the starts and the nodes nothing leads to on the
|
|
262
|
+
* first row, each other node a row below the lowest node leading to it, under the exits it hangs from. Edges that
|
|
263
|
+
* loop back up do not count. Only the positions change.
|
|
264
|
+
*/
|
|
265
|
+
function layoutFlow(graph) {
|
|
266
|
+
const ids = new Set(graph.nodes.map((node) => node.id));
|
|
267
|
+
const loops = loopEdges(graph);
|
|
268
|
+
const edges = graph.edges.filter((edge) => !loops.has(edge.id) && ids.has(edge.from) && ids.has(edge.to));
|
|
269
|
+
const rows = rowsOf(graph, edges);
|
|
270
|
+
const placed = /* @__PURE__ */ new Map();
|
|
271
|
+
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
|
+
id: node.id,
|
|
273
|
+
x: wantedX(graph, edges, node.id, placed)
|
|
274
|
+
})), placed);
|
|
275
|
+
return {
|
|
276
|
+
...graph,
|
|
277
|
+
nodes: graph.nodes.map((node) => ({
|
|
278
|
+
...node,
|
|
279
|
+
position: snap({
|
|
280
|
+
x: placed.get(node.id) ?? 0,
|
|
281
|
+
y: (rows.get(node.id) ?? 0) * ROW
|
|
282
|
+
})
|
|
283
|
+
}))
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
var clampZoom = (zoom) => Math.min(FLOW_ZOOM.max, Math.max(FLOW_ZOOM.min, zoom));
|
|
287
|
+
/** Where a point of the canvas (relative to its top left corner) is in the flow's own pixels. */
|
|
288
|
+
var toWorld = (viewport, screen) => ({
|
|
289
|
+
x: (screen.x - viewport.x) / viewport.zoom,
|
|
290
|
+
y: (screen.y - viewport.y) / viewport.zoom
|
|
291
|
+
});
|
|
292
|
+
/** Zooms by `factor`, keeping the point under `screen` where it is: zooming follows the pointer. */
|
|
293
|
+
function zoomAt(viewport, screen, factor) {
|
|
294
|
+
const zoom = clampZoom(viewport.zoom * factor);
|
|
295
|
+
const world = toWorld(viewport, screen);
|
|
296
|
+
return {
|
|
297
|
+
x: screen.x - world.x * zoom,
|
|
298
|
+
y: screen.y - world.y * zoom,
|
|
299
|
+
zoom
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/** The view that shows every node, centred in a canvas of this size, never larger than life. */
|
|
303
|
+
function fitView(nodes, width, height) {
|
|
304
|
+
if (nodes.length === 0) return {
|
|
305
|
+
x: FIT_PADDING,
|
|
306
|
+
y: FIT_PADDING,
|
|
307
|
+
zoom: 1
|
|
308
|
+
};
|
|
309
|
+
const left = Math.min(...nodes.map((node) => node.position.x));
|
|
310
|
+
const top = Math.min(...nodes.map((node) => node.position.y));
|
|
311
|
+
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));
|
|
313
|
+
const zoom = clampZoom(Math.min(1, (width - 96) / (right - left), (height - 96) / (bottom - top)));
|
|
314
|
+
return {
|
|
315
|
+
x: width / 2 - (left + right) / 2 * zoom,
|
|
316
|
+
y: height / 2 - (top + bottom) / 2 * zoom,
|
|
317
|
+
zoom
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
/** How far to move a span so that it fits between the margins of `size`: nothing when it already does. */
|
|
321
|
+
function shiftInto(start, end, size) {
|
|
322
|
+
if (start < REVEAL_MARGIN) return REVEAL_MARGIN - start;
|
|
323
|
+
return end > size - REVEAL_MARGIN ? size - REVEAL_MARGIN - end : 0;
|
|
324
|
+
}
|
|
325
|
+
/** The view moved just enough for a node to be seen whole, its exits included; unchanged when it already is. */
|
|
326
|
+
function revealIn(viewport, node, width, height) {
|
|
327
|
+
const { zoom } = viewport;
|
|
328
|
+
const left = viewport.x + node.position.x * zoom;
|
|
329
|
+
const top = viewport.y + node.position.y * zoom;
|
|
330
|
+
return {
|
|
331
|
+
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),
|
|
333
|
+
zoom
|
|
334
|
+
};
|
|
335
|
+
}
|
|
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 };
|
package/dist/lib/options.d.ts
CHANGED
|
@@ -3,3 +3,8 @@ export declare const TONES: readonly Tone[];
|
|
|
3
3
|
export declare const CONTROL_SIZES: readonly ControlSize[];
|
|
4
4
|
/** The icon that says each tone at a glance: alerts, toasts and callouts use it. */
|
|
5
5
|
export declare const TONE_ICONS: Record<Tone, string>;
|
|
6
|
+
/**
|
|
7
|
+
* Whether a tone's icon reads well filled (`jl-icon filled`): a sign in a circle or a triangle does, the accent's
|
|
8
|
+
* sparkles do not.
|
|
9
|
+
*/
|
|
10
|
+
export declare const TONE_ICON_FILLED: Record<Tone, boolean>;
|
package/dist/lib/options.js
CHANGED
|
@@ -21,4 +21,16 @@ var TONE_ICONS = {
|
|
|
21
21
|
ok: "circle-check",
|
|
22
22
|
warn: "triangle-alert"
|
|
23
23
|
};
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Whether a tone's icon reads well filled (`jl-icon filled`): a sign in a circle or a triangle does, the accent's
|
|
26
|
+
* sparkles do not.
|
|
27
|
+
*/
|
|
28
|
+
var TONE_ICON_FILLED = {
|
|
29
|
+
accent: false,
|
|
30
|
+
danger: true,
|
|
31
|
+
info: true,
|
|
32
|
+
neutral: true,
|
|
33
|
+
ok: true,
|
|
34
|
+
warn: true
|
|
35
|
+
};
|
|
36
|
+
export { CONTROL_SIZES, TONES, TONE_ICONS, TONE_ICON_FILLED };
|
|
@@ -2,7 +2,7 @@ import { BASE_STYLES } from "../styles/shared.js";
|
|
|
2
2
|
import { JlElement } from "../base/jl-element.js";
|
|
3
3
|
import { oneOf } from "../lib/attributes.js";
|
|
4
4
|
import { registerIcons } from "../lib/icons.js";
|
|
5
|
-
import { TONES, TONE_ICONS } from "../lib/options.js";
|
|
5
|
+
import { TONES, TONE_ICONS, TONE_ICON_FILLED } from "../lib/options.js";
|
|
6
6
|
import { hasSlotted } from "../lib/slots.js";
|
|
7
7
|
import { JlIcon } from "../atoms/jl-icon.js";
|
|
8
8
|
import { JlButton } from "../atoms/jl-button.js";
|
|
@@ -119,10 +119,12 @@ var JlAlert = class extends JlElement {
|
|
|
119
119
|
renderActions() {
|
|
120
120
|
this._refs.actions.hidden = !hasSlotted(this._refs.actionsSlot);
|
|
121
121
|
}
|
|
122
|
+
/** The tone's own icon is filled, as a toast's is; an icon of the page's stays as it is drawn. */
|
|
122
123
|
renderIcon() {
|
|
123
124
|
const own = this.icon.get();
|
|
124
125
|
const name = own === "" ? TONE_ICONS[this.tone.get()] : own;
|
|
125
126
|
this._refs.icon.name.set(name === NO_ICON ? "" : name);
|
|
127
|
+
this._refs.icon.filled.set(own === "" && TONE_ICON_FILLED[this.tone.get()]);
|
|
126
128
|
this._refs.icon.hidden = name === NO_ICON;
|
|
127
129
|
this.reflect("has-icon", name !== NO_ICON);
|
|
128
130
|
}
|
|
@@ -2,9 +2,10 @@ import { JlFormControl } from '../base/jl-form-control';
|
|
|
2
2
|
import type { Weekday } from '../types/calendar.types';
|
|
3
3
|
import type { ControlSize } from '../types/options.types';
|
|
4
4
|
/**
|
|
5
|
-
* A field for a date: it shows the date
|
|
6
|
-
* button, Alt + Down or Down. People may also type the date as
|
|
7
|
-
*
|
|
5
|
+
* A field for a date: it shows the date in digits, in the `locale`'s order (`25.10.2026`, `10/25/2026`), and opens a
|
|
6
|
+
* calendar from its button, Alt + Down or Down. People may also type the date: as it shows, with any separators or
|
|
7
|
+
* none (`25102026`), or year first. Phones show their number keys; letters are refused. `value` and the form hold
|
|
8
|
+
* the ISO date.
|
|
8
9
|
*/
|
|
9
10
|
export declare class JlDatePicker extends JlFormControl {
|
|
10
11
|
static readonly selector = "jl-date-picker";
|
|
@@ -16,6 +17,7 @@ export declare class JlDatePicker extends JlFormControl {
|
|
|
16
17
|
get(): string;
|
|
17
18
|
set(setter: string | ((prevState: string) => string)): void;
|
|
18
19
|
}>>;
|
|
20
|
+
/** Without one, the field shows how a date is typed in its locale: `dd.mm.yyyy`. */
|
|
19
21
|
readonly placeholder: Readonly<Readonly<{
|
|
20
22
|
get(): string;
|
|
21
23
|
set(setter: string | ((prevState: string) => string)): void;
|
|
@@ -91,7 +93,10 @@ export declare class JlDatePicker extends JlFormControl {
|
|
|
91
93
|
private onPick;
|
|
92
94
|
private openPopup;
|
|
93
95
|
private closePopup;
|
|
94
|
-
/**
|
|
96
|
+
/**
|
|
97
|
+
* The typed text: a date in the locale's order (any separators, or none) or year first sets the value, an empty
|
|
98
|
+
* field clears it, anything else goes back to the date there was.
|
|
99
|
+
*/
|
|
95
100
|
private commitText;
|
|
96
101
|
/** Sets the value as people chose it, and says so when it changed. */
|
|
97
102
|
private commit;
|
|
@@ -99,6 +104,7 @@ export declare class JlDatePicker extends JlFormControl {
|
|
|
99
104
|
/** The value when it is a real ISO date, otherwise nothing. */
|
|
100
105
|
private validIso;
|
|
101
106
|
private display;
|
|
107
|
+
private renderPlaceholder;
|
|
102
108
|
private renderValue;
|
|
103
109
|
private renderBound;
|
|
104
110
|
/** Required, and within min and max, as a native date field checks it. */
|
|
@@ -6,7 +6,7 @@ import { registerIcons } from "../lib/icons.js";
|
|
|
6
6
|
import { CONTROL_SIZES } from "../lib/options.js";
|
|
7
7
|
import { setOrRemove, uniqueId } from "../lib/dom.js";
|
|
8
8
|
import { JlIcon } from "../atoms/jl-icon.js";
|
|
9
|
-
import { compareDates, formatDate, parseIsoDate, toIsoDate } from "../lib/calendar.js";
|
|
9
|
+
import { NUMERIC_DATE, compareDates, dateOrder, datePattern, formatDate, isDateText, parseIsoDate, parseTypedDate, toIsoDate } from "../lib/calendar.js";
|
|
10
10
|
import { pageLocale } from "../lib/locale.js";
|
|
11
11
|
import { JlCalendar } from "./jl-calendar.js";
|
|
12
12
|
import jl_date_picker_default from "./jl-date-picker.css_raw.js";
|
|
@@ -28,16 +28,18 @@ var VALUE_MISSING = "Choose a date.";
|
|
|
28
28
|
var DIALOG_LABEL = "Choose a date";
|
|
29
29
|
var parseWeekday = (value) => Math.round(clampedNumber(MONDAY, SUNDAY, SATURDAY)(value));
|
|
30
30
|
/**
|
|
31
|
-
* A field for a date: it shows the date
|
|
32
|
-
* button, Alt + Down or Down. People may also type the date as
|
|
33
|
-
*
|
|
31
|
+
* A field for a date: it shows the date in digits, in the `locale`'s order (`25.10.2026`, `10/25/2026`), and opens a
|
|
32
|
+
* calendar from its button, Alt + Down or Down. People may also type the date: as it shows, with any separators or
|
|
33
|
+
* none (`25102026`), or year first. Phones show their number keys; letters are refused. `value` and the form hold
|
|
34
|
+
* the ISO date.
|
|
34
35
|
*/
|
|
35
36
|
var JlDatePicker = class extends JlFormControl {
|
|
36
37
|
static selector = "jl-date-picker";
|
|
37
38
|
events = this.useEvents();
|
|
38
39
|
/** The date, as ISO (`2026-10-25`); empty for none. */
|
|
39
40
|
value = this.useChange("", () => this.renderValue(), { attribute: "value" });
|
|
40
|
-
|
|
41
|
+
/** Without one, the field shows how a date is typed in its locale: `dd.mm.yyyy`. */
|
|
42
|
+
placeholder = this.useChange("", () => this.renderPlaceholder(), { attribute: "placeholder" });
|
|
41
43
|
/** The field's name for screen readers, when no visible label names it. */
|
|
42
44
|
label = this.useChange("", (value) => setOrRemove(this._refs.native, "aria-label", value), { attribute: "label" });
|
|
43
45
|
/** The first day that can be chosen, as ISO. */
|
|
@@ -47,6 +49,7 @@ var JlDatePicker = class extends JlFormControl {
|
|
|
47
49
|
/** The language the date and the calendar are written in: `fr-CH`. The page's by default. */
|
|
48
50
|
locale = this.useChange("", (value) => {
|
|
49
51
|
this._refs.calendar.locale.set(value);
|
|
52
|
+
this.renderPlaceholder();
|
|
50
53
|
this.renderValue();
|
|
51
54
|
}, { attribute: "locale" });
|
|
52
55
|
/** The day the calendar's weeks start on: 0 for Sunday, 1 for Monday. */
|
|
@@ -106,6 +109,7 @@ var JlDatePicker = class extends JlFormControl {
|
|
|
106
109
|
role: "combobox",
|
|
107
110
|
autocomplete: "off",
|
|
108
111
|
spellcheck: false,
|
|
112
|
+
inputMode: "decimal",
|
|
109
113
|
"aria-haspopup": "dialog",
|
|
110
114
|
"aria-expanded": "false",
|
|
111
115
|
"aria-controls": this._popupId
|
|
@@ -154,6 +158,9 @@ var JlDatePicker = class extends JlFormControl {
|
|
|
154
158
|
event.stopPropagation();
|
|
155
159
|
this.renderClear();
|
|
156
160
|
});
|
|
161
|
+
this.on(native, "beforeinput", (event) => {
|
|
162
|
+
if (event.data !== null && !isDateText(event.data)) event.preventDefault();
|
|
163
|
+
});
|
|
157
164
|
this.on(native, "change", () => this.commitText());
|
|
158
165
|
this.on(native, "keydown", (event) => this.onKey(event));
|
|
159
166
|
this.on(open, "pointerdown", () => {
|
|
@@ -232,7 +239,10 @@ var JlDatePicker = class extends JlFormControl {
|
|
|
232
239
|
closePopup() {
|
|
233
240
|
if (this.isOpen()) this._refs.popup.hidePopover();
|
|
234
241
|
}
|
|
235
|
-
/**
|
|
242
|
+
/**
|
|
243
|
+
* The typed text: a date in the locale's order (any separators, or none) or year first sets the value, an empty
|
|
244
|
+
* field clears it, anything else goes back to the date there was.
|
|
245
|
+
*/
|
|
236
246
|
commitText() {
|
|
237
247
|
const text = this._refs.native.value.trim();
|
|
238
248
|
if (text === this.display(this.value.get())) return;
|
|
@@ -240,7 +250,7 @@ var JlDatePicker = class extends JlFormControl {
|
|
|
240
250
|
this.commit("");
|
|
241
251
|
return;
|
|
242
252
|
}
|
|
243
|
-
const date =
|
|
253
|
+
const date = parseTypedDate(text, dateOrder(pageLocale(this.locale.get())));
|
|
244
254
|
if (date === null) {
|
|
245
255
|
this.renderValue();
|
|
246
256
|
return;
|
|
@@ -264,7 +274,10 @@ var JlDatePicker = class extends JlFormControl {
|
|
|
264
274
|
}
|
|
265
275
|
display(value) {
|
|
266
276
|
const date = parseIsoDate(value);
|
|
267
|
-
return date === null ? "" : formatDate(date, pageLocale(this.locale.get()));
|
|
277
|
+
return date === null ? "" : formatDate(date, pageLocale(this.locale.get()), NUMERIC_DATE);
|
|
278
|
+
}
|
|
279
|
+
renderPlaceholder() {
|
|
280
|
+
this._refs.native.placeholder = this.placeholder.get() || datePattern(pageLocale(this.locale.get()));
|
|
268
281
|
}
|
|
269
282
|
renderValue() {
|
|
270
283
|
const value = this.validIso();
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// src/molecules/jl-steps.css?raw
|
|
2
|
-
var jl_steps_default = ":host {\n display: block;\n container-type: inline-size;\n --marker: 28px;\n}\n\nol {\n display: flex;\n padding: 0;\n list-style: none;\n}\n\n.step {\n display: flex;\n flex: 1;\n min-width: 0;\n}\n\n.step:last-child {\n flex: none;\n}\n\n.head {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n padding: 0;\n border: none;\n border-radius: var(--jl-radius-xs);\n background: none;\n text-align: start;\n}\n\nbutton.head {\n cursor: pointer;\n}\n\n.marker {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: var(--marker);\n height: var(--marker);\n border: 1px solid var(--jl-line-strong);\n border-radius: 50%;\n background: var(--jl-surface);\n color: var(--jl-muted);\n font-size: var(--jl-font-sm);\n font-weight: 650;\n font-variant-numeric: tabular-nums;\n}\n\n.text {\n display: grid;\n gap: 2px;\n min-width: 0;\n padding-top: 4px;\n}\n\n.label {\n color: var(--jl-muted);\n font-size: var(--jl-font-md);\n font-weight: 600;\n line-height: 20px;\n}\n\n.description {\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n line-height: 1.45;\n}\n\n.step[data-status=\"done\"] .marker {\n border-color: var(--jl-accent-line);\n background: var(--jl-accent-soft);\n color: var(--jl-accent);\n}\n\n.step[data-status=\"done\"] .label {\n color: var(--jl-text);\n}\n\n.step[data-status=\"current\"] .marker {\n border-color: var(--jl-accent);\n background: var(--jl-accent);\n color: var(--jl-on-accent);\n box-shadow: 0 0 0 4px var(--jl-accent-soft);\n}\n\n.step[data-status=\"current\"] .label {\n color: var(--jl-
|
|
2
|
+
var jl_steps_default = ":host {\n display: block;\n container-type: inline-size;\n --marker: 28px;\n}\n\nol {\n display: flex;\n padding: 0;\n list-style: none;\n}\n\n.step {\n display: flex;\n flex: 1;\n min-width: 0;\n}\n\n.step:last-child {\n flex: none;\n}\n\n.head {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n padding: 0;\n border: none;\n border-radius: var(--jl-radius-xs);\n background: none;\n text-align: start;\n}\n\nbutton.head {\n cursor: pointer;\n}\n\n.marker {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: var(--marker);\n height: var(--marker);\n border: 1px solid var(--jl-line-strong);\n border-radius: 50%;\n background: var(--jl-surface);\n color: var(--jl-muted);\n font-size: var(--jl-font-sm);\n font-weight: 650;\n font-variant-numeric: tabular-nums;\n}\n\n.text {\n display: grid;\n gap: 2px;\n min-width: 0;\n padding-top: 4px;\n}\n\n.label {\n color: var(--jl-muted);\n font-size: var(--jl-font-md);\n font-weight: 600;\n line-height: 20px;\n}\n\n.description {\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n line-height: 1.45;\n}\n\n.step[data-status=\"done\"] .marker {\n border-color: var(--jl-accent-line);\n background: var(--jl-accent-soft);\n color: var(--jl-accent);\n}\n\n.step[data-status=\"done\"] .label {\n color: var(--jl-text);\n}\n\n.step[data-status=\"current\"] .marker {\n border-color: var(--jl-accent);\n background: var(--jl-accent);\n color: var(--jl-on-accent);\n box-shadow: 0 0 0 4px var(--jl-accent-soft);\n}\n\n.step[data-status=\"current\"] .label {\n color: var(--jl-accent);\n font-weight: 700;\n}\n\nbutton.head:hover .label {\n text-decoration: underline;\n text-underline-offset: 3px;\n}\n\n.line {\n flex: 1;\n min-width: 24px;\n height: 1px;\n /* Further from the label before it than from the next marker: a label reads as its own step's. */\n margin: calc(var(--marker) / 2) 12px 0 20px;\n background: var(--jl-line-strong);\n}\n\n.step[data-status=\"done\"] .line {\n background: var(--jl-accent);\n}\n\n.step:last-child .line {\n display: none;\n}\n\n/* Vertical: the markers in a column joined by a rule, the text beside them. */\n:host([orientation=\"vertical\"]) ol {\n flex-direction: column;\n}\n\n:host([orientation=\"vertical\"]) .step {\n position: relative;\n flex: none;\n padding-bottom: 24px;\n}\n\n:host([orientation=\"vertical\"]) .step:last-child {\n padding-bottom: 0;\n}\n\n:host([orientation=\"vertical\"]) .line {\n position: absolute;\n top: calc(var(--marker) + 4px);\n bottom: 4px;\n left: calc(var(--marker) / 2 - 0.5px);\n width: 1px;\n height: auto;\n min-width: 0;\n margin: 0;\n}\n\n/* Narrow, the horizontal steps keep their markers and only the current step's text. */\n@container (width < 560px) {\n /* Out of sight, still read: the same rules as .sr-only. */\n :host(:not([orientation=\"vertical\"])) .step:not([data-status=\"current\"]) .text {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n }\n\n :host(:not([orientation=\"vertical\"])) .description {\n display: none;\n }\n\n :host(:not([orientation=\"vertical\"])) .line {\n min-width: 8px;\n margin: calc(var(--marker) / 2) 8px 0;\n }\n\n /* The current step takes three shares of the room, the others one, never less than their marker and line: the\n label never runs into the next step, and takes an ellipsis only when there is no room at all. */\n :host(:not([orientation=\"vertical\"])) .step:not([data-status=\"current\"]) {\n min-width: min-content;\n }\n\n :host(:not([orientation=\"vertical\"])) .step[data-status=\"current\"] {\n flex: 3 1 0;\n }\n\n :host(:not([orientation=\"vertical\"])) .step[data-status=\"current\"] .label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n :host(:not([orientation=\"vertical\"])) .step[data-status=\"current\"] .line {\n min-width: 24px;\n margin-left: 16px;\n }\n}\n";
|
|
3
3
|
export { jl_steps_default as default };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// src/molecules/jl-timeline.css?raw
|
|
2
|
-
var jl_timeline_default = ":host {\n display: block;\n --marker: 22px;\n --gap: 20px;\n --title-size: var(--jl-font-md);\n}\n\n:host([compact]) {\n --marker: 18px;\n --gap: 12px;\n --title-size: var(--jl-font-sm);\n}\n\n.timeline {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.event {\n position: relative;\n display: grid;\n grid-template-columns: var(--marker) minmax(0, 1fr);\n
|
|
2
|
+
var jl_timeline_default = ":host {\n display: block;\n --marker: 22px;\n --gap: 20px;\n --title-size: var(--jl-font-md);\n}\n\n:host([compact]) {\n --marker: 18px;\n --gap: 12px;\n --title-size: var(--jl-font-sm);\n}\n\n.timeline {\n /* The markers (and the room after them), the times and the texts in three columns shared by every event: the times\n line up beside their markers, each next to its title, however wide the timeline. Without any time, the middle\n column takes no room. */\n display: grid;\n grid-template-columns: calc(var(--marker) + 12px) auto minmax(0, 1fr);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.event {\n position: relative;\n display: grid;\n grid-column: 1 / -1;\n /* Browsers without subgrid keep the columns per event. */\n grid-template-columns: calc(var(--marker) + 12px) auto minmax(0, 1fr);\n padding-bottom: var(--gap);\n --tone: var(--jl-muted);\n --soft: var(--jl-surface-3);\n}\n\n@supports (grid-template-columns: subgrid) {\n .event {\n grid-template-columns: subgrid;\n }\n}\n\n.event:last-child {\n padding-bottom: 0;\n}\n\n.event[data-tone=\"accent\"] {\n --tone: var(--jl-accent);\n --soft: var(--jl-accent-soft);\n}\n\n.event[data-tone=\"ok\"] {\n --tone: var(--jl-ok);\n --soft: var(--jl-ok-soft);\n}\n\n.event[data-tone=\"warn\"] {\n --tone: var(--jl-warn);\n --soft: var(--jl-warn-soft);\n}\n\n.event[data-tone=\"danger\"] {\n --tone: var(--jl-danger);\n --soft: var(--jl-danger-soft);\n}\n\n.event[data-tone=\"info\"] {\n --tone: var(--jl-info);\n --soft: var(--jl-info-soft);\n}\n\n/* The line runs from this marker's centre to the next one's, behind the markers. */\n.event:not(:last-child)::before {\n position: absolute;\n top: 10px;\n bottom: -10px;\n left: calc(var(--marker) / 2 - 0.5px);\n width: 1px;\n background: var(--jl-line-strong);\n content: \"\";\n}\n\n.rail {\n position: relative;\n grid-column: 1;\n width: var(--marker);\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n height: 20px;\n}\n\n.marker {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n}\n\n.dot {\n width: 10px;\n height: 10px;\n background: var(--tone);\n box-shadow: 0 0 0 3px var(--jl-surface);\n}\n\n:host([compact]) .dot {\n width: 8px;\n height: 8px;\n}\n\n.circle {\n width: var(--marker);\n height: var(--marker);\n border: 1px solid color-mix(in srgb, var(--tone) 30%, var(--soft));\n background: var(--soft);\n color: var(--tone);\n box-shadow: 0 0 0 3px var(--jl-surface);\n}\n\n.content {\n display: grid;\n grid-column: 3;\n gap: 2px;\n}\n\n.title {\n color: var(--jl-text);\n font-size: var(--title-size);\n font-weight: 600;\n line-height: 20px;\n}\n\n.time {\n grid-column: 2;\n padding-right: 12px;\n color: var(--jl-muted);\n font-family: var(--jl-font-mono);\n font-size: var(--jl-font-xs);\n /* On the title's line. */\n line-height: 20px;\n white-space: nowrap;\n}\n\n.description {\n color: var(--jl-muted);\n font-size: var(--jl-font-sm);\n line-height: 1.5;\n}\n\n:host([compact]) .description {\n font-size: var(--jl-font-xs);\n}\n";
|
|
3
3
|
export { jl_timeline_default as default };
|
|
@@ -2,7 +2,8 @@ import { JlElement } from '../base/jl-element';
|
|
|
2
2
|
import type { TimelineItem } from '../types/jl-timeline.types';
|
|
3
3
|
/**
|
|
4
4
|
* What happened, in order, along a vertical line: each event a dot (or an icon in a circle) in its tone's colour,
|
|
5
|
-
*
|
|
5
|
+
* its time beside the marker (the times in one column), a title and an optional description. `compact` tightens it
|
|
6
|
+
* for side panels.
|
|
6
7
|
*/
|
|
7
8
|
export declare class JlTimeline extends JlElement {
|
|
8
9
|
static readonly selector = "jl-timeline";
|
|
@@ -11,7 +11,8 @@ var ICON_SIZE = 14;
|
|
|
11
11
|
var COMPACT_ICON_SIZE = 12;
|
|
12
12
|
/**
|
|
13
13
|
* What happened, in order, along a vertical line: each event a dot (or an icon in a circle) in its tone's colour,
|
|
14
|
-
*
|
|
14
|
+
* its time beside the marker (the times in one column), a title and an optional description. `compact` tightens it
|
|
15
|
+
* for side panels.
|
|
15
16
|
*/
|
|
16
17
|
var JlTimeline = class extends JlElement {
|
|
17
18
|
static selector = "jl-timeline";
|
|
@@ -55,25 +56,26 @@ var JlTimeline = class extends JlElement {
|
|
|
55
56
|
return /* @__PURE__ */ jsxs("li", {
|
|
56
57
|
class: "event",
|
|
57
58
|
"data-tone": item.tone ?? "neutral",
|
|
58
|
-
children: [
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class: "
|
|
59
|
+
children: [
|
|
60
|
+
/* @__PURE__ */ jsx("span", {
|
|
61
|
+
class: "rail",
|
|
62
|
+
children: marker
|
|
63
|
+
}),
|
|
64
|
+
item.time === void 0 ? null : /* @__PURE__ */ jsx("time", {
|
|
65
|
+
class: "time",
|
|
66
|
+
children: item.time
|
|
67
|
+
}),
|
|
68
|
+
/* @__PURE__ */ jsxs("div", {
|
|
69
|
+
class: "content",
|
|
65
70
|
children: [/* @__PURE__ */ jsx("p", {
|
|
66
71
|
class: "title",
|
|
67
72
|
children: item.title
|
|
68
|
-
}), item.
|
|
69
|
-
class: "
|
|
70
|
-
children: item.
|
|
73
|
+
}), item.description === void 0 ? null : /* @__PURE__ */ jsx("p", {
|
|
74
|
+
class: "description",
|
|
75
|
+
children: item.description
|
|
71
76
|
})]
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
children: item.description
|
|
75
|
-
})]
|
|
76
|
-
})]
|
|
77
|
+
})
|
|
78
|
+
]
|
|
77
79
|
});
|
|
78
80
|
}
|
|
79
81
|
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// src/molecules/jl-toast.css?raw
|
|
2
|
-
var jl_toast_default = ":host {\n display: block;\n --
|
|
2
|
+
var jl_toast_default = ":host {\n display: block;\n /* Coloured as jl-alert is, from the same tokens: a toned toast is tinted, its border and icon in its tone. */\n --fg: var(--jl-text-2);\n --bg: var(--jl-surface);\n --border: var(--jl-line);\n --bar: var(--jl-brand);\n}\n\n:host([tone=\"ok\"]),\n:host([tone=\"warn\"]),\n:host([tone=\"danger\"]),\n:host([tone=\"info\"]) {\n --border: color-mix(in srgb, var(--fg) 24%, var(--bg));\n --bar: var(--fg);\n}\n\n:host([tone=\"ok\"]) {\n --fg: var(--jl-ok);\n --bg: var(--jl-ok-soft);\n}\n\n:host([tone=\"warn\"]) {\n --fg: var(--jl-warn);\n --bg: var(--jl-warn-soft);\n}\n\n:host([tone=\"danger\"]) {\n --fg: var(--jl-danger);\n --bg: var(--jl-danger-soft);\n}\n\n:host([tone=\"info\"]) {\n --fg: var(--jl-info);\n --bg: var(--jl-info-soft);\n}\n\n.toast {\n position: relative;\n display: flex;\n align-items: flex-start;\n gap: 12px;\n padding: 14px 12px 14px 16px;\n overflow: hidden;\n border: 1px solid var(--border);\n border-radius: var(--jl-radius-lg);\n background: var(--bg);\n color: var(--jl-text);\n font-size: var(--jl-font-md);\n line-height: 20px;\n box-shadow: var(--jl-shadow-lg);\n}\n\n.icon {\n margin-top: 1px;\n color: var(--fg);\n}\n\n.content {\n display: grid;\n flex: 1;\n gap: 2px;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n\n.heading {\n font-weight: 650;\n}\n\n.text {\n color: var(--jl-muted);\n font-size: var(--jl-font-sm);\n}\n\n.heading[hidden] + .text {\n color: var(--jl-text-2);\n font-size: var(--jl-font-md);\n}\n\n.buttons {\n display: flex;\n flex: none;\n align-items: center;\n gap: 2px;\n margin: -5px -4px -5px 0;\n}\n\n.buttons:not(:has(jl-button:not([hidden]))) {\n display: none;\n}\n\n.close {\n --jl-accent: var(--fg);\n --jl-accent-soft: color-mix(in srgb, var(--fg) 12%, transparent);\n}\n\n.progress {\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n height: 2px;\n}\n\n.bar {\n height: 100%;\n background: var(--bar);\n transform-origin: left center;\n}\n\n:host([paused]) .bar {\n opacity: 0.55;\n}\n\n/* The line still counts the time, but does not move for people who asked for less motion. */\n@media (prefers-reduced-motion: reduce) {\n .progress {\n visibility: hidden;\n }\n}\n";
|
|
3
3
|
export { jl_toast_default as default };
|
|
@@ -69,6 +69,7 @@ export declare class JlToast extends JlElement {
|
|
|
69
69
|
private hold;
|
|
70
70
|
/** Whether the focus, moving to `next`, stays in the toast (its buttons, or what the page slotted in it). */
|
|
71
71
|
private holdsFocus;
|
|
72
|
+
/** The tone's own icon is filled, so its colour shows at a glance; an icon of the page's stays as it is drawn. */
|
|
72
73
|
private renderIcon;
|
|
73
74
|
/** Inside a toaster, its live region announces the toast; alone, the toast is its own. */
|
|
74
75
|
private renderRole;
|