@sharadox/web-library 0.1.0 → 0.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.
Files changed (56) hide show
  1. package/README.md +15 -12
  2. package/distribution/animation/count-up/index.d.ts +6 -0
  3. package/distribution/animation/count-up/index.d.ts.map +1 -0
  4. package/distribution/animation/count-up/index.js +29 -0
  5. package/distribution/animation/count-up/index.js.map +1 -0
  6. package/distribution/internationalization/locale-preference/index.d.ts +5 -0
  7. package/distribution/internationalization/locale-preference/index.d.ts.map +1 -0
  8. package/distribution/internationalization/locale-preference/index.js +9 -0
  9. package/distribution/internationalization/locale-preference/index.js.map +1 -0
  10. package/distribution/internationalization/locale-redirect/index.d.ts +4 -3
  11. package/distribution/internationalization/locale-redirect/index.d.ts.map +1 -1
  12. package/distribution/internationalization/locale-redirect/index.js +12 -4
  13. package/distribution/internationalization/locale-redirect/index.js.map +1 -1
  14. package/distribution/internationalization/locale-redirect/type.d.ts +1 -1
  15. package/distribution/internationalization/locale-redirect/type.d.ts.map +1 -1
  16. package/distribution/pointer/tilt/index.d.ts +7 -0
  17. package/distribution/pointer/tilt/index.d.ts.map +1 -0
  18. package/distribution/pointer/tilt/index.js +34 -0
  19. package/distribution/pointer/tilt/index.js.map +1 -0
  20. package/distribution/style/apply/index.d.ts +11 -0
  21. package/distribution/style/apply/index.d.ts.map +1 -0
  22. package/distribution/style/apply/index.js +46 -0
  23. package/distribution/style/apply/index.js.map +1 -0
  24. package/distribution/style/apply/type.d.ts +10 -0
  25. package/distribution/style/apply/type.d.ts.map +1 -0
  26. package/distribution/style/apply/type.js +2 -0
  27. package/distribution/style/apply/type.js.map +1 -0
  28. package/distribution/style/definition/index.d.ts +12 -0
  29. package/distribution/style/definition/index.d.ts.map +1 -0
  30. package/distribution/style/definition/index.js +21 -0
  31. package/distribution/style/definition/index.js.map +1 -0
  32. package/distribution/style/definition/type.d.ts +24 -0
  33. package/distribution/style/definition/type.d.ts.map +1 -0
  34. package/distribution/style/definition/type.js +2 -0
  35. package/distribution/style/definition/type.js.map +1 -0
  36. package/distribution/style/style-script/index.d.ts +10 -0
  37. package/distribution/style/style-script/index.d.ts.map +1 -0
  38. package/distribution/style/style-script/index.js +16 -0
  39. package/distribution/style/style-script/index.js.map +1 -0
  40. package/distribution/style/style-script/type.d.ts +5 -0
  41. package/distribution/style/style-script/type.d.ts.map +1 -0
  42. package/distribution/style/style-script/type.js +2 -0
  43. package/distribution/style/style-script/type.js.map +1 -0
  44. package/distribution/style/style-sheet/index.d.ts +7 -0
  45. package/distribution/style/style-sheet/index.d.ts.map +1 -0
  46. package/distribution/style/style-sheet/index.js +19 -0
  47. package/distribution/style/style-sheet/index.js.map +1 -0
  48. package/distribution/style/use-style/index.d.ts +5 -0
  49. package/distribution/style/use-style/index.d.ts.map +1 -0
  50. package/distribution/style/use-style/index.js +34 -0
  51. package/distribution/style/use-style/index.js.map +1 -0
  52. package/distribution/style/use-style/type.d.ts +9 -0
  53. package/distribution/style/use-style/type.d.ts.map +1 -0
  54. package/distribution/style/use-style/type.js +2 -0
  55. package/distribution/style/use-style/type.js.map +1 -0
  56. package/package.json +2 -2
package/README.md CHANGED
@@ -8,18 +8,19 @@ pnpm add @sharadox/web-library
8
8
 
9
9
  Import each piece by its path:
10
10
 
