rei-kit 2.24.0 → 3.0.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/dist/styles.css CHANGED
@@ -1,26 +1,26 @@
1
1
 
2
- .sheet-enter-active[data-v-2855eea3],
3
- .sheet-leave-active[data-v-2855eea3] {
2
+ .sheet-enter-active[data-v-f7d30ec0],
3
+ .sheet-leave-active[data-v-f7d30ec0] {
4
4
  transition: opacity var(--duration-base) ease;
5
5
  }
6
- .sheet-enter-from[data-v-2855eea3],
7
- .sheet-leave-to[data-v-2855eea3] {
6
+ .sheet-enter-from[data-v-f7d30ec0],
7
+ .sheet-leave-to[data-v-f7d30ec0] {
8
8
  opacity: 0;
9
9
  }
10
10
 
11
11
  /* The panel travels further than the scrim fades, which is what makes the
12
12
  sheet read as rising rather than appearing. */
13
- .sheet-enter-active .sheet-panel[data-v-2855eea3],
14
- .sheet-leave-active .sheet-panel[data-v-2855eea3] {
13
+ .sheet-enter-active .sheet-panel[data-v-f7d30ec0],
14
+ .sheet-leave-active .sheet-panel[data-v-f7d30ec0] {
15
15
  transition: transform var(--duration-slow) var(--ease-sheet);
16
16
  }
17
- .sheet-enter-from .sheet-panel[data-v-2855eea3],
18
- .sheet-leave-to .sheet-panel[data-v-2855eea3] {
17
+ .sheet-enter-from .sheet-panel[data-v-f7d30ec0],
18
+ .sheet-leave-to .sheet-panel[data-v-f7d30ec0] {
19
19
  transform: translateY(6%);
20
20
  }
21
21
  @media (prefers-reduced-motion: reduce) {
22
- .sheet-enter-from .sheet-panel[data-v-2855eea3],
23
- .sheet-leave-to .sheet-panel[data-v-2855eea3] {
22
+ .sheet-enter-from .sheet-panel[data-v-f7d30ec0],
23
+ .sheet-leave-to .sheet-panel[data-v-f7d30ec0] {
24
24
  transform: none;
25
25
  }
26
26
  }
@@ -342,16 +342,16 @@
342
342
  background: linear-gradient(to right, var(--rk-scroll-fade), transparent);
343
343
  }
344
344
 
345
- .rk-listbox[data-v-7f8cd788] {
345
+ .rk-listbox[data-v-3cd5a3c6] {
346
346
  overflow-y: auto;
347
347
  border-radius: var(--radius-card);
348
348
  padding: 0.25rem;
349
349
  }
350
- .rk-listbox[data-v-7f8cd788]:focus-visible {
350
+ .rk-listbox[data-v-3cd5a3c6]:focus-visible {
351
351
  outline: 2px solid var(--color-primary);
352
352
  outline-offset: 1px;
353
353
  }
354
- .rk-listbox-option[data-v-7f8cd788] {
354
+ .rk-listbox-option[data-v-3cd5a3c6] {
355
355
  display: flex;
356
356
  cursor: pointer;
357
357
  align-items: center;
@@ -365,38 +365,43 @@
365
365
  /* The current row shows only while the list has focus. A list that is not
366
366
  being used has no "current" row, and a highlight left behind on one reads
367
367
  as a selection — which is exactly what it is not. */
368
- .rk-listbox:focus-within .rk-listbox-option.is-active[data-v-7f8cd788] {
368
+ .rk-listbox:focus-within .rk-listbox-option.is-active[data-v-3cd5a3c6] {
369
369
  background: var(--color-muted);
370
370
  }
371
- .rk-listbox-option.is-chosen[data-v-7f8cd788] {
372
- color: var(--color-primary);
371
+
372
+ /* The chosen row is its tinted ground, its weight and its tick — not a
373
+ primary-coloured label on a ten per cent tint of primary, which is the
374
+ same fault `BaseBadge` had: a faint wash of a colour is still nearly that
375
+ colour, and the pair measured 4.16:1. */
376
+ .rk-listbox-option.is-chosen[data-v-3cd5a3c6] {
377
+ color: var(--color-ink);
373
378
  font-weight: 500;
374
379
  }
375
380
 
376
381
  /* Chosen is the tick and the tint, and it holds whether the list is in use
377
382
  or not: the current option and the chosen one are different things. */
378
- .rk-listbox-option.is-chosen[data-v-7f8cd788] {
383
+ .rk-listbox-option.is-chosen[data-v-3cd5a3c6] {
379
384
  background: color-mix(in oklab, var(--color-primary) 10%, transparent);
380
385
  }
381
- .rk-listbox:focus-within .rk-listbox-option.is-chosen.is-active[data-v-7f8cd788] {
386
+ .rk-listbox:focus-within .rk-listbox-option.is-chosen.is-active[data-v-3cd5a3c6] {
382
387
  background: color-mix(in oklab, var(--color-primary) 20%, transparent);
383
388
  }
384
- .rk-listbox-option.is-disabled[data-v-7f8cd788] {
389
+ .rk-listbox-option.is-disabled[data-v-3cd5a3c6] {
385
390
  cursor: not-allowed;
386
391
  opacity: 0.45;
387
392
  }
388
- .rk-listbox-label[data-v-7f8cd788] {
393
+ .rk-listbox-label[data-v-3cd5a3c6] {
389
394
  display: block;
390
395
  overflow: hidden;
391
396
  text-overflow: ellipsis;
392
397
  white-space: nowrap;
393
398
  }
394
- .rk-listbox-description[data-v-7f8cd788] {
399
+ .rk-listbox-description[data-v-3cd5a3c6] {
395
400
  display: block;
396
401
  font-size: 0.75rem;
397
402
  color: var(--color-ink-soft);
398
403
  }
399
- .rk-listbox-empty[data-v-7f8cd788] {
404
+ .rk-listbox-empty[data-v-3cd5a3c6] {
400
405
  padding: 1rem;
401
406
  }
402
407
 
@@ -1073,7 +1078,7 @@ to {
1073
1078
  gap: var(--spacing, .25rem);
1074
1079
  min-height: 52px;
1075
1080
  padding-block: calc(var(--spacing, .25rem) * 1.5);
1076
- color: var(--color-ink-soft, #5c7480);
1081
+ color: var(--color-ink-soft, #586f7b);
1077
1082
  flex-direction: column;
1078
1083
  flex: 1;
1079
1084
  display: flex;
@@ -1406,20 +1411,20 @@ to {
1406
1411
  font-size: 0.8125rem;
1407
1412
  }
1408
1413
 
1409
- .rk-cal[data-v-d682f18a] {
1414
+ .rk-cal[data-v-36cbc7fd] {
1410
1415
  display: flex;
1411
1416
  width: 100%;
1412
1417
  max-width: 19rem;
1413
1418
  flex-direction: column;
1414
1419
  gap: 0.5rem;
1415
1420
  }
1416
- .rk-cal-head[data-v-d682f18a] {
1421
+ .rk-cal-head[data-v-36cbc7fd] {
1417
1422
  display: flex;
1418
1423
  align-items: center;
1419
1424
  justify-content: space-between;
1420
1425
  gap: 0.25rem;
1421
1426
  }
1422
- .rk-cal-arrow[data-v-d682f18a] {
1427
+ .rk-cal-arrow[data-v-36cbc7fd] {
1423
1428
  display: grid;
1424
1429
  width: 2rem;
1425
1430
  height: 2rem;
@@ -1428,40 +1433,40 @@ to {
1428
1433
  color: var(--color-ink);
1429
1434
  transition: background-color var(--duration-fast) var(--ease-standard);
1430
1435
  }
1431
- .rk-cal-arrow[data-v-d682f18a]:hover {
1436
+ .rk-cal-arrow[data-v-36cbc7fd]:hover {
1432
1437
  background: var(--color-muted);
1433
1438
  }
1434
- .rk-cal-title[data-v-d682f18a] {
1439
+ .rk-cal-title[data-v-36cbc7fd] {
1435
1440
  border-radius: var(--radius-cell);
1436
1441
  padding: 0.25rem 0.625rem;
1437
1442
  font-size: 0.875rem;
1438
1443
  font-weight: 600;
1439
1444
  color: var(--color-ink);
1440
1445
  }
1441
- .rk-cal-title[data-v-d682f18a]:hover {
1446
+ .rk-cal-title[data-v-36cbc7fd]:hover {
1442
1447
  background: var(--color-muted);
1443
1448
  }
1444
- .rk-cal-weekdays[data-v-d682f18a],
1445
- .rk-cal-week[data-v-d682f18a] {
1449
+ .rk-cal-weekdays[data-v-36cbc7fd],
1450
+ .rk-cal-week[data-v-36cbc7fd] {
1446
1451
  display: grid;
1447
1452
  grid-template-columns: repeat(7, minmax(0, 1fr));
1448
1453
  }
1449
- .rk-cal-weekdays[data-v-d682f18a] {
1454
+ .rk-cal-weekdays[data-v-36cbc7fd] {
1450
1455
  gap: 2px;
1451
1456
  font-size: 0.6875rem;
1452
1457
  font-weight: 600;
1453
1458
  text-align: center;
1454
1459
  color: var(--color-ink-soft);
1455
1460
  }
1456
- .rk-cal-grid[data-v-d682f18a] {
1461
+ .rk-cal-grid[data-v-36cbc7fd] {
1457
1462
  display: grid;
1458
1463
  gap: 2px;
1459
1464
  }
1460
- .rk-cal-cell[data-v-d682f18a] {
1465
+ .rk-cal-cell[data-v-36cbc7fd] {
1461
1466
  display: grid;
1462
1467
  place-items: center;
1463
1468
  }
1464
- .rk-cal-day[data-v-d682f18a] {
1469
+ .rk-cal-day[data-v-36cbc7fd] {
1465
1470
  display: grid;
1466
1471
  width: 100%;
1467
1472
  height: 2.25rem;
@@ -1474,53 +1479,57 @@ to {
1474
1479
  background-color var(--duration-fast) var(--ease-standard),
1475
1480
  color var(--duration-fast) var(--ease-standard);
1476
1481
  }
1477
- .rk-cal-day[data-v-d682f18a]:hover:not(:disabled) {
1482
+ .rk-cal-day[data-v-36cbc7fd]:hover:not(:disabled) {
1478
1483
  background: var(--color-muted);
1479
1484
  }
1480
- .rk-cal-day[data-v-d682f18a]:focus-visible {
1485
+ .rk-cal-day[data-v-36cbc7fd]:focus-visible {
1481
1486
  outline: 2px solid var(--color-primary);
1482
1487
  outline-offset: -2px;
1483
1488
  }
1484
1489
 
1485
1490
  /* A day from the month either side: still reachable, visibly not this month. */
1486
- .rk-cal-day.is-outside[data-v-d682f18a] {
1491
+ /* `ink-soft` and nothing else. It used to carry `opacity: 0.55` on top,
1492
+ which took a colour that clears AA down to 2.2:1 — and these are real
1493
+ buttons with real labels, reachable by script and read out by name, not
1494
+ decoration. The softer ink is already the difference between this month
1495
+ and the next. */
1496
+ .rk-cal-day.is-outside[data-v-36cbc7fd] {
1487
1497
  color: var(--color-ink-soft);
1488
- opacity: 0.55;
1489
1498
  }
1490
- .rk-cal-day.is-inside[data-v-d682f18a] {
1499
+ .rk-cal-day.is-inside[data-v-36cbc7fd] {
1491
1500
  background: color-mix(in oklab, var(--color-primary) 14%, transparent);
1492
1501
  border-radius: 0;
1493
1502
  }
1494
- .rk-cal-day.is-selected[data-v-d682f18a] {
1503
+ .rk-cal-day.is-selected[data-v-36cbc7fd] {
1495
1504
  background: var(--color-primary);
1496
1505
  color: var(--color-on-primary);
1497
1506
  font-weight: 600;
1498
1507
  opacity: 1;
1499
1508
  }
1500
- .rk-cal-day[aria-current='date'][data-v-d682f18a]:not(.is-selected) {
1509
+ .rk-cal-day[aria-current='date'][data-v-36cbc7fd]:not(.is-selected) {
1501
1510
  box-shadow: inset 0 0 0 1px var(--color-primary);
1502
1511
  }
1503
- .rk-cal-day[data-v-d682f18a]:disabled {
1512
+ .rk-cal-day[data-v-36cbc7fd]:disabled {
1504
1513
  cursor: not-allowed;
1505
1514
  opacity: 0.3;
1506
1515
  }
1507
- .rk-cal-blocks[data-v-d682f18a] {
1516
+ .rk-cal-blocks[data-v-36cbc7fd] {
1508
1517
  display: grid;
1509
1518
  grid-template-columns: repeat(3, minmax(0, 1fr));
1510
1519
  gap: 0.375rem;
1511
1520
  }
1512
- .rk-cal-block[data-v-d682f18a] {
1521
+ .rk-cal-block[data-v-36cbc7fd] {
1513
1522
  height: 2.5rem;
1514
1523
  border-radius: var(--radius-cell);
1515
1524
  font-size: 0.8125rem;
1516
1525
  color: var(--color-ink);
1517
1526
  transition: background-color var(--duration-fast) var(--ease-standard);
1518
1527
  }
1519
- .rk-cal-block[data-v-d682f18a]:hover {
1528
+ .rk-cal-block[data-v-36cbc7fd]:hover {
1520
1529
  background: var(--color-muted);
1521
1530
  }
1522
1531
 
1523
- .rk-chip[data-v-c94a820d] {
1532
+ .rk-chip[data-v-7939c4e0] {
1524
1533
  display: inline-flex;
1525
1534
  max-width: 100%;
1526
1535
  align-items: center;
@@ -1533,7 +1542,7 @@ to {
1533
1542
  background: var(--color-muted);
1534
1543
  color: var(--color-ink);
1535
1544
  }
1536
- .rk-chip-body[data-v-c94a820d] {
1545
+ .rk-chip-body[data-v-7939c4e0] {
1537
1546
  display: inline-flex;
1538
1547
  min-width: 0;
1539
1548
  align-items: center;
@@ -1541,42 +1550,49 @@ to {
1541
1550
  border-radius: 9999px;
1542
1551
  color: inherit;
1543
1552
  }
1544
- .rk-chip-body.is-button[data-v-c94a820d] {
1553
+ .rk-chip-body.is-button[data-v-7939c4e0] {
1545
1554
  cursor: pointer;
1546
1555
  }
1547
- .rk-chip.is-selected[data-v-c94a820d] {
1556
+ .rk-chip.is-selected[data-v-7939c4e0] {
1548
1557
  background: var(--color-primary);
1549
1558
  color: var(--color-on-primary);
1550
1559
  }
1551
- .rk-chip.is-disabled[data-v-c94a820d] {
1560
+ .rk-chip.is-disabled[data-v-7939c4e0] {
1552
1561
  cursor: not-allowed;
1553
1562
  opacity: 0.5;
1554
1563
  }
1555
1564
 
1556
1565
  /* The tones tint rather than fill: a row of chips in five solid colours is a
1557
1566
  row where nothing stands out. The selected one fills, and that is the point
1558
- of it. */
1559
- .rk-chip.tone-primary[data-v-c94a820d] {
1567
+ of it.
1568
+
1569
+ The ground and the edge carry the role; `ink` carries the words. Two of
1570
+ these used to write the role straight onto a tint of itself and the other
1571
+ two mixed it most of the way there, which is the same fault by degrees —
1572
+ a browser measured the warning chip at 4.13:1. There is one answer to
1573
+ this in the kit now, and `BaseBadge`, `BaseAlert` and `BaseListbox` give
1574
+ the same one. */
1575
+ .rk-chip.tone-primary[data-v-7939c4e0] {
1560
1576
  background: color-mix(in oklab, var(--color-primary) 12%, transparent);
1561
1577
  border-color: color-mix(in oklab, var(--color-primary) 25%, transparent);
1562
- color: var(--color-primary);
1578
+ color: var(--color-ink);
1563
1579
  }
1564
- .rk-chip.tone-success[data-v-c94a820d] {
1580
+ .rk-chip.tone-success[data-v-7939c4e0] {
1565
1581
  background: color-mix(in oklab, var(--color-positive) 14%, transparent);
1566
1582
  border-color: color-mix(in oklab, var(--color-positive) 28%, transparent);
1567
- color: color-mix(in oklab, var(--color-positive) 80%, var(--color-ink));
1583
+ color: var(--color-ink);
1568
1584
  }
1569
- .rk-chip.tone-warning[data-v-c94a820d] {
1585
+ .rk-chip.tone-warning[data-v-7939c4e0] {
1570
1586
  background: color-mix(in oklab, var(--color-warning) 16%, transparent);
1571
1587
  border-color: color-mix(in oklab, var(--color-warning) 30%, transparent);
1572
- color: color-mix(in oklab, var(--color-warning) 75%, var(--color-ink));
1588
+ color: var(--color-ink);
1573
1589
  }
1574
- .rk-chip.tone-danger[data-v-c94a820d] {
1590
+ .rk-chip.tone-danger[data-v-7939c4e0] {
1575
1591
  background: color-mix(in oklab, var(--color-negative) 12%, transparent);
1576
1592
  border-color: color-mix(in oklab, var(--color-negative) 26%, transparent);
1577
- color: var(--color-negative);
1593
+ color: var(--color-ink);
1578
1594
  }
1579
- .rk-chip-remove[data-v-c94a820d] {
1595
+ .rk-chip-remove[data-v-7939c4e0] {
1580
1596
  display: grid;
1581
1597
  width: 1.125rem;
1582
1598
  height: 1.125rem;
@@ -1587,7 +1603,7 @@ to {
1587
1603
  opacity: 0.7;
1588
1604
  transition: opacity var(--duration-fast) var(--ease-standard);
1589
1605
  }
1590
- .rk-chip-remove[data-v-c94a820d]:hover {
1606
+ .rk-chip-remove[data-v-7939c4e0]:hover {
1591
1607
  opacity: 1;
1592
1608
  background: color-mix(in oklab, currentColor 15%, transparent);
1593
1609
  }
@@ -2765,20 +2781,20 @@ to {
2765
2781
  }
2766
2782
  }
2767
2783
 
2768
- .install-enter-active[data-v-552a4ac9],
2769
- .install-leave-active[data-v-552a4ac9] {
2784
+ .install-enter-active[data-v-c5d173d3],
2785
+ .install-leave-active[data-v-c5d173d3] {
2770
2786
  transition:
2771
2787
  opacity var(--duration-base) ease,
2772
2788
  transform var(--duration-base) ease;
2773
2789
  }
2774
- .install-enter-from[data-v-552a4ac9],
2775
- .install-leave-to[data-v-552a4ac9] {
2790
+ .install-enter-from[data-v-c5d173d3],
2791
+ .install-leave-to[data-v-c5d173d3] {
2776
2792
  opacity: 0;
2777
2793
  transform: translateY(-4px);
2778
2794
  }
2779
2795
  @media (prefers-reduced-motion: reduce) {
2780
- .install-enter-from[data-v-552a4ac9],
2781
- .install-leave-to[data-v-552a4ac9] {
2796
+ .install-enter-from[data-v-c5d173d3],
2797
+ .install-leave-to[data-v-c5d173d3] {
2782
2798
  transform: none;
2783
2799
  }
2784
2800
  }
package/dist/tokens.css CHANGED
@@ -22,7 +22,7 @@
22
22
  --color-surface: #ffffff;
23
23
  --color-muted: #ddf4e7;
24
24
  --color-ink: #0f2229;
25
- --color-ink-soft: #5c7480;
25
+ --color-ink-soft: #586f7b;
26
26
  --color-hair: #e1edea;
27
27
 
28
28
  /* ---------------------------------------------------------------------
@@ -37,7 +37,20 @@
37
37
  /* ~68 characters. Longer and the eye loses the line it was returning to. */
38
38
  --measure-reading: 68ch;
39
39
 
40
+ /* ---------------------------------------------------------------------
41
+ Radii, named for what they wrap rather than for how big they are.
42
+
43
+ `--radius-control` is the fourth, and it existed before it had a name:
44
+ four components wrote `rounded-xl` for the same shape — a segment, an
45
+ icon tile inside a card — which is Tailwind's own 12px and not this
46
+ kit's. It meant an app setting `--radius-card: 0` got square cards with
47
+ rounded tiles inside them, and nothing said why. A test now fails on a
48
+ raw radius utility for that reason. `rounded-full` stays allowed: a
49
+ pill is a shape, not a size, and it has to survive every radius an app
50
+ picks.
51
+ --------------------------------------------------------------------- */
40
52
  --radius-cell: 3px;
53
+ --radius-control: 12px;
41
54
  --radius-card: 16px;
42
55
  --radius-shell: 30px;
43
56
  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
@@ -72,7 +85,7 @@
72
85
  --color-primary: #26667f;
73
86
  --color-accent: #124170;
74
87
  --color-positive: #67c090;
75
- --color-negative: #c9463b;
88
+ --color-negative: #c7453a;
76
89
  --color-warning: #d89a3e;
77
90
 
78
91
  --color-on-primary: #ffffff;
@@ -87,7 +100,7 @@
87
100
  --color-primary: #26667f;
88
101
  --color-accent: #124170;
89
102
  --color-positive: #67c090;
90
- --color-negative: #c9463b;
103
+ --color-negative: #c7453a;
91
104
  --color-warning: #d89a3e;
92
105
 
93
106
  --color-on-primary: #ffffff;
@@ -1,5 +1,5 @@
1
- import { c as useBoundValue } from "./focus-target-DRgd8UnQ.js";
2
- import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createStaticVNode, createTextVNode, defineComponent, isRef, mergeProps, nextTick, normalizeClass, onMounted, onUnmounted, openBlock, readonly, ref, renderSlot, toDisplayString, unref, useId, vModelDynamic, watch, withCtx, withDirectives } from "vue";
1
+ import { c as useBoundValue } from "./focus-target-BtcoJdmz.js";
2
+ import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createStaticVNode, createTextVNode, defineComponent, isRef, mergeProps, nextTick, normalizeClass, normalizeStyle, onMounted, onUnmounted, openBlock, readonly, ref, renderSlot, toDisplayString, unref, useAttrs, useId, vModelDynamic, watch, withCtx, withDirectives } from "vue";
3
3
  //#region src/utils/redirect.ts
4
4
  /**
5
5
  * Resolves a `?redirect=` query value into a safe in-app path.
@@ -428,6 +428,29 @@ var FormField_default = /* @__PURE__ */ defineComponent({
428
428
  }
429
429
  });
430
430
  //#endregion
431
+ //#region src/composables/use-field-attrs.ts
432
+ /**
433
+ * Splits a field's fallthrough attributes in two.
434
+ *
435
+ * The three text fields bind `$attrs` to their inner `<input>`, which is what
436
+ * makes `placeholder`, `autocomplete` and `inputmode` reach the control rather
437
+ * than pile up on a wrapper that cannot use them. `class` and `style` are the
438
+ * exception, and they were going along with the rest: Vue puts them on a
439
+ * component's root element, so `class="mt-4"` reads as "space this field" and
440
+ * was landing on the box _inside_ it, under the label.
441
+ */
442
+ function useFieldAttrs() {
443
+ const attrs = useAttrs();
444
+ return {
445
+ fieldClass: computed(() => attrs.class),
446
+ fieldStyle: computed(() => attrs.style),
447
+ controlAttrs: computed(() => {
448
+ const { class: _class, style: _style, ...rest } = attrs;
449
+ return rest;
450
+ })
451
+ };
452
+ }
453
+ //#endregion
431
454
  //#region src/components/BaseInput.vue?vue&type=script&setup=true&lang.ts
432
455
  var _hoisted_1$1 = [
433
456
  "id",
@@ -456,6 +479,7 @@ var BaseInput_default = /* @__PURE__ */ defineComponent({
456
479
  },
457
480
  emits: ["update:modelValue"],
458
481
  setup(__props, { emit: __emit }) {
482
+ const { fieldClass, fieldStyle, controlAttrs } = useFieldAttrs();
459
483
  /**
460
484
  * A text field with its label, hint and error already wired to it.
461
485
  *
@@ -466,6 +490,8 @@ var BaseInput_default = /* @__PURE__ */ defineComponent({
466
490
  const model = useBoundValue(() => __props.modelValue, (value) => emit("update:modelValue", value));
467
491
  return (_ctx, _cache) => {
468
492
  return openBlock(), createBlock(FormField_default, {
493
+ class: normalizeClass(unref(fieldClass)),
494
+ style: normalizeStyle(unref(fieldStyle)),
469
495
  label: __props.label,
470
496
  error: __props.error,
471
497
  hint: __props.hint,
@@ -478,13 +504,15 @@ var BaseInput_default = /* @__PURE__ */ defineComponent({
478
504
  type: __props.type,
479
505
  "aria-invalid": invalid,
480
506
  "aria-describedby": describedBy
481
- }, _ctx.$attrs, { class: [
507
+ }, unref(controlAttrs), { class: [
482
508
  __props.variant === "unstyled" ? "" : "control text-ink rounded-card focus-visible:outline-primary px-3 focus-visible:outline-2 focus-visible:outline-offset-1",
483
509
  __props.variant === "unstyled" ? "text-base" : CONTROL_CLASS,
484
510
  __props.variant !== "unstyled" && invalid ? "border-negative" : ""
485
511
  ] }), null, 16, _hoisted_1$1), [[vModelDynamic, unref(model)]])]),
486
512
  _: 1
487
513
  }, 8, [
514
+ "class",
515
+ "style",
488
516
  "label",
489
517
  "error",
490
518
  "hint",
@@ -563,6 +591,6 @@ function useAnnounce() {
563
591
  };
564
592
  }
565
593
  //#endregion
566
- export { FormField_default as a, useOnline as c, readStoredTheme as d, setThemeStorageKey as f, toRedirectPath as g, safeRedirect as h, BaseInput_default as i, applyTheme as l, tapFeedback as m, useAnnounce as n, BaseAlert_default as o, useTheme as p, GoogleButton_default as r, useToast as s, announce as t, isThemePreference as u };
594
+ export { toRedirectPath as _, useFieldAttrs as a, useToast as c, isThemePreference as d, readStoredTheme as f, safeRedirect as g, tapFeedback as h, BaseInput_default as i, useOnline as l, useTheme as m, useAnnounce as n, FormField_default as o, setThemeStorageKey as p, GoogleButton_default as r, BaseAlert_default as s, announce as t, applyTheme as u };
567
595
 
568
- //# sourceMappingURL=use-announce-qM0ZD9Ey.js.map
596
+ //# sourceMappingURL=use-announce-B_JGTXHZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-announce-B_JGTXHZ.js","names":["$slots"],"sources":["../src/utils/redirect.ts","../src/utils/haptics.ts","../src/composables/use-theme.ts","../src/composables/use-online.ts","../src/composables/use-toast.ts","../src/components/BaseAlert.vue","../src/components/BaseAlert.vue","../src/components/FormField.vue","../src/components/FormField.vue","../src/composables/use-field-attrs.ts","../src/components/BaseInput.vue","../src/components/BaseInput.vue","../src/components/GoogleButton.vue","../src/components/GoogleButton.vue","../src/composables/use-announce.ts"],"sourcesContent":["/**\n * What a router hands back for one query key.\n *\n * Inlined rather than imported from vue-router: the shape is `string | null`\n * either way, and a helper this small should not drag a router into the\n * package's dependencies.\n */\nexport type QueryValue = string | null\n\n/**\n * Resolves a `?redirect=` query value into a safe in-app path.\n *\n * Only same-origin paths are accepted. Anything else falls back to `/`,\n * so a crafted link cannot bounce a user from the real login page to a\n * phishing clone.\n *\n * Pure: takes the query value instead of reading the router, so it also\n * works inside navigation guards and can be unit tested.\n *\n * @param target - Raw `route.query.redirect` value. May be a string, an\n * array (repeated query key), `null`, or `undefined`.\n * @returns A path starting with a single `/`. Defaults to `/`.\n *\n * @example\n * ```ts\n * // in a view\n * await router.push(safeRedirect(route.query.redirect))\n *\n * // in a guard\n * return safeRedirect(to.query.redirect)\n * ```\n *\n * @example\n * ```ts\n * safeRedirect('/week') // '/week'\n * safeRedirect('https://evil.com') // '/'\n * safeRedirect('//evil.com') // '/' (protocol-relative URL)\n * safeRedirect(['/a', '/b']) // '/'\n * safeRedirect(undefined) // '/'\n * ```\n */\nexport function safeRedirect(target: QueryValue | QueryValue[] | undefined): string {\n if (typeof target === 'string' && target.startsWith('/') && !target.startsWith('//')) {\n return target\n }\n\n return '/'\n}\n\n/**\n * Where to send somebody back to after signing in, without the fragment.\n *\n * Supabase's implicit OAuth flow hands the browser back with the access and\n * refresh tokens in the URL fragment. A fragment is client-side only — it is\n * never sent to a server. Copied into a query parameter it stops being one:\n * `/login?redirect=/%23access_token=…` is sent on the very next request and\n * lands in the host's access logs, in `Referer` headers and in browser history.\n *\n * So the redirect keeps the path and the query and drops everything from the\n * `#`. There is nothing after it worth returning to anyway.\n *\n * One of the two phone apps had this and the other was passing `fullPath`\n * straight through, which is the leak above with nothing in the way of it. That\n * is the shape of bug a shared kit exists to end: it was fixed once, in the app\n * whose author happened to think of it.\n *\n * @example\n * ```ts\n * toRedirectPath('/ledger?direction=out') // '/ledger?direction=out'\n * toRedirectPath('/#access_token=abc') // '/'\n * ```\n */\nexport function toRedirectPath(fullPath: string): string {\n const [path] = fullPath.split('#')\n\n return path === undefined || path === '' ? '/' : path\n}\n","/**\n * A short vibration for a confirmed tap.\n *\n * Optional chaining is not decoration: iOS Safari has no `vibrate` at all, and\n * calling it unguarded would throw on every marked day.\n *\n * @param duration - Milliseconds. Keep it under ~15ms; longer reads as an alert.\n */\nexport function tapFeedback(duration = 10): void {\n navigator.vibrate?.(duration)\n}\n","import { ref, watch } from 'vue'\nimport type { Ref } from 'vue'\n\n/** What the user asked for; `system` follows the OS. */\nexport type ThemePreference = 'system' | 'light' | 'dark'\n\n/**\n * Namespaced by the app, not by this package.\n *\n * Two rei-kit apps served from the same origin would otherwise share one theme\n * setting — and during development on localhost, they will be.\n */\nlet storageKey = 'rei-theme'\n\nexport function isThemePreference(value: unknown): value is ThemePreference {\n return value === 'system' || value === 'light' || value === 'dark'\n}\n\n/** Reads the stored preference, falling back to `system`. */\nexport function readStoredTheme(): ThemePreference {\n try {\n const stored = localStorage.getItem(storageKey)\n\n return isThemePreference(stored) ? stored : 'system'\n } catch {\n return 'system'\n }\n}\n\nfunction storeTheme(preference: ThemePreference): void {\n try {\n localStorage.setItem(storageKey, preference)\n } catch {\n // Private mode or blocked storage: the choice just will not persist.\n }\n}\n\n/**\n * Does the environment prefer a dark scheme?\n *\n * `matchMedia` is checked for on its own rather than inferred from `document`.\n * Having one does not imply having the other: jsdom supplies a document and no\n * `matchMedia`, so a consumer's component test that so much as mounts something\n * calling `useTheme` threw — and some embedded webviews are the same. Where\n * there is nothing to ask, the answer is no rather than an exception.\n */\nfunction prefersDarkScheme(): boolean {\n return typeof window !== 'undefined' && typeof window.matchMedia === 'function'\n ? window.matchMedia('(prefers-color-scheme: dark)').matches\n : false\n}\n\n/**\n * Adds or removes `.dark` on `<html>`, resolving `system` against the OS.\n *\n * A no-op without a document. There is no OS preference to read on a server and\n * no `<html>` to write to, so a prerender leaves the class off and the app\n * decides the theme before hydration — see the note in the README.\n */\nexport function applyTheme(preference: ThemePreference): void {\n if (typeof document === 'undefined') return\n\n const isDark = preference === 'dark' || (preference === 'system' && prefersDarkScheme())\n\n document.documentElement.classList.toggle('dark', isDark)\n}\n\n/**\n * The shared preference, created on first use rather than at import.\n *\n * Lazy on purpose: reading storage at import time would lock in the default key\n * before an app had a chance to set its own, leaving the controller reading one\n * key and writing another.\n */\nlet preference: Ref<ThemePreference> | null = null\n\nfunction controller(): Ref<ThemePreference> {\n if (preference) return preference\n\n preference = ref<ThemePreference>(readStoredTheme())\n\n watch(\n preference,\n (next) => {\n storeTheme(next)\n applyTheme(next)\n },\n { immediate: true },\n )\n\n // While on `system`, follow the OS if the user flips it at night. Only where\n // there is something to listen to; see `prefersDarkScheme`.\n if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {\n window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {\n if (preference?.value === 'system') applyTheme('system')\n })\n }\n\n return preference\n}\n\n/**\n * Sets where the preference is stored.\n *\n * Safe in either order: called before the first `useTheme()` it simply changes\n * the key, and called after it re-reads under the new one, so the controller\n * never reads from one key while writing to another.\n *\n * @example\n * ```ts\n * setThemeStorageKey('hibi-theme') // once, at startup\n * ```\n */\nexport function setThemeStorageKey(key: string): void {\n storageKey = key\n if (preference) preference.value = readStoredTheme()\n}\n\n/** @returns The shared preference ref; assigning to it stores and applies it. */\nexport function useTheme(): Ref<ThemePreference> {\n return controller()\n}\n","import { onMounted, onUnmounted, readonly, ref } from 'vue'\n\n/**\n * Tracks whether the browser thinks it has a network connection.\n *\n * Note the limit: `navigator.onLine` only reports whether a network interface\n * is up, not whether requests actually succeed. Treat it as a hint for the UI,\n * never as a reason to skip error handling.\n *\n * Listeners are removed on unmount, so the composable is safe to call per view.\n *\n * @returns A readonly ref that flips with the browser's online/offline events.\n *\n * @example\n * ```ts\n * const isOnline = useOnline()\n * // <p v-if=\"!isOnline\">You're offline.</p>\n * ```\n */\nexport function useOnline() {\n const isOnline = ref(true)\n\n function update() {\n isOnline.value = navigator.onLine\n }\n\n onMounted(() => {\n update()\n window.addEventListener('online', update)\n window.addEventListener('offline', update)\n })\n\n onUnmounted(() => {\n window.removeEventListener('online', update)\n window.removeEventListener('offline', update)\n })\n\n return readonly(isOnline)\n}\n","import { readonly, ref } from 'vue'\n\n/**\n * Saying that something happened.\n *\n * The reason this exists is a measurement rather than a preference: the word\n * \"toast\" appeared **zero times** across all three consuming apps. Not because\n * they had decided against it — because there was no mechanism, so every save,\n * every delete and every export finished in silence and the only way to know\n * it had worked was that nothing had visibly broken.\n *\n * ── What is deliberately not here ──\n *\n * **No text.** The kit never knows a sentence. Callers pass the message; a\n * component that called a translator would force one on the app.\n *\n * **Not for form errors.** A field that was rejected says so beside itself,\n * where the reader's eye already is and where it stays until fixed. A toast\n * that disappears after four seconds is the wrong place for something the\n * reader has to act on. Use `BaseAlert` and `FormField` for those; use this\n * for what has already happened.\n *\n * **A singleton, on purpose.** Two hosts would mean two stacks racing for the\n * same corner. The store lives at module scope and `ToastHost` renders it.\n */\nexport type ToastTone = 'info' | 'success' | 'warning' | 'danger'\n\n/**\n * One button on a toast — nearly always \"Undo\".\n *\n * The toast is dismissed when it is pressed, so the handler only has to do\n * the undoing.\n */\nexport interface ToastAction {\n /** The button's text. Already translated. */\n readonly label: string\n readonly onClick: () => void\n}\n\nexport interface Toast {\n readonly id: number\n readonly message: string\n readonly tone: ToastTone\n /** Milliseconds on screen. `0` stays until dismissed. */\n readonly duration: number\n readonly action?: ToastAction | undefined\n}\n\nexport interface ToastOptions {\n /** Milliseconds on screen; `0` stays until dismissed. */\n duration?: number | undefined\n /**\n * A button on the toast. An undo is the kindest confirmation there is: act\n * first, and let the reader take it back, instead of asking \"are you sure\"\n * before every delete.\n */\n action?: ToastAction | undefined\n}\n\n/**\n * Four seconds: long enough to read a short sentence twice, short enough that\n * a second action does not queue behind it.\n */\nconst DEFAULT_DURATION = 4000\n\n/**\n * A failure is read more slowly than a confirmation, and more often twice.\n */\nconst DANGER_DURATION = 7000\n\n/**\n * A toast with a button stays longer: the reader has to read it, decide and\n * reach the button. Four seconds is enough to read \"Deleted\", not to undo it.\n */\nconst ACTION_DURATION = 8000\n\n/**\n * Three at once. A fourth pushes the oldest out rather than growing the stack\n * off the top of the screen — an action that produces ten toasts is a loop,\n * and a loop should not be able to cover the app it is running in.\n */\nconst MAX_VISIBLE = 3\n\nconst items = ref<Toast[]>([])\n\nlet nextId = 0\n\ninterface Countdown {\n handle: ReturnType<typeof setTimeout>\n remaining: number\n startedAt: number\n}\n\nconst countdowns = new Map<number, Countdown>()\n\nfunction clearCountdown(id: number): void {\n const countdown = countdowns.get(id)\n if (countdown === undefined) return\n\n clearTimeout(countdown.handle)\n countdowns.delete(id)\n}\n\n/** Removes a toast, whether it timed out or was dismissed. */\nfunction dismiss(id: number): void {\n clearCountdown(id)\n items.value = items.value.filter((item) => item.id !== id)\n}\n\n/** Removes everything on screen. For a route change, or a sign-out. */\nfunction dismissAll(): void {\n for (const id of countdowns.keys()) clearCountdown(id)\n items.value = []\n}\n\nfunction arm(id: number, remaining: number): void {\n // A timer is a browser thing. On a server there is nothing to time and\n // nothing to see, and arming one would keep the process alive past the last\n // page — which is how a prerender build hangs instead of finishing.\n if (typeof window === 'undefined' || remaining <= 0) return\n\n countdowns.set(id, {\n handle: setTimeout(() => dismiss(id), remaining),\n remaining,\n startedAt: Date.now(),\n })\n}\n\n/**\n * Stops the clock on a toast the reader is pointing at.\n *\n * Somebody who has moved the pointer onto it is reading it, and taking it away\n * mid-sentence is the one thing a notification must not do.\n */\nfunction pause(id: number): void {\n const countdown = countdowns.get(id)\n if (countdown === undefined) return\n\n clearTimeout(countdown.handle)\n countdowns.set(id, {\n ...countdown,\n remaining: Math.max(0, countdown.remaining - (Date.now() - countdown.startedAt)),\n })\n}\n\n/** Starts it again, from where it stopped rather than from the beginning. */\nfunction resume(id: number): void {\n const countdown = countdowns.get(id)\n if (countdown === undefined) return\n\n arm(id, countdown.remaining)\n}\n\nfunction push(tone: ToastTone, message: string, options: ToastOptions = {}): number {\n const id = ++nextId\n const duration =\n options.duration ??\n (options.action ? ACTION_DURATION : tone === 'danger' ? DANGER_DURATION : DEFAULT_DURATION)\n\n const next = [...items.value, { id, message, tone, duration, action: options.action }]\n\n while (next.length > MAX_VISIBLE) {\n const oldest = next.shift()\n if (oldest !== undefined) clearCountdown(oldest.id)\n }\n\n items.value = next\n arm(id, duration)\n\n return id\n}\n\n/**\n * The stack, and the four ways to add to it.\n *\n * @example\n * ```ts\n * const toast = useToast()\n *\n * toast.success(t('habit.saved'))\n * toast.danger(t('common.failed'), { duration: 0 }) // stays until dismissed\n *\n * const id = toast.info(t('export.preparing'), { duration: 0 })\n * toast.dismiss(id)\n *\n * toast.success(t('entry.deleted'), {\n * action: { label: t('common.undo'), onClick: () => restore(entry) },\n * })\n * ```\n */\nexport function useToast() {\n return {\n /** Every toast on screen, oldest first. `ToastHost` renders this. */\n toasts: readonly(items),\n info: (message: string, options?: ToastOptions) => push('info', message, options),\n success: (message: string, options?: ToastOptions) => push('success', message, options),\n warning: (message: string, options?: ToastOptions) => push('warning', message, options),\n danger: (message: string, options?: ToastOptions) => push('danger', message, options),\n dismiss,\n dismissAll,\n pause,\n resume,\n }\n}\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\n/**\n * A message the reader has to take in before carrying on.\n *\n * Roles rather than colours, like everything else here: `info` is neutral,\n * `success` confirms, `warning` is a condition to know about, `danger` is\n * something that went wrong or is about to. A component that took a hex would\n * be a component that ignores the theme, and the theme is the whole reason the\n * kit exists.\n *\n * `assertive` decides how a screen reader treats it: a failed save interrupts,\n * a note about a form field waits its turn. Getting this wrong is invisible on\n * screen and rude in a screen reader, which is why it is a prop and not a\n * guess.\n */\nconst { tone = 'info', assertive = false } = defineProps<{\n tone?: 'info' | 'success' | 'warning' | 'danger' | undefined\n /** Announce immediately, interrupting. For failures the reader must act on. */\n assertive?: boolean | undefined\n}>()\n\nconst TONES = {\n info: 'border-hair bg-muted/40 text-ink',\n success: 'border-positive/35 bg-positive/8 text-ink',\n warning: 'border-warning/40 bg-warning/8 text-ink',\n danger: 'border-negative/35 bg-negative/8 text-ink',\n} as const\n\nconst MARKS = {\n info: 'bg-ink-soft/15 text-ink-soft',\n success: 'bg-positive/15 text-positive',\n warning: 'bg-warning/15 text-warning',\n danger: 'bg-negative/15 text-negative',\n} as const\n\nconst skin = computed(() => TONES[tone])\nconst mark = computed(() => MARKS[tone])\n</script>\n\n<template>\n <div\n class=\"rounded-card flex items-start gap-3 border px-4 py-3.5 text-sm leading-relaxed\"\n :class=\"skin\"\n :role=\"assertive ? 'alert' : 'status'\"\n :aria-live=\"assertive ? 'assertive' : 'polite'\"\n >\n <span\n v-if=\"$slots.mark\"\n class=\"mt-px grid size-6 shrink-0 place-items-center rounded-full text-xs font-semibold\"\n :class=\"mark\"\n aria-hidden=\"true\"\n >\n <slot name=\"mark\" />\n </span>\n\n <div class=\"min-w-0 flex-1\">\n <p v-if=\"$slots.title\" class=\"text-ink font-semibold\">\n <slot name=\"title\" />\n </p>\n <div :class=\"$slots.title ? 'mt-1' : ''\"><slot /></div>\n </div>\n\n <slot name=\"action\" />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\n/**\n * A message the reader has to take in before carrying on.\n *\n * Roles rather than colours, like everything else here: `info` is neutral,\n * `success` confirms, `warning` is a condition to know about, `danger` is\n * something that went wrong or is about to. A component that took a hex would\n * be a component that ignores the theme, and the theme is the whole reason the\n * kit exists.\n *\n * `assertive` decides how a screen reader treats it: a failed save interrupts,\n * a note about a form field waits its turn. Getting this wrong is invisible on\n * screen and rude in a screen reader, which is why it is a prop and not a\n * guess.\n */\nconst { tone = 'info', assertive = false } = defineProps<{\n tone?: 'info' | 'success' | 'warning' | 'danger' | undefined\n /** Announce immediately, interrupting. For failures the reader must act on. */\n assertive?: boolean | undefined\n}>()\n\nconst TONES = {\n info: 'border-hair bg-muted/40 text-ink',\n success: 'border-positive/35 bg-positive/8 text-ink',\n warning: 'border-warning/40 bg-warning/8 text-ink',\n danger: 'border-negative/35 bg-negative/8 text-ink',\n} as const\n\nconst MARKS = {\n info: 'bg-ink-soft/15 text-ink-soft',\n success: 'bg-positive/15 text-positive',\n warning: 'bg-warning/15 text-warning',\n danger: 'bg-negative/15 text-negative',\n} as const\n\nconst skin = computed(() => TONES[tone])\nconst mark = computed(() => MARKS[tone])\n</script>\n\n<template>\n <div\n class=\"rounded-card flex items-start gap-3 border px-4 py-3.5 text-sm leading-relaxed\"\n :class=\"skin\"\n :role=\"assertive ? 'alert' : 'status'\"\n :aria-live=\"assertive ? 'assertive' : 'polite'\"\n >\n <span\n v-if=\"$slots.mark\"\n class=\"mt-px grid size-6 shrink-0 place-items-center rounded-full text-xs font-semibold\"\n :class=\"mark\"\n aria-hidden=\"true\"\n >\n <slot name=\"mark\" />\n </span>\n\n <div class=\"min-w-0 flex-1\">\n <p v-if=\"$slots.title\" class=\"text-ink font-semibold\">\n <slot name=\"title\" />\n </p>\n <div :class=\"$slots.title ? 'mt-1' : ''\"><slot /></div>\n </div>\n\n <slot name=\"action\" />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useId } from 'vue'\n\n/**\n * A label, a hint, an error, and the wiring between them.\n *\n * This was inside `BaseInput`, which is why the kit had one form control\n * instead of five. The hard part of a field is not the `<input>` — it is\n * generating an id, pointing the label at it, deciding whether the description\n * is the hint or the error, and telling assistive tech which one to read. That\n * is identical for a select, a textarea and an input, and every app that\n * needed one of the other two wrote the whole thing again.\n *\n * The control comes in through the slot and is handed what it needs to be\n * described. It is a slot rather than a prop so the field never has to know\n * what it is wrapping.\n *\n * @example\n * ```vue\n * <FormField :label=\"t('profile.name')\" :error=\"errors.name\">\n * <template #default=\"{ id, describedBy, invalid }\">\n * <input :id=\"id\" :aria-describedby=\"describedBy\" :aria-invalid=\"invalid\" />\n * </template>\n * </FormField>\n * ```\n */\nconst {\n label,\n error = '',\n hint = '',\n labelHidden = false,\n fieldId = undefined,\n size = 'md',\n} = defineProps<{\n label: string\n error?: string | undefined\n hint?: string | undefined\n /**\n * `sm` for a control that sits inside something else — a toolbar, a filter\n * row, a settings line — rather than in a form of its own.\n *\n * It exists because every hand-written select in all three apps was the\n * small one, and the kit only had the large one. A part is not reusable if\n * reaching for it costs a size somebody chose on purpose.\n */\n size?: 'sm' | 'md' | undefined\n /**\n * Hides the label visually but keeps it for assistive tech. For fields whose\n * surrounding row already names them — dropping the label entirely would\n * leave the control with no accessible name at all.\n */\n labelHidden?: boolean | undefined\n /**\n * The control's `id`, when the app needs to address it from outside —\n * an `ErrorSummary` linking to the field it is about, a label elsewhere\n * on the page. Generated when it is not given, which is almost always.\n */\n fieldId?: string | undefined\n}>()\n\nconst generated = useId()\nconst id = computed(() => fieldId ?? generated)\nconst errorId = computed(() => `${id.value}-error`)\nconst hintId = computed(() => `${id.value}-hint`)\n\n/* One description at a time, and the error wins. Announcing the hint as well\n buries the reason the field was rejected under advice the reader has already\n had. */\nconst describedBy = computed(() => {\n if (error) return errorId.value\n if (hint) return hintId.value\n return undefined\n})\n</script>\n\n<template>\n <div class=\"flex flex-col\" :class=\"size === 'sm' ? 'gap-1' : 'gap-1.5'\">\n <label\n :for=\"id\"\n class=\"font-medium\"\n :class=\"[\n labelHidden ? 'sr-only' : '',\n size === 'sm' ? 'text-ink-soft text-xs' : 'text-ink text-sm',\n ]\"\n >\n {{ label }}\n </label>\n\n <slot :id=\"id\" :described-by=\"describedBy\" :invalid=\"Boolean(error)\" :size=\"size\" />\n\n <p v-if=\"error\" :id=\"errorId\" class=\"text-negative text-xs\">{{ error }}</p>\n <p v-else-if=\"hint\" :id=\"hintId\" class=\"text-ink-soft text-xs\">{{ hint }}</p>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useId } from 'vue'\n\n/**\n * A label, a hint, an error, and the wiring between them.\n *\n * This was inside `BaseInput`, which is why the kit had one form control\n * instead of five. The hard part of a field is not the `<input>` — it is\n * generating an id, pointing the label at it, deciding whether the description\n * is the hint or the error, and telling assistive tech which one to read. That\n * is identical for a select, a textarea and an input, and every app that\n * needed one of the other two wrote the whole thing again.\n *\n * The control comes in through the slot and is handed what it needs to be\n * described. It is a slot rather than a prop so the field never has to know\n * what it is wrapping.\n *\n * @example\n * ```vue\n * <FormField :label=\"t('profile.name')\" :error=\"errors.name\">\n * <template #default=\"{ id, describedBy, invalid }\">\n * <input :id=\"id\" :aria-describedby=\"describedBy\" :aria-invalid=\"invalid\" />\n * </template>\n * </FormField>\n * ```\n */\nconst {\n label,\n error = '',\n hint = '',\n labelHidden = false,\n fieldId = undefined,\n size = 'md',\n} = defineProps<{\n label: string\n error?: string | undefined\n hint?: string | undefined\n /**\n * `sm` for a control that sits inside something else — a toolbar, a filter\n * row, a settings line — rather than in a form of its own.\n *\n * It exists because every hand-written select in all three apps was the\n * small one, and the kit only had the large one. A part is not reusable if\n * reaching for it costs a size somebody chose on purpose.\n */\n size?: 'sm' | 'md' | undefined\n /**\n * Hides the label visually but keeps it for assistive tech. For fields whose\n * surrounding row already names them — dropping the label entirely would\n * leave the control with no accessible name at all.\n */\n labelHidden?: boolean | undefined\n /**\n * The control's `id`, when the app needs to address it from outside —\n * an `ErrorSummary` linking to the field it is about, a label elsewhere\n * on the page. Generated when it is not given, which is almost always.\n */\n fieldId?: string | undefined\n}>()\n\nconst generated = useId()\nconst id = computed(() => fieldId ?? generated)\nconst errorId = computed(() => `${id.value}-error`)\nconst hintId = computed(() => `${id.value}-hint`)\n\n/* One description at a time, and the error wins. Announcing the hint as well\n buries the reason the field was rejected under advice the reader has already\n had. */\nconst describedBy = computed(() => {\n if (error) return errorId.value\n if (hint) return hintId.value\n return undefined\n})\n</script>\n\n<template>\n <div class=\"flex flex-col\" :class=\"size === 'sm' ? 'gap-1' : 'gap-1.5'\">\n <label\n :for=\"id\"\n class=\"font-medium\"\n :class=\"[\n labelHidden ? 'sr-only' : '',\n size === 'sm' ? 'text-ink-soft text-xs' : 'text-ink text-sm',\n ]\"\n >\n {{ label }}\n </label>\n\n <slot :id=\"id\" :described-by=\"describedBy\" :invalid=\"Boolean(error)\" :size=\"size\" />\n\n <p v-if=\"error\" :id=\"errorId\" class=\"text-negative text-xs\">{{ error }}</p>\n <p v-else-if=\"hint\" :id=\"hintId\" class=\"text-ink-soft text-xs\">{{ hint }}</p>\n </div>\n</template>\n","import { computed, useAttrs } from 'vue'\n\n/**\n * Splits a field's fallthrough attributes in two.\n *\n * The three text fields bind `$attrs` to their inner `<input>`, which is what\n * makes `placeholder`, `autocomplete` and `inputmode` reach the control rather\n * than pile up on a wrapper that cannot use them. `class` and `style` are the\n * exception, and they were going along with the rest: Vue puts them on a\n * component's root element, so `class=\"mt-4\"` reads as \"space this field\" and\n * was landing on the box _inside_ it, under the label.\n */\nexport function useFieldAttrs() {\n const attrs = useAttrs()\n return {\n fieldClass: computed(() => attrs.class),\n fieldStyle: computed(() => attrs.style),\n controlAttrs: computed(() => {\n const { class: _class, style: _style, ...rest } = attrs\n return rest\n }),\n }\n}\n","<script setup lang=\"ts\" generic=\"T extends string | number | undefined = string\">\nimport type { InputHTMLAttributes } from 'vue'\n\nimport FormField from './FormField.vue'\nimport { useFieldAttrs } from '../composables/use-field-attrs'\nimport { useBoundValue } from '../composables/use-bound-value'\n\ndefineOptions({ inheritAttrs: false })\n\nconst { fieldClass, fieldStyle, controlAttrs } = useFieldAttrs()\n\n/**\n * A text field with its label, hint and error already wired to it.\n *\n * Every attribute of a native input — `placeholder`, `autocomplete`,\n * `inputmode` — goes to the input itself, not to the wrapper around it.\n */\nconst {\n modelValue = undefined,\n label,\n error = '',\n hint = '',\n type = 'text',\n labelHidden = false,\n size = 'md',\n variant = 'default',\n} = defineProps<\n {\n /** The value, with `v-model`. Its type is whatever you bind. */\n modelValue?: T | undefined\n label: string\n error?: string | undefined\n hint?: string | undefined\n /**\n * Hides the label visually but keeps it for assistive tech. For fields whose\n * surrounding row already names them — dropping the label entirely would\n * leave the input with no accessible name at all.\n */\n labelHidden?: boolean | undefined\n /**\n * Every type a text field can be, because the ones missing were the ones\n * apps needed: `date` and `search` were hand-written three times each and\n * `url` twice, in files that already imported this component.\n */\n type?:\n | 'text'\n | 'email'\n | 'password'\n | 'number'\n | 'search'\n | 'tel'\n | 'url'\n | 'date'\n | 'time'\n | 'datetime-local'\n | undefined\n /** `sm` for a field inside a row rather than in a form of its own. */\n size?: 'sm' | 'md' | undefined\n /**\n * `unstyled` keeps the wiring and drops the surface.\n *\n * The label, the generated id, `aria-describedby` and the error are what a\n * field is; the border and the height are what it looks like. A search box\n * inside a bordered row, a url field in an editor popover — those places\n * were hand-writing the whole thing to avoid the appearance, and losing the\n * wiring with it.\n *\n * The same reasoning as `BaseButton`'s `unstyled`, and the same test: a\n * primitive is finished when the app can take its behaviour without its\n * paint.\n */\n variant?: 'default' | 'unstyled' | undefined\n /* The input's own attributes — `placeholder`, `autocomplete`, `inputmode` —\n typed for strictTemplates and passed to the input through `$attrs`. Not\n `size` and `type`, which are this component's own. */\n } & /* @vue-ignore */ Omit<InputHTMLAttributes, 'size' | 'type'>\n>()\n\n/* The control keeps 16px at every size, and that is not a rounding of the\n scale — it is the rule. iOS zooms the viewport when a text field it is\n focusing has a font-size under 16px, and the page never zooms back. Both\n phone consumers had written `input { font-size: 16px }` into their base\n layer to stop exactly this, and a `text-sm` utility from here would have\n overridden it in every app at once.\n\n So `size` reaches the label and the spacing, through FormField, and leaves\n the typing target alone. `BaseSelect` is free to shrink: a select opens a\n native picker rather than a caret, and does not trigger the zoom. */\nconst CONTROL_CLASS = 'h-11 text-base'\n\n/**\n * A number field's value is a number.\n *\n * Typed to `string` alone, `type=\"number\"` forced the caller to keep a string\n * ref and convert on both sides of it — and a component you have to wrap in\n * order to use is one you write yourself instead, which is exactly what the\n * first numeric field tried to reach for it did.\n *\n * Generic rather than `string | number`, so the value is whatever the app\n * bound: a `ref('')` gets strings back and a `ref(0)` numbers, and a\n * `string | undefined` from a form library is accepted as it is.\n */\nconst emit = defineEmits<{ 'update:modelValue': [value: T] }>()\n// The destructured prop is typed by T's constraint, not by T itself — a\n// limit of destructuring in a generic component — so it is narrowed here.\nconst model = useBoundValue<T>(\n () => modelValue as T | undefined,\n (value) => emit('update:modelValue', value),\n)\n</script>\n\n<template>\n <FormField\n :class=\"fieldClass\"\n :style=\"fieldStyle\"\n :label=\"label\"\n :error=\"error\"\n :hint=\"hint\"\n :label-hidden=\"labelHidden\"\n :size=\"size\"\n >\n <template #default=\"{ id, describedBy, invalid }\">\n <input\n :id=\"id\"\n v-model=\"model\"\n :type=\"type\"\n :aria-invalid=\"invalid\"\n :aria-describedby=\"describedBy\"\n v-bind=\"controlAttrs\"\n :class=\"[\n variant === 'unstyled'\n ? ''\n : 'control text-ink rounded-card focus-visible:outline-primary px-3 focus-visible:outline-2 focus-visible:outline-offset-1',\n variant === 'unstyled' ? 'text-base' : CONTROL_CLASS,\n variant !== 'unstyled' && invalid ? 'border-negative' : '',\n ]\"\n />\n </template>\n </FormField>\n</template>\n","<script setup lang=\"ts\" generic=\"T extends string | number | undefined = string\">\nimport type { InputHTMLAttributes } from 'vue'\n\nimport FormField from './FormField.vue'\nimport { useFieldAttrs } from '../composables/use-field-attrs'\nimport { useBoundValue } from '../composables/use-bound-value'\n\ndefineOptions({ inheritAttrs: false })\n\nconst { fieldClass, fieldStyle, controlAttrs } = useFieldAttrs()\n\n/**\n * A text field with its label, hint and error already wired to it.\n *\n * Every attribute of a native input — `placeholder`, `autocomplete`,\n * `inputmode` — goes to the input itself, not to the wrapper around it.\n */\nconst {\n modelValue = undefined,\n label,\n error = '',\n hint = '',\n type = 'text',\n labelHidden = false,\n size = 'md',\n variant = 'default',\n} = defineProps<\n {\n /** The value, with `v-model`. Its type is whatever you bind. */\n modelValue?: T | undefined\n label: string\n error?: string | undefined\n hint?: string | undefined\n /**\n * Hides the label visually but keeps it for assistive tech. For fields whose\n * surrounding row already names them — dropping the label entirely would\n * leave the input with no accessible name at all.\n */\n labelHidden?: boolean | undefined\n /**\n * Every type a text field can be, because the ones missing were the ones\n * apps needed: `date` and `search` were hand-written three times each and\n * `url` twice, in files that already imported this component.\n */\n type?:\n | 'text'\n | 'email'\n | 'password'\n | 'number'\n | 'search'\n | 'tel'\n | 'url'\n | 'date'\n | 'time'\n | 'datetime-local'\n | undefined\n /** `sm` for a field inside a row rather than in a form of its own. */\n size?: 'sm' | 'md' | undefined\n /**\n * `unstyled` keeps the wiring and drops the surface.\n *\n * The label, the generated id, `aria-describedby` and the error are what a\n * field is; the border and the height are what it looks like. A search box\n * inside a bordered row, a url field in an editor popover — those places\n * were hand-writing the whole thing to avoid the appearance, and losing the\n * wiring with it.\n *\n * The same reasoning as `BaseButton`'s `unstyled`, and the same test: a\n * primitive is finished when the app can take its behaviour without its\n * paint.\n */\n variant?: 'default' | 'unstyled' | undefined\n /* The input's own attributes — `placeholder`, `autocomplete`, `inputmode` —\n typed for strictTemplates and passed to the input through `$attrs`. Not\n `size` and `type`, which are this component's own. */\n } & /* @vue-ignore */ Omit<InputHTMLAttributes, 'size' | 'type'>\n>()\n\n/* The control keeps 16px at every size, and that is not a rounding of the\n scale — it is the rule. iOS zooms the viewport when a text field it is\n focusing has a font-size under 16px, and the page never zooms back. Both\n phone consumers had written `input { font-size: 16px }` into their base\n layer to stop exactly this, and a `text-sm` utility from here would have\n overridden it in every app at once.\n\n So `size` reaches the label and the spacing, through FormField, and leaves\n the typing target alone. `BaseSelect` is free to shrink: a select opens a\n native picker rather than a caret, and does not trigger the zoom. */\nconst CONTROL_CLASS = 'h-11 text-base'\n\n/**\n * A number field's value is a number.\n *\n * Typed to `string` alone, `type=\"number\"` forced the caller to keep a string\n * ref and convert on both sides of it — and a component you have to wrap in\n * order to use is one you write yourself instead, which is exactly what the\n * first numeric field tried to reach for it did.\n *\n * Generic rather than `string | number`, so the value is whatever the app\n * bound: a `ref('')` gets strings back and a `ref(0)` numbers, and a\n * `string | undefined` from a form library is accepted as it is.\n */\nconst emit = defineEmits<{ 'update:modelValue': [value: T] }>()\n// The destructured prop is typed by T's constraint, not by T itself — a\n// limit of destructuring in a generic component — so it is narrowed here.\nconst model = useBoundValue<T>(\n () => modelValue as T | undefined,\n (value) => emit('update:modelValue', value),\n)\n</script>\n\n<template>\n <FormField\n :class=\"fieldClass\"\n :style=\"fieldStyle\"\n :label=\"label\"\n :error=\"error\"\n :hint=\"hint\"\n :label-hidden=\"labelHidden\"\n :size=\"size\"\n >\n <template #default=\"{ id, describedBy, invalid }\">\n <input\n :id=\"id\"\n v-model=\"model\"\n :type=\"type\"\n :aria-invalid=\"invalid\"\n :aria-describedby=\"describedBy\"\n v-bind=\"controlAttrs\"\n :class=\"[\n variant === 'unstyled'\n ? ''\n : 'control text-ink rounded-card focus-visible:outline-primary px-3 focus-visible:outline-2 focus-visible:outline-offset-1',\n variant === 'unstyled' ? 'text-base' : CONTROL_CLASS,\n variant !== 'unstyled' && invalid ? 'border-negative' : '',\n ]\"\n />\n </template>\n </FormField>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Sign in with Google, in Google's own clothes.\n *\n * The mark is inlined rather than themed, and that is not an oversight: Google's\n * brand guidelines require their colours, and a reader scanning for the button\n * they already know is scanning for those exact four. It is the one component\n * here that carries a colour value, and the reason is written on the path.\n */\nconst { label, disabled = false } = defineProps<{\n label: string\n /**\n * Greyed out and unclickable while a sign-in is already in flight.\n *\n * A real prop rather than a fallthrough attribute: the button needs to look\n * disabled as well as be it, and a bare `disabled` landing on the element by\n * itself gets the second half only.\n */\n disabled?: boolean | undefined\n}>()\n\nconst emit = defineEmits<{ click: [] }>()\n</script>\n\n<template>\n <button\n type=\"button\"\n :disabled=\"disabled\"\n class=\"control text-ink rounded-card hover:bg-muted focus-ring flex h-11 w-full items-center justify-center gap-2 text-sm font-medium transition-colors duration-(--duration-fast) active:scale-(--press-scale) disabled:pointer-events-none disabled:opacity-50\"\n @click=\"emit('click')\"\n >\n <!-- Google asks for its own mark, so it is inlined rather than themed. -->\n <svg class=\"size-4\" viewBox=\"0 0 48 48\" aria-hidden=\"true\">\n <path\n fill=\"#EA4335\"\n d=\"M24 9.5c3.5 0 6.6 1.2 9 3.6l6.7-6.7C35.6 2.7 30.2.5 24 .5 14.6.5 6.5 5.9 2.6 13.7l7.8 6.1C12.3 13.7 17.7 9.5 24 9.5z\"\n />\n <path\n fill=\"#4285F4\"\n d=\"M46.5 24.5c0-1.6-.1-3.1-.4-4.5H24v9h12.7c-.6 3-2.3 5.6-4.9 7.3l7.6 5.9c4.4-4.1 7.1-10.2 7.1-17.7z\"\n />\n <path\n fill=\"#FBBC05\"\n d=\"M10.4 28.2a14.6 14.6 0 0 1 0-8.4l-7.8-6.1a24 24 0 0 0 0 20.6l7.8-6.1z\"\n />\n <path\n fill=\"#34A853\"\n d=\"M24 47.5c6.2 0 11.5-2 15.4-5.6l-7.6-5.9c-2.1 1.4-4.8 2.3-7.8 2.3-6.3 0-11.7-4.2-13.6-10l-7.8 6.1C6.5 42.1 14.6 47.5 24 47.5z\"\n />\n </svg>\n {{ label }}\n </button>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Sign in with Google, in Google's own clothes.\n *\n * The mark is inlined rather than themed, and that is not an oversight: Google's\n * brand guidelines require their colours, and a reader scanning for the button\n * they already know is scanning for those exact four. It is the one component\n * here that carries a colour value, and the reason is written on the path.\n */\nconst { label, disabled = false } = defineProps<{\n label: string\n /**\n * Greyed out and unclickable while a sign-in is already in flight.\n *\n * A real prop rather than a fallthrough attribute: the button needs to look\n * disabled as well as be it, and a bare `disabled` landing on the element by\n * itself gets the second half only.\n */\n disabled?: boolean | undefined\n}>()\n\nconst emit = defineEmits<{ click: [] }>()\n</script>\n\n<template>\n <button\n type=\"button\"\n :disabled=\"disabled\"\n class=\"control text-ink rounded-card hover:bg-muted focus-ring flex h-11 w-full items-center justify-center gap-2 text-sm font-medium transition-colors duration-(--duration-fast) active:scale-(--press-scale) disabled:pointer-events-none disabled:opacity-50\"\n @click=\"emit('click')\"\n >\n <!-- Google asks for its own mark, so it is inlined rather than themed. -->\n <svg class=\"size-4\" viewBox=\"0 0 48 48\" aria-hidden=\"true\">\n <path\n fill=\"#EA4335\"\n d=\"M24 9.5c3.5 0 6.6 1.2 9 3.6l6.7-6.7C35.6 2.7 30.2.5 24 .5 14.6.5 6.5 5.9 2.6 13.7l7.8 6.1C12.3 13.7 17.7 9.5 24 9.5z\"\n />\n <path\n fill=\"#4285F4\"\n d=\"M46.5 24.5c0-1.6-.1-3.1-.4-4.5H24v9h12.7c-.6 3-2.3 5.6-4.9 7.3l7.6 5.9c4.4-4.1 7.1-10.2 7.1-17.7z\"\n />\n <path\n fill=\"#FBBC05\"\n d=\"M10.4 28.2a14.6 14.6 0 0 1 0-8.4l-7.8-6.1a24 24 0 0 0 0 20.6l7.8-6.1z\"\n />\n <path\n fill=\"#34A853\"\n d=\"M24 47.5c6.2 0 11.5-2 15.4-5.6l-7.6-5.9c-2.1 1.4-4.8 2.3-7.8 2.3-6.3 0-11.7-4.2-13.6-10l-7.8 6.1C6.5 42.1 14.6 47.5 24 47.5z\"\n />\n </svg>\n {{ label }}\n </button>\n</template>\n","import { nextTick, readonly, ref } from 'vue'\n\n/**\n * Saying something to a screen reader when nothing on screen has changed\n * enough to say it.\n *\n * The kit already has a live region in a dozen places — `ToastHost`,\n * `BaseSpinner`, `OfflineBanner`, `CopyButton` — but every one of them\n * belongs to a component that happens to be on screen. What has no home is\n * everything else: a filter that narrows a list to three results, a route\n * that changed under a client-side navigation, a draft that saved itself.\n * On screen all of those are obvious. To a reader they are silence.\n *\n * ── Three things this gets right that a hand-written region does not ──\n *\n * **The region exists before the text does.** A live region inserted into\n * the page together with its first message is usually not announced at\n * all: the reader has nothing to notice a change *to*. So `AnnounceHost`\n * renders both regions empty, from mount, and only the text inside them\n * ever changes. This is the single most common reason a hand-rolled\n * announcement does nothing, and it fails silently on every platform.\n *\n * **The same sentence twice is still said twice.** \"3 results\" after\n * \"3 results\" is not a mutation, so a reader says nothing — and the reader\n * has no idea the filter did anything. The text is cleared and set again\n * on the next tick, which is a change either way.\n *\n * **Polite by default.** `assertive` interrupts whatever is being read,\n * mid-word. That is right for \"connection lost\" and rude for \"saved\", and\n * a region that is always assertive trains the reader to resent it.\n *\n * ── What is deliberately not here ──\n *\n * **No text of its own.** Like everything else in the kit, the sentence is\n * the app's.\n *\n * **Not for anything on screen.** If there is a visible message, it should\n * be the live region — `BaseAlert`, `FormField`, a toast. This is only for\n * what a sighted reader learns from the screen itself.\n *\n * **A singleton, on purpose.** Two hosts would be two regions, and a\n * message would be read twice.\n *\n * @example\n * ```ts\n * const { announce } = useAnnounce()\n * watch(results, (rows) => announce(t('search.found', { count: rows.length })))\n * ```\n */\nexport interface AnnounceOptions {\n /**\n * Interrupt whatever is being read. For something the reader has to know\n * now — a connection lost, a session about to expire — and nothing else.\n */\n assertive?: boolean | undefined\n}\n\nconst polite = ref('')\nconst assertive = ref('')\n\n/** Long enough to be read, short enough that a reader landing in the region\n * later does not find a sentence about something that has since changed. */\nconst CLEAR_AFTER_MS = 7000\n\nlet timer: ReturnType<typeof setTimeout> | undefined\n\n/**\n * Says something once. Render one `AnnounceHost` in the app for it to reach a\n * reader at all.\n */\nexport function announce(message: string, { assertive: urgent = false }: AnnounceOptions = {}) {\n const target = urgent ? assertive : polite\n\n if (timer !== undefined) clearTimeout(timer)\n\n /* Cleared first, then set on the next tick. Setting the same string again\n is not a change, and a reader announces changes — so without this, the\n second \"3 results\" is silence. */\n polite.value = ''\n assertive.value = ''\n\n void nextTick(() => {\n target.value = message\n\n timer = setTimeout(() => {\n target.value = ''\n timer = undefined\n }, CLEAR_AFTER_MS)\n })\n}\n\nexport function useAnnounce() {\n return {\n announce,\n /** What the regions are saying. `AnnounceHost` renders these; apps read them in tests. */\n polite: readonly(polite),\n assertive: readonly(assertive),\n }\n}\n"],"mappings":"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"}