@d0nj/dsh-ui-custom 0.0.0-stage → 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.
package/dist/index.js ADDED
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Host half: the customization document and its validation.
3
+ *
4
+ * The plugin does no rendering and holds no state of its own on the Host. It
5
+ * contributes one Config schema, which the profile stores and the browser half
6
+ * reads through its settings namespace, so a choice made in the panel survives a
7
+ * reload and a machine switch without this process tracking anything.
8
+ *
9
+ * `Config` is the contract; `resolve.ts` is the meaning. Keeping them apart means
10
+ * the panel can preview a change with the same resolver the stored document
11
+ * goes through, instead of reimplementing the merge rules in the browser.
12
+ *
13
+ * @module @d0nj/dsh-ui-custom
14
+ */
15
+ import z from '@deepseek-ai/schemastery';
16
+ import { DENSITIES, FONT_SIZE_RANGES, PRESET_IDS, RADIUS_MAX, RADIUS_MIN } from './spec.js';
17
+ /** Cordis plugin name used by Loader diagnostics. */
18
+ export const name = 'ui-custom';
19
+ /** No services are required: this plugin only contributes configuration. */
20
+ export const inject = [];
21
+ /** One token override, with both palette modes required. */
22
+ const TokenModesConfig = z.object({
23
+ light: z.string().description('Value used while the light palette is active.'),
24
+ dark: z.string().description('Value used while the dark palette is active.'),
25
+ });
26
+ /** Font stacks and sizes. */
27
+ const TypographyConfig = z.object({
28
+ textFontFamily: z
29
+ .string()
30
+ .description('Interface and body font stack. Empty keeps the built-in stack.'),
31
+ codeFontFamily: z.string().description('Code font stack. Empty keeps the built-in stack.'),
32
+ terminalFontFamily: z
33
+ .string()
34
+ .description('Sidebar terminal font stack. Empty keeps the built-in stack.'),
35
+ fontSize: z
36
+ .number()
37
+ .step(1)
38
+ .min(FONT_SIZE_RANGES.text.min)
39
+ .max(FONT_SIZE_RANGES.text.max)
40
+ .default(FONT_SIZE_RANGES.text.fallback)
41
+ .description('Conversation text size in px.'),
42
+ codeFontSize: z
43
+ .number()
44
+ .step(1)
45
+ .min(FONT_SIZE_RANGES.code.min)
46
+ .max(FONT_SIZE_RANGES.code.max)
47
+ .default(FONT_SIZE_RANGES.code.fallback)
48
+ .description('Code block size in px.'),
49
+ terminalFontSize: z
50
+ .number()
51
+ .step(1)
52
+ .min(FONT_SIZE_RANGES.terminal.min)
53
+ .max(FONT_SIZE_RANGES.terminal.max)
54
+ .default(FONT_SIZE_RANGES.terminal.fallback)
55
+ .description('Sidebar terminal size in px.'),
56
+ });
57
+ /** Corner radius and spacing density. */
58
+ const GeometryConfig = z.object({
59
+ radius: z
60
+ .number()
61
+ .step(1)
62
+ .min(RADIUS_MIN)
63
+ .max(RADIUS_MAX)
64
+ .default(8)
65
+ .description('Corner radius in px.'),
66
+ density: z
67
+ .union([z.const('compact'), z.const('comfortable'), z.const('spacious')])
68
+ .default('comfortable')
69
+ .description('Spacing density.'),
70
+ });
71
+ /**
72
+ * The plugin's config: one customization document.
73
+ *
74
+ * `basePreset` accepts any preset id or `none`. The resolver reports an unknown
75
+ * id as a warning rather than failing validation, because a stored document
76
+ * naming a preset from a newer version must not stop the plugin mounting.
77
+ */
78
+ // Typed as the schema base so the emitted declaration stays portable: the fully
79
+ // inferred type of a z.object chain names cosmokit through a pnpm store path.
80
+ export const Config = z.object({
81
+ basePreset: z
82
+ .union([z.const('none'), ...PRESET_IDS.map((id) => z.const(id))])
83
+ .default('none')
84
+ .volatile()
85
+ .description(`Preset to build on: one of ${PRESET_IDS.join(', ')}, or none.`),
86
+ tokens: z
87
+ .dict(TokenModesConfig)
88
+ .volatile()
89
+ .description('Per-token overrides applied over the preset, keyed by CSS custom property name.'),
90
+ typography: TypographyConfig.volatile(),
91
+ geometry: GeometryConfig.volatile(),
92
+ });
93
+ /**
94
+ * Project a stored config into a complete customization document.
95
+ *
96
+ * The schema defaults every field, but a config read back from a partially
97
+ * written document may still be missing sections, so this fills them once here
98
+ * rather than at each use site.
99
+ * @param config - the config handed to `apply`.
100
+ * @returns a complete document.
101
+ */
102
+ export function toDocument(input) {
103
+ const config = (input ?? {});
104
+ const read = (value) => {
105
+ if (value !== null && typeof value === 'object' && 'get' in value) {
106
+ return value.get();
107
+ }
108
+ return value;
109
+ };
110
+ const geometry = read(config.geometry);
111
+ const typography = read(config.typography);
112
+ const density = geometry?.density;
113
+ return {
114
+ basePreset: read(config.basePreset) ?? 'none',
115
+ tokens: read(config.tokens) ?? {},
116
+ typography: {
117
+ textFontFamily: typography?.textFontFamily ?? '',
118
+ codeFontFamily: typography?.codeFontFamily ?? '',
119
+ terminalFontFamily: typography?.terminalFontFamily ?? '',
120
+ fontSize: typography?.fontSize ?? FONT_SIZE_RANGES.text.fallback,
121
+ codeFontSize: typography?.codeFontSize ?? FONT_SIZE_RANGES.code.fallback,
122
+ terminalFontSize: typography?.terminalFontSize ?? FONT_SIZE_RANGES.terminal.fallback,
123
+ },
124
+ geometry: {
125
+ radius: geometry?.radius ?? 8,
126
+ density: density !== undefined && DENSITIES.includes(density)
127
+ ? density
128
+ : 'comfortable',
129
+ },
130
+ };
131
+ }
132
+ /**
133
+ * Host plugin body.
134
+ *
135
+ * The Host contributes nothing at runtime: the browser half owns the theme
136
+ * layer and fonts, and the settings service owns persistence.
137
+ */
138
+ export function apply() { }
139
+ export { PRESETS, TOKEN_SPECS, TOKEN_GROUPS, PRESET_IDS } from './spec.js';
140
+ export { resolveCustomization } from './resolve.js';
141
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Resolving a customization document into theme tokens.
3
+ *
4
+ * Three inputs combine, in this order:
5
+ *
6
+ * 1. A **preset**, which is a complete token set.
7
+ * 2. **Per-token overrides**, applied on top of the preset.
8
+ * 3. **Geometry**, expanded into whichever tokens it can express.
9
+ *
10
+ * The result is one `ThemeTokenOverrides` object — token name to `{light, dark}` —
11
+ * which is exactly what `ctx.theme.overrideTokens` accepts. Keeping the merge
12
+ * here, as a pure function, is what lets the panel preview a change and the Host
13
+ * validate one from the same code.
14
+ *
15
+ * @module @d0nj/dsh-ui-custom/resolve
16
+ */
17
+ import { TOKEN_NAMES } from './spec.js';
18
+ import type { Density, FontRole, TokenModes } from './spec.js';
19
+ /** Geometry knobs the panel exposes. */
20
+ export interface Geometry {
21
+ /** Corner radius in CSS pixels, 0–24. */
22
+ readonly radius: number;
23
+ /** Spacing density. */
24
+ readonly density: Density;
25
+ }
26
+ /** Typography settings the plugin applies through the theme service. */
27
+ export interface Typography {
28
+ /** Interface and body font stack; empty keeps the built-in stack. */
29
+ readonly textFontFamily: string;
30
+ /** Code font stack; empty keeps the built-in stack. */
31
+ readonly codeFontFamily: string;
32
+ /** Sidebar terminal font stack; empty keeps the built-in stack. */
33
+ readonly terminalFontFamily: string;
34
+ /** Font sizes per role, each within its own range. */
35
+ readonly fontSize: number;
36
+ readonly codeFontSize: number;
37
+ readonly terminalFontSize: number;
38
+ }
39
+ /** The customization document, as the Host config carries it. */
40
+ export interface CustomizationDocument {
41
+ /** Preset id, or `none` for no base. */
42
+ readonly basePreset: string;
43
+ /** Per-token overrides applied over the preset. */
44
+ readonly tokens: Readonly<Record<string, TokenModes>>;
45
+ /** Typography applied through the theme service. */
46
+ readonly typography: Typography;
47
+ /** Geometry applied as derived tokens. */
48
+ readonly geometry: Geometry;
49
+ }
50
+ /** Result of resolving a document. */
51
+ export interface ResolvedCustomization {
52
+ /** Complete token overrides for `ctx.theme.overrideTokens`. */
53
+ readonly tokens: Readonly<Record<string, TokenModes>>;
54
+ /** Typography to apply, with out-of-range sizes corrected. */
55
+ readonly typography: Typography;
56
+ /** Problems found while resolving, for the panel to show. */
57
+ readonly warnings: readonly string[];
58
+ }
59
+ /**
60
+ * Whether a value is a plausible CSS colour.
61
+ *
62
+ * Colour tokens are rendered straight into CSS custom properties, so an
63
+ * arbitrary string would not be a security hole but would silently produce an
64
+ * unstyled surface. Rejecting it here turns that into a visible warning.
65
+ * @param value - candidate colour value.
66
+ * @returns true when the value looks like a CSS colour.
67
+ */
68
+ export declare function isColor(value: string): boolean;
69
+ /**
70
+ * Clamp a number into a closed range.
71
+ * @param value - candidate value.
72
+ * @param min - lower bound.
73
+ * @param max - upper bound.
74
+ * @returns the clamped integer.
75
+ */
76
+ export declare function clamp(value: number, min: number, max: number): number;
77
+ /**
78
+ * Expand geometry into token values.
79
+ *
80
+ * The documented token set has no radius or spacing tokens, so geometry is
81
+ * expressed as a *shadow layer* of `--d0nj-*` variables the panel's own styles
82
+ * consume, plus any `--dsw-*` tokens that exist. Emitting our own names keeps
83
+ * this additive: a token the harness does not define cannot break a surface,
84
+ * and the panel styles read ours.
85
+ *
86
+ * @param geometry - the requested geometry.
87
+ * @returns derived custom properties with identical light and dark values.
88
+ */
89
+ export declare function geometryTokens(geometry: Geometry): Record<string, TokenModes>;
90
+ /**
91
+ * Correct one role's font size into its accepted range.
92
+ * @param role - font role.
93
+ * @param px - requested size.
94
+ * @returns the in-range size.
95
+ */
96
+ export declare function correctFontSize(role: FontRole, px: number): number;
97
+ /**
98
+ * Whether a string names a known density.
99
+ * @param value - candidate density.
100
+ * @returns true for a known density.
101
+ */
102
+ export declare function isDensity(value: string): value is Density;
103
+ /**
104
+ * @param stack - comma-separated font stack.
105
+ * @returns the concrete family names, in order, without generics.
106
+ */
107
+ export declare function familyNames(stack: string): string[];
108
+ /**
109
+ * Resolve a customization document into theme tokens and typography.
110
+ *
111
+ * Merge order is preset, then explicit overrides, then geometry, so a user who
112
+ * picks a preset and then tweaks one token keeps the tweak, and geometry always
113
+ * wins over a token of the same name (it is the more specific control).
114
+ *
115
+ * @param document - the customization document.
116
+ * @returns the token overrides, corrected typography, and any warnings.
117
+ */
118
+ export declare function resolveCustomization(document: CustomizationDocument): ResolvedCustomization;
119
+ /** Every documented token name, re-exported for the panel's reset action. */
120
+ export { TOKEN_NAMES };
121
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Resolving a customization document into theme tokens.
3
+ *
4
+ * Three inputs combine, in this order:
5
+ *
6
+ * 1. A **preset**, which is a complete token set.
7
+ * 2. **Per-token overrides**, applied on top of the preset.
8
+ * 3. **Geometry**, expanded into whichever tokens it can express.
9
+ *
10
+ * The result is one `ThemeTokenOverrides` object — token name to `{light, dark}` —
11
+ * which is exactly what `ctx.theme.overrideTokens` accepts. Keeping the merge
12
+ * here, as a pure function, is what lets the panel preview a change and the Host
13
+ * validate one from the same code.
14
+ *
15
+ * @module @d0nj/dsh-ui-custom/resolve
16
+ */
17
+ import { DENSITIES, FONT_SIZE_RANGES, RADIUS_MAX, RADIUS_MIN, TOKEN_NAMES, findPreset, tokenSpec, } from './spec.js';
18
+ /** A colour in one of the forms the theme service and browsers accept. */
19
+ const COLOR_PATTERN = /^(#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})|(?:rgb|hsl|oklch|lab|lch|color)\([^)]*\)|[a-z]+)$/iu;
20
+ /** CSS generic families, which always resolve and so never need a local match. */
21
+ const GENERIC_FAMILIES = new Set([
22
+ 'serif', 'sans-serif', 'monospace', 'cursive', 'fantasy', 'system-ui',
23
+ 'ui-serif', 'ui-sans-serif', 'ui-monospace', 'ui-rounded', 'emoji', 'math',
24
+ 'fangsong', '-apple-system', 'blinkmacsystemfont',
25
+ 'inherit', 'initial', 'unset',
26
+ ]);
27
+ /**
28
+ * Whether a value is a plausible CSS colour.
29
+ *
30
+ * Colour tokens are rendered straight into CSS custom properties, so an
31
+ * arbitrary string would not be a security hole but would silently produce an
32
+ * unstyled surface. Rejecting it here turns that into a visible warning.
33
+ * @param value - candidate colour value.
34
+ * @returns true when the value looks like a CSS colour.
35
+ */
36
+ export function isColor(value) {
37
+ return COLOR_PATTERN.test(value.trim());
38
+ }
39
+ /**
40
+ * Clamp a number into a closed range.
41
+ * @param value - candidate value.
42
+ * @param min - lower bound.
43
+ * @param max - upper bound.
44
+ * @returns the clamped integer.
45
+ */
46
+ export function clamp(value, min, max) {
47
+ if (!Number.isFinite(value))
48
+ return min;
49
+ return Math.min(max, Math.max(min, Math.round(value)));
50
+ }
51
+ /**
52
+ * Expand geometry into token values.
53
+ *
54
+ * The documented token set has no radius or spacing tokens, so geometry is
55
+ * expressed as a *shadow layer* of `--d0nj-*` variables the panel's own styles
56
+ * consume, plus any `--dsw-*` tokens that exist. Emitting our own names keeps
57
+ * this additive: a token the harness does not define cannot break a surface,
58
+ * and the panel styles read ours.
59
+ *
60
+ * @param geometry - the requested geometry.
61
+ * @returns derived custom properties with identical light and dark values.
62
+ */
63
+ export function geometryTokens(geometry) {
64
+ const radius = clamp(geometry.radius, RADIUS_MIN, RADIUS_MAX);
65
+ const scale = geometry.density === 'compact' ? 0.8 : geometry.density === 'spacious' ? 1.25 : 1;
66
+ const value = (px) => ({ light: px, dark: px });
67
+ return {
68
+ '--d0nj-radius-sm': value(`${String(Math.round(radius * 0.5))}px`),
69
+ '--d0nj-radius-md': value(`${String(radius)}px`),
70
+ '--d0nj-radius-lg': value(`${String(Math.round(radius * 1.5))}px`),
71
+ '--d0nj-space-1': value(`${String(Math.round(4 * scale))}px`),
72
+ '--d0nj-space-2': value(`${String(Math.round(8 * scale))}px`),
73
+ '--d0nj-space-3': value(`${String(Math.round(12 * scale))}px`),
74
+ '--d0nj-space-4': value(`${String(Math.round(16 * scale))}px`),
75
+ };
76
+ }
77
+ /**
78
+ * Correct one role's font size into its accepted range.
79
+ * @param role - font role.
80
+ * @param px - requested size.
81
+ * @returns the in-range size.
82
+ */
83
+ export function correctFontSize(role, px) {
84
+ const range = FONT_SIZE_RANGES[role];
85
+ return clamp(px, range.min, range.max);
86
+ }
87
+ /**
88
+ * Whether a string names a known density.
89
+ * @param value - candidate density.
90
+ * @returns true for a known density.
91
+ */
92
+ export function isDensity(value) {
93
+ return DENSITIES.includes(value);
94
+ }
95
+ /**
96
+ * @param stack - comma-separated font stack.
97
+ * @returns the concrete family names, in order, without generics.
98
+ */
99
+ export function familyNames(stack) {
100
+ return stack
101
+ .split(',')
102
+ .map((part) => part.trim().replace(/^["']|["']$/gu, '').trim())
103
+ .filter((name) => name !== '' && !GENERIC_FAMILIES.has(name.toLowerCase()));
104
+ }
105
+ /**
106
+ * Resolve a customization document into theme tokens and typography.
107
+ *
108
+ * Merge order is preset, then explicit overrides, then geometry, so a user who
109
+ * picks a preset and then tweaks one token keeps the tweak, and geometry always
110
+ * wins over a token of the same name (it is the more specific control).
111
+ *
112
+ * @param document - the customization document.
113
+ * @returns the token overrides, corrected typography, and any warnings.
114
+ */
115
+ export function resolveCustomization(document) {
116
+ const warnings = [];
117
+ const tokens = {};
118
+ if (document.basePreset !== 'none') {
119
+ const preset = findPreset(document.basePreset);
120
+ if (preset === undefined) {
121
+ warnings.push(`unknown preset "${document.basePreset}"; no base tokens applied`);
122
+ }
123
+ else {
124
+ for (const [name, modes] of Object.entries(preset.tokens))
125
+ tokens[name] = modes;
126
+ }
127
+ }
128
+ for (const [name, modes] of Object.entries(document.tokens)) {
129
+ const spec = tokenSpec(name);
130
+ if (spec === undefined) {
131
+ // A hand-edited document can name a token this build does not document.
132
+ // Skipping it with a warning beats applying a property nothing reads.
133
+ warnings.push(`ignoring undocumented token "${name}"`);
134
+ continue;
135
+ }
136
+ const light = modes.light.trim();
137
+ const dark = modes.dark.trim();
138
+ if (light.length === 0 || dark.length === 0) {
139
+ warnings.push(`token "${name}" needs both a light and a dark value`);
140
+ continue;
141
+ }
142
+ if (spec.kind === 'color' && (!isColor(light) || !isColor(dark))) {
143
+ warnings.push(`token "${name}" is not a valid CSS colour`);
144
+ continue;
145
+ }
146
+ tokens[name] = { light, dark };
147
+ }
148
+ for (const [name, modes] of Object.entries(geometryTokens(document.geometry))) {
149
+ tokens[name] = modes;
150
+ }
151
+ const typography = {
152
+ textFontFamily: document.typography.textFontFamily,
153
+ codeFontFamily: document.typography.codeFontFamily,
154
+ terminalFontFamily: document.typography.terminalFontFamily,
155
+ fontSize: correctFontSize('text', document.typography.fontSize),
156
+ codeFontSize: correctFontSize('code', document.typography.codeFontSize),
157
+ terminalFontSize: correctFontSize('terminal', document.typography.terminalFontSize),
158
+ };
159
+ if (typography.fontSize !== document.typography.fontSize) {
160
+ warnings.push(`text font size corrected to ${String(typography.fontSize)}px (allowed ${String(FONT_SIZE_RANGES.text.min)}–${String(FONT_SIZE_RANGES.text.max)})`);
161
+ }
162
+ return { tokens, typography, warnings };
163
+ }
164
+ /** Every documented token name, re-exported for the panel's reset action. */
165
+ export { TOKEN_NAMES };
166
+ //# sourceMappingURL=resolve.js.map
package/dist/spec.d.ts ADDED
@@ -0,0 +1,101 @@
1
+ /**
2
+ * The token catalog and the shipped presets.
3
+ *
4
+ * Every override must supply **both** palette modes. The theme runtime requires
5
+ * it (`ThemeTokenModes`), because a token defined for only one scheme goes
6
+ * illegible when the user flips to the other — so this module represents that
7
+ * requirement in the type rather than trusting each caller to remember it.
8
+ *
9
+ * @module @d0nj/dsh-ui-custom/spec
10
+ */
11
+ /** One token's value in each palette mode. */
12
+ export interface TokenModes {
13
+ readonly light: string;
14
+ readonly dark: string;
15
+ }
16
+ /** Whether a token is a colour (validated as a colour) or an arbitrary CSS value. */
17
+ export type TokenKind = 'color' | 'length' | 'shadow' | 'font';
18
+ /** One documented token the editor can change. */
19
+ export interface TokenSpec {
20
+ /** CSS custom property name, such as `--dsw-alias-bg-base`. */
21
+ readonly name: string;
22
+ /** Short human label for the editor row. */
23
+ readonly label: string;
24
+ /** Grouping in the editor. */
25
+ readonly group: TokenGroup;
26
+ /** How the value is validated and rendered. */
27
+ readonly kind: TokenKind;
28
+ /** What the token affects, shown as the row hint. */
29
+ readonly description: string;
30
+ }
31
+ /** Editor grouping, ordered as the panel renders them. */
32
+ export type TokenGroup = 'surface' | 'border' | 'text' | 'accent' | 'state';
33
+ /** Groups in display order, with their headings. */
34
+ export declare const TOKEN_GROUPS: readonly {
35
+ readonly id: TokenGroup;
36
+ readonly label: string;
37
+ }[];
38
+ /**
39
+ * The documented alias tokens.
40
+ *
41
+ * These are the tokens the theme Inspect provider reports for this build, so the
42
+ * editor never offers a row that would silently do nothing.
43
+ */
44
+ export declare const TOKEN_SPECS: readonly TokenSpec[];
45
+ /** Every documented token name, for validation. */
46
+ export declare const TOKEN_NAMES: readonly string[];
47
+ /**
48
+ * Look up one token's specification.
49
+ * @param name - CSS custom property name.
50
+ * @returns the specification, or `undefined` when the token is not documented.
51
+ */
52
+ export declare function tokenSpec(name: string): TokenSpec | undefined;
53
+ /** One shipped preset: a complete, legible theme in both modes. */
54
+ export interface ThemePreset {
55
+ /** Stable id, used as the `basePreset` config value. */
56
+ readonly id: string;
57
+ /** Display name. */
58
+ readonly label: string;
59
+ /** One-line description shown on the preset card. */
60
+ readonly description: string;
61
+ /** The base palette this preset reads as. */
62
+ readonly colorScheme: 'light' | 'dark';
63
+ /** Complete token set — every documented token, in both modes. */
64
+ readonly tokens: Readonly<Record<string, TokenModes>>;
65
+ }
66
+ /**
67
+ * Shipped presets.
68
+ *
69
+ * Each is a complete token set rather than a partial overlay, so selecting one
70
+ * gives a predictable result no matter what the generated base palette does.
71
+ * The colours are chosen to keep body text at or above WCAG AA contrast against
72
+ * their own surfaces, which the tests check.
73
+ */
74
+ export declare const PRESETS: readonly ThemePreset[];
75
+ /** Every preset id, for validation. */
76
+ export declare const PRESET_IDS: readonly string[];
77
+ /**
78
+ * Look up one preset by id.
79
+ * @param id - preset id.
80
+ * @returns the preset, or `undefined` for an unknown or `none` id.
81
+ */
82
+ export declare function findPreset(id: string): ThemePreset | undefined;
83
+ /** The role names the theme runtime accepts for typography. */
84
+ export declare const FONT_ROLES: readonly ["text", "code", "terminal"];
85
+ /** One font role. */
86
+ export type FontRole = (typeof FONT_ROLES)[number];
87
+ /** Accepted font-size range per role, mirroring the shipped Appearance rows. */
88
+ export declare const FONT_SIZE_RANGES: Readonly<Record<FontRole, {
89
+ readonly min: number;
90
+ readonly max: number;
91
+ readonly fallback: number;
92
+ }>>;
93
+ /** Density presets, expressed as a spacing multiplier applied to derived tokens. */
94
+ export declare const DENSITIES: readonly ["compact", "comfortable", "spacious"];
95
+ /** One density option. */
96
+ export type Density = (typeof DENSITIES)[number];
97
+ /** Radius bounds in pixels. */
98
+ export declare const RADIUS_MIN = 0;
99
+ /** Upper radius bound in pixels. */
100
+ export declare const RADIUS_MAX = 24;
101
+ //# sourceMappingURL=spec.d.ts.map