11
- | Topic | Pieces |
12
- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
13
- | `internationalization/` | `locale` (`isLocale`, `languageTag`, `openGraphLocale`, `Translated`), `translate`, `locale-redirect` (Accept-Language redirects for next.config) |
14
- | `search-engine/` | `sitemap` (`localizedSitemap`), `robots`, `structured-data` (JSON-LD component) |
15
- | `animation/` | `reveal`, `scroll-word` (text that lights up word by word), `in-view`, `smooth-scroll` |
16
- | `pointer/` | `spotlight` (pointer position as CSS variables) |
17
- | `time/` | `zone-minute`, `format-time`, `sun-time` (sunrise and sunset), `sun-today` (React hook), `open-state` |
18
- | `form/` | `rate-limit`, `bot-check` (honeypot and fill time), `field` |
19
- | `html/` | `escape` |
20
- | `math/` | `radians`, `clamp`, `pseudo-random` |
21
- | `test/` | `visual-snapshot` (Playwright: frozen clock, settled reveals) |
22
- | — | `class-name` |
11
+ | Topic | Pieces |
12
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
13
+ | `internationalization/` | `locale` (`isLocale`, `languageTag`, `openGraphLocale`, `Translated`), `translate`, `locale-redirect` (next.config redirects: remembered language, then Accept-Language), `locale-preference` (`rememberLocale`) |
14
+ | `search-engine/` | `sitemap` (`localizedSitemap`), `robots`, `structured-data` (JSON-LD component) |
15
+ | `animation/` | `reveal`, `scroll-word` (text that lights up word by word), `in-view`, `count-up`, `smooth-scroll` |
16
+ | `style/` | `definition` (`resolveStyle`), `style-script` (`<StyleScript>` for `<head>`: every style's CSS variables and the no-flash script), `use-style` (picker hook), `apply`, `style-sheet` |
17
+ | `pointer/` | `spotlight` (pointer position as CSS variables), `tilt` (3D tilt towards the pointer) |
18
+ | `time/` | `zone-minute`, `format-time`, `sun-time` (sunrise and sunset), `sun-today` (React hook), `open-state` |
19
+ | `form/` | `rate-limit`, `bot-check` (honeypot and fill time), `field` |
20
+ | `html/` | `escape` |
21
+ | `math/` | `radians`, `clamp`, `pseudo-random` |
22
+ | `test/` | `visual-snapshot` (Playwright: frozen clock, settled reveals) |
23
+ | — | `class-name` |
23
24
 
24
25
  ```tsx
25
26
  import { Reveal } from "@sharadox/web-library/animation/reveal";
@@ -28,6 +29,8 @@ import { translate } from "@sharadox/web-library/internationalization/translate"
28
29
  <Reveal delay={0.1}>{translate({ en: "Hello", pt: "Olá" }, locale)}</Reveal>;
29
30
  ```
30
31
 
32
+ Styles are what other projects call themes: any number of dark and light styles, a default for each scheme, "system" as the default choice, and a manual choice remembered in browser storage.
33
+
31
34
  React components are client components (`"use client"`), accept `className`, and can be restyled per site. React (and, for `test/`, Playwright) are optional peer dependencies.
32
35
 
33
36
  ## Licence
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Counts from 0 to `target` with an ease-out once `start` is true (for example from `useInView`). Jumps straight to
3
+ * the target when the visitor prefers reduced motion.
4
+ */
5
+ export declare function useCountUp(target: number, start: boolean, durationMilliseconds?: number): number;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../source/animation/count-up/index.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,oBAAoB,SAAO,GAAG,MAAM,CAmB9F"}
@@ -0,0 +1,29 @@
1
+ "use client";
2
+ import { useEffect, useState } from "react";
3
+ /**
4
+ * Counts from 0 to `target` with an ease-out once `start` is true (for example from `useInView`). Jumps straight to
5
+ * the target when the visitor prefers reduced motion.
6
+ */
7
+ export function useCountUp(target, start, durationMilliseconds = 1600) {
8
+ const [value, setValue] = useState(0);
9
+ useEffect(() => {
10
+ if (!start)
11
+ return;
12
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
13
+ const frame = requestAnimationFrame(() => setValue(target));
14
+ return () => cancelAnimationFrame(frame);
15
+ }
16
+ const begin = performance.now();
17
+ let frame = 0;
18
+ const tick = (now) => {
19
+ const progress = Math.min(1, (now - begin) / durationMilliseconds);
20
+ setValue(Math.round(target * (1 - Math.pow(1 - progress, 3))));
21
+ if (progress < 1)
22
+ frame = requestAnimationFrame(tick);
23
+ };
24
+ frame = requestAnimationFrame(tick);
25
+ return () => cancelAnimationFrame(frame);
26
+ }, [target, start, durationMilliseconds]);
27
+ return value;
28
+ }
29
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../source/animation/count-up/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AACb,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE5C;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,KAAc,EAAE,oBAAoB,GAAG,IAAI;IACpF,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IACtC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,IAAI,MAAM,CAAC,UAAU,CAAC,kCAAkC,CAAC,CAAC,OAAO,EAAE,CAAC;YAClE,MAAM,KAAK,GAAG,qBAAqB,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;YAC5D,OAAO,GAAG,EAAE,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC;QAC3C,CAAC;QACD,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAChC,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,EAAE;YAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,oBAAoB,CAAC,CAAC;YACnE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/D,IAAI,QAAQ,GAAG,CAAC;gBAAE,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;QACxD,CAAC,CAAC;QACF,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;QACpC,OAAO,GAAG,EAAE,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAC1C,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** Cookie that remembers the language a visitor picked by hand; `localeRedirects` sends `/` to it. */
2
+ export declare const LOCALE_COOKIE = "locale";
3
+ /** Remembers a manual language choice for a year. Call it when the visitor switches language. */
4
+ export declare function rememberLocale(locale: string, cookie?: string): void;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../source/internationalization/locale-preference/index.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,eAAO,MAAM,aAAa,WAAW,CAAC;AAGtC,iGAAiG;AACjG,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,MAAsB,GAAG,IAAI,CAGnF"}
@@ -0,0 +1,9 @@
1
+ /** Cookie that remembers the language a visitor picked by hand; `localeRedirects` sends `/` to it. */
2
+ export const LOCALE_COOKIE = "locale";
3
+ const ONE_YEAR = 60 * 60 * 24 * 365;
4
+ /** Remembers a manual language choice for a year. Call it when the visitor switches language. */
5
+ export function rememberLocale(locale, cookie = LOCALE_COOKIE) {
6
+ const secure = location.protocol === "https:" ? "; secure" : "";
7
+ document.cookie = `${cookie}=${encodeURIComponent(locale)}; path=/; max-age=${ONE_YEAR}; samesite=lax${secure}`;
8
+ }
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../source/internationalization/locale-preference/index.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,MAAM,CAAC,MAAM,aAAa,GAAG,QAAQ,CAAC;AACtC,MAAM,QAAQ,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC;AAEpC,iGAAiG;AACjG,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,SAAiB,aAAa;IAC3E,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;IAChE,QAAQ,CAAC,MAAM,GAAG,GAAG,MAAM,IAAI,kBAAkB,CAAC,MAAM,CAAC,qBAAqB,QAAQ,iBAAiB,MAAM,EAAE,CAAC;AAClH,CAAC"}
@@ -1,7 +1,8 @@
1
1
  import type { LocaleRedirect } from "@sharadox/web-library/internationalization/locale-redirect/type";
