@adia-ai/web-components 0.6.35 → 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 (126) hide show
  1. package/CHANGELOG.md +56 -0
  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/combobox/combobox.css +12 -0
  20. package/components/context-menu/context-menu.a2ui.json +159 -0
  21. package/components/context-menu/context-menu.class.js +275 -0
  22. package/components/context-menu/context-menu.css +56 -0
  23. package/components/context-menu/context-menu.d.ts +70 -0
  24. package/components/context-menu/context-menu.js +17 -0
  25. package/components/context-menu/context-menu.yaml +136 -0
  26. package/components/date-range-picker/date-range-picker.a2ui.json +15 -0
  27. package/components/date-range-picker/date-range-picker.class.js +3 -1
  28. package/components/date-range-picker/date-range-picker.css +4 -1
  29. package/components/date-range-picker/date-range-picker.yaml +14 -0
  30. package/components/datetime-picker/datetime-picker.a2ui.json +15 -0
  31. package/components/datetime-picker/datetime-picker.class.js +3 -1
  32. package/components/datetime-picker/datetime-picker.css +7 -1
  33. package/components/datetime-picker/datetime-picker.d.ts +2 -0
  34. package/components/datetime-picker/datetime-picker.yaml +14 -0
  35. package/components/empty-state/empty-state.class.js +2 -0
  36. package/components/feed/feed.class.js +13 -5
  37. package/components/feed/feed.css +14 -0
  38. package/components/index.js +9 -0
  39. package/components/input/input.css +15 -1
  40. package/components/input/input.test.js +40 -0
  41. package/components/integration-card/integration-card.class.js +9 -0
  42. package/components/integration-card/integration-card.test.js +4 -3
  43. package/components/nav-group/nav-group.css +7 -1
  44. package/components/number-format/number-format.a2ui.json +180 -0
  45. package/components/number-format/number-format.class.js +96 -0
  46. package/components/number-format/number-format.css +18 -0
  47. package/components/number-format/number-format.d.ts +68 -0
  48. package/components/number-format/number-format.js +17 -0
  49. package/components/number-format/number-format.yaml +204 -0
  50. package/components/pagination/pagination.a2ui.json +19 -2
  51. package/components/pagination/pagination.class.js +90 -37
  52. package/components/pagination/pagination.css +32 -127
  53. package/components/pagination/pagination.d.ts +8 -2
  54. package/components/pagination/pagination.test.js +195 -0
  55. package/components/pagination/pagination.yaml +22 -1
  56. package/components/password-strength/password-strength.a2ui.json +152 -0
  57. package/components/password-strength/password-strength.class.js +157 -0
  58. package/components/password-strength/password-strength.css +80 -0
  59. package/components/password-strength/password-strength.d.ts +59 -0
  60. package/components/password-strength/password-strength.js +17 -0
  61. package/components/password-strength/password-strength.yaml +153 -0
  62. package/components/popover/popover.css +43 -23
  63. package/components/popover/popover.yaml +8 -4
  64. package/components/qr-code/QR-TEST.svg +4 -0
  65. package/components/qr-code/qr-code.a2ui.json +154 -0
  66. package/components/qr-code/qr-code.class.js +129 -0
  67. package/components/qr-code/qr-code.css +41 -0
  68. package/components/qr-code/qr-code.d.ts +83 -0
  69. package/components/qr-code/qr-code.js +17 -0
  70. package/components/qr-code/qr-code.yaml +203 -0
  71. package/components/qr-code/qr-encoder.js +633 -0
  72. package/components/relative-time/relative-time.a2ui.json +120 -0
  73. package/components/relative-time/relative-time.class.js +136 -0
  74. package/components/relative-time/relative-time.css +22 -0
  75. package/components/relative-time/relative-time.d.ts +51 -0
  76. package/components/relative-time/relative-time.js +17 -0
  77. package/components/relative-time/relative-time.yaml +133 -0
  78. package/components/search/search.class.js +2 -0
  79. package/components/segmented/segmented.class.js +5 -1
  80. package/components/select/select.class.js +4 -0
  81. package/components/skip-nav/skip-nav.a2ui.json +92 -0
  82. package/components/skip-nav/skip-nav.class.js +45 -0
  83. package/components/skip-nav/skip-nav.css +54 -0
  84. package/components/skip-nav/skip-nav.d.ts +27 -0
  85. package/components/skip-nav/skip-nav.js +12 -0
  86. package/components/skip-nav/skip-nav.yaml +68 -0
  87. package/components/slider/slider.a2ui.json +16 -1
  88. package/components/slider/slider.class.js +264 -122
  89. package/components/slider/slider.css +82 -2
  90. package/components/slider/slider.d.ts +19 -3
  91. package/components/slider/slider.test.js +55 -0
  92. package/components/slider/slider.yaml +28 -6
  93. package/components/table/table.class.js +29 -6
  94. package/components/table/table.css +31 -4
  95. package/components/table-toolbar/table-toolbar.class.js +4 -1
  96. package/components/tag/tag.a2ui.json +10 -0
  97. package/components/tag/tag.class.js +8 -1
  98. package/components/tag/tag.css +108 -20
  99. package/components/tag/tag.d.ts +14 -0
  100. package/components/tag/tag.test.js +99 -1
  101. package/components/tag/tag.yaml +20 -0
  102. package/components/tags-input/tags-input.class.js +10 -3
  103. package/components/tags-input/tags-input.css +12 -3
  104. package/components/textarea/textarea.css +10 -1
  105. package/components/toast/toast.class.js +12 -4
  106. package/components/toc/toc.a2ui.json +159 -0
  107. package/components/toc/toc.class.js +222 -0
  108. package/components/toc/toc.css +92 -0
  109. package/components/toc/toc.d.ts +61 -0
  110. package/components/toc/toc.js +17 -0
  111. package/components/toc/toc.yaml +180 -0
  112. package/components/toolbar/toolbar.class.js +3 -0
  113. package/components/visually-hidden/visually-hidden.a2ui.json +71 -0
  114. package/components/visually-hidden/visually-hidden.class.js +14 -0
  115. package/components/visually-hidden/visually-hidden.css +25 -0
  116. package/components/visually-hidden/visually-hidden.d.ts +26 -0
  117. package/components/visually-hidden/visually-hidden.js +12 -0
  118. package/components/visually-hidden/visually-hidden.yaml +54 -0
  119. package/core/anchor.js +19 -3
  120. package/core/provider.js +19 -2
  121. package/dist/web-components.min.css +1 -1
  122. package/dist/web-components.min.js +101 -89
  123. package/package.json +1 -1
  124. package/styles/colors/semantics.css +11 -2
  125. package/styles/components.css +9 -0
  126. package/styles/resets.css +10 -0
