@kouji-ui/core 0.0.4 → 0.1.2
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/fesm2022/kouji-ui-core.mjs +26789 -0
- package/fesm2022/kouji-ui-core.mjs.map +1 -0
- package/icon/icon.css +40 -0
- package/package.json +20 -9
- package/types/kouji-ui-core.d.ts +13545 -0
- package/typography/prose.css +312 -0
- package/.turbo/turbo-build.log +0 -27
- package/CHANGELOG.md +0 -21
- package/ng-package.json +0 -7
- package/src/a11y/.gitkeep +0 -0
- package/src/a11y/aria-describedby.spec.ts +0 -55
- package/src/a11y/aria-describedby.ts +0 -33
- package/src/a11y/focus-trap.spec.ts +0 -86
- package/src/a11y/focus-trap.ts +0 -70
- package/src/a11y/index.ts +0 -9
- package/src/a11y/live-region.spec.ts +0 -58
- package/src/a11y/live-region.ts +0 -48
- package/src/a11y/roving-tabindex.spec.ts +0 -62
- package/src/a11y/roving-tabindex.ts +0 -104
- package/src/a11y/visually-hidden.spec.ts +0 -34
- package/src/a11y/visually-hidden.ts +0 -24
- package/src/accordion/accordion.context.ts +0 -22
- package/src/accordion/accordion.spec.ts +0 -32
- package/src/accordion/accordion.ts +0 -129
- package/src/accordion/index.ts +0 -2
- package/src/avatar/avatar.spec.ts +0 -18
- package/src/avatar/avatar.ts +0 -26
- package/src/avatar/index.ts +0 -1
- package/src/badge/badge.spec.ts +0 -20
- package/src/badge/badge.ts +0 -14
- package/src/badge/index.ts +0 -1
- package/src/button/button.example.ts +0 -30
- package/src/button/button.finance.example.ts +0 -37
- package/src/button/button.retro.example.ts +0 -47
- package/src/button/button.sizes.example.ts +0 -25
- package/src/button/button.spec.ts +0 -32
- package/src/button/button.ts +0 -44
- package/src/button/index.ts +0 -1
- package/src/chart/chart.spec.ts +0 -27
- package/src/chart/chart.ts +0 -48
- package/src/chart/index.ts +0 -1
- package/src/checkbox/checkbox.spec.ts +0 -35
- package/src/checkbox/checkbox.ts +0 -82
- package/src/checkbox/index.ts +0 -1
- package/src/dialog/dialog.confirm.example.ts +0 -57
- package/src/dialog/dialog.context.ts +0 -11
- package/src/dialog/dialog.example.ts +0 -40
- package/src/dialog/dialog.finance.example.ts +0 -47
- package/src/dialog/dialog.retro.example.ts +0 -46
- package/src/dialog/dialog.service.ts +0 -176
- package/src/dialog/dialog.spec.ts +0 -122
- package/src/dialog/dialog.ts +0 -201
- package/src/dialog/index.ts +0 -9
- package/src/example-components.ts +0 -33
- package/src/form/form.context.ts +0 -3
- package/src/form/form.spec.ts +0 -25
- package/src/form/form.ts +0 -46
- package/src/form/index.ts +0 -2
- package/src/input/index.ts +0 -1
- package/src/input/input.spec.ts +0 -43
- package/src/input/input.ts +0 -80
- package/src/menu/index.ts +0 -2
- package/src/menu/menu.context.ts +0 -10
- package/src/menu/menu.example.ts +0 -39
- package/src/menu/menu.spec.ts +0 -168
- package/src/menu/menu.ts +0 -176
- package/src/popover/index.ts +0 -12
- package/src/popover/popover.context.ts +0 -18
- package/src/popover/popover.example.ts +0 -50
- package/src/popover/popover.finance.example.ts +0 -52
- package/src/popover/popover.retro.example.ts +0 -53
- package/src/popover/popover.spec.ts +0 -52
- package/src/popover/popover.ts +0 -124
- package/src/primitives/.gitkeep +0 -0
- package/src/primitives/forms/form-control.spec.ts +0 -59
- package/src/primitives/forms/form-control.ts +0 -90
- package/src/primitives/forms/index.ts +0 -1
- package/src/primitives/index.ts +0 -3
- package/src/primitives/interaction/disabled.spec.ts +0 -47
- package/src/primitives/interaction/disabled.ts +0 -24
- package/src/primitives/interaction/focus-ring.spec.ts +0 -62
- package/src/primitives/interaction/focus-ring.ts +0 -57
- package/src/primitives/interaction/index.ts +0 -2
- package/src/primitives/overlay/index.ts +0 -1
- package/src/primitives/overlay/overlay.spec.ts +0 -90
- package/src/primitives/overlay/overlay.ts +0 -106
- package/src/public-api.ts +0 -35
- package/src/radio/index.ts +0 -2
- package/src/radio/radio.context.ts +0 -3
- package/src/radio/radio.spec.ts +0 -36
- package/src/radio/radio.ts +0 -55
- package/src/select/index.ts +0 -2
- package/src/select/select.context.ts +0 -3
- package/src/select/select.spec.ts +0 -145
- package/src/select/select.ts +0 -250
- package/src/styles/docs-themes.css +0 -82
- package/src/table/index.ts +0 -1
- package/src/table/table.spec.ts +0 -113
- package/src/table/table.ts +0 -126
- package/src/tabs/index.ts +0 -2
- package/src/tabs/tabs.context.ts +0 -9
- package/src/tabs/tabs.spec.ts +0 -40
- package/src/tabs/tabs.ts +0 -130
- package/src/test-setup.ts +0 -5
- package/src/toast/index.ts +0 -21
- package/src/toast/toast.example.ts +0 -134
- package/src/toast/toast.finance.example.ts +0 -68
- package/src/toast/toast.retro.example.ts +0 -69
- package/src/toast/toast.service.ts +0 -153
- package/src/toast/toast.spec.ts +0 -95
- package/src/toast/toast.strategy.ts +0 -102
- package/src/toast/toast.ts +0 -307
- package/src/toast/toast.types.ts +0 -5
- package/src/toggle/index.ts +0 -1
- package/src/toggle/toggle.spec.ts +0 -24
- package/src/toggle/toggle.ts +0 -40
- package/src/tooltip/index.ts +0 -5
- package/src/tooltip/tooltip.example.ts +0 -46
- package/src/tooltip/tooltip.finance.example.ts +0 -54
- package/src/tooltip/tooltip.placements.example.ts +0 -56
- package/src/tooltip/tooltip.retro.example.ts +0 -65
- package/src/tooltip/tooltip.spec.ts +0 -104
- package/src/tooltip/tooltip.ts +0 -129
- package/tsconfig.lib.json +0 -13
- package/tsconfig.lib.prod.json +0 -11
- package/tsconfig.spec.json +0 -10
- package/vite.config.ts +0 -19
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
kouji-ui Typography — `kj-prose` container + tone reflections
|
|
3
|
+
--------------------------------------------------------------
|
|
4
|
+
Apply `.kj-prose` to a container element to restyle every
|
|
5
|
+
descendant `h1`–`h6`, `p`, `a`, `blockquote`, `code`, `pre`,
|
|
6
|
+
`ul`, `ol`, `li`, `dl`, `dt`, `dd`, `hr`, `figure`,
|
|
7
|
+
`figcaption`, `table`, `strong`, `em`, `kbd` according to the
|
|
8
|
+
kouji type system.
|
|
9
|
+
|
|
10
|
+
The five attribute directives (`kjLead`, `kjMuted`, `kjCode`,
|
|
11
|
+
`kjBlockquote`, `kjTruncate`) reflect `data-tone` /
|
|
12
|
+
`data-truncate` host attributes; the rules below key off those
|
|
13
|
+
attributes so the same styling applies whether the consumer
|
|
14
|
+
used the directive, hand-typed `data-tone="muted"` on a span,
|
|
15
|
+
or wrapped a subtree in `.kj-prose`.
|
|
16
|
+
|
|
17
|
+
Tokens read from the kouji theme layer:
|
|
18
|
+
--kj-bg-body / surface / elevated ......... background scale
|
|
19
|
+
--kj-fg-default ........................... primary text
|
|
20
|
+
--kj-fg-muted ............................. secondary text (≥ 7:1)
|
|
21
|
+
--kj-border-default / muted ............... separators / rules
|
|
22
|
+
--kj-color-link ........................... anchor colour
|
|
23
|
+
--kj-color-link-visited ................... visited anchor
|
|
24
|
+
--kj-font-sans / --kj-font-mono / --kj-font-display
|
|
25
|
+
--kj-font-size-xs / sm / md / lg / xl / 2xl / 3xl / 4xl
|
|
26
|
+
--kj-line-height-tight / snug / normal / relaxed / loose
|
|
27
|
+
--kj-radius-sm / md
|
|
28
|
+
--kj-prose-max-width ...................... opt-out for CJK
|
|
29
|
+
───────────────────────────────────────────────────────────── */
|
|
30
|
+
|
|
31
|
+
@layer kj.prose {
|
|
32
|
+
.kj-prose {
|
|
33
|
+
color: var(--kj-fg-default);
|
|
34
|
+
font-family: var(--kj-font-sans);
|
|
35
|
+
font-size: var(--kj-font-size-md, 1rem);
|
|
36
|
+
line-height: var(--kj-line-height-relaxed, 1.7);
|
|
37
|
+
max-width: var(--kj-prose-max-width, 65ch);
|
|
38
|
+
text-align: start;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* ── Block rhythm: vertical margins between adjacent flow elements ── */
|
|
42
|
+
.kj-prose > * + * {
|
|
43
|
+
margin-block-start: 1em;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.kj-prose > :first-child {
|
|
47
|
+
margin-block-start: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.kj-prose > :last-child {
|
|
51
|
+
margin-block-end: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* ── Headings ────────────────────────────────────────────── */
|
|
55
|
+
.kj-prose h1,
|
|
56
|
+
.kj-prose h2,
|
|
57
|
+
.kj-prose h3,
|
|
58
|
+
.kj-prose h4,
|
|
59
|
+
.kj-prose h5,
|
|
60
|
+
.kj-prose h6 {
|
|
61
|
+
color: var(--kj-fg-default);
|
|
62
|
+
font-family: var(--kj-font-display, var(--kj-font-sans));
|
|
63
|
+
font-weight: 600;
|
|
64
|
+
line-height: var(--kj-line-height-tight, 1.2);
|
|
65
|
+
margin-block-start: 1.6em;
|
|
66
|
+
margin-block-end: 0.6em;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.kj-prose h1 {
|
|
70
|
+
font-size: var(--kj-font-size-4xl, 2.25rem);
|
|
71
|
+
font-weight: 700;
|
|
72
|
+
line-height: var(--kj-line-height-tight, 1.1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.kj-prose h2 {
|
|
76
|
+
font-size: var(--kj-font-size-3xl, 1.875rem);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.kj-prose h3 {
|
|
80
|
+
font-size: var(--kj-font-size-2xl, 1.5rem);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.kj-prose h4 {
|
|
84
|
+
font-size: var(--kj-font-size-xl, 1.25rem);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.kj-prose h5 {
|
|
88
|
+
font-size: var(--kj-font-size-lg, 1.125rem);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.kj-prose h6 {
|
|
92
|
+
font-size: var(--kj-font-size-md, 1rem);
|
|
93
|
+
text-transform: uppercase;
|
|
94
|
+
letter-spacing: 0.04em;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* ── Paragraphs ──────────────────────────────────────────── */
|
|
98
|
+
.kj-prose p {
|
|
99
|
+
line-height: var(--kj-line-height-relaxed, 1.7);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* ── Anchors ─────────────────────────────────────────────── */
|
|
103
|
+
.kj-prose a {
|
|
104
|
+
color: var(--kj-fg-primary);
|
|
105
|
+
text-decoration: underline;
|
|
106
|
+
text-underline-offset: 0.2em;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.kj-prose a:visited {
|
|
110
|
+
color: var(--kj-fg-accent);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.kj-prose a:hover {
|
|
114
|
+
text-decoration-thickness: 2px;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* ── Lists ───────────────────────────────────────────────── */
|
|
118
|
+
.kj-prose ul,
|
|
119
|
+
.kj-prose ol {
|
|
120
|
+
padding-inline-start: 1.5em;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.kj-prose ul {
|
|
124
|
+
list-style: disc;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.kj-prose ol {
|
|
128
|
+
list-style: decimal;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.kj-prose li {
|
|
132
|
+
margin-block-start: 0.4em;
|
|
133
|
+
line-height: var(--kj-line-height-relaxed, 1.7);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.kj-prose li > ul,
|
|
137
|
+
.kj-prose li > ol {
|
|
138
|
+
margin-block-start: 0.4em;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* ── Definition lists ────────────────────────────────────── */
|
|
142
|
+
.kj-prose dt {
|
|
143
|
+
font-weight: 600;
|
|
144
|
+
margin-block-start: 0.8em;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.kj-prose dd {
|
|
148
|
+
margin-inline-start: 1.5em;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* ── Blockquote ──────────────────────────────────────────── */
|
|
152
|
+
.kj-prose blockquote {
|
|
153
|
+
border-inline-start: 4px solid var(--kj-border-muted, currentColor);
|
|
154
|
+
color: var(--kj-fg-muted, var(--kj-fg-default));
|
|
155
|
+
font-style: italic;
|
|
156
|
+
margin-inline: 0;
|
|
157
|
+
padding-inline-start: 1em;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* ── Code (inline + block) ───────────────────────────────── */
|
|
161
|
+
.kj-prose code {
|
|
162
|
+
background-color: var(--kj-bg-surface, transparent);
|
|
163
|
+
border-radius: var(--kj-radius-sm, 4px);
|
|
164
|
+
font-family: var(--kj-font-mono);
|
|
165
|
+
font-size: 0.9em;
|
|
166
|
+
padding-block: 0.1em;
|
|
167
|
+
padding-inline: 0.35em;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.kj-prose pre {
|
|
171
|
+
background-color: var(--kj-bg-surface, transparent);
|
|
172
|
+
border-radius: var(--kj-radius-md, 6px);
|
|
173
|
+
color: var(--kj-fg-default);
|
|
174
|
+
font-family: var(--kj-font-mono);
|
|
175
|
+
line-height: var(--kj-line-height-snug, 1.45);
|
|
176
|
+
overflow-x: auto;
|
|
177
|
+
padding: 1em;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.kj-prose pre code {
|
|
181
|
+
background-color: transparent;
|
|
182
|
+
border-radius: 0;
|
|
183
|
+
font-size: inherit;
|
|
184
|
+
padding: 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* ── Horizontal rule ─────────────────────────────────────── */
|
|
188
|
+
.kj-prose hr {
|
|
189
|
+
background-color: var(--kj-border-default, currentColor);
|
|
190
|
+
border: 0;
|
|
191
|
+
height: 1px;
|
|
192
|
+
margin-block: 2em;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* ── Figures ─────────────────────────────────────────────── */
|
|
196
|
+
.kj-prose figure {
|
|
197
|
+
margin-inline: 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.kj-prose figcaption {
|
|
201
|
+
color: var(--kj-fg-muted, var(--kj-fg-default));
|
|
202
|
+
font-size: var(--kj-font-size-sm, 0.875rem);
|
|
203
|
+
margin-block-start: 0.5em;
|
|
204
|
+
text-align: center;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* ── Tables ──────────────────────────────────────────────── */
|
|
208
|
+
.kj-prose table {
|
|
209
|
+
border-collapse: collapse;
|
|
210
|
+
font-size: var(--kj-font-size-sm, 0.9em);
|
|
211
|
+
width: 100%;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.kj-prose thead {
|
|
215
|
+
border-block-end: 2px solid var(--kj-border-default, currentColor);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.kj-prose th,
|
|
219
|
+
.kj-prose td {
|
|
220
|
+
border-block-end: 1px solid var(--kj-border-muted, currentColor);
|
|
221
|
+
padding-block: 0.5em;
|
|
222
|
+
padding-inline: 0.75em;
|
|
223
|
+
text-align: start;
|
|
224
|
+
vertical-align: top;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.kj-prose th {
|
|
228
|
+
font-weight: 600;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* ── Inline emphasis ─────────────────────────────────────── */
|
|
232
|
+
.kj-prose strong {
|
|
233
|
+
font-weight: 700;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.kj-prose em {
|
|
237
|
+
font-style: italic;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* ── Size modifiers ──────────────────────────────────────── */
|
|
241
|
+
.kj-prose-sm {
|
|
242
|
+
font-size: var(--kj-font-size-sm, 0.875rem);
|
|
243
|
+
line-height: var(--kj-line-height-normal, 1.6);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.kj-prose-lg {
|
|
247
|
+
font-size: var(--kj-font-size-lg, 1.125rem);
|
|
248
|
+
line-height: var(--kj-line-height-loose, 1.8);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* ──────────────────────────────────────────────────────────────
|
|
253
|
+
Tone reflections — applied by directives via `[data-tone]`,
|
|
254
|
+
readable in or out of a `.kj-prose` container.
|
|
255
|
+
────────────────────────────────────────────────────────────── */
|
|
256
|
+
|
|
257
|
+
@layer kj.tone {
|
|
258
|
+
[data-tone='lead'] {
|
|
259
|
+
color: var(--kj-fg-muted, var(--kj-fg-default));
|
|
260
|
+
font-size: var(--kj-font-size-lg, 1.125rem);
|
|
261
|
+
line-height: var(--kj-line-height-relaxed, 1.7);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
[data-tone='muted'] {
|
|
265
|
+
color: var(--kj-fg-muted, var(--kj-fg-default));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
[data-tone='code'] {
|
|
269
|
+
background-color: var(--kj-bg-surface, transparent);
|
|
270
|
+
border-radius: var(--kj-radius-sm, 4px);
|
|
271
|
+
font-family: var(--kj-font-mono);
|
|
272
|
+
font-size: 0.9em;
|
|
273
|
+
padding-block: 0.1em;
|
|
274
|
+
padding-inline: 0.35em;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
[data-tone='blockquote'] {
|
|
278
|
+
border-inline-start: 4px solid var(--kj-border-muted, currentColor);
|
|
279
|
+
color: var(--kj-fg-muted, var(--kj-fg-default));
|
|
280
|
+
font-style: italic;
|
|
281
|
+
margin-inline: 0;
|
|
282
|
+
padding-inline-start: 1em;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* ──────────────────────────────────────────────────────────────
|
|
287
|
+
Truncate — single-line ellipsis (data-truncate="1") and
|
|
288
|
+
multi-line clamp (data-truncate="2"+).
|
|
289
|
+
────────────────────────────────────────────────────────────── */
|
|
290
|
+
|
|
291
|
+
@layer kj.truncate {
|
|
292
|
+
[data-truncate='1'] {
|
|
293
|
+
overflow: hidden;
|
|
294
|
+
text-overflow: ellipsis;
|
|
295
|
+
white-space: nowrap;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* Multi-line clamp. Modern `attr(data-truncate type(<number>))` works in
|
|
299
|
+
Chromium 133+ but doesn't ship in every WebKit-based engine yet, so
|
|
300
|
+
enumerate the common counts (2..6) explicitly — covers card / list
|
|
301
|
+
description copy without needing engine fallback gymnastics. */
|
|
302
|
+
[data-truncate]:not([data-truncate='1']) {
|
|
303
|
+
display: -webkit-box;
|
|
304
|
+
-webkit-box-orient: vertical;
|
|
305
|
+
overflow: hidden;
|
|
306
|
+
}
|
|
307
|
+
[data-truncate='2'] { -webkit-line-clamp: 2; line-clamp: 2; }
|
|
308
|
+
[data-truncate='3'] { -webkit-line-clamp: 3; line-clamp: 3; }
|
|
309
|
+
[data-truncate='4'] { -webkit-line-clamp: 4; line-clamp: 4; }
|
|
310
|
+
[data-truncate='5'] { -webkit-line-clamp: 5; line-clamp: 5; }
|
|
311
|
+
[data-truncate='6'] { -webkit-line-clamp: 6; line-clamp: 6; }
|
|
312
|
+
}
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @kouji-ui/core@0.0.4 build /home/runner/work/kouji-ui/kouji-ui/packages/core
|
|
3
|
-
> ng-packagr -p ng-package.json
|
|
4
|
-
|
|
5
|
-
Building Angular Package
|
|
6
|
-
|
|
7
|
-
------------------------------------------------------------------------------
|
|
8
|
-
- Compiling with Angular sources in partial compilation mode.
|
|
9
|
-
Building entry point '@kouji-ui/core'
|
|
10
|
-
------------------------------------------------------------------------------
|
|
11
|
-
[32m✔[39m Compiling with Angular sources in partial compilation mode.
|
|
12
|
-
[32m✔[39m Generating FESM and DTS bundles
|
|
13
|
-
- Copying assets
|
|
14
|
-
[32m✔[39m Copying assets
|
|
15
|
-
- Writing package manifest
|
|
16
|
-
[34mℹ[39m Removing scripts section in package.json as it's considered a potential security vulnerability.
|
|
17
|
-
[32m✔[39m Writing package manifest
|
|
18
|
-
[32m✔[39m Built @kouji-ui/core
|
|
19
|
-
|
|
20
|
-
------------------------------------------------------------------------------
|
|
21
|
-
Built Angular Package
|
|
22
|
-
- from: /home/runner/work/kouji-ui/kouji-ui/packages/core
|
|
23
|
-
- to: /home/runner/work/kouji-ui/kouji-ui/dist/kj-core
|
|
24
|
-
------------------------------------------------------------------------------
|
|
25
|
-
|
|
26
|
-
Build at: 2026-05-04T22:25:26.490Z - Time: 5957ms
|
|
27
|
-
|
package/CHANGELOG.md
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# @kouji-ui/core
|
|
2
|
-
|
|
3
|
-
## 0.0.4
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- 202d9de: CI fix: husky pre-push hook now skips in CI environments (`$CI` or `$GITHUB_ACTIONS` set). Prevents the Changesets action's automated push from being blocked by the changeset-status gate. No runtime effect on the published package.
|
|
8
|
-
- dc0fe0f: Test-only fix: rewrite `KjToastService` test suite to use `TestBed.inject()` so `inject(KJ_TOAST_STRATEGY)` resolves through Angular DI (was crashing with `new KjToastService()` outside an injection context). No runtime change.
|
|
9
|
-
- d6b40e1: Internal lint cleanup — replace `any` casts in select option storage with typed `HTMLElement & { __kjOptionValue?: unknown }` and convert ternary statement to if/else in overlay toggle. No public API or behavior change.
|
|
10
|
-
|
|
11
|
-
## 0.0.3
|
|
12
|
-
|
|
13
|
-
### Patch Changes
|
|
14
|
-
|
|
15
|
-
- c177d1e: Rewrite README with real install/usage docs, primitive index, and design principles. Replaces the Angular CLI scaffold placeholder.
|
|
16
|
-
|
|
17
|
-
## 0.0.2
|
|
18
|
-
|
|
19
|
-
### Patch Changes
|
|
20
|
-
|
|
21
|
-
- 1813d21: Initial publish — headless Angular 21 UI primitives over CDK with WCAG 2.1 AAA semantics and zero CSS.
|
package/ng-package.json
DELETED
package/src/a11y/.gitkeep
DELETED
|
File without changes
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { render } from '@testing-library/angular';
|
|
2
|
-
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
3
|
-
import { KjAriaDescribedBy } from './aria-describedby';
|
|
4
|
-
|
|
5
|
-
expect.extend(toHaveNoViolations);
|
|
6
|
-
|
|
7
|
-
describe('KjAriaDescribedBy', () => {
|
|
8
|
-
it('sets aria-describedby from a single id string', async () => {
|
|
9
|
-
const { container } = await render(
|
|
10
|
-
`<input kjAriaDescribedBy [kjDescribedBy]="'hint-1'" />
|
|
11
|
-
<span id="hint-1">This is a hint</span>`,
|
|
12
|
-
{ imports: [KjAriaDescribedBy] },
|
|
13
|
-
);
|
|
14
|
-
expect(container.querySelector('input')).toHaveAttribute('aria-describedby', 'hint-1');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('sets aria-describedby from multiple ids', async () => {
|
|
18
|
-
const { container } = await render(
|
|
19
|
-
`<input kjAriaDescribedBy [kjDescribedBy]="['hint-1', 'error-1']" />
|
|
20
|
-
<span id="hint-1">Hint</span>
|
|
21
|
-
<span id="error-1">Error</span>`,
|
|
22
|
-
{ imports: [KjAriaDescribedBy] },
|
|
23
|
-
);
|
|
24
|
-
expect(container.querySelector('input')).toHaveAttribute(
|
|
25
|
-
'aria-describedby',
|
|
26
|
-
'hint-1 error-1',
|
|
27
|
-
);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('removes aria-describedby when given empty array', async () => {
|
|
31
|
-
const { container } = await render(
|
|
32
|
-
`<input kjAriaDescribedBy [kjDescribedBy]="[]" />`,
|
|
33
|
-
{ imports: [KjAriaDescribedBy] },
|
|
34
|
-
);
|
|
35
|
-
expect(container.querySelector('input')).not.toHaveAttribute('aria-describedby');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('removes aria-describedby when given empty string', async () => {
|
|
39
|
-
const { container } = await render(
|
|
40
|
-
`<input kjAriaDescribedBy [kjDescribedBy]="''" />`,
|
|
41
|
-
{ imports: [KjAriaDescribedBy] },
|
|
42
|
-
);
|
|
43
|
-
expect(container.querySelector('input')).not.toHaveAttribute('aria-describedby');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('passes axe accessibility audit', async () => {
|
|
47
|
-
const { container } = await render(
|
|
48
|
-
`<label for="f">Name</label>
|
|
49
|
-
<input id="f" kjAriaDescribedBy [kjDescribedBy]="'hint'" />
|
|
50
|
-
<span id="hint">Enter your full name</span>`,
|
|
51
|
-
{ imports: [KjAriaDescribedBy] },
|
|
52
|
-
);
|
|
53
|
-
expect(await axe(container)).toHaveNoViolations();
|
|
54
|
-
});
|
|
55
|
-
});
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { Directive, computed, input } from '@angular/core';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Wires `aria-describedby` to one or more element IDs.
|
|
5
|
-
* Accepts a single ID string or an array of ID strings.
|
|
6
|
-
* Removes the attribute automatically when the value is empty.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```html
|
|
10
|
-
* <input kjAriaDescribedBy [kjDescribedBy]="['hint-id', errorId()]" />
|
|
11
|
-
* <span id="hint-id">Format: DD/MM/YYYY</span>
|
|
12
|
-
* ```
|
|
13
|
-
* @category Core/Accessibility
|
|
14
|
-
*/
|
|
15
|
-
@Directive({
|
|
16
|
-
selector: '[kjAriaDescribedBy]',
|
|
17
|
-
standalone: true,
|
|
18
|
-
host: {
|
|
19
|
-
'[attr.aria-describedby]': 'ariaDescribedBy()',
|
|
20
|
-
},
|
|
21
|
-
})
|
|
22
|
-
export class KjAriaDescribedBy {
|
|
23
|
-
/** One or more element IDs to reference via `aria-describedby`. */
|
|
24
|
-
kjDescribedBy = input<string | string[]>('');
|
|
25
|
-
|
|
26
|
-
/** @internal */
|
|
27
|
-
readonly ariaDescribedBy = computed(() => {
|
|
28
|
-
const val = this.kjDescribedBy();
|
|
29
|
-
const ids = Array.isArray(val) ? val : [val];
|
|
30
|
-
const filtered = ids.filter(Boolean);
|
|
31
|
-
return filtered.length ? filtered.join(' ') : null;
|
|
32
|
-
});
|
|
33
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { render, fireEvent } from '@testing-library/angular';
|
|
2
|
-
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
3
|
-
import { KjFocusTrap } from './focus-trap';
|
|
4
|
-
|
|
5
|
-
expect.extend(toHaveNoViolations);
|
|
6
|
-
|
|
7
|
-
describe('KjFocusTrap', () => {
|
|
8
|
-
it('renders without error when enabled', async () => {
|
|
9
|
-
const { container } = await render(
|
|
10
|
-
`<div kjFocusTrap [kjFocusTrapEnabled]="true">
|
|
11
|
-
<button>First</button>
|
|
12
|
-
<button>Last</button>
|
|
13
|
-
</div>`,
|
|
14
|
-
{ imports: [KjFocusTrap] },
|
|
15
|
-
);
|
|
16
|
-
expect(container.querySelector('[kjFocusTrap]')).toBeInTheDocument();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('renders without error when disabled', async () => {
|
|
20
|
-
const { container } = await render(
|
|
21
|
-
`<div kjFocusTrap [kjFocusTrapEnabled]="false">
|
|
22
|
-
<button>First</button>
|
|
23
|
-
</div>`,
|
|
24
|
-
{ imports: [KjFocusTrap] },
|
|
25
|
-
);
|
|
26
|
-
expect(container.querySelector('[kjFocusTrap]')).toBeInTheDocument();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('wraps focus from last to first on Tab when enabled', async () => {
|
|
30
|
-
const { container } = await render(
|
|
31
|
-
`<div kjFocusTrap [kjFocusTrapEnabled]="true">
|
|
32
|
-
<button>First</button>
|
|
33
|
-
<button>Last</button>
|
|
34
|
-
</div>`,
|
|
35
|
-
{ imports: [KjFocusTrap] },
|
|
36
|
-
);
|
|
37
|
-
const buttons = container.querySelectorAll('button');
|
|
38
|
-
const last = buttons[buttons.length - 1] as HTMLElement;
|
|
39
|
-
last.focus();
|
|
40
|
-
fireEvent.keyDown(document, { key: 'Tab', shiftKey: false });
|
|
41
|
-
expect(document.activeElement).toBe(buttons[0]);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('wraps focus from first to last on Shift+Tab when enabled', async () => {
|
|
45
|
-
const { container } = await render(
|
|
46
|
-
`<div kjFocusTrap [kjFocusTrapEnabled]="true">
|
|
47
|
-
<button>First</button>
|
|
48
|
-
<button>Last</button>
|
|
49
|
-
</div>`,
|
|
50
|
-
{ imports: [KjFocusTrap] },
|
|
51
|
-
);
|
|
52
|
-
const buttons = container.querySelectorAll('button');
|
|
53
|
-
const first = buttons[0] as HTMLElement;
|
|
54
|
-
first.focus();
|
|
55
|
-
fireEvent.keyDown(document, { key: 'Tab', shiftKey: true });
|
|
56
|
-
expect(document.activeElement).toBe(buttons[buttons.length - 1]);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('does not trap focus when disabled', async () => {
|
|
60
|
-
const { container } = await render(
|
|
61
|
-
`<div kjFocusTrap [kjFocusTrapEnabled]="false">
|
|
62
|
-
<button>First</button>
|
|
63
|
-
<button>Last</button>
|
|
64
|
-
</div>`,
|
|
65
|
-
{ imports: [KjFocusTrap] },
|
|
66
|
-
);
|
|
67
|
-
const buttons = container.querySelectorAll('button');
|
|
68
|
-
const last = buttons[buttons.length - 1] as HTMLElement;
|
|
69
|
-
last.focus();
|
|
70
|
-
// Tab should not redirect focus — no focus change from the handler.
|
|
71
|
-
const activeBeforeTab = document.activeElement;
|
|
72
|
-
fireEvent.keyDown(document, { key: 'Tab', shiftKey: false });
|
|
73
|
-
// When disabled, the handler does nothing, so focus stays on last.
|
|
74
|
-
expect(document.activeElement).toBe(activeBeforeTab);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('passes axe accessibility audit', async () => {
|
|
78
|
-
const { container } = await render(
|
|
79
|
-
`<div role="dialog" aria-label="Test dialog" kjFocusTrap [kjFocusTrapEnabled]="true">
|
|
80
|
-
<button>Action</button>
|
|
81
|
-
</div>`,
|
|
82
|
-
{ imports: [KjFocusTrap] },
|
|
83
|
-
);
|
|
84
|
-
expect(await axe(container)).toHaveNoViolations();
|
|
85
|
-
});
|
|
86
|
-
});
|
package/src/a11y/focus-trap.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { Directive, DestroyRef, ElementRef, afterNextRender, inject, input } from '@angular/core';
|
|
2
|
-
import { PLATFORM_ID } from '@angular/core';
|
|
3
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
4
|
-
|
|
5
|
-
const FOCUSABLE = [
|
|
6
|
-
'a[href]', 'button:not([disabled])', 'input:not([disabled])',
|
|
7
|
-
'select:not([disabled])', 'textarea:not([disabled])',
|
|
8
|
-
'[tabindex]:not([tabindex="-1"])', '[contenteditable="true"]',
|
|
9
|
-
].join(',');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Traps keyboard focus within the host element using a native Tab-key interceptor.
|
|
13
|
-
* Designed for modal dialogs, drawers, and other overlay patterns.
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* ```html
|
|
17
|
-
* <div role="dialog" kjFocusTrap [kjFocusTrapEnabled]="isOpen()">
|
|
18
|
-
* <button>Action</button>
|
|
19
|
-
* </div>
|
|
20
|
-
* ```
|
|
21
|
-
* @category Core/Accessibility
|
|
22
|
-
*/
|
|
23
|
-
@Directive({
|
|
24
|
-
selector: '[kjFocusTrap]',
|
|
25
|
-
standalone: true,
|
|
26
|
-
})
|
|
27
|
-
export class KjFocusTrap {
|
|
28
|
-
private readonly el = inject(ElementRef<HTMLElement>);
|
|
29
|
-
private readonly destroyRef = inject(DestroyRef);
|
|
30
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
31
|
-
|
|
32
|
-
/** Whether the focus trap is active. Set to true when the overlay is open. */
|
|
33
|
-
kjFocusTrapEnabled = input<boolean>(false);
|
|
34
|
-
|
|
35
|
-
constructor() {
|
|
36
|
-
afterNextRender(() => {
|
|
37
|
-
if (!isPlatformBrowser(this.platformId)) return;
|
|
38
|
-
|
|
39
|
-
const handleKeydown = (e: KeyboardEvent) => {
|
|
40
|
-
if (!this.kjFocusTrapEnabled() || e.key !== 'Tab') return;
|
|
41
|
-
const focusable = (Array.from(
|
|
42
|
-
this.el.nativeElement.querySelectorAll(FOCUSABLE)
|
|
43
|
-
) as HTMLElement[]).filter(el => !el.closest('[hidden]') && getComputedStyle(el).display !== 'none');
|
|
44
|
-
|
|
45
|
-
if (!focusable.length) { e.preventDefault(); return; }
|
|
46
|
-
const first = focusable[0];
|
|
47
|
-
const last = focusable[focusable.length - 1];
|
|
48
|
-
|
|
49
|
-
if (e.shiftKey) {
|
|
50
|
-
if (document.activeElement === first || !this.el.nativeElement.contains(document.activeElement)) {
|
|
51
|
-
e.preventDefault(); last.focus();
|
|
52
|
-
}
|
|
53
|
-
} else {
|
|
54
|
-
if (document.activeElement === last || !this.el.nativeElement.contains(document.activeElement)) {
|
|
55
|
-
e.preventDefault(); first.focus();
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
document.addEventListener('keydown', handleKeydown);
|
|
61
|
-
this.destroyRef.onDestroy(() => document.removeEventListener('keydown', handleKeydown));
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** Focuses the first tabbable element inside the trap. */
|
|
66
|
-
focusFirst(): void {
|
|
67
|
-
const el = this.el.nativeElement.querySelector(FOCUSABLE) as HTMLElement | null;
|
|
68
|
-
el?.focus();
|
|
69
|
-
}
|
|
70
|
-
}
|
package/src/a11y/index.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export { KjAriaDescribedBy } from './aria-describedby';
|
|
2
|
-
export { KjFocusTrap } from './focus-trap';
|
|
3
|
-
export { KjLiveRegion, type KjLivePoliteness } from './live-region';
|
|
4
|
-
export {
|
|
5
|
-
KjRovingTabindex,
|
|
6
|
-
KjRovingTabindexItemDirective,
|
|
7
|
-
KJ_ROVING_TABINDEX,
|
|
8
|
-
} from './roving-tabindex';
|
|
9
|
-
export { KjVisuallyHidden } from './visually-hidden';
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { render } from '@testing-library/angular';
|
|
2
|
-
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
3
|
-
import { vi } from 'vitest';
|
|
4
|
-
import { KjLiveRegion } from './live-region';
|
|
5
|
-
|
|
6
|
-
expect.extend(toHaveNoViolations);
|
|
7
|
-
|
|
8
|
-
describe('KjLiveRegion', () => {
|
|
9
|
-
it('sets aria-live to polite by default', async () => {
|
|
10
|
-
const { container } = await render(
|
|
11
|
-
`<div kjLiveRegion></div>`,
|
|
12
|
-
{ imports: [KjLiveRegion] },
|
|
13
|
-
);
|
|
14
|
-
expect(container.querySelector('div')).toHaveAttribute('aria-live', 'polite');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('sets aria-live to assertive when specified', async () => {
|
|
18
|
-
const { container } = await render(
|
|
19
|
-
`<div kjLiveRegion [kjPoliteness]="'assertive'"></div>`,
|
|
20
|
-
{ imports: [KjLiveRegion] },
|
|
21
|
-
);
|
|
22
|
-
expect(container.querySelector('div')).toHaveAttribute('aria-live', 'assertive');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('sets aria-atomic to true', async () => {
|
|
26
|
-
const { container } = await render(
|
|
27
|
-
`<div kjLiveRegion></div>`,
|
|
28
|
-
{ imports: [KjLiveRegion] },
|
|
29
|
-
);
|
|
30
|
-
expect(container.querySelector('div')).toHaveAttribute('aria-atomic', 'true');
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('announces a message via DOM text content after a brief timeout', async () => {
|
|
34
|
-
vi.useFakeTimers();
|
|
35
|
-
const { container } = await render(
|
|
36
|
-
`<div kjLiveRegion></div>`,
|
|
37
|
-
{ imports: [KjLiveRegion] },
|
|
38
|
-
);
|
|
39
|
-
const div = container.querySelector('div')!;
|
|
40
|
-
|
|
41
|
-
// Simulate the announce mechanism: clear, then set after 50ms.
|
|
42
|
-
div.textContent = '';
|
|
43
|
-
setTimeout(() => { div.textContent = 'Item saved'; }, 50);
|
|
44
|
-
|
|
45
|
-
vi.advanceTimersByTime(50);
|
|
46
|
-
expect(div.textContent).toBe('Item saved');
|
|
47
|
-
|
|
48
|
-
vi.useRealTimers();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('passes axe accessibility audit', async () => {
|
|
52
|
-
const { container } = await render(
|
|
53
|
-
`<div kjLiveRegion></div>`,
|
|
54
|
-
{ imports: [KjLiveRegion] },
|
|
55
|
-
);
|
|
56
|
-
expect(await axe(container)).toHaveNoViolations();
|
|
57
|
-
});
|
|
58
|
-
});
|