@adia-ai/web-components 0.6.36 → 0.6.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/components/badge/badge.a2ui.json +10 -0
  3. package/components/badge/badge.css +70 -0
  4. package/components/badge/badge.yaml +20 -0
  5. package/components/blockquote/blockquote.a2ui.json +121 -0
  6. package/components/blockquote/blockquote.class.js +68 -0
  7. package/components/blockquote/blockquote.css +46 -0
  8. package/components/blockquote/blockquote.d.ts +31 -0
  9. package/components/blockquote/blockquote.js +17 -0
  10. package/components/blockquote/blockquote.yaml +124 -0
  11. package/components/button/button.css +11 -3
  12. package/components/calendar-picker/calendar-picker.a2ui.json +15 -0
  13. package/components/calendar-picker/calendar-picker.class.js +7 -1
  14. package/components/calendar-picker/calendar-picker.yaml +14 -0
  15. package/components/color-input/color-input.a2ui.json +2 -2
  16. package/components/color-input/color-input.class.js +9 -2
  17. package/components/color-input/color-input.yaml +2 -2
  18. package/components/combobox/combobox.class.js +4 -0
  19. package/components/context-menu/context-menu.a2ui.json +159 -0
  20. package/components/context-menu/context-menu.class.js +275 -0
  21. package/components/context-menu/context-menu.css +56 -0
  22. package/components/context-menu/context-menu.d.ts +70 -0
  23. package/components/context-menu/context-menu.js +17 -0
  24. package/components/context-menu/context-menu.yaml +136 -0
  25. package/components/date-range-picker/date-range-picker.a2ui.json +15 -0
  26. package/components/date-range-picker/date-range-picker.class.js +2 -0
  27. package/components/date-range-picker/date-range-picker.yaml +14 -0
  28. package/components/datetime-picker/datetime-picker.a2ui.json +15 -0
  29. package/components/datetime-picker/datetime-picker.class.js +3 -1
  30. package/components/datetime-picker/datetime-picker.d.ts +2 -0
  31. package/components/datetime-picker/datetime-picker.yaml +14 -0
  32. package/components/empty-state/empty-state.class.js +2 -0
  33. package/components/feed/feed.class.js +13 -5
  34. package/components/feed/feed.css +14 -0
  35. package/components/index.js +9 -0
  36. package/components/integration-card/integration-card.class.js +9 -0
  37. package/components/integration-card/integration-card.test.js +4 -3
  38. package/components/nav-group/nav-group.css +7 -1
  39. package/components/number-format/number-format.a2ui.json +180 -0
  40. package/components/number-format/number-format.class.js +96 -0
  41. package/components/number-format/number-format.css +18 -0
  42. package/components/number-format/number-format.d.ts +68 -0
  43. package/components/number-format/number-format.js +17 -0
  44. package/components/number-format/number-format.yaml +204 -0
  45. package/components/pagination/pagination.a2ui.json +19 -2
  46. package/components/pagination/pagination.class.js +90 -37
  47. package/components/pagination/pagination.css +32 -127
  48. package/components/pagination/pagination.d.ts +8 -2
  49. package/components/pagination/pagination.test.js +195 -0
  50. package/components/pagination/pagination.yaml +22 -1
  51. package/components/password-strength/password-strength.a2ui.json +152 -0
  52. package/components/password-strength/password-strength.class.js +157 -0
  53. package/components/password-strength/password-strength.css +80 -0
  54. package/components/password-strength/password-strength.d.ts +59 -0
  55. package/components/password-strength/password-strength.js +17 -0
  56. package/components/password-strength/password-strength.yaml +153 -0
  57. package/components/popover/popover.css +43 -23
  58. package/components/popover/popover.yaml +8 -4
  59. package/components/qr-code/QR-TEST.svg +4 -0
  60. package/components/qr-code/qr-code.a2ui.json +154 -0
  61. package/components/qr-code/qr-code.class.js +129 -0
  62. package/components/qr-code/qr-code.css +41 -0
  63. package/components/qr-code/qr-code.d.ts +83 -0
  64. package/components/qr-code/qr-code.js +17 -0
  65. package/components/qr-code/qr-code.yaml +203 -0
  66. package/components/qr-code/qr-encoder.js +633 -0
  67. package/components/relative-time/relative-time.a2ui.json +120 -0
  68. package/components/relative-time/relative-time.class.js +136 -0
  69. package/components/relative-time/relative-time.css +22 -0
  70. package/components/relative-time/relative-time.d.ts +51 -0
  71. package/components/relative-time/relative-time.js +17 -0
  72. package/components/relative-time/relative-time.yaml +133 -0
  73. package/components/segmented/segmented.class.js +5 -1
  74. package/components/select/select.class.js +4 -0
  75. package/components/skip-nav/skip-nav.a2ui.json +92 -0
  76. package/components/skip-nav/skip-nav.class.js +45 -0
  77. package/components/skip-nav/skip-nav.css +54 -0
  78. package/components/skip-nav/skip-nav.d.ts +27 -0
  79. package/components/skip-nav/skip-nav.js +12 -0
  80. package/components/skip-nav/skip-nav.yaml +68 -0
  81. package/components/slider/slider.a2ui.json +16 -1
  82. package/components/slider/slider.class.js +264 -122
  83. package/components/slider/slider.css +82 -2
  84. package/components/slider/slider.d.ts +19 -3
  85. package/components/slider/slider.test.js +55 -0
  86. package/components/slider/slider.yaml +28 -6
  87. package/components/table/table.class.js +29 -6
  88. package/components/table/table.css +31 -4
  89. package/components/table-toolbar/table-toolbar.class.js +3 -1
  90. package/components/tag/tag.a2ui.json +3 -2
  91. package/components/tag/tag.css +35 -11
  92. package/components/tag/tag.d.ts +14 -0
  93. package/components/tag/tag.test.js +35 -11
  94. package/components/tag/tag.yaml +13 -7
  95. package/components/toast/toast.class.js +12 -4
  96. package/components/toc/toc.a2ui.json +159 -0
  97. package/components/toc/toc.class.js +222 -0
  98. package/components/toc/toc.css +92 -0
  99. package/components/toc/toc.d.ts +61 -0
  100. package/components/toc/toc.js +17 -0
  101. package/components/toc/toc.yaml +180 -0
  102. package/components/toolbar/toolbar.class.js +3 -0
  103. package/components/visually-hidden/visually-hidden.a2ui.json +71 -0
  104. package/components/visually-hidden/visually-hidden.class.js +14 -0
  105. package/components/visually-hidden/visually-hidden.css +25 -0
  106. package/components/visually-hidden/visually-hidden.d.ts +26 -0
  107. package/components/visually-hidden/visually-hidden.js +12 -0
  108. package/components/visually-hidden/visually-hidden.yaml +54 -0
  109. package/core/anchor.js +19 -3
  110. package/dist/web-components.min.css +1 -1
  111. package/dist/web-components.min.js +100 -89
  112. package/package.json +1 -1
  113. package/styles/colors/semantics.css +11 -2
  114. package/styles/components.css +9 -0
  115. package/styles/resets.css +10 -0
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Non-side-effect class export for `<password-strength-ui>`.
3
+ *
4
+ * Importing this file gives you the class without auto-registering the
5
+ * tag. Useful for test isolation, subclassing with tag-name override,
6
+ * or selective composition.
7
+ *
8
+ * The auto-register path stays at `@adia-ai/web-components/components/password-strength`
9
+ * (which imports this file + calls `defineIfFree()`).
10
+ *
11
+ * @see ../../USAGE.md#registration--auto-vs-explicit
12
+ */
13
+
14
+ /**
15
+ * <password-strength-ui></password-strength-ui>
16
+ *
17
+ * Layout:
18
+ * ▮▮▮▯ ← 4-segment bar, lit segments coloured per score
19
+ * Good ← optional text label
20
+ *
21
+ * Pair with `<input-ui type="password">`:
22
+ * input.addEventListener('input', e => meter.value = e.target.value);
23
+ *
24
+ * Score buckets (heuristic):
25
+ * 0 Weak — short or single-class
26
+ * 1 Fair — 8+ chars OR mixed classes
27
+ * 2 Good — 12+ chars + ≥3 classes
28
+ * 3 Strong — 16+ chars + 4 classes
29
+ *
30
+ * NOT zxcvbn — this is a tiny first-line heuristic. For security-
31
+ * critical surfaces, consumers can override the score by listening
32
+ * to `input` on the field and setting their own `data-score` on the
33
+ * meter, OR by extending this class.
34
+ *
35
+ * Security: `value` is JS-property only. NOT reflected to a DOM
36
+ * attribute; the password never appears in rendered HTML.
37
+ */
38
+
39
+ import { UIElement } from '../../core/element.js';
40
+
41
+ const LABELS = ['Weak', 'Fair', 'Good', 'Strong'];
42
+
43
+ /**
44
+ * Compute a 0..3 score for the given password string.
45
+ * Returns -1 for empty (sentinel — caller uses to grey out the bar).
46
+ *
47
+ * Scoring rubric:
48
+ * +1 length >= 8
49
+ * +1 length >= 12
50
+ * +1 length >= 16
51
+ * +1 ≥ 2 character classes (lower / upper / digit / symbol)
52
+ * +1 ≥ 3 character classes
53
+ * +1 all 4 character classes
54
+ * -1 contains 3+ same-char run (e.g. "aaa")
55
+ * -1 password is too short (< 8 chars) — clamps score to ≤ 0
56
+ *
57
+ * Clamped to [0, 3].
58
+ */
59
+ function scorePassword(pwd) {
60
+ if (!pwd) return -1;
61
+ let score = 0;
62
+ if (pwd.length >= 8) score++;
63
+ if (pwd.length >= 12) score++;
64
+ if (pwd.length >= 16) score++;
65
+
66
+ const classes = [
67
+ /[a-z]/.test(pwd),
68
+ /[A-Z]/.test(pwd),
69
+ /[0-9]/.test(pwd),
70
+ /[^a-zA-Z0-9]/.test(pwd),
71
+ ].filter(Boolean).length;
72
+ if (classes >= 2) score++;
73
+ if (classes >= 3) score++;
74
+ if (classes === 4) score++;
75
+
76
+ // Penalty: same-char run of 3+ (aaa, 111)
77
+ if (/(.)\1{2,}/.test(pwd)) score--;
78
+
79
+ // Hard floor for very short passwords — never above "Weak"
80
+ if (pwd.length < 8) score = Math.min(score, 0);
81
+
82
+ return Math.max(0, Math.min(3, score));
83
+ }
84
+
85
+ export class UIPasswordStrength extends UIElement {
86
+ static properties = {
87
+ // JS-property only — no `reflect: true` so the password never
88
+ // appears in the DOM attribute set. `dynamic: true` keeps it out
89
+ // of any static-attribute extraction the a2ui pipeline does.
90
+ value: { type: String, default: '' },
91
+ minScore: { type: Number, default: 2, reflect: true, attribute: 'min-score' },
92
+ showLabel: { type: Boolean, default: true, reflect: true, attribute: 'show-label' },
93
+ };
94
+
95
+ static template = () => null;
96
+
97
+ #lastEmittedScore = -2; // sentinel — different from any valid score / -1
98
+
99
+ connected() {
100
+ super.connected();
101
+ if (!this.querySelector('[slot="bar"]')) {
102
+ this.innerHTML = `
103
+ <div slot="bar" aria-hidden="true">
104
+ <div slot="segment" data-i="0"></div>
105
+ <div slot="segment" data-i="1"></div>
106
+ <div slot="segment" data-i="2"></div>
107
+ <div slot="segment" data-i="3"></div>
108
+ </div>
109
+ <span slot="label" aria-live="polite"></span>
110
+ `;
111
+ }
112
+ // ARIA meter semantics
113
+ this.setAttribute('role', 'meter');
114
+ this.setAttribute('aria-valuemin', '0');
115
+ this.setAttribute('aria-valuemax', '3');
116
+ }
117
+
118
+ render() {
119
+ const score = scorePassword(this.value);
120
+
121
+ if (score < 0) {
122
+ this.removeAttribute('data-score');
123
+ this.removeAttribute('aria-valuenow');
124
+ this.setAttribute('aria-valuetext', 'Empty');
125
+ } else {
126
+ this.setAttribute('data-score', String(score));
127
+ this.setAttribute('aria-valuenow', String(score));
128
+ this.setAttribute('aria-valuetext', LABELS[score]);
129
+ }
130
+
131
+ // Update segment lit state via [data-lit]
132
+ const segments = this.querySelectorAll('[slot="segment"]');
133
+ segments.forEach((seg, i) => {
134
+ if (score >= 0 && i <= score) seg.setAttribute('data-lit', '');
135
+ else seg.removeAttribute('data-lit');
136
+ });
137
+
138
+ // Update label text
139
+ const labelEl = this.querySelector('[slot="label"]');
140
+ if (labelEl) {
141
+ labelEl.textContent = score >= 0 && this.showLabel !== false ? LABELS[score] : '';
142
+ }
143
+
144
+ // Emit score-change only on bucket transitions
145
+ if (score !== this.#lastEmittedScore) {
146
+ this.#lastEmittedScore = score;
147
+ this.dispatchEvent(new CustomEvent('score-change', {
148
+ bubbles: true,
149
+ detail: {
150
+ score,
151
+ label: score >= 0 ? LABELS[score] : '',
152
+ satisfied: score >= this.minScore,
153
+ },
154
+ }));
155
+ }
156
+ }
157
+ }
@@ -0,0 +1,80 @@
1
+ /* ═══════════════════════════════════════════════════════════════
2
+ PASSWORD-STRENGTH-UI — 4-segment strength meter + label.
3
+ ═══════════════════════════════════════════════════════════════ */
4
+
5
+ @scope (password-strength-ui) {
6
+ :where(:scope) {
7
+ /* ── Layout ── */
8
+ --password-strength-gap-default: var(--a-space-1);
9
+ --password-strength-segment-height-default: 4px;
10
+ --password-strength-radius-default: var(--a-radius-full);
11
+ --password-strength-stack-gap-default: var(--a-space-1);
12
+
13
+ /* ── Colors ── */
14
+ --password-strength-segment-bg-default: var(--a-canvas-1-scrim);
15
+ --password-strength-color-weak-default: var(--a-danger-bg);
16
+ --password-strength-color-fair-default: var(--a-warning-bg);
17
+ --password-strength-color-good-default: var(--a-info-bg);
18
+ --password-strength-color-strong-default: var(--a-success-bg);
19
+
20
+ /* ── Typography ── */
21
+ --password-strength-label-fg-default: var(--a-fg-muted);
22
+ --password-strength-label-size-default: var(--a-ui-sm);
23
+ --password-strength-label-weight-default: var(--a-weight-medium);
24
+
25
+ /* ── Per-score label color (mirrors the lit-segment color so the
26
+ label visually links to the bar fill state). ── */
27
+ --password-strength-label-fg-weak-default: var(--password-strength-color-weak, var(--password-strength-color-weak-default));
28
+ --password-strength-label-fg-fair-default: var(--password-strength-color-fair, var(--password-strength-color-fair-default));
29
+ --password-strength-label-fg-good-default: var(--password-strength-color-good, var(--password-strength-color-good-default));
30
+ --password-strength-label-fg-strong-default: var(--password-strength-color-strong, var(--password-strength-color-strong-default));
31
+ }
32
+
33
+ /* ── Host ── */
34
+ :scope {
35
+ box-sizing: border-box;
36
+ display: flex;
37
+ flex-direction: column;
38
+ gap: var(--password-strength-stack-gap, var(--password-strength-stack-gap-default));
39
+ width: 100%;
40
+ }
41
+
42
+ /* ── Bar ── */
43
+ [slot="bar"] {
44
+ display: grid;
45
+ grid-template-columns: repeat(4, 1fr);
46
+ gap: var(--password-strength-gap, var(--password-strength-gap-default));
47
+ }
48
+
49
+ [slot="segment"] {
50
+ height: var(--password-strength-segment-height, var(--password-strength-segment-height-default));
51
+ background: var(--password-strength-segment-bg, var(--password-strength-segment-bg-default));
52
+ border-radius: var(--password-strength-radius, var(--password-strength-radius-default));
53
+ transition: background var(--a-duration-fast) var(--a-easing);
54
+ }
55
+
56
+ /* ── Lit segments — color depends on host [data-score] ── */
57
+ :scope[data-score="0"] [slot="segment"][data-lit] { background: var(--password-strength-color-weak, var(--password-strength-color-weak-default)); }
58
+ :scope[data-score="1"] [slot="segment"][data-lit] { background: var(--password-strength-color-fair, var(--password-strength-color-fair-default)); }
59
+ :scope[data-score="2"] [slot="segment"][data-lit] { background: var(--password-strength-color-good, var(--password-strength-color-good-default)); }
60
+ :scope[data-score="3"] [slot="segment"][data-lit] { background: var(--password-strength-color-strong, var(--password-strength-color-strong-default)); }
61
+
62
+ /* ── Label ── */
63
+ [slot="label"] {
64
+ font-size: var(--password-strength-label-size, var(--password-strength-label-size-default));
65
+ font-weight: var(--password-strength-label-weight, var(--password-strength-label-weight-default));
66
+ color: var(--password-strength-label-fg, var(--password-strength-label-fg-default));
67
+ min-height: 1lh; /* prevent layout jump when label appears */
68
+ }
69
+
70
+ :scope[data-score="0"] [slot="label"] { color: var(--password-strength-label-fg-weak, var(--password-strength-label-fg-weak-default)); }
71
+ :scope[data-score="1"] [slot="label"] { color: var(--password-strength-label-fg-fair, var(--password-strength-label-fg-fair-default)); }
72
+ :scope[data-score="2"] [slot="label"] { color: var(--password-strength-label-fg-good, var(--password-strength-label-fg-good-default)); }
73
+ :scope[data-score="3"] [slot="label"] { color: var(--password-strength-label-fg-strong, var(--password-strength-label-fg-strong-default)); }
74
+
75
+ /* ── Hide label when [show-label] is false-y. The class.js clears
76
+ the textContent in that case; this provides belt-and-suspenders. ── */
77
+ :scope:not([show-label]) [slot="label"] {
78
+ display: none;
79
+ }
80
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * `<password-strength-ui>` — Visual strength indicator for password inputs — 4-segment bar (weak /
3
+ fair / good / strong) computed from a heuristic combining length,
4
+ character-class diversity, and repeat-pattern penalty. Pairs with
5
+ `<input-ui type="password">` via JS:
6
+ input.addEventListener('input', e => meter.value = e.target.value)
7
+ Read-only display primitive — no form participation. Emits a
8
+ `score-change` event when the bucket changes so consumers can gate
9
+ a submit button on `detail.satisfied` (score ≥ min-score).
10
+
11
+ **Security note:** [value] is held as a JS property only, NOT
12
+ reflected to a DOM attribute. The element stamps the bar + label
13
+ from the property; the password never appears in the rendered HTML.
14
+ Do not set the value via setAttribute (it will be no-op).
15
+
16
+ *
17
+ * @see https://ui-kit.exe.xyz/site/components/password-strength
18
+ *
19
+ * Type declarations generated by scripts/build/dts-codegen.mjs from
20
+ * the component's `.a2ui.json` sidecar(s). Edit the source `.yaml`,
21
+ * run `npm run build:components`, then `npm run codegen:dts` to
22
+ * regenerate; or hand-author this file fully if rich event types are
23
+ * needed beyond what the yaml `events:` block can express.
24
+ */
25
+
26
+ import { UIElement } from '../../core/element.js';
27
+
28
+ export interface PasswordStrengthScoreChangeEventDetail {
29
+ label: string;
30
+ satisfied: string;
31
+ score: string;
32
+ }
33
+
34
+ export type PasswordStrengthScoreChangeEvent = CustomEvent<PasswordStrengthScoreChangeEventDetail>;
35
+
36
+ export class UIPasswordStrength extends UIElement {
37
+ /** Minimum acceptable score (0–3). The `score-change` event's
38
+ `detail.satisfied` boolean reflects whether the current score
39
+ meets this threshold. Useful for gating a submit button.
40
+ */
41
+ minScore: number;
42
+ /** Display the textual score label ("Weak" / "Fair" / "Good" /
43
+ "Strong") below the bar. Defaults to true.
44
+ */
45
+ showLabel: boolean;
46
+ /** The password string to score. JS-property only — NOT reflected
47
+ to a DOM attribute (so the password never leaks into rendered
48
+ HTML). Wire to an input via `input.addEventListener('input', e =>
49
+ meter.value = e.target.value)`.
50
+ */
51
+ value: string;
52
+
53
+ addEventListener<K extends keyof HTMLElementEventMap>(
54
+ type: K,
55
+ listener: (this: UIPasswordStrength, ev: HTMLElementEventMap[K]) => unknown,
56
+ options?: boolean | AddEventListenerOptions,
57
+ ): void;
58
+ addEventListener(type: 'score-change', listener: (ev: PasswordStrengthScoreChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
59
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `<password-strength-ui>` — auto-registers the tag on import.
3
+ *
4
+ * For non-side-effect class import (test isolation, tag override), use
5
+ * the `class` subpath:
6
+ *
7
+ * import { UIPasswordStrength } from '@adia-ai/web-components/components/password-strength/class';
8
+ *
9
+ * @see ../../USAGE.md#registration--auto-vs-explicit
10
+ */
11
+
12
+ import { defineIfFree } from '../../core/register.js';
13
+ import { UIPasswordStrength } from './password-strength.class.js';
14
+
15
+ defineIfFree('password-strength-ui', UIPasswordStrength);
16
+
17
+ export { UIPasswordStrength };
@@ -0,0 +1,153 @@
1
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
2
+ name: UIPasswordStrength
3
+ tag: password-strength-ui
4
+ status: stable
5
+ component: PasswordStrength
6
+ category: display
7
+ version: 1
8
+ description: |
9
+ Visual strength indicator for password inputs — 4-segment bar (weak /
10
+ fair / good / strong) computed from a heuristic combining length,
11
+ character-class diversity, and repeat-pattern penalty. Pairs with
12
+ `<input-ui type="password">` via JS:
13
+ input.addEventListener('input', e => meter.value = e.target.value)
14
+ Read-only display primitive — no form participation. Emits a
15
+ `score-change` event when the bucket changes so consumers can gate
16
+ a submit button on `detail.satisfied` (score ≥ min-score).
17
+
18
+ **Security note:** [value] is held as a JS property only, NOT
19
+ reflected to a DOM attribute. The element stamps the bar + label
20
+ from the property; the password never appears in the rendered HTML.
21
+ Do not set the value via setAttribute (it will be no-op).
22
+ props:
23
+ value:
24
+ description: |
25
+ The password string to score. JS-property only — NOT reflected
26
+ to a DOM attribute (so the password never leaks into rendered
27
+ HTML). Wire to an input via `input.addEventListener('input', e =>
28
+ meter.value = e.target.value)`.
29
+ type: string
30
+ default: ""
31
+ dynamic: true
32
+ minScore:
33
+ description: |
34
+ Minimum acceptable score (0–3). The `score-change` event's
35
+ `detail.satisfied` boolean reflects whether the current score
36
+ meets this threshold. Useful for gating a submit button.
37
+ type: number
38
+ default: 2
39
+ reflect: true
40
+ attribute: min-score
41
+ showLabel:
42
+ description: |
43
+ Display the textual score label ("Weak" / "Fair" / "Good" /
44
+ "Strong") below the bar. Defaults to true.
45
+ type: boolean
46
+ default: true
47
+ reflect: true
48
+ attribute: show-label
49
+ events:
50
+ score-change:
51
+ description: Fired when the computed score crosses a bucket boundary (not on every keystroke). Detail carries the new score + label + satisfied flag.
52
+ detail:
53
+ score: number
54
+ label: string
55
+ satisfied: boolean
56
+ slots:
57
+ default:
58
+ description: Optional area for requirements checklist content (e.g. <ul> of "at least 8 characters", "mixed case", etc.).
59
+ states:
60
+ - name: idle
61
+ description: Default — no value set, all segments grey.
62
+ - name: scored
63
+ description: Value present; segments lit per score 0..3.
64
+ attribute: data-score
65
+ tokens:
66
+ --password-strength-segment-height:
67
+ description: Height of each bar segment.
68
+ default: 4px
69
+ --password-strength-gap:
70
+ description: Gap between segments.
71
+ default: var(--a-space-1)
72
+ --password-strength-radius:
73
+ description: Segment border radius.
74
+ default: var(--a-radius-full)
75
+ --password-strength-segment-bg:
76
+ description: Unlit segment background (track color).
77
+ default: var(--a-canvas-1-scrim)
78
+ --password-strength-color-weak:
79
+ description: Color for score=0 lit segments.
80
+ default: var(--a-danger-bg)
81
+ --password-strength-color-fair:
82
+ description: Color for score=1 lit segments.
83
+ default: var(--a-warning-bg)
84
+ --password-strength-color-good:
85
+ description: Color for score=2 lit segments.
86
+ default: var(--a-info-bg)
87
+ --password-strength-color-strong:
88
+ description: Color for score=3 lit segments.
89
+ default: var(--a-success-bg)
90
+ --password-strength-label-fg:
91
+ description: Label text color.
92
+ default: var(--a-fg-muted)
93
+ --password-strength-label-size:
94
+ description: Label font size.
95
+ default: var(--a-ui-sm)
96
+ a2ui:
97
+ rules:
98
+ - rule: "Pair with <input-ui type=password> via a JS listener (input.addEventListener('input', e => meter.value = e.target.value)). The meter does NOT participate in form data — it is a display indicator."
99
+ reason: "Pairing contract."
100
+ - rule: "The score is 0 (Weak) / 1 (Fair) / 2 (Good) / 3 (Strong). [min-score] sets the threshold for the `satisfied` boolean in score-change events. Use the boolean to gate a submit button."
101
+ reason: "Score semantics + threshold contract."
102
+ - rule: "Do NOT set value via setAttribute — value is JS-property only and never appears in rendered HTML (security: avoids leaking the password into the DOM)."
103
+ reason: "Security contract."
104
+ anti_patterns:
105
+ - wrong: |
106
+ <password-strength-ui value="hunter2"></password-strength-ui>
107
+ why: |
108
+ `value` is JS-property only; the attribute is ignored. The
109
+ password also shouldn't be authored into HTML at all (server
110
+ template / static page) — that defeats the purpose.
111
+ fix: |
112
+ <password-strength-ui id="m"></password-strength-ui>
113
+ <script>document.getElementById('m').value = pwd;</script>
114
+ examples:
115
+ - name: paired-with-input
116
+ description: Standard pairing with input-ui[type=password] via input listener.
117
+ a2ui: |
118
+ [
119
+ {
120
+ "id": "field",
121
+ "component": "Field",
122
+ "label": "Password",
123
+ "children": ["pwd", "meter"]
124
+ },
125
+ {
126
+ "id": "pwd",
127
+ "component": "Input",
128
+ "type": "password",
129
+ "name": "password"
130
+ },
131
+ {
132
+ "id": "meter",
133
+ "component": "PasswordStrength"
134
+ }
135
+ ]
136
+ keywords:
137
+ - password-strength
138
+ - strength-meter
139
+ - password
140
+ - strength
141
+ - meter
142
+ - security
143
+ synonyms:
144
+ password:
145
+ - password-strength
146
+ - strength-meter
147
+ strength:
148
+ - password-strength
149
+ - meter
150
+ related:
151
+ - input
152
+ - field
153
+ - progress
@@ -5,15 +5,11 @@
5
5
  --popover-py-default: var(--a-space-2);
6
6
  --popover-radius-default: var(--a-radius-lg);
7
7
 
8
- /* ── Colors ── */
8
+ /* ── Colors (default panel chrome — opt out per `:has(>card-ui)` rule
9
+ below or by setting tokens to transparent / 0) ── */
9
10
  --popover-bg-default: var(--a-bg-subtle);
10
11
  --popover-border-default: var(--a-border-subtle);
11
12
  --popover-shadow-default: var(--a-shadow-lg);
12
- --popover-fg-default: var(--a-fg);
13
-
14
- /* ── Typography ── */
15
- --popover-font-default: var(--a-font-family);
16
- --popover-font-size-default: var(--a-ui-size);
17
13
  }
18
14
 
19
15
  :scope {
@@ -34,25 +30,55 @@
34
30
  display: none !important;
35
31
  }
36
32
 
33
+ /* ── popover-ui's CSS opinions, narrowly scoped ──
34
+ *
35
+ * What's HERE and why:
36
+ * • [slot="content"]:not(:popover-open) display:none — required Popover
37
+ * API mechanics (closed-state visibility for slotted children).
38
+ * • [slot="content"] margin: 0 — reset UA stylesheet's `margin: auto`
39
+ * on popover elements (otherwise popover centers in viewport).
40
+ * • [slot="content"] box-sizing — universal sanity.
41
+ * • Default panel chrome — defensive for the bare-HTML case
42
+ * (<div slot="content">Hello</div> needs to be visible against the
43
+ * page). Opt out by putting a surface primitive (card-ui / drawer-ui
44
+ * / menu-ui) as the only child — they own their own chrome.
45
+ * • Light enter/exit animation (opacity + 4px translate) tied to the
46
+ * :popover-open state; honors prefers-reduced-motion.
47
+ *
48
+ * What's deliberately NOT here:
49
+ * • width / max-width / max-height / overflow-y — set by core/anchor.js
50
+ * inline during anchoring; those are the anchor module's contract,
51
+ * not the shell's.
52
+ * • font-family / font-size / color — inherit from the page naturally.
53
+ * • Prose first/last-child margin resets — touched consumer DOM.
54
+ */
37
55
  [slot="content"] {
38
56
  box-sizing: border-box;
57
+ /* Reset UA stylesheet's `[popover]` defaults — it sets `margin: auto`
58
+ (centers in viewport) AND `padding: 0.25em` (~3.75 px at default
59
+ font-size, leaks into card-ui / drawer-ui consumers and looks like
60
+ phantom inset between popover edge and inner surface). Both must
61
+ be zeroed; the chrome rule below re-applies padding only when no
62
+ surface primitive is in the slot. */
39
63
  margin: 0;
64
+ padding: 0;
65
+ opacity: 1;
66
+ translate: 0 0;
67
+ transition: opacity var(--a-duration-fast) var(--a-easing-out),
68
+ translate var(--a-duration-fast) var(--a-easing-out);
69
+ }
70
+
71
+ /* Skip chrome when the slot IS a surface primitive itself
72
+ (`<card-ui slot="content">…`) AND when it CONTAINS one as the only
73
+ child (`<div slot="content"><card-ui>…</card-ui></div>`). Both
74
+ shapes appear in consumer code; both should let the inner surface
75
+ own padding + bg + border + radius + shadow. */
76
+ [slot="content"]:not(card-ui):not(drawer-ui):not(menu-ui):not(:has(> card-ui:only-child)):not(:has(> drawer-ui:only-child)):not(:has(> menu-ui:only-child)) {
40
77
  padding: var(--popover-py, var(--popover-py-default)) var(--popover-px, var(--popover-px-default));
41
78
  background: var(--popover-bg, var(--popover-bg-default));
42
79
  border: 1px solid var(--popover-border, var(--popover-border-default));
43
80
  border-radius: var(--popover-radius, var(--popover-radius-default));
44
81
  box-shadow: var(--popover-shadow, var(--popover-shadow-default));
45
- font-family: var(--popover-font, var(--popover-font-default));
46
- font-size: var(--popover-font-size, var(--popover-font-size-default));
47
- color: var(--popover-fg, var(--popover-fg-default));
48
- max-height: calc(100vh - 3rem);
49
- overflow-y: auto;
50
- /* Fade + lift in on first paint. @starting-style is the initial frame
51
- browsers paint before the popover transitions to its open state. */
52
- opacity: 1;
53
- translate: 0 0;
54
- transition: opacity var(--a-duration-fast) var(--a-easing-out),
55
- translate var(--a-duration-fast) var(--a-easing-out);
56
82
  }
57
83
 
58
84
  [slot="content"]:popover-open {
@@ -65,10 +91,4 @@
65
91
  @media (prefers-reduced-motion: reduce) {
66
92
  [slot="content"] { transition: none; }
67
93
  }
68
-
69
- /* Collapse default margins on the first/last block child so the
70
- content sits snug inside --popover-py (no phantom extra space
71
- below <p>, <h*>, etc.). */
72
- [slot="content"] > :first-child { margin-block-start: 0; }
73
- [slot="content"] > :last-child { margin-block-end: 0; }
74
94
  }
@@ -76,10 +76,14 @@ a2ui:
76
76
  everything else (inline forms, color pickers, theme panels,
77
77
  export menus with non-action content).
78
78
  - >-
79
- Use [placement="bottom-end"] for topbar overflow / settings
80
- buttons; [placement="bottom-start"] for inline form fields;
81
- top-* variants when trigger sits low in the viewport
82
- (statusbar). [gap] (default 4px) controls offset from anchor.
79
+ Placement convention (ADR-0034): default `bottom` centers under
80
+ the trigger — correct for wide pickers (calendar, color,
81
+ date-range, filter forms). Use `bottom-start` for trigger-width
82
+ menus (action lists, listboxes, breadcrumb overflow popover
83
+ width ≈ trigger width). Use `bottom-end` only when the trigger
84
+ sits at the right edge of a container by construction (toolbar
85
+ spillover). Use `top-*` when the trigger sits low in the
86
+ viewport (statusbar). [gap] (default 4px) sets offset from anchor.
83
87
  - >-
84
88
  [trigger="hover"] is for non-essential disclosure only — never
85
89
  use it for popovers containing inputs, destructive actions, or
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 330 330" width="330" height="330" shape-rendering="crispEdges">
2
+ <rect width="100%" height="100%" fill="#fff"/>
3
+ <g fill="#000"><rect x="40" y="40" width="10" height="10"/><rect x="50" y="40" width="10" height="10"/><rect x="60" y="40" width="10" height="10"/><rect x="70" y="40" width="10" height="10"/><rect x="80" y="40" width="10" height="10"/><rect x="90" y="40" width="10" height="10"/><rect x="100" y="40" width="10" height="10"/><rect x="120" y="40" width="10" height="10"/><rect x="150" y="40" width="10" height="10"/><rect x="170" y="40" width="10" height="10"/><rect x="180" y="40" width="10" height="10"/><rect x="190" y="40" width="10" height="10"/><rect x="220" y="40" width="10" height="10"/><rect x="230" y="40" width="10" height="10"/><rect x="240" y="40" width="10" height="10"/><rect x="250" y="40" width="10" height="10"/><rect x="260" y="40" width="10" height="10"/><rect x="270" y="40" width="10" height="10"/><rect x="280" y="40" width="10" height="10"/><rect x="40" y="50" width="10" height="10"/><rect x="100" y="50" width="10" height="10"/><rect x="190" y="50" width="10" height="10"/><rect x="220" y="50" width="10" height="10"/><rect x="280" y="50" width="10" height="10"/><rect x="40" y="60" width="10" height="10"/><rect x="60" y="60" width="10" height="10"/><rect x="70" y="60" width="10" height="10"/><rect x="80" y="60" width="10" height="10"/><rect x="100" y="60" width="10" height="10"/><rect x="120" y="60" width="10" height="10"/><rect x="140" y="60" width="10" height="10"/><rect x="160" y="60" width="10" height="10"/><rect x="170" y="60" width="10" height="10"/><rect x="220" y="60" width="10" height="10"/><rect x="240" y="60" width="10" height="10"/><rect x="250" y="60" width="10" height="10"/><rect x="260" y="60" width="10" height="10"/><rect x="280" y="60" width="10" height="10"/><rect x="40" y="70" width="10" height="10"/><rect x="60" y="70" width="10" height="10"/><rect x="70" y="70" width="10" height="10"/><rect x="80" y="70" width="10" height="10"/><rect x="100" y="70" width="10" height="10"/><rect x="130" y="70" width="10" height="10"/><rect x="150" y="70" width="10" height="10"/><rect x="160" y="70" width="10" height="10"/><rect x="190" y="70" width="10" height="10"/><rect x="220" y="70" width="10" height="10"/><rect x="240" y="70" width="10" height="10"/><rect x="250" y="70" width="10" height="10"/><rect x="260" y="70" width="10" height="10"/><rect x="280" y="70" width="10" height="10"/><rect x="40" y="80" width="10" height="10"/><rect x="60" y="80" width="10" height="10"/><rect x="70" y="80" width="10" height="10"/><rect x="80" y="80" width="10" height="10"/><rect x="100" y="80" width="10" height="10"/><rect x="130" y="80" width="10" height="10"/><rect x="190" y="80" width="10" height="10"/><rect x="200" y="80" width="10" height="10"/><rect x="220" y="80" width="10" height="10"/><rect x="240" y="80" width="10" height="10"/><rect x="250" y="80" width="10" height="10"/><rect x="260" y="80" width="10" height="10"/><rect x="280" y="80" width="10" height="10"/><rect x="40" y="90" width="10" height="10"/><rect x="100" y="90" width="10" height="10"/><rect x="120" y="90" width="10" height="10"/><rect x="160" y="90" width="10" height="10"/><rect x="170" y="90" width="10" height="10"/><rect x="180" y="90" width="10" height="10"/><rect x="220" y="90" width="10" height="10"/><rect x="280" y="90" width="10" height="10"/><rect x="40" y="100" width="10" height="10"/><rect x="50" y="100" width="10" height="10"/><rect x="60" y="100" width="10" height="10"/><rect x="70" y="100" width="10" height="10"/><rect x="80" y="100" width="10" height="10"/><rect x="90" y="100" width="10" height="10"/><rect x="100" y="100" width="10" height="10"/><rect x="120" y="100" width="10" height="10"/><rect x="140" y="100" width="10" height="10"/><rect x="160" y="100" width="10" height="10"/><rect x="180" y="100" width="10" height="10"/><rect x="200" y="100" width="10" height="10"/><rect x="220" y="100" width="10" height="10"/><rect x="230" y="100" width="10" height="10"/><rect x="240" y="100" width="10" height="10"/><rect x="250" y="100" width="10" height="10"/><rect x="260" y="100" width="10" height="10"/><rect x="270" y="100" width="10" height="10"/><rect x="280" y="100" width="10" height="10"/><rect x="160" y="110" width="10" height="10"/><rect x="170" y="110" width="10" height="10"/><rect x="190" y="110" width="10" height="10"/><rect x="200" y="110" width="10" height="10"/><rect x="40" y="120" width="10" height="10"/><rect x="60" y="120" width="10" height="10"/><rect x="100" y="120" width="10" height="10"/><rect x="110" y="120" width="10" height="10"/><rect x="130" y="120" width="10" height="10"/><rect x="140" y="120" width="10" height="10"/><rect x="150" y="120" width="10" height="10"/><rect x="160" y="120" width="10" height="10"/><rect x="170" y="120" width="10" height="10"/><rect x="190" y="120" width="10" height="10"/><rect x="230" y="120" width="10" height="10"/><rect x="260" y="120" width="10" height="10"/><rect x="280" y="120" width="10" height="10"/><rect x="40" y="130" width="10" height="10"/><rect x="50" y="130" width="10" height="10"/><rect x="60" y="130" width="10" height="10"/><rect x="70" y="130" width="10" height="10"/><rect x="130" y="130" width="10" height="10"/><rect x="150" y="130" width="10" height="10"/><rect x="160" y="130" width="10" height="10"/><rect x="190" y="130" width="10" height="10"/><rect x="200" y="130" width="10" height="10"/><rect x="220" y="130" width="10" height="10"/><rect x="230" y="130" width="10" height="10"/><rect x="250" y="130" width="10" height="10"/><rect x="270" y="130" width="10" height="10"/><rect x="280" y="130" width="10" height="10"/><rect x="60" y="140" width="10" height="10"/><rect x="70" y="140" width="10" height="10"/><rect x="80" y="140" width="10" height="10"/><rect x="100" y="140" width="10" height="10"/><rect x="110" y="140" width="10" height="10"/><rect x="130" y="140" width="10" height="10"/><rect x="140" y="140" width="10" height="10"/><rect x="170" y="140" width="10" height="10"/><rect x="180" y="140" width="10" height="10"/><rect x="190" y="140" width="10" height="10"/><rect x="220" y="140" width="10" height="10"/><rect x="240" y="140" width="10" height="10"/><rect x="250" y="140" width="10" height="10"/><rect x="260" y="140" width="10" height="10"/><rect x="280" y="140" width="10" height="10"/><rect x="70" y="150" width="10" height="10"/><rect x="80" y="150" width="10" height="10"/><rect x="110" y="150" width="10" height="10"/><rect x="160" y="150" width="10" height="10"/><rect x="180" y="150" width="10" height="10"/><rect x="200" y="150" width="10" height="10"/><rect x="250" y="150" width="10" height="10"/><rect x="40" y="160" width="10" height="10"/><rect x="70" y="160" width="10" height="10"/><rect x="90" y="160" width="10" height="10"/><rect x="100" y="160" width="10" height="10"/><rect x="130" y="160" width="10" height="10"/><rect x="150" y="160" width="10" height="10"/><rect x="170" y="160" width="10" height="10"/><rect x="180" y="160" width="10" height="10"/><rect x="190" y="160" width="10" height="10"/><rect x="200" y="160" width="10" height="10"/><rect x="220" y="160" width="10" height="10"/><rect x="230" y="160" width="10" height="10"/><rect x="280" y="160" width="10" height="10"/><rect x="70" y="170" width="10" height="10"/><rect x="80" y="170" width="10" height="10"/><rect x="90" y="170" width="10" height="10"/><rect x="110" y="170" width="10" height="10"/><rect x="120" y="170" width="10" height="10"/><rect x="170" y="170" width="10" height="10"/><rect x="180" y="170" width="10" height="10"/><rect x="190" y="170" width="10" height="10"/><rect x="200" y="170" width="10" height="10"/><rect x="220" y="170" width="10" height="10"/><rect x="230" y="170" width="10" height="10"/><rect x="270" y="170" width="10" height="10"/><rect x="280" y="170" width="10" height="10"/><rect x="40" y="180" width="10" height="10"/><rect x="50" y="180" width="10" height="10"/><rect x="60" y="180" width="10" height="10"/><rect x="90" y="180" width="10" height="10"/><rect x="100" y="180" width="10" height="10"/><rect x="110" y="180" width="10" height="10"/><rect x="130" y="180" width="10" height="10"/><rect x="150" y="180" width="10" height="10"/><rect x="190" y="180" width="10" height="10"/><rect x="200" y="180" width="10" height="10"/><rect x="210" y="180" width="10" height="10"/><rect x="250" y="180" width="10" height="10"/><rect x="260" y="180" width="10" height="10"/><rect x="280" y="180" width="10" height="10"/><rect x="60" y="190" width="10" height="10"/><rect x="80" y="190" width="10" height="10"/><rect x="90" y="190" width="10" height="10"/><rect x="120" y="190" width="10" height="10"/><rect x="130" y="190" width="10" height="10"/><rect x="140" y="190" width="10" height="10"/><rect x="170" y="190" width="10" height="10"/><rect x="200" y="190" width="10" height="10"/><rect x="210" y="190" width="10" height="10"/><rect x="230" y="190" width="10" height="10"/><rect x="240" y="190" width="10" height="10"/><rect x="250" y="190" width="10" height="10"/><rect x="40" y="200" width="10" height="10"/><rect x="50" y="200" width="10" height="10"/><rect x="70" y="200" width="10" height="10"/><rect x="90" y="200" width="10" height="10"/><rect x="100" y="200" width="10" height="10"/><rect x="110" y="200" width="10" height="10"/><rect x="120" y="200" width="10" height="10"/><rect x="150" y="200" width="10" height="10"/><rect x="160" y="200" width="10" height="10"/><rect x="170" y="200" width="10" height="10"/><rect x="190" y="200" width="10" height="10"/><rect x="200" y="200" width="10" height="10"/><rect x="210" y="200" width="10" height="10"/><rect x="220" y="200" width="10" height="10"/><rect x="230" y="200" width="10" height="10"/><rect x="240" y="200" width="10" height="10"/><rect x="270" y="200" width="10" height="10"/><rect x="120" y="210" width="10" height="10"/><rect x="130" y="210" width="10" height="10"/><rect x="140" y="210" width="10" height="10"/><rect x="160" y="210" width="10" height="10"/><rect x="200" y="210" width="10" height="10"/><rect x="240" y="210" width="10" height="10"/><rect x="280" y="210" width="10" height="10"/><rect x="40" y="220" width="10" height="10"/><rect x="50" y="220" width="10" height="10"/><rect x="60" y="220" width="10" height="10"/><rect x="70" y="220" width="10" height="10"/><rect x="80" y="220" width="10" height="10"/><rect x="90" y="220" width="10" height="10"/><rect x="100" y="220" width="10" height="10"/><rect x="120" y="220" width="10" height="10"/><rect x="160" y="220" width="10" height="10"/><rect x="170" y="220" width="10" height="10"/><rect x="180" y="220" width="10" height="10"/><rect x="200" y="220" width="10" height="10"/><rect x="220" y="220" width="10" height="10"/><rect x="240" y="220" width="10" height="10"/><rect x="280" y="220" width="10" height="10"/><rect x="40" y="230" width="10" height="10"/><rect x="100" y="230" width="10" height="10"/><rect x="130" y="230" width="10" height="10"/><rect x="140" y="230" width="10" height="10"/><rect x="150" y="230" width="10" height="10"/><rect x="160" y="230" width="10" height="10"/><rect x="180" y="230" width="10" height="10"/><rect x="190" y="230" width="10" height="10"/><rect x="200" y="230" width="10" height="10"/><rect x="240" y="230" width="10" height="10"/><rect x="40" y="240" width="10" height="10"/><rect x="60" y="240" width="10" height="10"/><rect x="70" y="240" width="10" height="10"/><rect x="80" y="240" width="10" height="10"/><rect x="100" y="240" width="10" height="10"/><rect x="130" y="240" width="10" height="10"/><rect x="170" y="240" width="10" height="10"/><rect x="180" y="240" width="10" height="10"/><rect x="190" y="240" width="10" height="10"/><rect x="200" y="240" width="10" height="10"/><rect x="210" y="240" width="10" height="10"/><rect x="220" y="240" width="10" height="10"/><rect x="230" y="240" width="10" height="10"/><rect x="240" y="240" width="10" height="10"/><rect x="270" y="240" width="10" height="10"/><rect x="280" y="240" width="10" height="10"/><rect x="40" y="250" width="10" height="10"/><rect x="60" y="250" width="10" height="10"/><rect x="70" y="250" width="10" height="10"/><rect x="80" y="250" width="10" height="10"/><rect x="100" y="250" width="10" height="10"/><rect x="140" y="250" width="10" height="10"/><rect x="170" y="250" width="10" height="10"/><rect x="180" y="250" width="10" height="10"/><rect x="200" y="250" width="10" height="10"/><rect x="210" y="250" width="10" height="10"/><rect x="240" y="250" width="10" height="10"/><rect x="260" y="250" width="10" height="10"/><rect x="270" y="250" width="10" height="10"/><rect x="40" y="260" width="10" height="10"/><rect x="60" y="260" width="10" height="10"/><rect x="70" y="260" width="10" height="10"/><rect x="80" y="260" width="10" height="10"/><rect x="100" y="260" width="10" height="10"/><rect x="120" y="260" width="10" height="10"/><rect x="150" y="260" width="10" height="10"/><rect x="210" y="260" width="10" height="10"/><rect x="230" y="260" width="10" height="10"/><rect x="240" y="260" width="10" height="10"/><rect x="250" y="260" width="10" height="10"/><rect x="270" y="260" width="10" height="10"/><rect x="280" y="260" width="10" height="10"/><rect x="40" y="270" width="10" height="10"/><rect x="100" y="270" width="10" height="10"/><rect x="130" y="270" width="10" height="10"/><rect x="170" y="270" width="10" height="10"/><rect x="200" y="270" width="10" height="10"/><rect x="230" y="270" width="10" height="10"/><rect x="240" y="270" width="10" height="10"/><rect x="40" y="280" width="10" height="10"/><rect x="50" y="280" width="10" height="10"/><rect x="60" y="280" width="10" height="10"/><rect x="70" y="280" width="10" height="10"/><rect x="80" y="280" width="10" height="10"/><rect x="90" y="280" width="10" height="10"/><rect x="100" y="280" width="10" height="10"/><rect x="120" y="280" width="10" height="10"/><rect x="130" y="280" width="10" height="10"/><rect x="150" y="280" width="10" height="10"/><rect x="160" y="280" width="10" height="10"/><rect x="170" y="280" width="10" height="10"/><rect x="190" y="280" width="10" height="10"/><rect x="200" y="280" width="10" height="10"/><rect x="220" y="280" width="10" height="10"/><rect x="250" y="280" width="10" height="10"/><rect x="280" y="280" width="10" height="10"/></g>
4
+ </svg>