@@ -0,0 +1,152 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/PasswordStrength.json",
4
+ "title": "PasswordStrength",
5
+ "description": "Visual strength indicator for password inputs — 4-segment bar (weak /\nfair / good / strong) computed from a heuristic combining length,\ncharacter-class diversity, and repeat-pattern penalty. Pairs with\n`<input-ui type=\"password\">` via JS:\n input.addEventListener('input', e => meter.value = e.target.value)\nRead-only display primitive — no form participation. Emits a\n`score-change` event when the bucket changes so consumers can gate\na submit button on `detail.satisfied` (score ≥ min-score).\n\n**Security note:** [value] is held as a JS property only, NOT\nreflected to a DOM attribute. The element stamps the bar + label\nfrom the property; the password never appears in the rendered HTML.\nDo not set the value via setAttribute (it will be no-op).\n",
6
+ "type": "object",
7
+ "allOf": [
8
+ {
9
+ "$ref": "common_types.json#/$defs/ComponentCommon"
10
+ },
11
+ {
12
+ "$ref": "common_types.json#/$defs/CatalogComponentCommon"
13
+ }
14
+ ],
15
+ "properties": {
16
+ "component": {
17
+ "const": "PasswordStrength"
18
+ },
19
+ "minScore": {
20
+ "description": "Minimum acceptable score (0–3). The `score-change` event's\n`detail.satisfied` boolean reflects whether the current score\nmeets this threshold. Useful for gating a submit button.\n",
21
+ "type": "number",
22
+ "default": 2
23
+ },
24
+ "showLabel": {
25
+ "description": "Display the textual score label (\"Weak\" / \"Fair\" / \"Good\" /\n\"Strong\") below the bar. Defaults to true.\n",
26
+ "type": "boolean",
27
+ "default": true
28
+ },
29
+ "value": {
30
+ "description": "The password string to score. JS-property only — NOT reflected\nto a DOM attribute (so the password never leaks into rendered\nHTML). Wire to an input via `input.addEventListener('input', e =>\nmeter.value = e.target.value)`.\n",
31
+ "$ref": "common_types.json#/$defs/DynamicString"
32
+ }
33
+ },
34
+ "required": [
35
+ "component"
36
+ ],
37
+ "unevaluatedProperties": false,
38
+ "x-adiaui": {
39
+ "anti_patterns": [
40
+ {
41
+ "fix": "<password-strength-ui id=\"m\"></password-strength-ui>\n<script>document.getElementById('m').value = pwd;</script>\n",
42
+ "why": "`value` is JS-property only; the attribute is ignored. The\npassword also shouldn't be authored into HTML at all (server\ntemplate / static page) — that defeats the purpose.\n",
43
+ "wrong": "<password-strength-ui value=\"hunter2\"></password-strength-ui>\n"
44
+ }
45
+ ],
46
+ "category": "display",
47
+ "composes": [],
48
+ "events": {
49
+ "score-change": {
50
+ "description": "Fired when the computed score crosses a bucket boundary (not on every keystroke). Detail carries the new score + label + satisfied flag.",
51
+ "detail": {
52
+ "label": "string",
53
+ "satisfied": "boolean",
54
+ "score": "number"
55
+ }
56
+ }
57
+ },
58
+ "examples": [
59
+ {
60
+ "description": "Standard pairing with input-ui[type=password] via input listener.",
61
+ "a2ui": "[\n {\n \"id\": \"field\",\n \"component\": \"Field\",\n \"label\": \"Password\",\n \"children\": [\"pwd\", \"meter\"]\n },\n {\n \"id\": \"pwd\",\n \"component\": \"Input\",\n \"type\": \"password\",\n \"name\": \"password\"\n },\n {\n \"id\": \"meter\",\n \"component\": \"PasswordStrength\"\n }\n]\n",
62
+ "name": "paired-with-input"
63
+ }
64
+ ],
65
+ "keywords": [
66
+ "password-strength",
67
+ "strength-meter",
68
+ "password",
69
+ "strength",
70
+ "meter",
71
+ "security"
72
+ ],
73
+ "name": "UIPasswordStrength",
74
+ "related": [
75
+ "input",
76
+ "field",
77
+ "progress"
78
+ ],
79
+ "slots": {
80
+ "default": {
81
+ "description": "Optional area for requirements checklist content (e.g. <ul> of \"at least 8 characters\", \"mixed case\", etc.)."
82
+ }
83
+ },
84
+ "states": [
85
+ {
86
+ "description": "Default — no value set, all segments grey.",
87
+ "name": "idle"
88
+ },
89
+ {
90
+ "description": "Value present; segments lit per score 0..3.",
91
+ "attribute": "data-score",
92
+ "name": "scored"
93
+ }
94
+ ],
95
+ "status": "stable",
96
+ "synonyms": {
97
+ "password": [
98
+ "password-strength",
99
+ "strength-meter"
100
+ ],
101
+ "strength": [
102
+ "password-strength",
103
+ "meter"
104
+ ]
105
+ },
106
+ "tag": "password-strength-ui",
107
+ "tokens": {
108
+ "--password-strength-color-fair": {
109
+ "description": "Color for score=1 lit segments.",
110
+ "default": "var(--a-warning-bg)"
111
+ },
112
+ "--password-strength-color-good": {
113
+ "description": "Color for score=2 lit segments.",
114
+ "default": "var(--a-info-bg)"
115
+ },
116
+ "--password-strength-color-strong": {
117
+ "description": "Color for score=3 lit segments.",
118
+ "default": "var(--a-success-bg)"
119
+ },
120
+ "--password-strength-color-weak": {
121
+ "description": "Color for score=0 lit segments.",
122
+ "default": "var(--a-danger-bg)"
123
+ },
124
+ "--password-strength-gap": {
125
+ "description": "Gap between segments.",
126
+ "default": "var(--a-space-1)"
127
+ },
128
+ "--password-strength-label-fg": {
129
+ "description": "Label text color.",
130
+ "default": "var(--a-fg-muted)"
131
+ },
132
+ "--password-strength-label-size": {
133
+ "description": "Label font size.",
134
+ "default": "var(--a-ui-sm)"
135
+ },
136
+ "--password-strength-radius": {
137
+ "description": "Segment border radius.",
138
+ "default": "var(--a-radius-full)"
139
+ },
140
+ "--password-strength-segment-bg": {
141
+ "description": "Unlit segment background (track color).",
142
+ "default": "var(--a-canvas-1-scrim)"
143
+ },
144
+ "--password-strength-segment-height": {
145
+ "description": "Height of each bar segment.",
146
+ "default": "4px"
147
+ }
148
+ },
149
+ "traits": [],
150
+ "version": 1
151
+ }
152
+ }
@@ -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