@shadow-js/ui 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +598 -2
  4. package/dist/cli.mjs +2293 -0
  5. package/dist/dev/cli/schema.d.ts +73 -0
  6. package/dist/dev/index.d.ts +6 -0
  7. package/dist/dev/index.js +5 -0
  8. package/dist/dev/locales/ar.d.ts +141 -0
  9. package/dist/dev/locales/ar.js +128 -0
  10. package/dist/dev/locales/ar.js.map +1 -0
  11. package/dist/dev/locales/en.d.ts +141 -0
  12. package/dist/dev/locales/en.js +128 -0
  13. package/dist/dev/locales/en.js.map +1 -0
  14. package/dist/dev/registry/utils/utils.d.ts +29 -0
  15. package/dist/dev/registry/utils/utils.js +27 -0
  16. package/dist/dev/registry/utils/utils.js.map +1 -0
  17. package/dist/dev/theme/theme-logic.d.ts +187 -0
  18. package/dist/dev/theme/theme-logic.js +184 -0
  19. package/dist/dev/theme/theme-logic.js.map +1 -0
  20. package/dist/dev/theme/theme-provider.d.ts +90 -0
  21. package/dist/dev/theme/theme-provider.js +150 -0
  22. package/dist/dev/theme/theme-provider.js.map +1 -0
  23. package/dist/prod/index.js +1 -0
  24. package/dist/prod/locales/ar.js +2 -0
  25. package/dist/prod/locales/ar.js.map +1 -0
  26. package/dist/prod/locales/en.js +2 -0
  27. package/dist/prod/locales/en.js.map +1 -0
  28. package/dist/prod/registry/utils/utils.js +2 -0
  29. package/dist/prod/registry/utils/utils.js.map +1 -0
  30. package/dist/prod/theme/theme-logic.js +2 -0
  31. package/dist/prod/theme/theme-logic.js.map +1 -0
  32. package/dist/prod/theme/theme-provider.js +2 -0
  33. package/dist/prod/theme/theme-provider.js.map +1 -0
  34. package/package.json +102 -4
  35. package/registry/accordion/accordion.tsx +210 -0
  36. package/registry/accordion/item.json +4 -0
  37. package/registry/alert/alert.tsx +200 -0
  38. package/registry/alert/item.json +4 -0
  39. package/registry/alert-dialog/alert-dialog.tsx +530 -0
  40. package/registry/alert-dialog/item.json +4 -0
  41. package/registry/aspect-ratio/aspect-ratio.tsx +71 -0
  42. package/registry/aspect-ratio/item.json +4 -0
  43. package/registry/attachment/attachment.tsx +459 -0
  44. package/registry/attachment/item.json +4 -0
  45. package/registry/avatar/avatar.tsx +385 -0
  46. package/registry/avatar/item.json +4 -0
  47. package/registry/badge/badge.tsx +108 -0
  48. package/registry/badge/item.json +4 -0
  49. package/registry/breadcrumb/breadcrumb.tsx +354 -0
  50. package/registry/breadcrumb/item.json +4 -0
  51. package/registry/bubble/bubble.tsx +260 -0
  52. package/registry/bubble/item.json +4 -0
  53. package/registry/button/button.tsx +125 -0
  54. package/registry/button/item.json +4 -0
  55. package/registry/button-group/button-group.tsx +189 -0
  56. package/registry/button-group/item.json +4 -0
  57. package/registry/calendar/calendar.tsx +348 -0
  58. package/registry/calendar/item.json +4 -0
  59. package/registry/card/card.tsx +311 -0
  60. package/registry/card/item.json +4 -0
  61. package/registry/carousel/carousel.tsx +851 -0
  62. package/registry/carousel/item.json +4 -0
  63. package/registry/chart/chart.tsx +1956 -0
  64. package/registry/chart/item.json +4 -0
  65. package/registry/chart-scale/chart-scale.ts +670 -0
  66. package/registry/chart-scale/item.json +5 -0
  67. package/registry/checkbox/checkbox.tsx +79 -0
  68. package/registry/checkbox/item.json +4 -0
  69. package/registry/code-block/code-block.tsx +470 -0
  70. package/registry/code-block/item.json +4 -0
  71. package/registry/collapsible/collapsible.tsx +117 -0
  72. package/registry/collapsible/item.json +4 -0
  73. package/registry/combobox/combobox.tsx +977 -0
  74. package/registry/combobox/item.json +4 -0
  75. package/registry/command/command.tsx +757 -0
  76. package/registry/command/item.json +4 -0
  77. package/registry/context-menu/context-menu.tsx +697 -0
  78. package/registry/context-menu/item.json +4 -0
  79. package/registry/data-table/data-table.tsx +437 -0
  80. package/registry/data-table/item.json +4 -0
  81. package/registry/date-picker/date-picker.tsx +349 -0
  82. package/registry/date-picker/item.json +4 -0
  83. package/registry/dialog/dialog.tsx +412 -0
  84. package/registry/dialog/item.json +4 -0
  85. package/registry/direction/direction.tsx +88 -0
  86. package/registry/direction/item.json +4 -0
  87. package/registry/drawer/drawer.tsx +787 -0
  88. package/registry/drawer/item.json +4 -0
  89. package/registry/dropdown-menu/dropdown-menu.tsx +692 -0
  90. package/registry/dropdown-menu/item.json +4 -0
  91. package/registry/empty/empty.tsx +240 -0
  92. package/registry/empty/item.json +4 -0
  93. package/registry/field/field.tsx +624 -0
  94. package/registry/field/item.json +4 -0
  95. package/registry/form/form.tsx +1112 -0
  96. package/registry/form/item.json +4 -0
  97. package/registry/hover-card/hover-card.tsx +144 -0
  98. package/registry/hover-card/item.json +4 -0
  99. package/registry/icons/icons.tsx +1691 -0
  100. package/registry/icons/item.json +4 -0
  101. package/registry/input/input.tsx +43 -0
  102. package/registry/input/item.json +4 -0
  103. package/registry/input-group/input-group.tsx +315 -0
  104. package/registry/input-group/item.json +4 -0
  105. package/registry/input-otp/input-otp.tsx +329 -0
  106. package/registry/input-otp/item.json +4 -0
  107. package/registry/item/item.json +4 -0
  108. package/registry/item/item.tsx +439 -0
  109. package/registry/kbd/item.json +4 -0
  110. package/registry/kbd/kbd.tsx +65 -0
  111. package/registry/label/item.json +4 -0
  112. package/registry/label/label.tsx +60 -0
  113. package/registry/marker/item.json +4 -0
  114. package/registry/marker/marker.tsx +175 -0
  115. package/registry/menubar/item.json +4 -0
  116. package/registry/menubar/menubar.tsx +694 -0
  117. package/registry/message/item.json +4 -0
  118. package/registry/message/message.tsx +280 -0
  119. package/registry/message-scroller/item.json +4 -0
  120. package/registry/message-scroller/message-scroller.tsx +262 -0
  121. package/registry/native-select/item.json +4 -0
  122. package/registry/native-select/native-select.tsx +113 -0
  123. package/registry/navigation-menu/item.json +4 -0
  124. package/registry/navigation-menu/navigation-menu.tsx +362 -0
  125. package/registry/pagination/item.json +4 -0
  126. package/registry/pagination/pagination.tsx +460 -0
  127. package/registry/password-input/item.json +5 -0
  128. package/registry/password-input/password-input.tsx +494 -0
  129. package/registry/popover/item.json +4 -0
  130. package/registry/popover/popover.tsx +284 -0
  131. package/registry/progress/item.json +4 -0
  132. package/registry/progress/progress.tsx +190 -0
  133. package/registry/questionnaire/item.json +4 -0
  134. package/registry/questionnaire/questionnaire.tsx +569 -0
  135. package/registry/radio-group/item.json +4 -0
  136. package/registry/radio-group/radio-group.tsx +101 -0
  137. package/registry/resizable/item.json +4 -0
  138. package/registry/resizable/resizable.tsx +455 -0
  139. package/registry/scroll-area/item.json +4 -0
  140. package/registry/scroll-area/scroll-area.tsx +149 -0
  141. package/registry/select/item.json +4 -0
  142. package/registry/select/select.tsx +571 -0
  143. package/registry/separator/item.json +4 -0
  144. package/registry/separator/separator.tsx +52 -0
  145. package/registry/sheet/item.json +4 -0
  146. package/registry/sheet/sheet.tsx +476 -0
  147. package/registry/sidebar/item.json +4 -0
  148. package/registry/sidebar/sidebar.tsx +1870 -0
  149. package/registry/skeleton/item.json +4 -0
  150. package/registry/skeleton/skeleton.tsx +47 -0
  151. package/registry/slider/item.json +4 -0
  152. package/registry/slider/slider.tsx +149 -0
  153. package/registry/spinner/item.json +4 -0
  154. package/registry/spinner/spinner.tsx +51 -0
  155. package/registry/switch/item.json +4 -0
  156. package/registry/switch/switch.tsx +93 -0
  157. package/registry/table/item.json +4 -0
  158. package/registry/table/table.tsx +373 -0
  159. package/registry/tabs/item.json +4 -0
  160. package/registry/tabs/tabs.tsx +209 -0
  161. package/registry/textarea/item.json +4 -0
  162. package/registry/textarea/textarea.tsx +51 -0
  163. package/registry/toast/item.json +4 -0
  164. package/registry/toast/toast.tsx +949 -0
  165. package/registry/toggle/item.json +4 -0
  166. package/registry/toggle/toggle.tsx +110 -0
  167. package/registry/toggle-group/item.json +4 -0
  168. package/registry/toggle-group/toggle-group.tsx +189 -0
  169. package/registry/tooltip/item.json +4 -0
  170. package/registry/tooltip/tooltip.tsx +169 -0
  171. package/registry/trace/item.json +4 -0
  172. package/registry/trace/trace.tsx +356 -0
  173. package/registry/ui-strings/item.json +5 -0
  174. package/registry/ui-strings/ui-strings.tsx +454 -0
  175. package/registry/use-highlighted-code/item.json +5 -0
  176. package/registry/use-highlighted-code/use-highlighted-code.tsx +145 -0
  177. package/registry/use-mobile/item.json +5 -0
  178. package/registry/use-mobile/use-mobile.ts +46 -0
  179. package/registry/utils/item.json +5 -0
  180. package/registry/utils/utils.ts +38 -0
  181. package/registry.json +3274 -0
  182. package/styles/animations.css +321 -0
  183. package/styles/theme-core.css +55 -0
  184. package/styles/theme.css +12 -0
  185. package/styles/themes/gray.css +69 -0
  186. package/styles/themes/neutral.css +69 -0
  187. package/styles/themes/slate.css +69 -0
  188. package/styles/themes/stone.css +69 -0
  189. package/styles/themes/zinc.css +69 -0
  190. package/styles/typeset.css +251 -0
