@refrakt-md/svelte 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@refrakt-md/svelte",
3
3
  "description": "Svelte renderer for refrakt.md content",
4
- "version": "0.15.0",
4
+ "version": "0.16.0",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -27,9 +27,9 @@
27
27
  ],
28
28
  "dependencies": {
29
29
  "@markdoc/markdoc": "0.4.0",
30
- "@refrakt-md/behaviors": "0.15.0",
31
- "@refrakt-md/transform": "0.15.0",
32
- "@refrakt-md/types": "0.15.0"
30
+ "@refrakt-md/behaviors": "0.16.0",
31
+ "@refrakt-md/transform": "0.16.0",
32
+ "@refrakt-md/types": "0.16.0"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "svelte": "^5.0.0"
package/src/index.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export { default as Renderer } from './Renderer.svelte';
2
2
  export { default as ThemeShell } from './ThemeShell.svelte';
3
- export { default as ThemeToggle } from './ThemeToggle.svelte';
4
3
  export type { SerializedTag, RendererNode } from './types.js';
5
4
  export { serialize, serializeTree } from './serialize.js';
6
5
  export { setRegistry, getComponent, setElementOverrides, getElementOverrides } from './context.js';
@@ -1,147 +0,0 @@
1
- <script lang="ts">
2
- import { onMount } from 'svelte';
3
-
4
- /**
5
- * Theme toggle — three-state button cycling auto → light → dark → auto.
6
- *
7
- * Reads and writes the `rf-theme` localStorage key in lockstep with the
8
- * canonical pre-paint script (see `prePaintScript()` in
9
- * `@refrakt-md/content`). On click, applies the new value as
10
- * `document.documentElement.dataset.theme` so the change takes effect
11
- * immediately without a page reload.
12
- *
13
- * **Locked pages** (`<html data-tint-lock="true">`, set by the cascade
14
- * SSR per SPEC-052) hide the toggle entirely — the saved preference is
15
- * preserved in localStorage but ignored while the lock is active, and
16
- * surfacing a toggle that does nothing would be confusing.
17
- *
18
- * Used by any refrakt site rendering through `@refrakt-md/svelte`. The
19
- * default rendering is a small inline button labelled with an icon and
20
- * a tooltip; override via `class` or a `children` snippet for custom
21
- * presentation while keeping the behaviour.
22
- */
23
- type ThemePref = 'auto' | 'light' | 'dark';
24
-
25
- let {
26
- class: className = '',
27
- children,
28
- }: { class?: string; children?: import('svelte').Snippet<[{ pref: ThemePref }]> } = $props();
29
-
30
- let pref = $state<ThemePref>('auto');
31
- let locked = $state(false);
32
- let mounted = $state(false);
33
-
34
- onMount(() => {
35
- mounted = true;
36
- locked = document.documentElement.dataset.tintLock === 'true';
37
-
38
- try {
39
- const saved = localStorage.getItem('rf-theme') as ThemePref | null;
40
- if (saved === 'light' || saved === 'dark' || saved === 'auto') {
41
- pref = saved;
42
- }
43
- } catch (_) {
44
- // localStorage may be unavailable (private mode, file://, etc.).
45
- }
46
-
47
- // Watch for changes to data-tint-lock during client-side navigation —
48
- // SvelteKit may rewrite this attribute when navigating between pages
49
- // with different cascade states.
50
- const observer = new MutationObserver(() => {
51
- locked = document.documentElement.dataset.tintLock === 'true';
52
- });
53
- observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-tint-lock'] });
54
- return () => observer.disconnect();
55
- });
56
-
57
- function cycle() {
58
- const next: ThemePref = pref === 'auto' ? 'light' : pref === 'light' ? 'dark' : 'auto';
59
- pref = next;
60
- try {
61
- localStorage.setItem('rf-theme', next);
62
- } catch (_) {
63
- // Persist failures are silent — UI still works in-tab.
64
- }
65
- applyPref(next);
66
- }
67
-
68
- function applyPref(p: ThemePref) {
69
- const d = document.documentElement;
70
- if (p === 'auto') {
71
- // Remove the explicit attribute; the @media (prefers-color-scheme)
72
- // rule takes over.
73
- delete d.dataset.theme;
74
- return;
75
- }
76
- d.dataset.theme = p;
77
- }
78
-
79
- const label = $derived(
80
- pref === 'auto' ? 'Theme: auto (system)' : pref === 'light' ? 'Theme: light' : 'Theme: dark',
81
- );
82
- </script>
83
-
84
- {#if mounted && !locked}
85
- <button
86
- type="button"
87
- class="rf-theme-toggle {className}"
88
- data-theme-pref={pref}
89
- aria-label={label}
90
- title={label}
91
- onclick={cycle}
92
- >
93
- {#if children}
94
- {@render children({ pref })}
95
- {:else}
96
- <span aria-hidden="true" class="rf-theme-toggle__icon rf-theme-toggle__icon--{pref}"></span>
97
- {/if}
98
- </button>
99
- {/if}
100
-
101
- <style>
102
- .rf-theme-toggle {
103
- display: inline-flex;
104
- align-items: center;
105
- justify-content: center;
106
- width: 2rem;
107
- height: 2rem;
108
- padding: 0;
109
- border: 1px solid var(--rf-color-border, transparent);
110
- border-radius: var(--rf-radius-md, 8px);
111
- background: transparent;
112
- color: var(--rf-color-text);
113
- cursor: pointer;
114
- transition: background-color 120ms ease, border-color 120ms ease;
115
- }
116
-
117
- .rf-theme-toggle:hover {
118
- background: var(--rf-color-surface-hover, rgba(0, 0, 0, 0.04));
119
- }
120
-
121
- .rf-theme-toggle:focus-visible {
122
- outline: 2px solid var(--rf-color-primary);
123
- outline-offset: 2px;
124
- }
125
-
126
- .rf-theme-toggle__icon {
127
- display: inline-block;
128
- width: 1rem;
129
- height: 1rem;
130
- background: currentColor;
131
- mask-size: contain;
132
- mask-repeat: no-repeat;
133
- mask-position: center;
134
- }
135
-
136
- .rf-theme-toggle__icon--auto {
137
- mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='10'/><path d='M12 2a10 10 0 0 0 0 20Z' fill='currentColor'/></svg>");
138
- }
139
-
140
- .rf-theme-toggle__icon--light {
141
- mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='4'/><path d='M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41'/></svg>");
142
- }
143
-
144
- .rf-theme-toggle__icon--dark {
145
- mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z'/></svg>");
146
- }
147
- </style>