@ultimat3/ui 19.1.2 → 19.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/package.json +5 -5
- package/src/a11y.ts +20 -2
- package/src/components/Divider.module.scss +1 -1
- package/src/errors.ts +1 -1
- package/src/theme/runtime-slot.ts +6 -1
- package/src/theme/solid-adapter.ts +10 -2
- package/src/tokens/_typography.scss +4 -0
- package/src/tokens/reset.scss +12 -2
package/README.md
CHANGED
|
@@ -186,7 +186,7 @@ to render on the server.**
|
|
|
186
186
|
| | Server render | Client render |
|
|
187
187
|
|---|---|---|
|
|
188
188
|
| The renderer | `@ultimat3/render`'s inert JSX factory — a component is a plain function, called once | Solid, with a reactive graph |
|
|
189
|
-
| The runtime | `INERT_SOLID_RUNTIME`, handed out automatically: signals hold, memos recompute on read, effects never run | the real one, registered once: `setSolidRuntime(
|
|
189
|
+
| The runtime | `INERT_SOLID_RUNTIME`, handed out automatically: signals hold, memos recompute on read, effects never run | the real one, registered once: `setSolidRuntime({ createContext, useContext, createSignal, createMemo, createEffect, onCleanup })`, six named imports from `'solid-js'` — never `import * as`, which keeps every export of solid-js in the chunk (14.8 kB minified, measured) |
|
|
190
190
|
| Where `useUi()` reads | the request — `currentLocale()`, `currentTimeZone()`, `useI18n()` | `<UiProvider>`, through Solid's context |
|
|
191
191
|
| `<UiProvider>` | **throws** `X_UI_RUNTIME_MISSING` | the one injection point |
|
|
192
192
|
|
|
@@ -224,7 +224,14 @@ import '../../shared/global'; // `shared/global.scss` is the app's one `@use '@u
|
|
|
224
224
|
import { createTranslator } from '@ultimat3/i18n';
|
|
225
225
|
import { setSolidRuntime, UiProvider } from '@ultimat3/ui';
|
|
226
226
|
import type { JSX } from 'solid-js';
|
|
227
|
-
import
|
|
227
|
+
import {
|
|
228
|
+
createContext,
|
|
229
|
+
createEffect,
|
|
230
|
+
createMemo,
|
|
231
|
+
createSignal,
|
|
232
|
+
onCleanup,
|
|
233
|
+
useContext,
|
|
234
|
+
} from 'solid-js';
|
|
228
235
|
import { render } from 'solid-js/web';
|
|
229
236
|
|
|
230
237
|
interface Props {
|
|
@@ -240,7 +247,10 @@ interface Props {
|
|
|
240
247
|
export function mount(el: HTMLElement, props: Props): void {
|
|
241
248
|
// NOT `await import('solid-js')`: the chunk already carries Solid statically, so the await buys
|
|
242
249
|
// no bytes and makes `mount` async — and the hydration runtime calls it synchronously.
|
|
243
|
-
|
|
250
|
+
// NOT `import * as solidRuntime` either: a namespace handed to a function cannot be shaken, and
|
|
251
|
+
// it costs 14.8 kB of solid-js per island chunk that nothing calls. The six picks are the whole
|
|
252
|
+
// contract.
|
|
253
|
+
setSolidRuntime({ createContext, useContext, createSignal, createMemo, createEffect, onCleanup });
|
|
244
254
|
el.textContent = ''; // Solid's `render` APPENDS; the server's shell would stay above this one.
|
|
245
255
|
render(
|
|
246
256
|
() => (
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/ui",
|
|
3
|
-
"version": "19.
|
|
3
|
+
"version": "19.2.0",
|
|
4
4
|
"description": "SolidJS design system: semantic design tokens, dark/RTL-ready SCSS modules, a11y primitives",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"icons": "bun run src/icons/build-icons.ts"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@ultimat3/core": "19.
|
|
48
|
-
"@ultimat3/i18n": "19.
|
|
49
|
-
"@ultimat3/money": "19.
|
|
50
|
-
"@ultimat3/time": "19.
|
|
47
|
+
"@ultimat3/core": "19.2.0",
|
|
48
|
+
"@ultimat3/i18n": "19.2.0",
|
|
49
|
+
"@ultimat3/money": "19.2.0",
|
|
50
|
+
"@ultimat3/time": "19.2.0"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"solid-js": "^1.9.0"
|
package/src/a11y.ts
CHANGED
|
@@ -197,6 +197,25 @@ export function createRovingTabindex(
|
|
|
197
197
|
|
|
198
198
|
export type Politeness = 'polite' | 'assertive';
|
|
199
199
|
|
|
200
|
+
/**
|
|
201
|
+
* One region per politeness level — this plus `-polite` / `-assertive` is the id — and this on its
|
|
202
|
+
* own is the CLASS both of them wear, because they are hidden identically.
|
|
203
|
+
*
|
|
204
|
+
* A class, and not the `region.style.cssText` this wrote until 19.1.3: a style a script applies at
|
|
205
|
+
* runtime is not among the inline `<style>` bodies the framework's own CSP hashed at render time
|
|
206
|
+
* (`packages/cli/src/style-csp.ts`, sent by `dev-roles.ts`), so a clean browser logged `Applying
|
|
207
|
+
* inline style violates the following Content Security Policy directive 'style-src'` on every page
|
|
208
|
+
* load — and an app that promotes that report-only policy to enforcing would have the declaration
|
|
209
|
+
* dropped and the announcement painted on screen as visible page content. The rules live in
|
|
210
|
+
* `tokens/reset.scss`, through the same `visually-hidden` mixin every other off-screen label in
|
|
211
|
+
* this package uses.
|
|
212
|
+
*
|
|
213
|
+
* That the app loads this package's global stylesheet is not a new assumption — every rule this
|
|
214
|
+
* package emits reads a custom property from it, and `x verify` refuses a document defining none
|
|
215
|
+
* (`X_STYLES_GLOBAL_MISSING`). Being ANNOUNCED does not depend on it either way: `aria-live` is
|
|
216
|
+
* read off the accessibility tree, so a region with no stylesheet is still read out, and only the
|
|
217
|
+
* hiding is lost.
|
|
218
|
+
*/
|
|
200
219
|
const LIVE_REGION_ID = 'ultimate-live-region';
|
|
201
220
|
|
|
202
221
|
/**
|
|
@@ -211,11 +230,10 @@ export function announce(message: string, politeness: Politeness = 'polite'): vo
|
|
|
211
230
|
if (region === null) {
|
|
212
231
|
region = document.createElement('div');
|
|
213
232
|
region.id = id;
|
|
233
|
+
region.className = LIVE_REGION_ID;
|
|
214
234
|
region.setAttribute('role', politeness === 'assertive' ? 'alert' : 'status');
|
|
215
235
|
region.setAttribute('aria-live', politeness);
|
|
216
236
|
region.setAttribute('aria-atomic', 'true');
|
|
217
|
-
region.style.cssText =
|
|
218
|
-
'position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip-path:inset(50%)';
|
|
219
237
|
document.body.appendChild(region);
|
|
220
238
|
}
|
|
221
239
|
region.textContent = '';
|
package/src/errors.ts
CHANGED
|
@@ -107,7 +107,7 @@ export function providerNeedsRuntimeError(): UiError {
|
|
|
107
107
|
code: UI_ERROR_CODES.runtimeMissing,
|
|
108
108
|
cause:
|
|
109
109
|
'<UiProvider> was rendered with no Solid runtime registered, so its locale, time zone, currency and translator would reach no component',
|
|
110
|
-
fix: "in an island, paste `import
|
|
110
|
+
fix: "in an island, paste `import { createContext, createEffect, createMemo, createSignal, onCleanup, useContext } from 'solid-js';` at the top of the *.island.tsx and `setSolidRuntime({ createContext, useContext, createSignal, createMemo, createEffect, onCleanup });` as the first line of its mount(), above the render() that builds <UiProvider> — six named imports, never `import * as`, which keeps all of solid-js in the chunk; on the server, delete <UiProvider> — useUi() already reads the request locale and time zone",
|
|
111
111
|
});
|
|
112
112
|
}
|
|
113
113
|
|
|
@@ -10,7 +10,12 @@ import type { SolidRuntime } from './solid-adapter';
|
|
|
10
10
|
|
|
11
11
|
let runtime: SolidRuntime | null = null;
|
|
12
12
|
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Register once, in the app entry, before the first render. Hand it the six named imports —
|
|
15
|
+
* `{ createContext, useContext, createSignal, createMemo, createEffect, onCleanup }` — and not
|
|
16
|
+
* `import * as solidRuntime`: the namespace registers too, and costs 14.8 kB of unshaken solid-js
|
|
17
|
+
* per island chunk (`solid-adapter.ts` has the measurement).
|
|
18
|
+
*/
|
|
14
19
|
export function setSolidRuntime(next: SolidRuntime): void {
|
|
15
20
|
runtime = next;
|
|
16
21
|
}
|
|
@@ -29,7 +29,15 @@ export interface SolidContext<T> {
|
|
|
29
29
|
readonly Provider: (props: { value: T; children: JSX.Element }) => JSX.Element;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* The exact slice of solid-js the design system touches — six functions, and the registration is
|
|
34
|
+
* meant to be handed exactly those six as NAMED imports: `setSolidRuntime({ createContext,
|
|
35
|
+
* useContext, createSignal, createMemo, createEffect, onCleanup })`. A `typeof import('solid-js')`
|
|
36
|
+
* namespace still satisfies this shape and still registers, but a namespace object passed to a
|
|
37
|
+
* function is one the bundler cannot shake — every export of solid-js stays in the chunk. Measured
|
|
38
|
+
* with the island bundler's own settings (minified, production, no splitting): the namespace form
|
|
39
|
+
* is 28,556 B against 13,708 B for the six picks, 14.8 kB per island that nothing ever calls.
|
|
40
|
+
*/
|
|
33
41
|
export interface SolidRuntime {
|
|
34
42
|
createContext<T>(defaultValue: T): SolidContext<T>;
|
|
35
43
|
useContext<T>(context: SolidContext<T>): T;
|
|
@@ -64,7 +72,7 @@ export function solid(): SolidRuntime {
|
|
|
64
72
|
// ONE literal, never a concatenation: `fix-scan.ts` reads a single literal in this position
|
|
65
73
|
// and counts anything else `unreadable`, so a fix split across `+` is a fix the gate stops
|
|
66
74
|
// checking.
|
|
67
|
-
"paste `import
|
|
75
|
+
"paste `import { createContext, createEffect, createMemo, createSignal, onCleanup, useContext } from 'solid-js';` at the top of your *.island.tsx and `setSolidRuntime({ createContext, useContext, createSignal, createMemo, createEffect, onCleanup });` as the first line of its mount(), above render() — six named imports, never `import * as`, which keeps all of solid-js in the chunk; a server render needs none",
|
|
68
76
|
);
|
|
69
77
|
}
|
|
70
78
|
return INERT_SOLID_RUNTIME;
|
package/src/tokens/reset.scss
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Modern reset. Assumes `theme.scss` has already run — every value here is a
|
|
2
|
-
// token, so the reset itself flips with the theme.
|
|
2
|
+
// token, so the reset itself flips with the theme. Plus the one GLOBAL class the package owns:
|
|
3
|
+
// the element wearing it is created by `announce()` at runtime, so no component module can hold it.
|
|
3
4
|
|
|
4
5
|
@use 'index' as t;
|
|
5
6
|
|
|
@@ -41,7 +42,7 @@ h6 {
|
|
|
41
42
|
color: t.role('fg-strong');
|
|
42
43
|
font-weight: t.weight(semibold);
|
|
43
44
|
line-height: t.leading(tight);
|
|
44
|
-
letter-spacing:
|
|
45
|
+
letter-spacing: t.tracking(tight);
|
|
45
46
|
text-wrap: balance;
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -134,6 +135,15 @@ html:has(dialog:modal) {
|
|
|
134
135
|
overflow: hidden;
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
// The live region `announce()` (`../a11y.ts`) appends to <body>, hidden from here rather than
|
|
139
|
+
// through `element.style`: a style a script applies at runtime is not among the inline styles the
|
|
140
|
+
// CSP the framework itself sends has hashed, so it logged a `style-src` violation on every page
|
|
141
|
+
// load and would lose its styling outright under an enforcing policy — putting a screen-reader
|
|
142
|
+
// message on screen. Off-screen, never `display: none`, which would stop it being announced at all.
|
|
143
|
+
.ultimate-live-region {
|
|
144
|
+
@include t.visually-hidden;
|
|
145
|
+
}
|
|
146
|
+
|
|
137
147
|
// Global motion guard. Placed last so it wins over every component rule.
|
|
138
148
|
@media (prefers-reduced-motion: reduce) {
|
|
139
149
|
html {
|