2
2
  /**
3
- * Redirects for next.config `redirects()`: `/` goes to the visitor's language when their browser asks for one of
4
- * the published locales first (Accept-Language), otherwise to `/<defaultLocale>`.
3
+ * Redirects for next.config `redirects()`. `/` goes to, in order: the language the visitor picked before (the
4
+ * `locale` cookie set by `rememberLocale`), the language their browser asks for first (Accept-Language) when it is
5
+ * published, and otherwise `/<defaultLocale>`.
5
6
  */
6
- export declare function localeRedirects(locales: readonly string[], defaultLocale: string): LocaleRedirect[];
7
+ export declare function localeRedirects(locales: readonly string[], defaultLocale: string, cookie?: string): LocaleRedirect[];
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../source/internationalization/locale-redirect/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iEAAiE,CAAC;AAEtG;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,aAAa,EAAE,MAAM,GAAG,cAAc,EAAE,CAUnG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../source/internationalization/locale-redirect/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iEAAiE,CAAC;AAEtG;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,aAAa,EAAE,MAAM,EACrB,MAAM,GAAE,MAAsB,GAC7B,cAAc,EAAE,CAgBlB"}
@@ -1,8 +1,16 @@
1
+ import { LOCALE_COOKIE } from "@sharadox/web-library/internationalization/locale-preference";
1
2
  /**
2
- * Redirects for next.config `redirects()`: `/` goes to the visitor's language when their browser asks for one of
3
- * the published locales first (Accept-Language), otherwise to `/<defaultLocale>`.
3
+ * Redirects for next.config `redirects()`. `/` goes to, in order: the language the visitor picked before (the
4
+ * `locale` cookie set by `rememberLocale`), the language their browser asks for first (Accept-Language) when it is
5
+ * published, and otherwise `/<defaultLocale>`.
4
6
  */
