@deepseek-insight/container-base 0.0.0-stage → 0.1.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 (80) hide show
  1. package/README.md +77 -2
  2. package/dist/components/ChordPaletteOverlay.svelte +99 -0
  3. package/dist/components/ChordPaletteOverlay.svelte.d.ts +25 -0
  4. package/dist/components/PaneBase.svelte +159 -0
  5. package/dist/components/PaneBase.svelte.d.ts +27 -0
  6. package/dist/components/PanelHeaderSkeleton.svelte +85 -0
  7. package/dist/components/PanelHeaderSkeleton.svelte.d.ts +19 -0
  8. package/dist/components/PanelsZoom.svelte +213 -0
  9. package/dist/components/PanelsZoom.svelte.d.ts +26 -0
  10. package/dist/components/RailColumn.svelte +91 -0
  11. package/dist/components/RailColumn.svelte.d.ts +15 -0
  12. package/dist/components/ResizeGutter.svelte +68 -0
  13. package/dist/components/ResizeGutter.svelte.d.ts +13 -0
  14. package/dist/components/SidebarContainer.svelte +174 -0
  15. package/dist/components/SidebarContainer.svelte.d.ts +42 -0
  16. package/dist/components/SliderWidth.svelte +80 -0
  17. package/dist/components/SliderWidth.svelte.d.ts +27 -0
  18. package/dist/components/SliderZoom.svelte +77 -0
  19. package/dist/components/SliderZoom.svelte.d.ts +25 -0
  20. package/dist/i18n/LanguageMenu.svelte +165 -0
  21. package/dist/i18n/LanguageMenu.svelte.d.ts +25 -0
  22. package/dist/i18n/locale-prefs.svelte.d.ts +24 -0
  23. package/dist/i18n/locale-prefs.svelte.js +85 -0
  24. package/dist/index.d.ts +34 -0
  25. package/dist/index.js +35 -0
  26. package/dist/interaction/.gitkeep +0 -0
  27. package/dist/interaction/chord-machine.d.ts +108 -0
  28. package/dist/interaction/chord-machine.js +121 -0
  29. package/dist/interaction/gutter-resize.d.ts +37 -0
  30. package/dist/interaction/gutter-resize.js +41 -0
  31. package/dist/layout/.gitkeep +0 -0
  32. package/dist/layout/floor-math.d.ts +48 -0
  33. package/dist/layout/floor-math.js +60 -0
  34. package/dist/layout/placement.d.ts +18 -0
  35. package/dist/layout/placement.js +27 -0
  36. package/dist/registry/.gitkeep +0 -0
  37. package/dist/registry/action-registry.d.ts +30 -0
  38. package/dist/registry/action-registry.js +44 -0
  39. package/dist/storage/create-prefs-store.d.ts +25 -0
  40. package/dist/storage/create-prefs-store.js +67 -0
  41. package/dist/storage/namespace.d.ts +27 -0
  42. package/dist/storage/namespace.js +32 -0
  43. package/dist/theme/.gitkeep +0 -0
  44. package/dist/theme/tokens.css +35 -0
  45. package/dist/theming/ThemeToggle.svelte +170 -0
  46. package/dist/theming/ThemeToggle.svelte.d.ts +27 -0
  47. package/dist/theming/applier.d.ts +15 -0
  48. package/dist/theming/applier.js +29 -0
  49. package/dist/theming/theme-prefs.svelte.d.ts +21 -0
  50. package/dist/theming/theme-prefs.svelte.js +91 -0
  51. package/dist/types/.gitkeep +0 -0
  52. package/package.json +34 -5
  53. package/src/components/ChordPaletteOverlay.svelte +99 -0
  54. package/src/components/PaneBase.svelte +159 -0
  55. package/src/components/PanelHeaderSkeleton.svelte +85 -0
  56. package/src/components/PanelsZoom.svelte +213 -0
  57. package/src/components/RailColumn.svelte +91 -0
  58. package/src/components/ResizeGutter.svelte +68 -0
  59. package/src/components/SidebarContainer.svelte +174 -0
  60. package/src/components/SliderWidth.svelte +80 -0
  61. package/src/components/SliderZoom.svelte +77 -0
  62. package/src/i18n/LanguageMenu.svelte +165 -0
  63. package/src/i18n/locale-prefs.svelte.ts +112 -0
  64. package/src/index.ts +67 -0
  65. package/src/interaction/.gitkeep +0 -0
  66. package/src/interaction/chord-machine.ts +208 -0
  67. package/src/interaction/gutter-resize.ts +76 -0
  68. package/src/layout/.gitkeep +0 -0
  69. package/src/layout/floor-math.ts +94 -0
  70. package/src/layout/placement.ts +30 -0
  71. package/src/registry/.gitkeep +0 -0
  72. package/src/registry/action-registry.ts +62 -0
  73. package/src/storage/create-prefs-store.ts +87 -0
  74. package/src/storage/namespace.ts +34 -0
  75. package/src/theme/.gitkeep +0 -0
  76. package/src/theme/tokens.css +35 -0
  77. package/src/theming/ThemeToggle.svelte +170 -0
  78. package/src/theming/applier.ts +30 -0
  79. package/src/theming/theme-prefs.svelte.ts +118 -0
  80. package/src/types/.gitkeep +0 -0
