@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.
- package/README.md +77 -2
- package/dist/components/ChordPaletteOverlay.svelte +99 -0
- package/dist/components/ChordPaletteOverlay.svelte.d.ts +25 -0
- package/dist/components/PaneBase.svelte +159 -0
- package/dist/components/PaneBase.svelte.d.ts +27 -0
- package/dist/components/PanelHeaderSkeleton.svelte +85 -0
- package/dist/components/PanelHeaderSkeleton.svelte.d.ts +19 -0
- package/dist/components/PanelsZoom.svelte +213 -0
- package/dist/components/PanelsZoom.svelte.d.ts +26 -0
- package/dist/components/RailColumn.svelte +91 -0
- package/dist/components/RailColumn.svelte.d.ts +15 -0
- package/dist/components/ResizeGutter.svelte +68 -0
- package/dist/components/ResizeGutter.svelte.d.ts +13 -0
- package/dist/components/SidebarContainer.svelte +174 -0
- package/dist/components/SidebarContainer.svelte.d.ts +42 -0
- package/dist/components/SliderWidth.svelte +80 -0
- package/dist/components/SliderWidth.svelte.d.ts +27 -0
- package/dist/components/SliderZoom.svelte +77 -0
- package/dist/components/SliderZoom.svelte.d.ts +25 -0
- package/dist/i18n/LanguageMenu.svelte +165 -0
- package/dist/i18n/LanguageMenu.svelte.d.ts +25 -0
- package/dist/i18n/locale-prefs.svelte.d.ts +24 -0
- package/dist/i18n/locale-prefs.svelte.js +85 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +35 -0
- package/dist/interaction/.gitkeep +0 -0
- package/dist/interaction/chord-machine.d.ts +108 -0
- package/dist/interaction/chord-machine.js +121 -0
- package/dist/interaction/gutter-resize.d.ts +37 -0
- package/dist/interaction/gutter-resize.js +41 -0
- package/dist/layout/.gitkeep +0 -0
- package/dist/layout/floor-math.d.ts +48 -0
- package/dist/layout/floor-math.js +60 -0
- package/dist/layout/placement.d.ts +18 -0
- package/dist/layout/placement.js +27 -0
- package/dist/registry/.gitkeep +0 -0
- package/dist/registry/action-registry.d.ts +30 -0
- package/dist/registry/action-registry.js +44 -0
- package/dist/storage/create-prefs-store.d.ts +25 -0
- package/dist/storage/create-prefs-store.js +67 -0
- package/dist/storage/namespace.d.ts +27 -0
- package/dist/storage/namespace.js +32 -0
- package/dist/theme/.gitkeep +0 -0
- package/dist/theme/tokens.css +35 -0
- package/dist/theming/ThemeToggle.svelte +170 -0
- package/dist/theming/ThemeToggle.svelte.d.ts +27 -0
- package/dist/theming/applier.d.ts +15 -0
- package/dist/theming/applier.js +29 -0
- package/dist/theming/theme-prefs.svelte.d.ts +21 -0
- package/dist/theming/theme-prefs.svelte.js +91 -0
- package/dist/types/.gitkeep +0 -0
- package/package.json +34 -5
- package/src/components/ChordPaletteOverlay.svelte +99 -0
- package/src/components/PaneBase.svelte +159 -0
- package/src/components/PanelHeaderSkeleton.svelte +85 -0
- package/src/components/PanelsZoom.svelte +213 -0
- package/src/components/RailColumn.svelte +91 -0
- package/src/components/ResizeGutter.svelte +68 -0
- package/src/components/SidebarContainer.svelte +174 -0
- package/src/components/SliderWidth.svelte +80 -0
- package/src/components/SliderZoom.svelte +77 -0
- package/src/i18n/LanguageMenu.svelte +165 -0
- package/src/i18n/locale-prefs.svelte.ts +112 -0
- package/src/index.ts +67 -0
- package/src/interaction/.gitkeep +0 -0
- package/src/interaction/chord-machine.ts +208 -0
- package/src/interaction/gutter-resize.ts +76 -0
- package/src/layout/.gitkeep +0 -0
- package/src/layout/floor-math.ts +94 -0
- package/src/layout/placement.ts +30 -0
- package/src/registry/.gitkeep +0 -0
- package/src/registry/action-registry.ts +62 -0
- package/src/storage/create-prefs-store.ts +87 -0
- package/src/storage/namespace.ts +34 -0
- package/src/theme/.gitkeep +0 -0
- package/src/theme/tokens.css +35 -0
- package/src/theming/ThemeToggle.svelte +170 -0
- package/src/theming/applier.ts +30 -0
- package/src/theming/theme-prefs.svelte.ts +118 -0
- package/src/types/.gitkeep +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SliderWidth — the honest uniform-width control (ADR-0063 D4, moved
|
|
4
|
+
* from DSI's control bar). Default bounds are the panel-prefs defaults
|
|
5
|
+
* the floor clamps against (480..860, step 1); a consumer overrides
|
|
6
|
+
* them via props — the clamp site stays the CONSUMER's (the commit
|
|
7
|
+
* callback routes wherever the route owner decides), so the
|
|
8
|
+
* single-bound-site rule holds. Every thumb move commits (per-input
|
|
9
|
+
* cadence) — panels resize LIVE under the dragging thumb.
|
|
10
|
+
*/
|
|
11
|
+
export const WIDTH_BOUNDS = { min: 480, max: 860, step: 1 } as const;
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<script lang="ts">
|
|
15
|
+
let {
|
|
16
|
+
/** Bound width (px); updates flow back up via $bindable per input. */
|
|
17
|
+
value = $bindable(730),
|
|
18
|
+
/** Bounds — default WIDTH_BOUNDS, prop-overridable. */
|
|
19
|
+
min = WIDTH_BOUNDS.min,
|
|
20
|
+
max = WIDTH_BOUNDS.max,
|
|
21
|
+
step = WIDTH_BOUNDS.step,
|
|
22
|
+
/** LIVE commit per input event (the honest-slider contract). */
|
|
23
|
+
oncommit,
|
|
24
|
+
/** Caption content — consumer words (the base ships none). */
|
|
25
|
+
caption,
|
|
26
|
+
/** Accessible name for the input (consumer words). */
|
|
27
|
+
ariaLabel,
|
|
28
|
+
/** Testid stem: <stem>, <stem>-input, <stem>-value. */
|
|
29
|
+
testId = 'cb-slider-width'
|
|
30
|
+
}: {
|
|
31
|
+
value?: number;
|
|
32
|
+
min?: number;
|
|
33
|
+
max?: number;
|
|
34
|
+
step?: number;
|
|
35
|
+
oncommit?: (width: number) => void;
|
|
36
|
+
caption?: import('svelte').Snippet<[number]>;
|
|
37
|
+
ariaLabel: string;
|
|
38
|
+
testId?: string;
|
|
39
|
+
} = $props();
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<label class="cb-slider-width" data-testid={testId}>
|
|
43
|
+
{#if caption}
|
|
44
|
+
<span class="cb-slider-caption">
|
|
45
|
+
{@render caption(value)}
|
|
46
|
+
</span>
|
|
47
|
+
{/if}
|
|
48
|
+
<input
|
|
49
|
+
type="range"
|
|
50
|
+
{min}
|
|
51
|
+
{max}
|
|
52
|
+
{step}
|
|
53
|
+
bind:value
|
|
54
|
+
oninput={() => oncommit?.(value)}
|
|
55
|
+
data-testid={testId + '-input'}
|
|
56
|
+
aria-label={ariaLabel}
|
|
57
|
+
/>
|
|
58
|
+
</label>
|
|
59
|
+
|
|
60
|
+
<style>
|
|
61
|
+
.cb-slider-width {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
gap: 0.25rem;
|
|
65
|
+
font-size: 0.6875rem;
|
|
66
|
+
color: var(--color-text-secondary);
|
|
67
|
+
}
|
|
68
|
+
.cb-slider-caption {
|
|
69
|
+
display: flex;
|
|
70
|
+
justify-content: space-between;
|
|
71
|
+
}
|
|
72
|
+
.cb-slider-width input {
|
|
73
|
+
width: 100%;
|
|
74
|
+
accent-color: var(--color-accent-blue);
|
|
75
|
+
}
|
|
76
|
+
.cb-slider-width input:focus-visible {
|
|
77
|
+
outline: 2px solid var(--color-accent-blue);
|
|
78
|
+
outline-offset: 2px;
|
|
79
|
+
}
|
|
80
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SliderWidth — the honest uniform-width control (ADR-0063 D4, moved
|
|
3
|
+
* from DSI's control bar). Default bounds are the panel-prefs defaults
|
|
4
|
+
* the floor clamps against (480..860, step 1); a consumer overrides
|
|
5
|
+
* them via props — the clamp site stays the CONSUMER's (the commit
|
|
6
|
+
* callback routes wherever the route owner decides), so the
|
|
7
|
+
* single-bound-site rule holds. Every thumb move commits (per-input
|
|
8
|
+
* cadence) — panels resize LIVE under the dragging thumb.
|
|
9
|
+
*/
|
|
10
|
+
export declare const WIDTH_BOUNDS: {
|
|
11
|
+
readonly min: 480;
|
|
12
|
+
readonly max: 860;
|
|
13
|
+
readonly step: 1;
|
|
14
|
+
};
|
|
15
|
+
type $$ComponentProps = {
|
|
16
|
+
value?: number;
|
|
17
|
+
min?: number;
|
|
18
|
+
max?: number;
|
|
19
|
+
step?: number;
|
|
20
|
+
oncommit?: (width: number) => void;
|
|
21
|
+
caption?: import('svelte').Snippet<[number]>;
|
|
22
|
+
ariaLabel: string;
|
|
23
|
+
testId?: string;
|
|
24
|
+
};
|
|
25
|
+
declare const SliderWidth: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
26
|
+
type SliderWidth = ReturnType<typeof SliderWidth>;
|
|
27
|
+
export default SliderWidth;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SliderZoom — the honest floor-zoom control (ADR-0063 D4, moved from
|
|
4
|
+
* DSI's control bar). Default bounds 0.25..1.25 (the floor's zoom
|
|
5
|
+
* scale); prop-overridable. The bound value carries EVERY thumb move
|
|
6
|
+
* live to the owner (PanelsZoom re-measures per move — spec-13's
|
|
7
|
+
* no-phantom-scrollbar contract rides on this cadence).
|
|
8
|
+
*/
|
|
9
|
+
export const ZOOM_BOUNDS = { min: 0.25, max: 1.25, step: 0.001 } as const;
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
let {
|
|
14
|
+
/** Floor zoom multiplier; updates flow back up via $bindable per input. */
|
|
15
|
+
value = $bindable(1),
|
|
16
|
+
min = ZOOM_BOUNDS.min,
|
|
17
|
+
max = ZOOM_BOUNDS.max,
|
|
18
|
+
step = ZOOM_BOUNDS.step,
|
|
19
|
+
/** Optional LIVE commit per input event (bind already carries value). */
|
|
20
|
+
oncommit,
|
|
21
|
+
/** Caption content — consumer words (the base ships none). */
|
|
22
|
+
caption,
|
|
23
|
+
/** Accessible name for the input (consumer words). */
|
|
24
|
+
ariaLabel,
|
|
25
|
+
/** Testid stem: <stem>, <stem>-input, <stem>-value. */
|
|
26
|
+
testId = 'cb-slider-zoom'
|
|
27
|
+
}: {
|
|
28
|
+
value?: number;
|
|
29
|
+
min?: number;
|
|
30
|
+
max?: number;
|
|
31
|
+
step?: number;
|
|
32
|
+
oncommit?: (zoom: number) => void;
|
|
33
|
+
caption?: import('svelte').Snippet<[number]>;
|
|
34
|
+
ariaLabel: string;
|
|
35
|
+
testId?: string;
|
|
36
|
+
} = $props();
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<label class="cb-slider-zoom" data-testid={testId}>
|
|
40
|
+
{#if caption}
|
|
41
|
+
<span class="cb-slider-caption">
|
|
42
|
+
{@render caption(value)}
|
|
43
|
+
</span>
|
|
44
|
+
{/if}
|
|
45
|
+
<input
|
|
46
|
+
type="range"
|
|
47
|
+
{min}
|
|
48
|
+
{max}
|
|
49
|
+
{step}
|
|
50
|
+
bind:value
|
|
51
|
+
oninput={() => oncommit?.(value)}
|
|
52
|
+
data-testid={testId + '-input'}
|
|
53
|
+
aria-label={ariaLabel}
|
|
54
|
+
/>
|
|
55
|
+
</label>
|
|
56
|
+
|
|
57
|
+
<style>
|
|
58
|
+
.cb-slider-zoom {
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
gap: 0.25rem;
|
|
62
|
+
font-size: 0.6875rem;
|
|
63
|
+
color: var(--color-text-secondary);
|
|
64
|
+
}
|
|
65
|
+
.cb-slider-caption {
|
|
66
|
+
display: flex;
|
|
67
|
+
justify-content: space-between;
|
|
68
|
+
}
|
|
69
|
+
.cb-slider-zoom input {
|
|
70
|
+
width: 100%;
|
|
71
|
+
accent-color: var(--color-accent-blue);
|
|
72
|
+
}
|
|
73
|
+
.cb-slider-zoom input:focus-visible {
|
|
74
|
+
outline: 2px solid var(--color-accent-blue);
|
|
75
|
+
outline-offset: 2px;
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SliderZoom — the honest floor-zoom control (ADR-0063 D4, moved from
|
|
3
|
+
* DSI's control bar). Default bounds 0.25..1.25 (the floor's zoom
|
|
4
|
+
* scale); prop-overridable. The bound value carries EVERY thumb move
|
|
5
|
+
* live to the owner (PanelsZoom re-measures per move — spec-13's
|
|
6
|
+
* no-phantom-scrollbar contract rides on this cadence).
|
|
7
|
+
*/
|
|
8
|
+
export declare const ZOOM_BOUNDS: {
|
|
9
|
+
readonly min: 0.25;
|
|
10
|
+
readonly max: 1.25;
|
|
11
|
+
readonly step: 0.001;
|
|
12
|
+
};
|
|
13
|
+
type $$ComponentProps = {
|
|
14
|
+
value?: number;
|
|
15
|
+
min?: number;
|
|
16
|
+
max?: number;
|
|
17
|
+
step?: number;
|
|
18
|
+
oncommit?: (zoom: number) => void;
|
|
19
|
+
caption?: import('svelte').Snippet<[number]>;
|
|
20
|
+
ariaLabel: string;
|
|
21
|
+
testId?: string;
|
|
22
|
+
};
|
|
23
|
+
declare const SliderZoom: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
24
|
+
type SliderZoom = ReturnType<typeof SliderZoom>;
|
|
25
|
+
export default SliderZoom;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* LanguageMenu — the generic mount over a LocalePrefs store (ADR-0063 D3).
|
|
4
|
+
* The base carries NO words and NO skin: the language list and its labels
|
|
5
|
+
* are consumer props, the trigger icon is a consumer component, colors
|
|
6
|
+
* cross only through tokens. Native names are NOT translated copy —
|
|
7
|
+
* every locale shows every name in its own language (that is the point
|
|
8
|
+
* of a language menu), which is why they arrive as data, not snippets.
|
|
9
|
+
*/
|
|
10
|
+
import type { Component } from 'svelte';
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
/** The consumer's createLocalePrefs instance. */
|
|
14
|
+
prefs,
|
|
15
|
+
/** Locale ids in display order (validated by the store). */
|
|
16
|
+
locales,
|
|
17
|
+
/** Native names per locale id. */
|
|
18
|
+
labels,
|
|
19
|
+
/** aria-label / title for the trigger (consumer's words). */
|
|
20
|
+
triggerLabel,
|
|
21
|
+
/** The trigger icon (consumer's icon set — e.g. a languages glyph). */
|
|
22
|
+
icon,
|
|
23
|
+
/** Open the list upward (rail-bottom mounts). */
|
|
24
|
+
dropUp = false
|
|
25
|
+
}: {
|
|
26
|
+
prefs: {
|
|
27
|
+
readonly locale: string;
|
|
28
|
+
set: (next: string) => void;
|
|
29
|
+
};
|
|
30
|
+
locales: readonly string[];
|
|
31
|
+
labels: Record<string, string>;
|
|
32
|
+
triggerLabel: string;
|
|
33
|
+
icon: Component<{ size?: number }>;
|
|
34
|
+
dropUp?: boolean;
|
|
35
|
+
} = $props();
|
|
36
|
+
|
|
37
|
+
let open = $state(false);
|
|
38
|
+
let rootEl = $state<HTMLElement | null>(null);
|
|
39
|
+
|
|
40
|
+
// outside click closes — one window listener while open (removed on close)
|
|
41
|
+
$effect(() => {
|
|
42
|
+
if (!open) return;
|
|
43
|
+
const onDocClick = (ev: MouseEvent) => {
|
|
44
|
+
if (rootEl && !rootEl.contains(ev.target as Node)) open = false;
|
|
45
|
+
};
|
|
46
|
+
document.addEventListener('click', onDocClick);
|
|
47
|
+
return () => document.removeEventListener('click', onDocClick);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
function choose(loc: string): void {
|
|
51
|
+
open = false;
|
|
52
|
+
prefs.set(loc);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const TriggerIcon = $derived(icon);
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<div
|
|
59
|
+
class="cb-language-menu"
|
|
60
|
+
data-testid="language-menu"
|
|
61
|
+
bind:this={rootEl}
|
|
62
|
+
>
|
|
63
|
+
<button
|
|
64
|
+
type="button"
|
|
65
|
+
class="cb-language-trigger"
|
|
66
|
+
title={triggerLabel}
|
|
67
|
+
aria-label={triggerLabel}
|
|
68
|
+
aria-expanded={open}
|
|
69
|
+
data-testid="language-menu-trigger"
|
|
70
|
+
onclick={() => (open = !open)}
|
|
71
|
+
>
|
|
72
|
+
<TriggerIcon size={16} />
|
|
73
|
+
<span class="cb-language-code">{prefs.locale.toUpperCase()}</span>
|
|
74
|
+
</button>
|
|
75
|
+
{#if open}
|
|
76
|
+
<div
|
|
77
|
+
class="cb-language-list"
|
|
78
|
+
class:cb-drop-up={dropUp}
|
|
79
|
+
data-testid="language-menu-list"
|
|
80
|
+
role="menu"
|
|
81
|
+
>
|
|
82
|
+
{#each locales as loc (loc)}
|
|
83
|
+
<button
|
|
84
|
+
type="button"
|
|
85
|
+
class="cb-language-option"
|
|
86
|
+
class:cb-option-active={prefs.locale === loc}
|
|
87
|
+
data-testid={'locale-option-' + loc}
|
|
88
|
+
data-active={prefs.locale === loc ? 'true' : 'false'}
|
|
89
|
+
role="menuitem"
|
|
90
|
+
onclick={() => choose(loc)}
|
|
91
|
+
>
|
|
92
|
+
{labels[loc] ?? loc}
|
|
93
|
+
{#if prefs.locale === loc}<span aria-hidden="true">✓</span>{/if}
|
|
94
|
+
</button>
|
|
95
|
+
{/each}
|
|
96
|
+
</div>
|
|
97
|
+
{/if}
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<style>
|
|
101
|
+
/* Structure only — colors cross through tokens (ground rule). */
|
|
102
|
+
.cb-language-menu {
|
|
103
|
+
position: relative;
|
|
104
|
+
display: inline-block;
|
|
105
|
+
}
|
|
106
|
+
.cb-language-trigger {
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
gap: 0.25rem;
|
|
110
|
+
border: none;
|
|
111
|
+
background: transparent;
|
|
112
|
+
color: var(--color-text-secondary);
|
|
113
|
+
border-radius: 0.25rem;
|
|
114
|
+
padding: 0.25rem;
|
|
115
|
+
font-size: 0.75rem;
|
|
116
|
+
font-weight: 500;
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
}
|
|
119
|
+
.cb-language-trigger:hover {
|
|
120
|
+
color: var(--color-text-primary);
|
|
121
|
+
}
|
|
122
|
+
.cb-language-code {
|
|
123
|
+
text-transform: uppercase;
|
|
124
|
+
}
|
|
125
|
+
.cb-language-list {
|
|
126
|
+
position: absolute;
|
|
127
|
+
right: 0;
|
|
128
|
+
z-index: 50;
|
|
129
|
+
width: 9rem;
|
|
130
|
+
overflow: hidden;
|
|
131
|
+
margin-top: 0.25rem;
|
|
132
|
+
padding-top: 0.25rem;
|
|
133
|
+
padding-bottom: 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-language-list.cb-drop-up {
|
|
141
|
+
bottom: 100%;
|
|
142
|
+
margin-top: 0;
|
|
143
|
+
margin-bottom: 0.25rem;
|
|
144
|
+
}
|
|
145
|
+
.cb-language-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-language-option:hover {
|
|
159
|
+
background: var(--color-surface-secondary);
|
|
160
|
+
}
|
|
161
|
+
.cb-language-option.cb-option-active {
|
|
162
|
+
color: var(--color-accent-purple);
|
|
163
|
+
font-weight: 600;
|
|
164
|
+
}
|
|
165
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LanguageMenu — the generic mount over a LocalePrefs store (ADR-0063 D3).
|
|
3
|
+
* The base carries NO words and NO skin: the language list and its labels
|
|
4
|
+
* are consumer props, the trigger icon is a consumer component, colors
|
|
5
|
+
* cross only through tokens. Native names are NOT translated copy —
|
|
6
|
+
* every locale shows every name in its own language (that is the point
|
|
7
|
+
* of a language menu), which is why they arrive as data, not snippets.
|
|
8
|
+
*/
|
|
9
|
+
import type { Component } from 'svelte';
|
|
10
|
+
type $$ComponentProps = {
|
|
11
|
+
prefs: {
|
|
12
|
+
readonly locale: string;
|
|
13
|
+
set: (next: string) => void;
|
|
14
|
+
};
|
|
15
|
+
locales: readonly string[];
|
|
16
|
+
labels: Record<string, string>;
|
|
17
|
+
triggerLabel: string;
|
|
18
|
+
icon: Component<{
|
|
19
|
+
size?: number;
|
|
20
|
+
}>;
|
|
21
|
+
dropUp?: boolean;
|
|
22
|
+
};
|
|
23
|
+
declare const LanguageMenu: Component<$$ComponentProps, {}, "">;
|
|
24
|
+
type LanguageMenu = ReturnType<typeof LanguageMenu>;
|
|
25
|
+
export default LanguageMenu;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface LocalePrefsOptions {
|
|
2
|
+
/** The un-suffixed localStorage key (e.g. 'dsi.locale', 'pg.locale'). */
|
|
3
|
+
baseKey: string;
|
|
4
|
+
/** The mirror cookie name. The consumer's SSR chain reads THIS name. */
|
|
5
|
+
cookieName: string;
|
|
6
|
+
/** The valid locale ids — anything else is junk. */
|
|
7
|
+
locales: readonly string[];
|
|
8
|
+
/** Fallback when nothing (or junk) is stored. */
|
|
9
|
+
defaultLocale: string;
|
|
10
|
+
/** Cookie lifetime seconds (default ~400 days, matching common SSR locale cookies). */
|
|
11
|
+
cookieMaxAge?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface LocalePrefs {
|
|
14
|
+
readonly locale: string;
|
|
15
|
+
/** The exact storage key this store uses for a profile. */
|
|
16
|
+
key(profile?: string | null): string;
|
|
17
|
+
/** Boot-time adoption: resolve cookie → storage → default into state. */
|
|
18
|
+
adopt(): void;
|
|
19
|
+
/** Validate + dual-write (storage + cookie) + update state. */
|
|
20
|
+
set(next: string): void;
|
|
21
|
+
/** Raw resolution without touching state (SSR-safe). */
|
|
22
|
+
read(): string;
|
|
23
|
+
}
|
|
24
|
+
export declare function createLocalePrefs(options: LocalePrefsOptions): LocalePrefs;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// The base locale survival store (ADR-0063 D3): a client-side choice with
|
|
2
|
+
// DUAL-WRITE persistence — localStorage[<baseKey>] (profile-suffixed via
|
|
3
|
+
// storage/namespace) AND a mirror cookie the consumer names (DSI passes
|
|
4
|
+
// 'dsi.locale' so its SSR chain stays the reader; the base only mirrors,
|
|
5
|
+
// never races the SSR chain). Reads resolve cookie first, then storage,
|
|
6
|
+
// then the consumer default. Junk is junk: anything outside the
|
|
7
|
+
// consumer's locale list falls back to the default, never throws.
|
|
8
|
+
//
|
|
9
|
+
// SSR (no document / no localStorage): reads return the default, writes
|
|
10
|
+
// are silent no-ops — the consumer's SSR chain owns truth.
|
|
11
|
+
import { storageKey, sanitizeWorkspaceProfile } from '../storage/namespace';
|
|
12
|
+
function readCookie(name) {
|
|
13
|
+
if (typeof document === 'undefined')
|
|
14
|
+
return null;
|
|
15
|
+
try {
|
|
16
|
+
const m = document.cookie.match(new RegExp('(?:^|;\\s*)' + name.replace(/[.$?*|{}()\[\]\\/+^]/g, '\\$&') + '=([^;]*)'));
|
|
17
|
+
return m ? decodeURIComponent(m[1]) : null;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function createLocalePrefs(options) {
|
|
24
|
+
const { baseKey, cookieName, locales, defaultLocale } = options;
|
|
25
|
+
const maxAge = options.cookieMaxAge ?? 34560000;
|
|
26
|
+
let locale = $state(defaultLocale);
|
|
27
|
+
const currentProfile = () => typeof location === 'undefined'
|
|
28
|
+
? null
|
|
29
|
+
: sanitizeWorkspaceProfile(new URLSearchParams(location.search).get('profile') ?? '');
|
|
30
|
+
const keyFor = (profile) => storageKey(baseKey, profile ?? currentProfile());
|
|
31
|
+
const resolve = () => {
|
|
32
|
+
const cookie = readCookie(cookieName);
|
|
33
|
+
if (cookie !== null && locales.includes(cookie))
|
|
34
|
+
return cookie;
|
|
35
|
+
if (typeof localStorage !== 'undefined') {
|
|
36
|
+
try {
|
|
37
|
+
const raw = localStorage.getItem(keyFor());
|
|
38
|
+
if (raw !== null && locales.includes(raw))
|
|
39
|
+
return raw;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// blocked storage — the cookie already had its say
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return defaultLocale;
|
|
46
|
+
};
|
|
47
|
+
const persist = (next) => {
|
|
48
|
+
if (typeof localStorage !== 'undefined') {
|
|
49
|
+
try {
|
|
50
|
+
localStorage.setItem(keyFor(), next);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// store full or blocked — the cookie still holds the choice
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (typeof document !== 'undefined') {
|
|
57
|
+
try {
|
|
58
|
+
document.cookie = cookieName + '=' + encodeURIComponent(next) + '; path=/; max-age=' + maxAge + '; samesite=lax';
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// cookie write blocked — the storage mirror holds
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
get locale() {
|
|
67
|
+
return locale;
|
|
68
|
+
},
|
|
69
|
+
key(profile) {
|
|
70
|
+
return keyFor(profile);
|
|
71
|
+
},
|
|
72
|
+
read() {
|
|
73
|
+
return resolve();
|
|
74
|
+
},
|
|
75
|
+
adopt() {
|
|
76
|
+
locale = resolve();
|
|
77
|
+
},
|
|
78
|
+
set(next) {
|
|
79
|
+
if (!locales.includes(next))
|
|
80
|
+
return; // junk → silent no-op
|
|
81
|
+
locale = next;
|
|
82
|
+
persist(next);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deepseek-insight/container-base — the ONLY public entry.
|
|
3
|
+
* Internals are reached exclusively through here (no deep imports).
|
|
4
|
+
*/
|
|
5
|
+
/** Probe export for workspace resolution (Wave 1); real surface lands W2+. */
|
|
6
|
+
export declare const CONTAINER_BASE_VERSION = "0.1.0";
|
|
7
|
+
export { default as PanelsZoom } from './components/PanelsZoom.svelte';
|
|
8
|
+
export { default as ResizeGutter } from './components/ResizeGutter.svelte';
|
|
9
|
+
export { default as RailColumn } from './components/RailColumn.svelte';
|
|
10
|
+
export { default as PaneBase } from './components/PaneBase.svelte';
|
|
11
|
+
export { default as PanelHeaderSkeleton } from './components/PanelHeaderSkeleton.svelte';
|
|
12
|
+
export { default as ChordPaletteOverlay } from './components/ChordPaletteOverlay.svelte';
|
|
13
|
+
export type { PaneCore, WidthClamp, PaneMoveDir } from './layout/floor-math';
|
|
14
|
+
export { resizeOne, scaleProportionally, isUniformRow, movePanelWithin } from './layout/floor-math';
|
|
15
|
+
export { slotAfter, insertAtAnchor, focusedSlot } from './layout/placement';
|
|
16
|
+
export { CHORD_WINDOW_MS, IDLE_CHORD, chordKey, decideChordKeydown, hasAnyPreset, isEditableTarget } from './interaction/chord-machine';
|
|
17
|
+
export type { ChordPrefix, ChordState, ChordKey, ChordAction, ChordTables, ChordOptionRow } from './interaction/chord-machine';
|
|
18
|
+
export { createGutterResize } from './interaction/gutter-resize';
|
|
19
|
+
export type { GutterResizeMachine } from './interaction/gutter-resize';
|
|
20
|
+
export { createActionTable } from './registry/action-registry';
|
|
21
|
+
export type { ActionTable } from './registry/action-registry';
|
|
22
|
+
export { sanitizeWorkspaceProfile, storageKey } from './storage/namespace';
|
|
23
|
+
export { createPrefsStore } from './storage/create-prefs-store';
|
|
24
|
+
export type { PrefsStore, PrefsStoreOptions } from './storage/create-prefs-store';
|
|
25
|
+
export { createThemePrefs } from './theming/theme-prefs.svelte';
|
|
26
|
+
export type { ThemeChoice, ResolvedTheme, ThemePrefs, ThemePrefsOptions } from './theming/theme-prefs.svelte';
|
|
27
|
+
export { themeApplierScript } from './theming/applier';
|
|
28
|
+
export { default as ThemeToggle } from './theming/ThemeToggle.svelte';
|
|
29
|
+
export { createLocalePrefs } from './i18n/locale-prefs.svelte';
|
|
30
|
+
export type { LocalePrefs, LocalePrefsOptions } from './i18n/locale-prefs.svelte';
|
|
31
|
+
export { default as LanguageMenu } from './i18n/LanguageMenu.svelte';
|
|
32
|
+
export { default as SliderWidth, WIDTH_BOUNDS } from './components/SliderWidth.svelte';
|
|
33
|
+
export { default as SliderZoom, ZOOM_BOUNDS } from './components/SliderZoom.svelte';
|
|
34
|
+
export { default as SidebarContainer, SIDEBAR_COLLAPSED_WIDTH } from './components/SidebarContainer.svelte';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deepseek-insight/container-base — the ONLY public entry.
|
|
3
|
+
* Internals are reached exclusively through here (no deep imports).
|
|
4
|
+
*/
|
|
5
|
+
/** Probe export for workspace resolution (Wave 1); real surface lands W2+. */
|
|
6
|
+
export const CONTAINER_BASE_VERSION = '0.1.0';
|
|
7
|
+
// ── components (W4, D6/D8) ──
|
|
8
|
+
export { default as PanelsZoom } from './components/PanelsZoom.svelte';
|
|
9
|
+
export { default as ResizeGutter } from './components/ResizeGutter.svelte';
|
|
10
|
+
export { default as RailColumn } from './components/RailColumn.svelte';
|
|
11
|
+
export { default as PaneBase } from './components/PaneBase.svelte';
|
|
12
|
+
export { default as PanelHeaderSkeleton } from './components/PanelHeaderSkeleton.svelte';
|
|
13
|
+
export { default as ChordPaletteOverlay } from './components/ChordPaletteOverlay.svelte';
|
|
14
|
+
export { resizeOne, scaleProportionally, isUniformRow, movePanelWithin } from './layout/floor-math';
|
|
15
|
+
export { slotAfter, insertAtAnchor, focusedSlot } from './layout/placement';
|
|
16
|
+
// ── interaction (W5, D5) ──
|
|
17
|
+
export { CHORD_WINDOW_MS, IDLE_CHORD, chordKey, decideChordKeydown, hasAnyPreset, isEditableTarget } from './interaction/chord-machine';
|
|
18
|
+
export { createGutterResize } from './interaction/gutter-resize';
|
|
19
|
+
// ── registry (W3, D8) ──
|
|
20
|
+
export { createActionTable } from './registry/action-registry';
|
|
21
|
+
// ── storage (W2, D4) ──
|
|
22
|
+
export { sanitizeWorkspaceProfile, storageKey } from './storage/namespace';
|
|
23
|
+
export { createPrefsStore } from './storage/create-prefs-store';
|
|
24
|
+
// ── theming (R2 W2, ADR-0063 D2) ──
|
|
25
|
+
export { createThemePrefs } from './theming/theme-prefs.svelte';
|
|
26
|
+
export { themeApplierScript } from './theming/applier';
|
|
27
|
+
export { default as ThemeToggle } from './theming/ThemeToggle.svelte';
|
|
28
|
+
// ── i18n (R2 W3, ADR-0063 D3) ──
|
|
29
|
+
export { createLocalePrefs } from './i18n/locale-prefs.svelte';
|
|
30
|
+
export { default as LanguageMenu } from './i18n/LanguageMenu.svelte';
|
|
31
|
+
// ── sliders (R2 W4, ADR-0063 D4) ──
|
|
32
|
+
export { default as SliderWidth, WIDTH_BOUNDS } from './components/SliderWidth.svelte';
|
|
33
|
+
export { default as SliderZoom, ZOOM_BOUNDS } from './components/SliderZoom.svelte';
|
|
34
|
+
// ── sidebar (R2 W5, ADR-0063 D5) ──
|
|
35
|
+
export { default as SidebarContainer, SIDEBAR_COLLAPSED_WIDTH } from './components/SidebarContainer.svelte';
|
|
File without changes
|