@@ -0,0 +1,29 @@
1
+ //#region registry/utils/utils.d.ts
2
+ /**
3
+ * Anything a ShadowJS `class` attribute accepts: strings, numbers, nested (readonly) arrays and
4
+ * `{ name: condition }` objects. Falsy values are skipped.
5
+ * @since 1.0.0
6
+ */
7
+ export type ClassValue = string | number | bigint | boolean | null | undefined | readonly ClassValue[] | {
8
+ readonly [name: string]: unknown;
9
+ };
10
+ /**
11
+ * Joins class names (strings, arrays, `{ name: condition }` objects) and resolves Tailwind
12
+ * conflicts, so the last utility wins: `cn("px-2", props.class)` lets the caller override `px-2`.
13
+ *
14
+ * @param inputs - Any mix of class values; falsy values are skipped.
15
+ * @returns One class string with conflicting Tailwind utilities removed.
16
+ * @example
17
+ * ```ts
18
+ * import { cn } from "@/lib/utils";
19
+ *
20
+ * const active = true;
21
+ * export const classes = cn("px-2 py-1", active && "bg-primary", "px-4");
22
+ * // "py-1 bg-primary px-4"
23
+ * ```
24
+ * @since 1.0.0
25
+ * @see {@link ClassValue}
26
+ */
27
+ export declare function cn(...inputs: ClassValue[]): string;
28
+ //#endregion
29
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1,27 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ //#region registry/utils/utils.ts
4
+ /**
5
+ * Joins class names (strings, arrays, `{ name: condition }` objects) and resolves Tailwind
6
+ * conflicts, so the last utility wins: `cn("px-2", props.class)` lets the caller override `px-2`.
7
+ *
8
+ * @param inputs - Any mix of class values; falsy values are skipped.
9
+ * @returns One class string with conflicting Tailwind utilities removed.
10
+ * @example
11
+ * ```ts
12
+ * import { cn } from "@/lib/utils";
13
+ *
14
+ * const active = true;
15
+ * export const classes = cn("px-2 py-1", active && "bg-primary", "px-4");
16
+ * // "py-1 bg-primary px-4"
17
+ * ```
18
+ * @since 1.0.0
19
+ * @see {@link ClassValue}
20
+ */
21
+ function cn(...inputs) {
22
+ return twMerge(clsx(...inputs));
23
+ }
24
+ //#endregion
25
+ export { cn };
26
+
27
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../../../registry/utils/utils.ts"],"sourcesContent":["import { clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Anything a ShadowJS `class` attribute accepts: strings, numbers, nested (readonly) arrays and\n * `{ name: condition }` objects. Falsy values are skipped.\n * @since 1.0.0\n */\nexport type ClassValue =\n | string\n | number\n | bigint\n | boolean\n | null\n | undefined\n | readonly ClassValue[]\n | { readonly [name: string]: unknown };\n\n/**\n * Joins class names (strings, arrays, `{ name: condition }` objects) and resolves Tailwind\n * conflicts, so the last utility wins: `cn(\"px-2\", props.class)` lets the caller override `px-2`.\n *\n * @param inputs - Any mix of class values; falsy values are skipped.\n * @returns One class string with conflicting Tailwind utilities removed.\n * @example\n * ```ts\n * import { cn } from \"@/lib/utils\";\n *\n * const active = true;\n * export const classes = cn(\"px-2 py-1\", active && \"bg-primary\", \"px-4\");\n * // \"py-1 bg-primary px-4\"\n * ```\n * @since 1.0.0\n * @see {@link ClassValue}\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(...(inputs as Parameters<typeof clsx>)));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,GAAG,GAAG,QAA8B;CAClD,OAAO,QAAQ,KAAK,GAAI,MAAkC,CAAC;AAC7D"}
@@ -0,0 +1,187 @@
1
+ //#region src/theme/theme-logic.d.ts
2
+ /**
3
+ * Pure theme logic, shared by the provider and the no-flash script so both agree on what
4
+ * "system", "light" and "dark" mean. Nothing here touches `window` unless it is handed an object.
5
+ * @packageDocumentation
6
+ */
7
+ /**
8
+ * The theme a user picks: `"light"`, `"dark"`, or `"system"` to follow the operating system.
9
+ * @since 1.0.0
10
+ */
11
+ export type Theme = "light" | "dark" | "system";
12
+ /**
13
+ * The theme that is actually shown: `"light"` or `"dark"`.
14
+ * @since 1.0.0
15
+ */
16
+ export type ResolvedTheme = "light" | "dark";
17
+ /**
18
+ * Where the resolved theme is written on the root element. `"class"` (the default) sets a `light`
19
+ * or `dark` class, which the generated stylesheet matches for its dark styles (the `dark` variant of
20
+ * Tailwind is `.dark`). A `data-*` name such as `"data-theme"` sets that attribute to `"light"` or
21
+ * `"dark"` instead; the stylesheet then has to match that attribute as well.
22
+ * @since 1.0.0
23
+ */
24
+ export type ThemeAttribute = "class" | `data-${string}`;
25
+ /**
26
+ * Options shared by `ThemeProvider` and `themeScript`. Pass the same values to both, or the page can
27
+ * show one theme before the provider switches to another.
28
+ * @since 1.0.0
29
+ */
30
+ export interface ThemeOptions {
31
+ /**
32
+ * `localStorage` key that persists the choice. The stored value is `"light"`, `"dark"` or
33
+ * `"system"`; a missing or invalid value falls back to `defaultTheme`.
34
+ * @default "shadow-ui-theme"
35
+ */
36
+ readonly storageKey?: string | undefined;
37
+ /**
38
+ * Theme used before the user chooses one, and when the stored value is missing or invalid.
39
+ * @default "system"
40
+ */
41
+ readonly defaultTheme?: Theme | undefined;
42
+ /**
43
+ * Where the resolved theme is written: `"class"` for a `light` or `dark` class on `<html>`, or a
44
+ * `data-*` attribute name such as `"data-theme"` (see {@link ThemeAttribute}).
45
+ * @default "class"
46
+ */
47
+ readonly attribute?: ThemeAttribute | undefined;
48
+ /**
49
+ * Follow `prefers-color-scheme` while the theme is `"system"`. When `false`, `"system"` shows light.
50
+ * @default true
51
+ */
52
+ readonly enableSystem?: boolean | undefined;
53
+ }
54
+ /**
55
+ * The `localStorage` key that holds the theme choice when `storageKey` is not given.
56
+ * @example
57
+ * import { DEFAULT_STORAGE_KEY } from "@shadow-js/ui";
58
+ *
59
+ * window.localStorage.getItem(DEFAULT_STORAGE_KEY);
60
+ * @since 1.0.0
61
+ */
62
+ export declare const DEFAULT_STORAGE_KEY = "shadow-ui-theme";
63
+ /**
64
+ * Narrows an unknown value to a {@link Theme}. Use it on anything read from storage or from an event
65
+ * before trusting it.
66
+ *
67
+ * @param value Anything, such as a value read from storage.
68
+ * @returns `true` for `"light"`, `"dark"` and `"system"`, and `false` for everything else.
69
+ * @example
70
+ * import { DEFAULT_STORAGE_KEY, isTheme } from "@shadow-js/ui";
71
+ *
72
+ * isTheme(window.localStorage.getItem(DEFAULT_STORAGE_KEY)); // false when nothing is stored
73
+ * @since 1.0.0
74
+ */
75
+ export declare function isTheme(value: unknown): value is Theme;
76
+ /**
77
+ * Turns a theme choice into the theme to show. Only `"system"` depends on the operating system.
78
+ *
79
+ * @param theme The user's choice.
80
+ * @param systemPrefersDark Whether the operating system prefers a dark scheme.
81
+ * @param enableSystem When `false`, `"system"` resolves to light. Default `true`.
82
+ * @returns `"light"` or `"dark"`.
83
+ * @example
84
+ * import { resolveTheme } from "@shadow-js/ui";
85
+ *
86
+ * resolveTheme("system", true); // "dark"
87
+ * @since 1.0.0
88
+ */
89
+ export declare function resolveTheme(theme: Theme, systemPrefersDark: boolean, enableSystem?: boolean): ResolvedTheme;
90
+ /**
91
+ * Writes the resolved theme to the root element: sets the `light` or `dark` class (removing the
92
+ * other) or the `data-*` attribute, and sets `color-scheme` so form controls and scrollbars follow.
93
+ *
94
+ * @param root The element to write to, usually `document.documentElement`.
95
+ * @param resolved The theme to show.
96
+ * @param attribute Where to write it: `"class"` (the default) or a `data-*` attribute name.
97
+ * @example
98
+ * import { applyTheme } from "@shadow-js/ui";
99
+ *
100
+ * applyTheme(document.documentElement, "dark");
101
+ * @since 1.0.0
102
+ */
103
+ export declare function applyTheme(root: HTMLElement, resolved: ResolvedTheme, attribute?: ThemeAttribute): void;
104
+ /**
105
+ * Reads the stored theme. A missing or invalid value gives `undefined`. A storage the browser
106
+ * blocks (private windows, disabled site data) or has run out of room for also gives `undefined`:
107
+ * those raise a `SecurityError`, `NotAllowedError` or `QuotaExceededError` `DOMException`.
108
+ *
109
+ * @param storage The storage to read, usually `window.localStorage`.
110
+ * @param key The storage key, such as {@link DEFAULT_STORAGE_KEY}.
111
+ * @returns The stored theme, or `undefined` when it is missing, invalid or unavailable.
112
+ * @throws The original error when it is not one of the storage errors above, because that is a bug.
113
+ * @example
114
+ * import { DEFAULT_STORAGE_KEY, readStoredTheme } from "@shadow-js/ui";
115
+ *
116
+ * readStoredTheme(window.localStorage, DEFAULT_STORAGE_KEY);
117
+ * @since 1.0.0
118
+ */
119
+ export declare function readStoredTheme(storage: Storage, key: string): Theme | undefined;
120
+ /**
121
+ * Stores the theme under the key. A storage that is blocked or full does not throw: the function
122
+ * returns `false` instead.
123
+ *
124
+ * @param storage The storage to write, usually `window.localStorage`.
125
+ * @param key The storage key, such as {@link DEFAULT_STORAGE_KEY}.
126
+ * @param theme The theme to store.
127
+ * @returns `true` when the value was written, `false` when the storage refused it.
128
+ * @throws The original error when it is not one of the storage errors listed in {@link readStoredTheme}.
129
+ * @example
130
+ * import { DEFAULT_STORAGE_KEY, writeStoredTheme } from "@shadow-js/ui";
131
+ *
132
+ * writeStoredTheme(window.localStorage, DEFAULT_STORAGE_KEY, "dark");
133
+ * @since 1.0.0
134
+ */
135
+ export declare function writeStoredTheme(storage: Storage, key: string, theme: Theme): boolean;
136
+ /**
137
+ * The inline script that applies the stored theme before the first paint, so a dark-mode user does
138
+ * not see a flash of the light theme. Put the returned text in a `<script>` in the document
139
+ * `<head>`: a `<script>` in `index.html`, or the `document.head` option of Dusk's `createHandler`.
140
+ * An inline script in the head runs before the body is rendered. Pass the same options to
141
+ * `ThemeProvider`.
142
+ *
143
+ * The script is plain ES5 with no dependencies. It reads the stored theme, falls back to
144
+ * `defaultTheme` when the value is missing or invalid or when storage is blocked, and writes the same
145
+ * class or attribute and `color-scheme` as {@link applyTheme}. The function returns text only, so you
146
+ * place the tag yourself. {@link themeScriptTag} returns the same script in a tag with a CSP `nonce`.
147
+ *
148
+ * @param options Must match the `ThemeProvider` options.
149
+ * @returns JavaScript source text, without `<script>` tags.
150
+ * @example
151
+ * import { themeScript } from "@shadow-js/ui";
152
+ *
153
+ * const html = `<script>${themeScript({ defaultTheme: "dark" })}<\/script>`;
154
+ * @see {@link themeScriptTag}
155
+ * @since 1.0.0
156
+ */
157
+ export declare function themeScript(options?: ThemeOptions): string;
158
+ /**
159
+ * {@link themeScript} wrapped in a `<script>` element, as an HTML string, for a server that builds the
160
+ * document. Pass the page's CSP nonce so the inline script is allowed by a policy that uses nonces. In
161
+ * Dusk, the nonce of each response is `event.nonce`, available in the `document.head` function.
162
+ * Only base64 characters (`A-Z`, `a-z`, `0-9`, `+`, `/`, `=`, `_`, `-`) are kept from the nonce.
163
+ *
164
+ * @param options The theme options plus the CSP `nonce` of the page. Without a nonce the tag has no
165
+ * `nonce` attribute.
166
+ * @returns A `<script>` element as HTML text.
167
+ * @example
168
+ * import { createHandler, type DuskRuntimeManifest } from "@shadow-js/dusk/server";
169
+ * import { pages } from "@shadow-js/dusk/features/pages";
170
+ * import { themeScriptTag } from "@shadow-js/ui";
171
+ *
172
+ * declare const manifest: DuskRuntimeManifest;
173
+ *
174
+ * export const handler = createHandler({
175
+ * features: [pages()],
176
+ * manifest,
177
+ * security: { csp: {} },
178
+ * document: { head: (event) => themeScriptTag({ nonce: event.nonce }) },
179
+ * });
180
+ * @see {@link themeScript}
181
+ * @since 1.0.0
182
+ */
183
+ export declare function themeScriptTag(options?: ThemeOptions & {
184
+ readonly nonce?: string | undefined;
185
+ }): string;
186
+ //#endregion
187
+ //# sourceMappingURL=theme-logic.d.ts.map
@@ -0,0 +1,184 @@
1
+ //#region src/theme/theme-logic.ts
2
+ /**
3
+ * The `localStorage` key that holds the theme choice when `storageKey` is not given.
4
+ * @example
5
+ * import { DEFAULT_STORAGE_KEY } from "@shadow-js/ui";
6
+ *
7
+ * window.localStorage.getItem(DEFAULT_STORAGE_KEY);
8
+ * @since 1.0.0
9
+ */
10
+ const DEFAULT_STORAGE_KEY = "shadow-ui-theme";
11
+ /**
12
+ * Narrows an unknown value to a {@link Theme}. Use it on anything read from storage or from an event
13
+ * before trusting it.
14
+ *
15
+ * @param value Anything, such as a value read from storage.
16
+ * @returns `true` for `"light"`, `"dark"` and `"system"`, and `false` for everything else.
17
+ * @example
18
+ * import { DEFAULT_STORAGE_KEY, isTheme } from "@shadow-js/ui";
19
+ *
20
+ * isTheme(window.localStorage.getItem(DEFAULT_STORAGE_KEY)); // false when nothing is stored
21
+ * @since 1.0.0
22
+ */
23
+ function isTheme(value) {
24
+ return value === "light" || value === "dark" || value === "system";
25
+ }
26
+ /**
27
+ * Turns a theme choice into the theme to show. Only `"system"` depends on the operating system.
28
+ *
29
+ * @param theme The user's choice.
30
+ * @param systemPrefersDark Whether the operating system prefers a dark scheme.
31
+ * @param enableSystem When `false`, `"system"` resolves to light. Default `true`.
32
+ * @returns `"light"` or `"dark"`.
33
+ * @example
34
+ * import { resolveTheme } from "@shadow-js/ui";
35
+ *
36
+ * resolveTheme("system", true); // "dark"
37
+ * @since 1.0.0
38
+ */
39
+ function resolveTheme(theme, systemPrefersDark, enableSystem = true) {
40
+ if (theme === "system") return enableSystem && systemPrefersDark ? "dark" : "light";
41
+ return theme;
42
+ }
43
+ /**
44
+ * Writes the resolved theme to the root element: sets the `light` or `dark` class (removing the
45
+ * other) or the `data-*` attribute, and sets `color-scheme` so form controls and scrollbars follow.
46
+ *
47
+ * @param root The element to write to, usually `document.documentElement`.
48
+ * @param resolved The theme to show.
49
+ * @param attribute Where to write it: `"class"` (the default) or a `data-*` attribute name.
50
+ * @example
51
+ * import { applyTheme } from "@shadow-js/ui";
52
+ *
53
+ * applyTheme(document.documentElement, "dark");
54
+ * @since 1.0.0
55
+ */
56
+ function applyTheme(root, resolved, attribute = "class") {
57
+ if (attribute === "class") {
58
+ root.classList.remove("light", "dark");
59
+ root.classList.add(resolved);
60
+ } else root.setAttribute(attribute, resolved);
61
+ root.style.colorScheme = resolved;
62
+ }
63
+ /** The storage error names a locked-down browser raises; anything else is a bug and propagates. */
64
+ function isStorageUnavailable(error) {
65
+ return error instanceof DOMException && (error.name === "SecurityError" || error.name === "QuotaExceededError" || error.name === "NotAllowedError");
66
+ }
67
+ /**
68
+ * Reads the stored theme. A missing or invalid value gives `undefined`. A storage the browser
69
+ * blocks (private windows, disabled site data) or has run out of room for also gives `undefined`:
70
+ * those raise a `SecurityError`, `NotAllowedError` or `QuotaExceededError` `DOMException`.
71
+ *
72
+ * @param storage The storage to read, usually `window.localStorage`.
73
+ * @param key The storage key, such as {@link DEFAULT_STORAGE_KEY}.
74
+ * @returns The stored theme, or `undefined` when it is missing, invalid or unavailable.
75
+ * @throws The original error when it is not one of the storage errors above, because that is a bug.
76
+ * @example
77
+ * import { DEFAULT_STORAGE_KEY, readStoredTheme } from "@shadow-js/ui";
78
+ *
79
+ * readStoredTheme(window.localStorage, DEFAULT_STORAGE_KEY);
80
+ * @since 1.0.0
81
+ */
82
+ function readStoredTheme(storage, key) {
83
+ try {
84
+ const value = storage.getItem(key);
85
+ return isTheme(value) ? value : void 0;
86
+ } catch (error) {
87
+ if (isStorageUnavailable(error)) return void 0;
88
+ throw error;
89
+ }
90
+ }
91
+ /**
92
+ * Stores the theme under the key. A storage that is blocked or full does not throw: the function
93
+ * returns `false` instead.
94
+ *
95
+ * @param storage The storage to write, usually `window.localStorage`.
96
+ * @param key The storage key, such as {@link DEFAULT_STORAGE_KEY}.
97
+ * @param theme The theme to store.
98
+ * @returns `true` when the value was written, `false` when the storage refused it.
99
+ * @throws The original error when it is not one of the storage errors listed in {@link readStoredTheme}.
100
+ * @example
101
+ * import { DEFAULT_STORAGE_KEY, writeStoredTheme } from "@shadow-js/ui";
102
+ *
103
+ * writeStoredTheme(window.localStorage, DEFAULT_STORAGE_KEY, "dark");
104
+ * @since 1.0.0
105
+ */
106
+ function writeStoredTheme(storage, key, theme) {
107
+ try {
108
+ storage.setItem(key, theme);
109
+ return true;
110
+ } catch (error) {
111
+ if (isStorageUnavailable(error)) return false;
112
+ throw error;
113
+ }
114
+ }
115
+ /**
116
+ * The inline script that applies the stored theme before the first paint, so a dark-mode user does
117
+ * not see a flash of the light theme. Put the returned text in a `<script>` in the document
118
+ * `<head>`: a `<script>` in `index.html`, or the `document.head` option of Dusk's `createHandler`.
119
+ * An inline script in the head runs before the body is rendered. Pass the same options to
120
+ * `ThemeProvider`.
121
+ *
122
+ * The script is plain ES5 with no dependencies. It reads the stored theme, falls back to
123
+ * `defaultTheme` when the value is missing or invalid or when storage is blocked, and writes the same
124
+ * class or attribute and `color-scheme` as {@link applyTheme}. The function returns text only, so you
125
+ * place the tag yourself. {@link themeScriptTag} returns the same script in a tag with a CSP `nonce`.
126
+ *
127
+ * @param options Must match the `ThemeProvider` options.
128
+ * @returns JavaScript source text, without `<script>` tags.
129
+ * @example
130
+ * import { themeScript } from "@shadow-js/ui";
131
+ *
132
+ * const html = `<script>${themeScript({ defaultTheme: "dark" })}<\/script>`;
133
+ * @see {@link themeScriptTag}
134
+ * @since 1.0.0
135
+ */
136
+ function themeScript(options = {}) {
137
+ const key = options.storageKey ?? "shadow-ui-theme";
138
+ const fallback = options.defaultTheme ?? "system";
139
+ const attribute = options.attribute ?? "class";
140
+ const system = options.enableSystem ?? true;
141
+ const literal = (value) => JSON.stringify(value).replace(/</g, "\\u003c");
142
+ return [
143
+ "(function(){",
144
+ `var k=${literal(key)},f=${literal(fallback)},a=${literal(attribute)},s=${literal(system)};`,
145
+ "var r=document.documentElement;",
146
+ "function p(t){if(t==='system')return s&&window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';return t}",
147
+ "function w(m){if(a==='class'){r.classList.remove('light','dark');r.classList.add(m)}else{r.setAttribute(a,m)}r.style.colorScheme=m}",
148
+ "try{var t=localStorage.getItem(k);if(t!=='light'&&t!=='dark'&&t!=='system')t=f;w(p(t))}",
149
+ "catch(e){w(p(f))}",
150
+ "})();"
151
+ ].join("");
152
+ }
153
+ /**
154
+ * {@link themeScript} wrapped in a `<script>` element, as an HTML string, for a server that builds the
155
+ * document. Pass the page's CSP nonce so the inline script is allowed by a policy that uses nonces. In
156
+ * Dusk, the nonce of each response is `event.nonce`, available in the `document.head` function.
157
+ * Only base64 characters (`A-Z`, `a-z`, `0-9`, `+`, `/`, `=`, `_`, `-`) are kept from the nonce.
158
+ *
159
+ * @param options The theme options plus the CSP `nonce` of the page. Without a nonce the tag has no
160
+ * `nonce` attribute.
161
+ * @returns A `<script>` element as HTML text.
162
+ * @example
163
+ * import { createHandler, type DuskRuntimeManifest } from "@shadow-js/dusk/server";
164
+ * import { pages } from "@shadow-js/dusk/features/pages";
165
+ * import { themeScriptTag } from "@shadow-js/ui";
166
+ *
167
+ * declare const manifest: DuskRuntimeManifest;
168
+ *
169
+ * export const handler = createHandler({
170
+ * features: [pages()],
171
+ * manifest,
172
+ * security: { csp: {} },
173
+ * document: { head: (event) => themeScriptTag({ nonce: event.nonce }) },
174
+ * });
175
+ * @see {@link themeScript}
176
+ * @since 1.0.0
177
+ */
178
+ function themeScriptTag(options = {}) {
179
+ return `<script${options.nonce === void 0 ? "" : ` nonce="${options.nonce.replace(/[^A-Za-z0-9+/=_-]/g, "")}"`}>${themeScript(options)}<\/script>`;
180
+ }
181
+ //#endregion
182
+ export { DEFAULT_STORAGE_KEY, applyTheme, isTheme, readStoredTheme, resolveTheme, themeScript, themeScriptTag, writeStoredTheme };
183
+
184
+ //# sourceMappingURL=theme-logic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-logic.js","names":[],"sources":["../../../src/theme/theme-logic.ts"],"sourcesContent":["/**\n * Pure theme logic, shared by the provider and the no-flash script so both agree on what\n * \"system\", \"light\" and \"dark\" mean. Nothing here touches `window` unless it is handed an object.\n * @packageDocumentation\n */\n\n/**\n * The theme a user picks: `\"light\"`, `\"dark\"`, or `\"system\"` to follow the operating system.\n * @since 1.0.0\n */\nexport type Theme = \"light\" | \"dark\" | \"system\";\n\n/**\n * The theme that is actually shown: `\"light\"` or `\"dark\"`.\n * @since 1.0.0\n */\nexport type ResolvedTheme = \"light\" | \"dark\";\n\n/**\n * Where the resolved theme is written on the root element. `\"class\"` (the default) sets a `light`\n * or `dark` class, which the generated stylesheet matches for its dark styles (the `dark` variant of\n * Tailwind is `.dark`). A `data-*` name such as `\"data-theme\"` sets that attribute to `\"light\"` or\n * `\"dark\"` instead; the stylesheet then has to match that attribute as well.\n * @since 1.0.0\n */\nexport type ThemeAttribute = \"class\" | `data-${string}`;\n\n/**\n * Options shared by `ThemeProvider` and `themeScript`. Pass the same values to both, or the page can\n * show one theme before the provider switches to another.\n * @since 1.0.0\n */\nexport interface ThemeOptions {\n /**\n * `localStorage` key that persists the choice. The stored value is `\"light\"`, `\"dark\"` or\n * `\"system\"`; a missing or invalid value falls back to `defaultTheme`.\n * @default \"shadow-ui-theme\"\n */\n readonly storageKey?: string | undefined;\n /**\n * Theme used before the user chooses one, and when the stored value is missing or invalid.\n * @default \"system\"\n */\n readonly defaultTheme?: Theme | undefined;\n /**\n * Where the resolved theme is written: `\"class\"` for a `light` or `dark` class on `<html>`, or a\n * `data-*` attribute name such as `\"data-theme\"` (see {@link ThemeAttribute}).\n * @default \"class\"\n */\n readonly attribute?: ThemeAttribute | undefined;\n /**\n * Follow `prefers-color-scheme` while the theme is `\"system\"`. When `false`, `\"system\"` shows light.\n * @default true\n */\n readonly enableSystem?: boolean | undefined;\n}\n\n/**\n * The `localStorage` key that holds the theme choice when `storageKey` is not given.\n * @example\n * import { DEFAULT_STORAGE_KEY } from \"@shadow-js/ui\";\n *\n * window.localStorage.getItem(DEFAULT_STORAGE_KEY);\n * @since 1.0.0\n */\nexport const DEFAULT_STORAGE_KEY = \"shadow-ui-theme\";\n\n/**\n * Narrows an unknown value to a {@link Theme}. Use it on anything read from storage or from an event\n * before trusting it.\n *\n * @param value Anything, such as a value read from storage.\n * @returns `true` for `\"light\"`, `\"dark\"` and `\"system\"`, and `false` for everything else.\n * @example\n * import { DEFAULT_STORAGE_KEY, isTheme } from \"@shadow-js/ui\";\n *\n * isTheme(window.localStorage.getItem(DEFAULT_STORAGE_KEY)); // false when nothing is stored\n * @since 1.0.0\n */\nexport function isTheme(value: unknown): value is Theme {\n return value === \"light\" || value === \"dark\" || value === \"system\";\n}\n\n/**\n * Turns a theme choice into the theme to show. Only `\"system\"` depends on the operating system.\n *\n * @param theme The user's choice.\n * @param systemPrefersDark Whether the operating system prefers a dark scheme.\n * @param enableSystem When `false`, `\"system\"` resolves to light. Default `true`.\n * @returns `\"light\"` or `\"dark\"`.\n * @example\n * import { resolveTheme } from \"@shadow-js/ui\";\n *\n * resolveTheme(\"system\", true); // \"dark\"\n * @since 1.0.0\n */\nexport function resolveTheme(\n theme: Theme,\n systemPrefersDark: boolean,\n enableSystem = true,\n): ResolvedTheme {\n if (theme === \"system\") return enableSystem && systemPrefersDark ? \"dark\" : \"light\";\n return theme;\n}\n\n/**\n * Writes the resolved theme to the root element: sets the `light` or `dark` class (removing the\n * other) or the `data-*` attribute, and sets `color-scheme` so form controls and scrollbars follow.\n *\n * @param root The element to write to, usually `document.documentElement`.\n * @param resolved The theme to show.\n * @param attribute Where to write it: `\"class\"` (the default) or a `data-*` attribute name.\n * @example\n * import { applyTheme } from \"@shadow-js/ui\";\n *\n * applyTheme(document.documentElement, \"dark\");\n * @since 1.0.0\n */\nexport function applyTheme(\n root: HTMLElement,\n resolved: ResolvedTheme,\n attribute: ThemeAttribute = \"class\",\n): void {\n if (attribute === \"class\") {\n root.classList.remove(\"light\", \"dark\");\n root.classList.add(resolved);\n } else {\n root.setAttribute(attribute, resolved);\n }\n root.style.colorScheme = resolved;\n}\n\n/** The storage error names a locked-down browser raises; anything else is a bug and propagates. */\nfunction isStorageUnavailable(error: unknown): boolean {\n return (\n error instanceof DOMException &&\n (error.name === \"SecurityError\" ||\n error.name === \"QuotaExceededError\" ||\n error.name === \"NotAllowedError\")\n );\n}\n\n/**\n * Reads the stored theme. A missing or invalid value gives `undefined`. A storage the browser\n * blocks (private windows, disabled site data) or has run out of room for also gives `undefined`:\n * those raise a `SecurityError`, `NotAllowedError` or `QuotaExceededError` `DOMException`.\n *\n * @param storage The storage to read, usually `window.localStorage`.\n * @param key The storage key, such as {@link DEFAULT_STORAGE_KEY}.\n * @returns The stored theme, or `undefined` when it is missing, invalid or unavailable.\n * @throws The original error when it is not one of the storage errors above, because that is a bug.\n * @example\n * import { DEFAULT_STORAGE_KEY, readStoredTheme } from \"@shadow-js/ui\";\n *\n * readStoredTheme(window.localStorage, DEFAULT_STORAGE_KEY);\n * @since 1.0.0\n */\nexport function readStoredTheme(storage: Storage, key: string): Theme | undefined {\n try {\n const value = storage.getItem(key);\n return isTheme(value) ? value : undefined;\n } catch (error) {\n if (isStorageUnavailable(error)) return undefined;\n throw error;\n }\n}\n\n/**\n * Stores the theme under the key. A storage that is blocked or full does not throw: the function\n * returns `false` instead.\n *\n * @param storage The storage to write, usually `window.localStorage`.\n * @param key The storage key, such as {@link DEFAULT_STORAGE_KEY}.\n * @param theme The theme to store.\n * @returns `true` when the value was written, `false` when the storage refused it.\n * @throws The original error when it is not one of the storage errors listed in {@link readStoredTheme}.\n * @example\n * import { DEFAULT_STORAGE_KEY, writeStoredTheme } from \"@shadow-js/ui\";\n *\n * writeStoredTheme(window.localStorage, DEFAULT_STORAGE_KEY, \"dark\");\n * @since 1.0.0\n */\nexport function writeStoredTheme(storage: Storage, key: string, theme: Theme): boolean {\n try {\n storage.setItem(key, theme);\n return true;\n } catch (error) {\n if (isStorageUnavailable(error)) return false;\n throw error;\n }\n}\n\n/**\n * The inline script that applies the stored theme before the first paint, so a dark-mode user does\n * not see a flash of the light theme. Put the returned text in a `<script>` in the document\n * `<head>`: a `<script>` in `index.html`, or the `document.head` option of Dusk's `createHandler`.\n * An inline script in the head runs before the body is rendered. Pass the same options to\n * `ThemeProvider`.\n *\n * The script is plain ES5 with no dependencies. It reads the stored theme, falls back to\n * `defaultTheme` when the value is missing or invalid or when storage is blocked, and writes the same\n * class or attribute and `color-scheme` as {@link applyTheme}. The function returns text only, so you\n * place the tag yourself. {@link themeScriptTag} returns the same script in a tag with a CSP `nonce`.\n *\n * @param options Must match the `ThemeProvider` options.\n * @returns JavaScript source text, without `<script>` tags.\n * @example\n * import { themeScript } from \"@shadow-js/ui\";\n *\n * const html = `<script>${themeScript({ defaultTheme: \"dark\" })}</script>`;\n * @see {@link themeScriptTag}\n * @since 1.0.0\n */\nexport function themeScript(options: ThemeOptions = {}): string {\n const key = options.storageKey ?? DEFAULT_STORAGE_KEY;\n const fallback = options.defaultTheme ?? \"system\";\n const attribute = options.attribute ?? \"class\";\n const system = options.enableSystem ?? true;\n // `<` is escaped so a stored value can never close the surrounding <script> element.\n const literal = (value: unknown): string => JSON.stringify(value).replace(/</g, \"\\\\u003c\");\n return [\n \"(function(){\",\n `var k=${literal(key)},f=${literal(fallback)},a=${literal(attribute)},s=${literal(system)};`,\n \"var r=document.documentElement;\",\n \"function p(t){if(t==='system')return s&&window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';return t}\",\n \"function w(m){if(a==='class'){r.classList.remove('light','dark');r.classList.add(m)}else{r.setAttribute(a,m)}r.style.colorScheme=m}\",\n \"try{var t=localStorage.getItem(k);if(t!=='light'&&t!=='dark'&&t!=='system')t=f;w(p(t))}\",\n \"catch(e){w(p(f))}\",\n \"})();\",\n ].join(\"\");\n}\n\n/**\n * {@link themeScript} wrapped in a `<script>` element, as an HTML string, for a server that builds the\n * document. Pass the page's CSP nonce so the inline script is allowed by a policy that uses nonces. In\n * Dusk, the nonce of each response is `event.nonce`, available in the `document.head` function.\n * Only base64 characters (`A-Z`, `a-z`, `0-9`, `+`, `/`, `=`, `_`, `-`) are kept from the nonce.\n *\n * @param options The theme options plus the CSP `nonce` of the page. Without a nonce the tag has no\n * `nonce` attribute.\n * @returns A `<script>` element as HTML text.\n * @example\n * import { createHandler, type DuskRuntimeManifest } from \"@shadow-js/dusk/server\";\n * import { pages } from \"@shadow-js/dusk/features/pages\";\n * import { themeScriptTag } from \"@shadow-js/ui\";\n *\n * declare const manifest: DuskRuntimeManifest;\n *\n * export const handler = createHandler({\n * features: [pages()],\n * manifest,\n * security: { csp: {} },\n * document: { head: (event) => themeScriptTag({ nonce: event.nonce }) },\n * });\n * @see {@link themeScript}\n * @since 1.0.0\n */\nexport function themeScriptTag(\n options: ThemeOptions & { readonly nonce?: string | undefined } = {},\n): string {\n const nonce =\n options.nonce === undefined\n ? \"\"\n : ` nonce=\"${options.nonce.replace(/[^A-Za-z0-9+/=_-]/g, \"\")}\"`;\n return `<script${nonce}>${themeScript(options)}</script>`;\n}\n"],"mappings":";;;;;;;;;AAiEA,MAAa,sBAAsB;;;;;;;;;;;;;AAcnC,SAAgB,QAAQ,OAAgC;CACtD,OAAO,UAAU,WAAW,UAAU,UAAU,UAAU;AAC5D;;;;;;;;;;;;;;AAeA,SAAgB,aACd,OACA,mBACA,eAAe,MACA;CACf,IAAI,UAAU,UAAU,OAAO,gBAAgB,oBAAoB,SAAS;CAC5E,OAAO;AACT;;;;;;;;;;;;;;AAeA,SAAgB,WACd,MACA,UACA,YAA4B,SACtB;CACN,IAAI,cAAc,SAAS;EACzB,KAAK,UAAU,OAAO,SAAS,MAAM;EACrC,KAAK,UAAU,IAAI,QAAQ;CAC7B,OACE,KAAK,aAAa,WAAW,QAAQ;CAEvC,KAAK,MAAM,cAAc;AAC3B;;AAGA,SAAS,qBAAqB,OAAyB;CACrD,OACE,iBAAiB,iBAChB,MAAM,SAAS,mBACd,MAAM,SAAS,wBACf,MAAM,SAAS;AAErB;;;;;;;;;;;;;;;;AAiBA,SAAgB,gBAAgB,SAAkB,KAAgC;CAChF,IAAI;EACF,MAAM,QAAQ,QAAQ,QAAQ,GAAG;EACjC,OAAO,QAAQ,KAAK,IAAI,QAAQ,KAAA;CAClC,SAAS,OAAO;EACd,IAAI,qBAAqB,KAAK,GAAG,OAAO,KAAA;EACxC,MAAM;CACR;AACF;;;;;;;;;;;;;;;;AAiBA,SAAgB,iBAAiB,SAAkB,KAAa,OAAuB;CACrF,IAAI;EACF,QAAQ,QAAQ,KAAK,KAAK;EAC1B,OAAO;CACT,SAAS,OAAO;EACd,IAAI,qBAAqB,KAAK,GAAG,OAAO;EACxC,MAAM;CACR;AACF;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,YAAY,UAAwB,CAAC,GAAW;CAC9D,MAAM,MAAM,QAAQ,cAAA;CACpB,MAAM,WAAW,QAAQ,gBAAgB;CACzC,MAAM,YAAY,QAAQ,aAAa;CACvC,MAAM,SAAS,QAAQ,gBAAgB;CAEvC,MAAM,WAAW,UAA2B,KAAK,UAAU,KAAK,CAAC,CAAC,QAAQ,MAAM,SAAS;CACzF,OAAO;EACL;EACA,SAAS,QAAQ,GAAG,EAAE,KAAK,QAAQ,QAAQ,EAAE,KAAK,QAAQ,SAAS,EAAE,KAAK,QAAQ,MAAM,EAAE;EAC1F;EACA;EACA;EACA;EACA;EACA;CACF,CAAC,CAAC,KAAK,EAAE;AACX;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,eACd,UAAkE,CAAC,GAC3D;CAKR,OAAO,UAHL,QAAQ,UAAU,KAAA,IACd,KACA,WAAW,QAAQ,MAAM,QAAQ,sBAAsB,EAAE,EAAE,GAC1C,GAAG,YAAY,OAAO,EAAE;AACjD"}
@@ -0,0 +1,90 @@
1
+ import { ResolvedTheme, Theme, ThemeOptions } from "./theme-logic.js";
2
+ import { Accessor } from "@shadow-js/core";
3
+ import { JsxChild } from "@shadow-js/web/jsx-runtime";
4
+ //#region src/theme/theme-provider.d.ts
5
+ /**
6
+ * What {@link useTheme} returns. Every value is an accessor: call it to read.
7
+ * @since 1.0.0
8
+ */
9
+ export interface ThemeContextValue {
10
+ /** The user's choice: `"light"`, `"dark"` or `"system"`. Call it to read the value. */
11
+ readonly theme: Accessor<Theme>;
12
+ /**
13
+ * The theme that is shown: `"light"` or `"dark"`. `undefined` on the server and until the provider
14
+ * has mounted, so markup that depends on it hydrates without a mismatch.
15
+ */
16
+ readonly resolvedTheme: Accessor<ResolvedTheme | undefined>;
17
+ /**
18
+ * The operating system preference: `"light"` or `"dark"`. `undefined` on the server and until the
19
+ * provider has mounted.
20
+ */
21
+ readonly systemTheme: Accessor<ResolvedTheme | undefined>;
22
+ /**
23
+ * Chooses a theme, or derives one from the previous choice, and stores it under the provider's
24
+ * `storageKey`. Throws a `TypeError` for a value that is not `"light"`, `"dark"` or `"system"`.
25
+ */
26
+ readonly setTheme: (theme: Theme | ((previous: Theme) => Theme)) => void;
27
+ }
28
+ /**
29
+ * Props of {@link ThemeProvider}: the {@link ThemeOptions} plus a transition switch and the children.
30
+ * @since 1.0.0
31
+ */
32
+ export interface ThemeProviderProps extends ThemeOptions {
33
+ /**
34
+ * Stops CSS transitions for one frame while the theme changes, so colors flip at once instead
35
+ * of fading piecemeal.
36
+ * @default false
37
+ */
38
+ readonly disableTransitionOnChange?: boolean | undefined;
39
+ /** The app to render inside the theme context. */
40
+ readonly children?: JsxChild | undefined;
41
+ }
42
+ /**
43
+ * Provides the color theme to the app: `"light"`, `"dark"` or `"system"`. The choice is persisted in
44
+ * `localStorage`, applied to `<html>` as a `light` or `dark` class (or a `data-*` attribute), and kept
45
+ * in sync across tabs and with the operating system. The provider renders no element of its own, and
46
+ * nothing touches the DOM until it has mounted, so it is safe to render on the server. Pair it with
47
+ * {@link themeScript} in the document head so the first paint already has the right theme.
48
+ *
49
+ * @param props The {@link ThemeProviderProps}: theme options (`storageKey`, `defaultTheme`,
50
+ * `attribute`, `enableSystem`), `disableTransitionOnChange` and `children`.
51
+ * @returns The children inside the theme context.
52
+ * @example
53
+ * import { ThemeProvider } from "@shadow-js/ui";
54
+ *
55
+ * function App() {
56
+ * return <p>Hello</p>;
57
+ * }
58
+ *
59
+ * export const root = (
60
+ * <ThemeProvider defaultTheme="system">
61
+ * <App />
62
+ * </ThemeProvider>
63
+ * );
64
+ * @see {@link useTheme}
65
+ * @since 1.0.0
66
+ */
67
+ export declare function ThemeProvider(props: ThemeProviderProps): JsxChild;
68
+ /**
69
+ * Reads the theme and its setter. Call it inside a component rendered under {@link ThemeProvider}.
70
+ *
71
+ * @returns The {@link ThemeContextValue}: accessors for the chosen, the resolved and the system
72
+ * theme, and `setTheme`.
73
+ * @throws Error when no `ThemeProvider` is above the caller.
74
+ * @example
75
+ * import { useTheme } from "@shadow-js/ui";
76
+ *
77
+ * export function ThemeToggle() {
78
+ * const { resolvedTheme, setTheme } = useTheme();
79
+ * return (
80
+ * <button onClick={() => setTheme(resolvedTheme() === "dark" ? "light" : "dark")}>
81
+ * Toggle theme
82
+ * </button>
83
+ * );
84
+ * }
85
+ * @see {@link ThemeProvider}
86
+ * @since 1.0.0
87
+ */
88
+ export declare function useTheme(): ThemeContextValue;
89
+ //#endregion
90
+ //# sourceMappingURL=theme-provider.d.ts.map