5
- export function localeRedirects(locales, defaultLocale) {
7
+ export function localeRedirects(locales, defaultLocale, cookie = LOCALE_COOKIE) {
8
+ const remembered = locales.map((locale) => ({
9
+ source: "/",
10
+ has: [{ type: "cookie", key: cookie, value: locale }],
11
+ destination: `/${locale}`,
12
+ permanent: false,
13
+ }));
6
14
  const preferred = locales
7
15
  .filter((locale) => locale !== defaultLocale)
8
16
  .map((locale) => ({
@@ -11,6 +19,6 @@ export function localeRedirects(locales, defaultLocale) {
11
19
  destination: `/${locale}`,
12
20
  permanent: false,
13
21
  }));
14
- return [...preferred, { source: "/", destination: `/${defaultLocale}`, permanent: false }];
22
+ return [...remembered, ...preferred, { source: "/", destination: `/${defaultLocale}`, permanent: false }];
15
23
  }
16
24
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../source/internationalization/locale-redirect/index.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,OAA0B,EAAE,aAAqB;IAC/E,MAAM,SAAS,GAAqB,OAAO;SACxC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,KAAK,aAAa,CAAC;SAC5C,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAChB,MAAM,EAAE,GAAG;QACX,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,iBAAiB,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,EAAE,CAAC;QACvE,WAAW,EAAE,IAAI,MAAM,EAAE;QACzB,SAAS,EAAE,KAAK;KACjB,CAAC,CAAC,CAAC;IACN,OAAO,CAAC,GAAG,SAAS,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,aAAa,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;AAC7F,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../source/internationalization/locale-redirect/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,8DAA8D,CAAC;AAG7F;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,OAA0B,EAC1B,aAAqB,EACrB,SAAiB,aAAa;IAE9B,MAAM,UAAU,GAAqB,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC5D,MAAM,EAAE,GAAG;QACX,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QACrD,WAAW,EAAE,IAAI,MAAM,EAAE;QACzB,SAAS,EAAE,KAAK;KACjB,CAAC,CAAC,CAAC;IACJ,MAAM,SAAS,GAAqB,OAAO;SACxC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,KAAK,aAAa,CAAC;SAC5C,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAChB,MAAM,EAAE,GAAG;QACX,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,iBAAiB,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,EAAE,CAAC;QACvE,WAAW,EAAE,IAAI,MAAM,EAAE;QACzB,SAAS,EAAE,KAAK;KACjB,CAAC,CAAC,CAAC;IACN,OAAO,CAAC,GAAG,UAAU,EAAE,GAAG,SAAS,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,aAAa,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5G,CAAC"}
@@ -4,7 +4,7 @@ export interface LocaleRedirect {
4
4
  destination: string;
5
5
  permanent: false;
6
6
  has?: {
7
- type: "header";
7
+ type: "header" | "cookie";
8
8
  key: string;
9
9
  value: string;
10
10
  }[];
@@ -1 +1 @@
1
- {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../../../source/internationalization/locale-redirect/type.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,KAAK,CAAC;IACjB,GAAG,CAAC,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACxD"}
1
+ {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../../../source/internationalization/locale-redirect/type.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,KAAK,CAAC;IACjB,GAAG,CAAC,EAAE;QAAE,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACnE"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Tilts an element towards the pointer: sets `--tilt-x` and `--tilt-y` (degrees, up to `maximum`) while the pointer
3
+ * is over it and eases back to 0 when it leaves. Use them in CSS, for example
4
+ * `transform: perspective(900px) rotateX(var(--tilt-x)) rotateY(var(--tilt-y))`. Off on touch screens.
5
+ */
6
+ export declare function useTilt<Target extends HTMLElement>(maximum?: number): import("react").RefObject<Target | null>;
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../source/pointer/tilt/index.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,MAAM,SAAS,WAAW,EAAE,OAAO,SAAI,4CAwB9D"}
@@ -0,0 +1,34 @@
1
+ "use client";
2
+ import { useEffect, useRef } from "react";
3
+ /**
4
+ * Tilts an element towards the pointer: sets `--tilt-x` and `--tilt-y` (degrees, up to `maximum`) while the pointer
5
+ * is over it and eases back to 0 when it leaves. Use them in CSS, for example
6
+ * `transform: perspective(900px) rotateX(var(--tilt-x)) rotateY(var(--tilt-y))`. Off on touch screens.
7
+ */
8
+ export function useTilt(maximum = 8) {
9
+ const reference = useRef(null);
10
+ useEffect(() => {
11
+ const element = reference.current;
12
+ if (!element || !window.matchMedia("(pointer: fine)").matches)
13
+ return;
14
+ const move = (event) => {
15
+ const bounds = element.getBoundingClientRect();
16
+ const x = (event.clientX - bounds.left) / bounds.width - 0.5;
17
+ const y = (event.clientY - bounds.top) / bounds.height - 0.5;
18
+ element.style.setProperty("--tilt-x", `${(-y * maximum * 2).toFixed(2)}deg`);
19
+ element.style.setProperty("--tilt-y", `${(x * maximum * 2).toFixed(2)}deg`);
20
+ };
21
+ const leave = () => {
22
+ element.style.setProperty("--tilt-x", "0deg");
23
+ element.style.setProperty("--tilt-y", "0deg");
24
+ };
25
+ element.addEventListener("pointermove", move);
26
+ element.addEventListener("pointerleave", leave);
27
+ return () => {
28
+ element.removeEventListener("pointermove", move);
29
+ element.removeEventListener("pointerleave", leave);
30
+ };
31
+ }, [maximum]);
32
+ return reference;
33
+ }
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../source/pointer/tilt/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AACb,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAE1C;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAA6B,OAAO,GAAG,CAAC;IAC7D,MAAM,SAAS,GAAG,MAAM,CAAS,IAAI,CAAC,CAAC;IACvC,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC;QAClC,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,OAAO;YAAE,OAAO;QACtE,MAAM,IAAI,GAAG,CAAC,KAAmB,EAAE,EAAE;YACnC,MAAM,MAAM,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC;YAC/C,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC;YAC7D,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC;YAC7D,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC7E,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAC9E,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,GAAG,EAAE;YACjB,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;YAC9C,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAChD,CAAC,CAAC;QACF,OAAO,CAAC,gBAAgB,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;QAC9C,OAAO,CAAC,gBAAgB,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QAChD,OAAO,GAAG,EAAE;YACV,OAAO,CAAC,mBAAmB,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;YACjD,OAAO,CAAC,mBAAmB,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QACrD,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACd,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { StyleConfiguration } from "@sharadox/web-library/style/definition/type";
2
+ import type { StyleApplyData } from "@sharadox/web-library/style/apply/type";
3
+ /** The configuration reduced to what `applyStyle` needs. */
4
+ export declare function styleApplyData(configuration: StyleConfiguration): StyleApplyData;
5
+ /**
6
+ * Writes the style to `<html>`: `data-style` (the style in use), `data-style-choice` and `color-scheme`.
7
+ * Without a `choice` it reads the remembered one (or "system"). It is self-contained on purpose: the inline
8
+ * script in `<head>` embeds its source, so the page never shows the wrong style before React loads.
9
+ */
10
+ export declare function applyStyle(data: StyleApplyData, choice?: string): void;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../source/style/apply/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AAE7E,4DAA4D;AAC5D,wBAAgB,cAAc,CAAC,aAAa,EAAE,kBAAkB,GAAG,cAAc,CAOhF;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAyBtE"}
@@ -0,0 +1,46 @@
1
+ /** The configuration reduced to what `applyStyle` needs. */
2
+ export function styleApplyData(configuration) {
3
+ return {
4
+ storageKey: configuration.storageKey,
5
+ schemes: Object.fromEntries(configuration.styles.map((style) => [style.name, style.scheme])),
6
+ defaults: { ...configuration.defaults },
7
+ fallback: configuration.fallback,
8
+ };
9
+ }
10
+ /**
11
+ * Writes the style to `<html>`: `data-style` (the style in use), `data-style-choice` and `color-scheme`.
12
+ * Without a `choice` it reads the remembered one (or "system"). It is self-contained on purpose: the inline
13
+ * script in `<head>` embeds its source, so the page never shows the wrong style before React loads.
14
+ */
15
+ export function applyStyle(data, choice) {
16
+ let wanted = choice;
17
+ if (wanted === undefined) {
18
+ try {
19
+ wanted = localStorage.getItem(data.storageKey) || "system";
20
+ }
21
+ catch {
22
+ wanted = "system";
23
+ }
24
+ }
25
+ if (wanted !== "system" && !data.schemes[wanted])
26
+ wanted = "system";
27
+ let name = wanted;
28
+ if (wanted === "system") {
29
+ let scheme = data.fallback;
30
+ try {
31
+ if (matchMedia("(prefers-color-scheme: light)").matches)
32
+ scheme = "light";
33
+ else if (matchMedia("(prefers-color-scheme: dark)").matches)
34
+ scheme = "dark";
35
+ }
36
+ catch {
37
+ // No media queries: keep the fallback.
38
+ }
39
+ name = data.defaults[scheme];
40
+ }
41
+ const root = document.documentElement;
42
+ root.setAttribute("data-style", name);
43
+ root.setAttribute("data-style-choice", wanted);
44
+ root.style.colorScheme = data.schemes[name] ?? data.fallback;
45
+ }
46
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../source/style/apply/index.ts"],"names":[],"mappings":"AAGA,4DAA4D;AAC5D,MAAM,UAAU,cAAc,CAAC,aAAiC;IAC9D,OAAO;QACL,UAAU,EAAE,aAAa,CAAC,UAAU;QACpC,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;QAC5F,QAAQ,EAAE,EAAE,GAAG,aAAa,CAAC,QAAQ,EAAE;QACvC,QAAQ,EAAE,aAAa,CAAC,QAAQ;KACjC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAAoB,EAAE,MAAe;IAC9D,IAAI,MAAM,GAAG,MAAM,CAAC;IACpB,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,QAAQ,CAAC;QAC7D,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,GAAG,QAAQ,CAAC;QACpB,CAAC;IACH,CAAC;IACD,IAAI,MAAM,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,MAAM,GAAG,QAAQ,CAAC;IACpE,IAAI,IAAI,GAAG,MAAM,CAAC;IAClB,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QACxB,IAAI,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC3B,IAAI,CAAC;YACH,IAAI,UAAU,CAAC,+BAA+B,CAAC,CAAC,OAAO;gBAAE,MAAM,GAAG,OAAO,CAAC;iBACrE,IAAI,UAAU,CAAC,8BAA8B,CAAC,CAAC,OAAO;gBAAE,MAAM,GAAG,MAAM,CAAC;QAC/E,CAAC;QAAC,MAAM,CAAC;YACP,uCAAuC;QACzC,CAAC;QACD,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC;IACD,MAAM,IAAI,GAAG,QAAQ,CAAC,eAAe,CAAC;IACtC,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,YAAY,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;IAC/C,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { ColorScheme } from "@sharadox/web-library/style/definition/type";
2
+ /** The plain data `applyStyle` needs; it is serialized into the inline script, so it holds no functions. */
3
+ export interface StyleApplyData {
4
+ storageKey: string;
5
+ /** Style name → scheme. */
6
+ schemes: Record<string, ColorScheme>;
7
+ defaults: Record<ColorScheme, string>;
8
+ fallback: ColorScheme;
9
+ }
10
+ //# sourceMappingURL=type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../../../source/style/apply/type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6CAA6C,CAAC;AAE/E,4GAA4G;AAC5G,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACtC,QAAQ,EAAE,WAAW,CAAC;CACvB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.js","sourceRoot":"","sources":["../../../source/style/apply/type.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ import type { ColorScheme, StyleConfiguration, StyleDefinition } from "@sharadox/web-library/style/definition/type";
2
+ /** The choice that follows the device's light or dark setting. It is the default and is never stored. */
3
+ export declare const SYSTEM_STYLE = "system";
4
+ /** `<html>` attribute holding the style in use, for CSS (`[data-style="black"]`). */
5
+ export declare const STYLE_ATTRIBUTE = "data-style";
6
+ /** `<html>` attribute holding the visitor's choice ("system" or a style name), for the picker. */
7
+ export declare const STYLE_CHOICE_ATTRIBUTE = "data-style-choice";
8
+ /** Finds a style by name. */
9
+ export declare function findStyle(configuration: StyleConfiguration, name: string): StyleDefinition | undefined;
10
+ /** The style shown for a choice: the chosen style, or for "system" (and unknown names) the default for the scheme. */
11
+ export declare function resolveStyle(configuration: StyleConfiguration, choice: string, systemScheme: ColorScheme | undefined): StyleDefinition;
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../source/style/definition/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,6CAA6C,CAAC;AAEpH,yGAAyG;AACzG,eAAO,MAAM,YAAY,WAAW,CAAC;AACrC,qFAAqF;AACrF,eAAO,MAAM,eAAe,eAAe,CAAC;AAC5C,kGAAkG;AAClG,eAAO,MAAM,sBAAsB,sBAAsB,CAAC;AAE1D,6BAA6B;AAC7B,wBAAgB,SAAS,CAAC,aAAa,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAEtG;AAED,sHAAsH;AACtH,wBAAgB,YAAY,CAC1B,aAAa,EAAE,kBAAkB,EACjC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,WAAW,GAAG,SAAS,GACpC,eAAe,CASjB"}
@@ -0,0 +1,21 @@
1
+ /** The choice that follows the device's light or dark setting. It is the default and is never stored. */
2
+ export const SYSTEM_STYLE = "system";
3
+ /** `<html>` attribute holding the style in use, for CSS (`[data-style="black"]`). */
4
+ export const STYLE_ATTRIBUTE = "data-style";
5
+ /** `<html>` attribute holding the visitor's choice ("system" or a style name), for the picker. */
6
+ export const STYLE_CHOICE_ATTRIBUTE = "data-style-choice";
7
+ /** Finds a style by name. */
8
+ export function findStyle(configuration, name) {
9
+ return configuration.styles.find((style) => style.name === name);
10
+ }
11
+ /** The style shown for a choice: the chosen style, or for "system" (and unknown names) the default for the scheme. */
12
+ export function resolveStyle(configuration, choice, systemScheme) {
13
+ const chosen = choice === SYSTEM_STYLE ? undefined : findStyle(configuration, choice);
14
+ if (chosen)
15
+ return chosen;
16
+ const style = findStyle(configuration, configuration.defaults[systemScheme ?? configuration.fallback]);
17
+ if (!style)
18
+ throw new Error(`style configuration: no style named "${configuration.defaults[systemScheme ?? configuration.fallback]}"`);
19
+ return style;
20
+ }
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../source/style/definition/index.ts"],"names":[],"mappings":"AAEA,yGAAyG;AACzG,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAAC;AACrC,qFAAqF;AACrF,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC;AAC5C,kGAAkG;AAClG,MAAM,CAAC,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAE1D,6BAA6B;AAC7B,MAAM,UAAU,SAAS,CAAC,aAAiC,EAAE,IAAY;IACvE,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AACnE,CAAC;AAED,sHAAsH;AACtH,MAAM,UAAU,YAAY,CAC1B,aAAiC,EACjC,MAAc,EACd,YAAqC;IAErC,MAAM,MAAM,GAAG,MAAM,KAAK,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IACtF,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,MAAM,KAAK,GAAG,SAAS,CAAC,aAAa,EAAE,aAAa,CAAC,QAAQ,CAAC,YAAY,IAAI,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;IACvG,IAAI,CAAC,KAAK;QACR,MAAM,IAAI,KAAK,CACb,wCAAwC,aAAa,CAAC,QAAQ,CAAC,YAAY,IAAI,aAAa,CAAC,QAAQ,CAAC,GAAG,CAC1G,CAAC;IACJ,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,24 @@
1
+ /** Whether a style is dark or light. The system preference (`prefers-color-scheme`) is one of these. */
2
+ export type ColorScheme = "dark" | "light";
3
+ /** One style (what other projects call a theme): a name, its scheme and the CSS custom properties it sets. */
4
+ export interface StyleDefinition {
5
+ /** Lower-case identifier, stored and written to `<html data-style>`, for example `black`. */
6
+ name: string;
7
+ scheme: ColorScheme;
8
+ /** CSS custom properties without the leading dashes, for example `{ background: "#000" }` → `--background`. */
9
+ palette: Readonly<Record<string, string>>;
10
+ }
11
+ /** Every style a site offers, and how one is picked when the visitor hasn't chosen. */
12
+ export interface StyleConfiguration {
13
+ /** Any number of dark and light styles. */
14
+ styles: readonly StyleDefinition[];
15
+ /** The style used for each system scheme when the choice is "system". */
16
+ defaults: Readonly<Record<ColorScheme, string>>;
17
+ /** The scheme used when the system preference is unknown, and before scripts run. */
18
+ fallback: ColorScheme;
19
+ /** Browser storage key that remembers a manual choice. */
20
+ storageKey: string;
21
+ }
22
+ /** What the visitor picked: "system" (follow the device) or a style name. */
23
+ export type StyleChoice = string;
24
+ //# sourceMappingURL=type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../../../source/style/definition/type.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3C,8GAA8G;AAC9G,MAAM,WAAW,eAAe;IAC9B,6FAA6F;IAC7F,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,+GAA+G;IAC/G,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC3C;AAED,uFAAuF;AACvF,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;IACnC,yEAAyE;IACzE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,qFAAqF;IACrF,QAAQ,EAAE,WAAW,CAAC;IACtB,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,6EAA6E;AAC7E,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.js","sourceRoot":"","sources":["../../../source/style/definition/type.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import type { StyleConfiguration } from "@sharadox/web-library/style/definition/type";
2
+ import type { StyleScriptProperties } from "@sharadox/web-library/style/style-script/type";
3
+ /** The inline script: applies the remembered style, then follows the device and other tabs while the page is open. */
4
+ export declare function styleScript(configuration: StyleConfiguration): string;
5
+ /**
6
+ * Put in `<head>` (and add `suppressHydrationWarning` to `<html>`): every style's CSS custom properties, and the
7
+ * script that picks the style before the first paint.
8
+ */
9
+ export declare function StyleScript({ configuration }: StyleScriptProperties): import("react").JSX.Element;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../source/style/style-script/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACtF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+CAA+C,CAAC;AAE3F,sHAAsH;AACtH,wBAAgB,WAAW,CAAC,aAAa,EAAE,kBAAkB,GAAG,MAAM,CAGrE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAAE,aAAa,EAAE,EAAE,qBAAqB,+BAOnE"}
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { applyStyle, styleApplyData } from "@sharadox/web-library/style/apply";
3
+ import { styleSheet } from "@sharadox/web-library/style/style-sheet";
4
+ /** The inline script: applies the remembered style, then follows the device and other tabs while the page is open. */
5
+ export function styleScript(configuration) {
6
+ const data = JSON.stringify(styleApplyData(configuration)).replace(/</g, "\\u003c");
7
+ return `(function(){var apply=${applyStyle.toString()};var data=${data};apply(data);try{matchMedia("(prefers-color-scheme: light)").addEventListener("change",function(){apply(data)});addEventListener("storage",function(event){if(event.key===data.storageKey)apply(data)})}catch(error){}})();`;
8
+ }
9
+ /**
10
+ * Put in `<head>` (and add `suppressHydrationWarning` to `<html>`): every style's CSS custom properties, and the
11
+ * script that picks the style before the first paint.
12
+ */
13
+ export function StyleScript({ configuration }) {
14
+ return (_jsxs(_Fragment, { children: [_jsx("style", { dangerouslySetInnerHTML: { __html: styleSheet(configuration) } }), _jsx("script", { dangerouslySetInnerHTML: { __html: styleScript(configuration) } })] }));
15
+ }
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../source/style/style-script/index.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AAIrE,sHAAsH;AACtH,MAAM,UAAU,WAAW,CAAC,aAAiC;IAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACpF,OAAO,yBAAyB,UAAU,CAAC,QAAQ,EAAE,aAAa,IAAI,6NAA6N,CAAC;AACtS,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,aAAa,EAAyB;IAClE,OAAO,CACL,8BACE,gBAAO,uBAAuB,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,aAAa,CAAC,EAAE,GAAI,EACzE,iBAAQ,uBAAuB,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,aAAa,CAAC,EAAE,GAAI,IAC1E,CACJ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { StyleConfiguration } from "@sharadox/web-library/style/definition/type";
2
+ export interface StyleScriptProperties {
3
+ configuration: StyleConfiguration;
4
+ }
5
+ //# sourceMappingURL=type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../../../source/style/style-script/type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AAEtF,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,kBAAkB,CAAC;CACnC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.js","sourceRoot":"","sources":["../../../source/style/style-script/type.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ import type { StyleConfiguration } from "@sharadox/web-library/style/definition/type";
2
+ /**
3
+ * The CSS for every style, from the configuration alone: `[data-style="<name>"]` for each style, plus a fallback for
4
+ * the moment before the inline script runs (or when scripts are off) that still follows the device's scheme.
5
+ */
6
+ export declare function styleSheet(configuration: StyleConfiguration): string;
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../source/style/style-sheet/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAmB,MAAM,6CAA6C,CAAC;AAOvG;;;GAGG;AACH,wBAAgB,UAAU,CAAC,aAAa,EAAE,kBAAkB,GAAG,MAAM,CAUpE"}
@@ -0,0 +1,19 @@
1
+ import { findStyle } from "@sharadox/web-library/style/definition";
2
+ const declarations = (style) => [...Object.entries(style.palette).map(([key, value]) => `--${key}:${value}`), `color-scheme:${style.scheme}`].join(";");
3
+ /**
4
+ * The CSS for every style, from the configuration alone: `[data-style="<name>"]` for each style, plus a fallback for
5
+ * the moment before the inline script runs (or when scripts are off) that still follows the device's scheme.
6
+ */
7
+ export function styleSheet(configuration) {
8
+ const fallback = findStyle(configuration, configuration.defaults[configuration.fallback]);
9
+ const other = configuration.fallback === "dark" ? "light" : "dark";
10
+ const otherDefault = findStyle(configuration, configuration.defaults[other]);
11
+ if (!fallback || !otherDefault)
12
+ throw new Error("style configuration: a default style is missing");
13
+ return [
14
+ `:root{${declarations(fallback)}}`,
15
+ `@media (prefers-color-scheme:${other}){:root:not([data-style]){${declarations(otherDefault)}}}`,
16
+ ...configuration.styles.map((style) => `:root[data-style="${style.name}"]{${declarations(style)}}`),
17
+ ].join("\n");
18
+ }
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../source/style/style-sheet/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AAGnE,MAAM,YAAY,GAAG,CAAC,KAAsB,EAAE,EAAE,CAC9C,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC,EAAE,gBAAgB,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAChH,GAAG,CACJ,CAAC;AAEJ;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,aAAiC;IAC1D,MAAM,QAAQ,GAAG,SAAS,CAAC,aAAa,EAAE,aAAa,CAAC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC1F,MAAM,KAAK,GAAG,aAAa,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;IACnE,MAAM,YAAY,GAAG,SAAS,CAAC,aAAa,EAAE,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7E,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAY;QAAE,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACnG,OAAO;QACL,SAAS,YAAY,CAAC,QAAQ,CAAC,GAAG;QAClC,gCAAgC,KAAK,6BAA6B,YAAY,CAAC,YAAY,CAAC,IAAI;QAChG,GAAG,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,qBAAqB,KAAK,CAAC,IAAI,MAAM,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC;KACpG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { StyleConfiguration } from "@sharadox/web-library/style/definition/type";
2
+ import type { StyleState } from "@sharadox/web-library/style/use-style/type";
3
+ /** The visitor's style choice and the style in use, kept in sync with `<html>`; `choose` remembers a new choice. */
4
+ export declare function useStyle(configuration: StyleConfiguration): StyleState;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../source/style/use-style/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACtF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4CAA4C,CAAC;AAa7E,oHAAoH;AACpH,wBAAgB,QAAQ,CAAC,aAAa,EAAE,kBAAkB,GAAG,UAAU,CAkBtE"}
@@ -0,0 +1,34 @@
1
+ "use client";
2
+ import { useCallback, useMemo, useSyncExternalStore } from "react";
3
+ import { applyStyle, styleApplyData } from "@sharadox/web-library/style/apply";
4
+ import { STYLE_ATTRIBUTE, STYLE_CHOICE_ATTRIBUTE, SYSTEM_STYLE } from "@sharadox/web-library/style/definition";
5
+ const subscribe = (notify) => {
6
+ const observer = new MutationObserver(notify);
7
+ observer.observe(document.documentElement, {
8
+ attributes: true,
9
+ attributeFilter: [STYLE_ATTRIBUTE, STYLE_CHOICE_ATTRIBUTE],
10
+ });
11
+ return () => observer.disconnect();
12
+ };
13
+ const read = (attribute, fallback) => () => document.documentElement.getAttribute(attribute) ?? fallback;
14
+ /** The visitor's style choice and the style in use, kept in sync with `<html>`; `choose` remembers a new choice. */
15
+ export function useStyle(configuration) {
16
+ const fallbackStyle = configuration.defaults[configuration.fallback];
17
+ const choice = useSyncExternalStore(subscribe, read(STYLE_CHOICE_ATTRIBUTE, SYSTEM_STYLE), () => SYSTEM_STYLE);
18
+ const style = useSyncExternalStore(subscribe, read(STYLE_ATTRIBUTE, fallbackStyle), () => fallbackStyle);
19
+ const data = useMemo(() => styleApplyData(configuration), [configuration]);
20
+ const choose = useCallback((next) => {
21
+ try {
22
+ if (next === SYSTEM_STYLE)
23
+ localStorage.removeItem(data.storageKey);
24
+ else
25
+ localStorage.setItem(data.storageKey, next);
26
+ }
27
+ catch {
28
+ // Storage blocked (private mode): the choice still applies until the page closes.
29
+ }
30
+ applyStyle(data, next);
31
+ }, [data]);
32
+ return { choice, style, choose };
33
+ }
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../source/style/use-style/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AACb,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAC;AAI/G,MAAM,SAAS,GAAG,CAAC,MAAkB,EAAE,EAAE;IACvC,MAAM,QAAQ,GAAG,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC9C,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,eAAe,EAAE;QACzC,UAAU,EAAE,IAAI;QAChB,eAAe,EAAE,CAAC,eAAe,EAAE,sBAAsB,CAAC;KAC3D,CAAC,CAAC;IACH,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;AACrC,CAAC,CAAC;AACF,MAAM,IAAI,GAAG,CAAC,SAAiB,EAAE,QAAgB,EAAE,EAAE,CAAC,GAAG,EAAE,CACzD,QAAQ,CAAC,eAAe,CAAC,YAAY,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC;AAE/D,oHAAoH;AACpH,MAAM,UAAU,QAAQ,CAAC,aAAiC;IACxD,MAAM,aAAa,GAAG,aAAa,CAAC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IACrE,MAAM,MAAM,GAAG,oBAAoB,CAAC,SAAS,EAAE,IAAI,CAAC,sBAAsB,EAAE,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC;IAC/G,MAAM,KAAK,GAAG,oBAAoB,CAAC,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC;IACzG,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,aAAa,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,WAAW,CACxB,CAAC,IAAY,EAAE,EAAE;QACf,IAAI,CAAC;YACH,IAAI,IAAI,KAAK,YAAY;gBAAE,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;;gBAC/D,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,kFAAkF;QACpF,CAAC;QACD,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACzB,CAAC,EACD,CAAC,IAAI,CAAC,CACP,CAAC;IACF,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AACnC,CAAC"}
@@ -0,0 +1,9 @@
1
+ export interface StyleState {
2
+ /** What the visitor picked: "system" or a style name. */
3
+ choice: string;
4
+ /** The style in use right now. Before hydration (and on the server) it is the fallback default. */
5
+ style: string;
6
+ /** Picks a style (or "system"), remembers it and applies it at once. */
7
+ choose: (choice: string) => void;
8
+ }
9
+ //# sourceMappingURL=type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../../../source/style/use-style/type.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,mGAAmG;IACnG,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.js","sourceRoot":"","sources":["../../../source/style/use-style/type.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sharadox/web-library",
3
- "version": "0.1.0",
4
- "description": "Generic building blocks for web applications: internationalization, search engine metadata, animation, time and forms.",
3
+ "version": "0.2.0",
4
+ "description": "Generic building blocks for web applications: internationalization, styles, search engine metadata, animation, time and forms.",
5
5
  "private": false,
6
6
  "license": "SEE LICENSE IN LICENCA.md",
7
7
  "author": "Sharadox <email@sharadox.com>",