@@ -0,0 +1,170 @@
1
+ <script lang="ts">
2
+ /**
3
+ * ThemeToggle — the generic three-state lamp mount (ADR-0063 D2).
4
+ * The MECHANISM lives in theming/theme-prefs.svelte.ts; this component
5
+ * carries NO words and NO skin: every label comes in as a prop, every
6
+ * icon as a snippet, every color as a token var (boundary suite).
7
+ * Structure follows the DSI footer lamp: one trigger whose icon is the
8
+ * current choice, a popup with the three states, check on the active
9
+ * one, click-outside closes, dropUp for rail-bottom mounts.
10
+ */
11
+ import type { Component, Snippet } from 'svelte';
12
+
13
+ export type ThemeChoice = 'system' | 'light' | 'dark';
14
+
15
+ let {
16
+ /** The consumer's ThemePrefs instance (createThemePrefs). */
17
+ prefs,
18
+ /** aria-label / title for the trigger (consumer's words). */
19
+ triggerLabel,
20
+ /** Per-state words (consumer's dictionary). */
21
+ optionLabels,
22
+ /** Per-state icon components (consumer's icon set). */
23
+ icons,
24
+ /** Open the list upward (rail-bottom mounts). */
25
+ dropUp = false
26
+ }: {
27
+ prefs: {
28
+ readonly choice: ThemeChoice;
29
+ set: (c: ThemeChoice) => void;
30
+ };
31
+ triggerLabel: string;
32
+ optionLabels: Record<ThemeChoice, string>;
33
+ icons: Record<ThemeChoice, Component<{ size?: number; 'aria-hidden'?: boolean | 'true' | 'false' }>>;
34
+ dropUp?: boolean;
35
+ } = $props();
36
+
37
+ let open = $state(false);
38
+ let rootEl = $state<HTMLElement | null>(null);
39
+
40
+ const CHOICES: ThemeChoice[] = ['system', 'light', 'dark'];
41
+
42
+ const TriggerIcon = $derived(icons[prefs.choice]);
43
+
44
+ function choose(c: ThemeChoice): void {
45
+ open = false;
46
+ prefs.set(c);
47
+ }
48
+
49
+ // outside click closes — one window listener while open (removed on close)
50
+ $effect(() => {
51
+ if (!open) return;
52
+ const onDocClick = (ev: MouseEvent) => {
53
+ if (rootEl && !rootEl.contains(ev.target as Node)) open = false;
54
+ };
55
+ document.addEventListener('click', onDocClick);
56
+ return () => document.removeEventListener('click', onDocClick);
57
+ });
58
+ </script>
59
+
60
+ <div
61
+ class="cb-theme-toggle"
62
+ data-testid="cb-theme-toggle"
63
+ bind:this={rootEl}
64
+ >
65
+ <button
66
+ type="button"
67
+ class="cb-theme-toggle-trigger"
68
+ title={triggerLabel}
69
+ aria-label={triggerLabel}
70
+ aria-expanded={open}
71
+ data-testid="cb-theme-toggle-trigger"
72
+ onclick={() => (open = !open)}
73
+ >
74
+ <TriggerIcon size={16} />
75
+ </button>
76
+ {#if open}
77
+ <div
78
+ class="cb-theme-toggle-list"
79
+ class:cb-drop-up={dropUp}
80
+ data-testid="cb-theme-toggle-list"
81
+ role="menu"
82
+ >
83
+ {#each CHOICES as c (c)}
84
+ {@const Icon = icons[c]}
85
+ <button
86
+ type="button"
87
+ class="cb-theme-option"
88
+ class:cb-option-active={prefs.choice === c}
89
+ data-testid={'cb-theme-option-' + c}
90
+ data-active={prefs.choice === c ? 'true' : 'false'}
91
+ role="menuitem"
92
+ onclick={() => choose(c)}
93
+ >
94
+ <span class="cb-theme-option-row">
95
+ <Icon size={13} aria-hidden="true" />
96
+ {optionLabels[c]}
97
+ </span>
98
+ {#if prefs.choice === c}<span aria-hidden="true">✓</span>{/if}
99
+ </button>
100
+ {/each}
101
+ </div>
102
+ {/if}
103
+ </div>
104
+
105
+ <style>
106
+ /* Structure only — every color crosses through tokens (ground rule).
107
+ * The outside-click close effect lives with the consumer host for now
108
+ * (see tests); the component itself holds no globals. */
109
+ .cb-theme-toggle {
110
+ position: relative;
111
+ display: inline-block;
112
+ }
113
+ .cb-theme-toggle-trigger {
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 0.25rem;
117
+ border: none;
118
+ background: transparent;
119
+ color: var(--color-text-secondary);
120
+ border-radius: 0.25rem;
121
+ padding: 0.25rem;
122
+ cursor: pointer;
123
+ }
124
+ .cb-theme-toggle-trigger:hover {
125
+ color: var(--color-text-primary);
126
+ }
127
+ .cb-theme-toggle-list {
128
+ position: absolute;
129
+ right: 0;
130
+ z-index: 50;
131
+ width: 9rem;
132
+ overflow: hidden;
133
+ margin-top: 0.25rem;
134
+ border-radius: 0.375rem;
135
+ border: 1px solid var(--color-surface-border);
136
+ background: var(--color-surface-elevated);
137
+ color: var(--color-text-primary);
138
+ box-shadow: 0 4px 6px -1px var(--color-surface-border);
139
+ }
140
+ .cb-theme-toggle-list.cb-drop-up {
141
+ bottom: 100%;
142
+ margin-top: 0;
143
+ margin-bottom: 0.25rem;
144
+ }
145
+ .cb-theme-option {
146
+ display: flex;
147
+ width: 100%;
148
+ align-items: center;
149
+ justify-content: space-between;
150
+ padding: 0.375rem 0.75rem;
151
+ text-align: left;
152
+ font-size: 0.75rem;
153
+ border: none;
154
+ background: transparent;
155
+ color: inherit;
156
+ cursor: pointer;
157
+ }
158
+ .cb-theme-option:hover {
159
+ background: var(--color-surface-secondary);
160
+ }
161
+ .cb-theme-option.cb-option-active {
162
+ color: var(--color-accent-purple);
163
+ font-weight: 600;
164
+ }
165
+ .cb-theme-option-row {
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 0.375rem;
169
+ }
170
+ </style>
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The pre-paint theme applier — an inline SCRIPT SOURCE STRING the consumer
3
+ * embeds in its HTML template (ADR-0063 D2). It re-derives the storage key
4
+ * with the SAME grammar as theming/theme-prefs.svelte.ts (namespace.ts:
5
+ * <baseKey> + '_' + sanitized ?profile=), reads the choice, resolves system
6
+ * against the OS, and sets html[data-theme] BEFORE first paint — no flash.
7
+ *
8
+ * It cannot import modules (it runs before the bundle), so the derivation is
9
+ * generated from this one function: consumers never hand-copy it.
10
+ *
11
+ * Pure string generation — no runes — so plain node (strip-types) can import
12
+ * it for HTML template generation (scripts/sync-theme-applier.mjs).
13
+ */
14
+
15
+ /** Generate the inline script source for a consumer's theme base key. */
16
+ export function themeApplierScript(baseKey: string): string {
17
+ return `try {
18
+ var m = location.search.match(/[?&]profile=([^&]*)/);
19
+ var prof = m ? m[1].replace(/[^A-Za-z0-9_-]/g, '').slice(0, 40) : '';
20
+ var k = '${baseKey}' + (prof ? '_' + prof : '');
21
+ var t = localStorage.getItem(k);
22
+ if (t !== 'light' && t !== 'dark' && t !== 'system') t = 'system';
23
+ document.documentElement.dataset.theme =
24
+ t === 'dark' || (t === 'system' && window.matchMedia && matchMedia('(prefers-color-scheme: dark)').matches)
25
+ ? 'dark'
26
+ : 'light';
27
+ } catch (e) {
28
+ document.documentElement.dataset.theme = 'light';
29
+ }`;
30
+ }
@@ -0,0 +1,118 @@
1
+ // The base Three-State Lamp — the switch MECHANISM (ADR-0063 D2, moved out
2
+ // of DSI's hand-rolled twin). Owns a three-state choice (system / light /
3
+ // dark), persists it under localStorage[<baseKey>], resolves it against the
4
+ // OS, and sets html[data-theme] — the single hinge the token layer reads.
5
+ // The ONLY other place that derives the storage key is the inline pre-paint
6
+ // applier (theming/applier.ts) the consumer inlines in its HTML template;
7
+ // key grammar flows through storage/namespace so the two cannot drift.
8
+ // Key grammar: EVERY <baseKey> is profile-scoped (?profile= param), so each
9
+ // desk remembers its own lamp.
10
+
11
+ import { storageKey, sanitizeWorkspaceProfile } from '../storage/namespace';
12
+
13
+ export type ThemeChoice = 'system' | 'light' | 'dark';
14
+ export type ResolvedTheme = 'light' | 'dark';
15
+
16
+ export interface ThemePrefsOptions {
17
+ /** The un-suffixed localStorage key (e.g. 'dsi-theme', 'pg-theme'). */
18
+ baseKey: string;
19
+ }
20
+
21
+ export interface ThemePrefs {
22
+ readonly choice: ThemeChoice;
23
+ readonly resolved: ResolvedTheme;
24
+ /** Mount-time adoption: the pre-paint applier already resolved and set
25
+ * the attribute before the bundle ran — ADOPT it (flash-free),
26
+ * re-deriving the choice from storage. Paints only when the attribute
27
+ * was somehow missing. */
28
+ adopt(): void;
29
+ /** Persist the CHOICE (never the resolved value — system keeps following
30
+ * the OS), then paint. */
31
+ set(next: ThemeChoice): void;
32
+ /** Live OS following, only while the choice is system. */
33
+ listen(): void;
34
+ }
35
+
36
+ export function createThemePrefs(options: ThemePrefsOptions): ThemePrefs {
37
+ const baseKey = options.baseKey;
38
+
39
+ /** The active workspace profile — the sticky ?profile= param, sanitized
40
+ * once here (mirror of the pre-paint derivation in the applier). */
41
+ const currentProfile = (): string | null =>
42
+ typeof location === 'undefined'
43
+ ? null
44
+ : sanitizeWorkspaceProfile(new URLSearchParams(location.search).get('profile') ?? '');
45
+
46
+ const storageKeyFor = (): string => storageKey(baseKey, currentProfile());
47
+
48
+ const osPrefersDark = (): boolean =>
49
+ typeof window !== 'undefined' &&
50
+ !!window.matchMedia?.('(prefers-color-scheme: dark)').matches;
51
+
52
+ /** Defensive read — SSR-safe, junk -> system. */
53
+ const readStoredChoice = (): ThemeChoice => {
54
+ if (typeof localStorage === 'undefined') return 'system';
55
+ try {
56
+ const raw = localStorage.getItem(storageKeyFor());
57
+ return raw === 'light' || raw === 'dark' || raw === 'system' ? raw : 'system';
58
+ } catch {
59
+ return 'system';
60
+ }
61
+ };
62
+
63
+ const resolve = (choice: ThemeChoice): ResolvedTheme =>
64
+ choice === 'system' ? (osPrefersDark() ? 'dark' : 'light') : choice;
65
+
66
+ let choice = $state<ThemeChoice>('system');
67
+ let resolved = $state<ResolvedTheme>('light');
68
+ let listeningMq: MediaQueryList | null = null;
69
+
70
+ const apply = () => {
71
+ if (typeof document !== 'undefined') document.documentElement.dataset.theme = resolved;
72
+ };
73
+ const persist = () => {
74
+ if (typeof localStorage === 'undefined') return;
75
+ try {
76
+ localStorage.setItem(storageKeyFor(), choice);
77
+ } catch {
78
+ /* blocked storage — the attribute still flips for this session */
79
+ }
80
+ };
81
+
82
+ return {
83
+ get choice(): ThemeChoice {
84
+ return choice;
85
+ },
86
+ get resolved(): ResolvedTheme {
87
+ return resolved;
88
+ },
89
+ adopt() {
90
+ choice = readStoredChoice();
91
+ const attr = typeof document !== 'undefined' ? document.documentElement.dataset.theme : undefined;
92
+ if (attr === 'dark' || attr === 'light') {
93
+ resolved = attr;
94
+ } else {
95
+ resolved = resolve(choice);
96
+ apply();
97
+ }
98
+ this.listen();
99
+ },
100
+ set(next: ThemeChoice) {
101
+ choice = next;
102
+ resolved = resolve(next);
103
+ persist();
104
+ apply();
105
+ },
106
+ listen() {
107
+ if (typeof window === 'undefined' || !window.matchMedia) return;
108
+ const mq = window.matchMedia('(prefers-color-scheme: dark)');
109
+ if (listeningMq === mq) return; // one listener per media-query object
110
+ listeningMq = mq;
111
+ mq.addEventListener('change', (e) => {
112
+ if (choice !== 'system') return; // pinned ignores the OS
113
+ resolved = e.matches ? 'dark' : 'light';
114
+ apply();
115
+ });
116
+ }
117
+ };
118
+ }
File without changes