decantr 0.9.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/AGENTS.md +868 -0
- package/CHANGELOG.md +255 -0
- package/CLAUDE.md +178 -0
- package/LICENSE +21 -0
- package/README.md +229 -0
- package/cli/art.js +127 -0
- package/cli/commands/a11y.js +61 -0
- package/cli/commands/audit.js +225 -0
- package/cli/commands/build.js +38 -0
- package/cli/commands/dev.js +18 -0
- package/cli/commands/doctor.js +197 -0
- package/cli/commands/figma-sync.js +48 -0
- package/cli/commands/figma-tokens.js +55 -0
- package/cli/commands/generate.js +26 -0
- package/cli/commands/init.js +116 -0
- package/cli/commands/lint.js +209 -0
- package/cli/commands/mcp.js +530 -0
- package/cli/commands/migrate.js +175 -0
- package/cli/commands/test.js +38 -0
- package/cli/commands/validate.js +354 -0
- package/cli/index.js +113 -0
- package/package.json +95 -0
- package/reference/atoms.md +517 -0
- package/reference/behaviors.md +384 -0
- package/reference/build-tooling.md +275 -0
- package/reference/color-guidelines.md +965 -0
- package/reference/component-lifecycle.md +137 -0
- package/reference/compound-spacing.md +95 -0
- package/reference/decantation-process.md +499 -0
- package/reference/dev-server-routes.md +93 -0
- package/reference/form-system.md +253 -0
- package/reference/i18n.md +336 -0
- package/reference/icons.md +576 -0
- package/reference/llm-primer.md +953 -0
- package/reference/plugins.md +252 -0
- package/reference/registry-consumption.md +76 -0
- package/reference/router.md +217 -0
- package/reference/shells.md +116 -0
- package/reference/spatial-guidelines.md +541 -0
- package/reference/ssr.md +234 -0
- package/reference/state-data.md +215 -0
- package/reference/state-patterns.md +166 -0
- package/reference/state.md +194 -0
- package/reference/style-system.md +110 -0
- package/reference/tokens.md +460 -0
- package/src/app.js +19 -0
- package/src/chart/_animate.js +266 -0
- package/src/chart/_base.js +109 -0
- package/src/chart/_data.js +209 -0
- package/src/chart/_format.js +106 -0
- package/src/chart/_interact.js +364 -0
- package/src/chart/_palette.js +105 -0
- package/src/chart/_renderer.js +52 -0
- package/src/chart/_scene.js +262 -0
- package/src/chart/_shared.js +371 -0
- package/src/chart/index.js +637 -0
- package/src/chart/layouts/_layout-base.js +328 -0
- package/src/chart/layouts/cartesian.js +148 -0
- package/src/chart/layouts/hierarchy.js +562 -0
- package/src/chart/layouts/polar.js +101 -0
- package/src/chart/renderers/canvas.js +179 -0
- package/src/chart/renderers/svg.js +256 -0
- package/src/chart/renderers/webgpu.js +715 -0
- package/src/chart/types/_type-base.js +26 -0
- package/src/chart/types/area.js +134 -0
- package/src/chart/types/bar.js +173 -0
- package/src/chart/types/box-plot.js +125 -0
- package/src/chart/types/bubble.js +63 -0
- package/src/chart/types/candlestick.js +115 -0
- package/src/chart/types/chord.js +85 -0
- package/src/chart/types/combination.js +108 -0
- package/src/chart/types/funnel.js +68 -0
- package/src/chart/types/gauge.js +163 -0
- package/src/chart/types/heatmap.js +98 -0
- package/src/chart/types/histogram.js +71 -0
- package/src/chart/types/line.js +111 -0
- package/src/chart/types/org-chart.js +93 -0
- package/src/chart/types/pie.js +81 -0
- package/src/chart/types/radar.js +96 -0
- package/src/chart/types/radial.js +68 -0
- package/src/chart/types/range-area.js +55 -0
- package/src/chart/types/range-bar.js +61 -0
- package/src/chart/types/sankey.js +73 -0
- package/src/chart/types/scatter.js +66 -0
- package/src/chart/types/sparkline.js +81 -0
- package/src/chart/types/sunburst.js +69 -0
- package/src/chart/types/swimlane.js +88 -0
- package/src/chart/types/treemap.js +62 -0
- package/src/chart/types/waterfall.js +100 -0
- package/src/components/_base.js +1658 -0
- package/src/components/_behaviors.js +1140 -0
- package/src/components/_primitives.js +534 -0
- package/src/components/_qr-encoder.js +539 -0
- package/src/components/accordion.js +207 -0
- package/src/components/affix.js +62 -0
- package/src/components/alert-dialog.js +75 -0
- package/src/components/alert.js +47 -0
- package/src/components/aspect-ratio.js +24 -0
- package/src/components/avatar-group.js +55 -0
- package/src/components/avatar.js +38 -0
- package/src/components/back-top.js +75 -0
- package/src/components/badge.js +74 -0
- package/src/components/banner.js +68 -0
- package/src/components/breadcrumb.js +162 -0
- package/src/components/button.js +115 -0
- package/src/components/calendar.js +131 -0
- package/src/components/card.js +192 -0
- package/src/components/carousel.js +98 -0
- package/src/components/cascader.js +261 -0
- package/src/components/checkbox.js +80 -0
- package/src/components/chip.js +81 -0
- package/src/components/code-block.js +82 -0
- package/src/components/collapsible.js +50 -0
- package/src/components/color-palette.js +438 -0
- package/src/components/color-picker.js +314 -0
- package/src/components/combobox.js +181 -0
- package/src/components/command.js +174 -0
- package/src/components/comment.js +206 -0
- package/src/components/context-menu.js +76 -0
- package/src/components/data-table.js +724 -0
- package/src/components/date-picker.js +217 -0
- package/src/components/date-range-picker.js +244 -0
- package/src/components/datetime-picker.js +271 -0
- package/src/components/descriptions.js +68 -0
- package/src/components/drawer.js +179 -0
- package/src/components/dropdown.js +88 -0
- package/src/components/empty.js +41 -0
- package/src/components/float-button.js +90 -0
- package/src/components/form.js +106 -0
- package/src/components/hover-card.js +49 -0
- package/src/components/icon.js +87 -0
- package/src/components/image.js +97 -0
- package/src/components/index.js +117 -0
- package/src/components/input-group.js +75 -0
- package/src/components/input-number.js +155 -0
- package/src/components/input-otp.js +178 -0
- package/src/components/input.js +91 -0
- package/src/components/kbd.js +36 -0
- package/src/components/label.js +25 -0
- package/src/components/list.js +118 -0
- package/src/components/masked-input.js +236 -0
- package/src/components/mentions.js +165 -0
- package/src/components/menu.js +259 -0
- package/src/components/message.js +80 -0
- package/src/components/modal.js +147 -0
- package/src/components/navigation-menu.js +166 -0
- package/src/components/notification.js +84 -0
- package/src/components/pagination.js +104 -0
- package/src/components/placeholder.js +132 -0
- package/src/components/popconfirm.js +70 -0
- package/src/components/popover.js +58 -0
- package/src/components/progress.js +61 -0
- package/src/components/qrcode.js +251 -0
- package/src/components/radiogroup.js +120 -0
- package/src/components/range-slider.js +176 -0
- package/src/components/rate.js +186 -0
- package/src/components/resizable.js +83 -0
- package/src/components/result.js +57 -0
- package/src/components/scroll-area.js +43 -0
- package/src/components/segmented.js +97 -0
- package/src/components/select.js +165 -0
- package/src/components/separator.js +31 -0
- package/src/components/shell.js +407 -0
- package/src/components/skeleton.js +39 -0
- package/src/components/slider.js +141 -0
- package/src/components/sortable-list.js +176 -0
- package/src/components/space.js +42 -0
- package/src/components/spinner.js +112 -0
- package/src/components/splitter.js +147 -0
- package/src/components/statistic.js +136 -0
- package/src/components/steps.js +99 -0
- package/src/components/switch.js +95 -0
- package/src/components/table.js +44 -0
- package/src/components/tabs.js +216 -0
- package/src/components/tag.js +115 -0
- package/src/components/textarea.js +82 -0
- package/src/components/time-picker.js +153 -0
- package/src/components/time-range-picker.js +170 -0
- package/src/components/timeline.js +226 -0
- package/src/components/toast.js +71 -0
- package/src/components/toggle.js +213 -0
- package/src/components/tooltip.js +57 -0
- package/src/components/tour.js +159 -0
- package/src/components/transfer.js +163 -0
- package/src/components/tree-select.js +274 -0
- package/src/components/tree.js +141 -0
- package/src/components/typography.js +136 -0
- package/src/components/upload.js +118 -0
- package/src/components/visually-hidden.js +20 -0
- package/src/components/watermark.js +124 -0
- package/src/core/index.js +539 -0
- package/src/core/lifecycle.js +69 -0
- package/src/css/atoms.js +651 -0
- package/src/css/components.js +940 -0
- package/src/css/derive.js +1296 -0
- package/src/css/index.js +265 -0
- package/src/css/runtime.js +268 -0
- package/src/css/styles/addons/bioluminescent.js +93 -0
- package/src/css/styles/addons/clay.js +70 -0
- package/src/css/styles/addons/clean.js +57 -0
- package/src/css/styles/addons/command-center.js +143 -0
- package/src/css/styles/addons/dopamine.js +83 -0
- package/src/css/styles/addons/editorial.js +80 -0
- package/src/css/styles/addons/glassmorphism.js +99 -0
- package/src/css/styles/addons/liquid-glass.js +105 -0
- package/src/css/styles/addons/prismatic.js +100 -0
- package/src/css/styles/addons/retro.js +63 -0
- package/src/css/styles/auradecantism.js +96 -0
- package/src/css/theme-registry.js +444 -0
- package/src/data/entity.js +281 -0
- package/src/data/index.js +13 -0
- package/src/data/persist.js +225 -0
- package/src/data/query.js +839 -0
- package/src/data/realtime.js +299 -0
- package/src/data/url.js +177 -0
- package/src/data/worker.js +134 -0
- package/src/explorer/archetypes.js +243 -0
- package/src/explorer/atoms.js +228 -0
- package/src/explorer/charts.js +497 -0
- package/src/explorer/components.js +129 -0
- package/src/explorer/foundations.js +949 -0
- package/src/explorer/icons.js +178 -0
- package/src/explorer/patterns.js +247 -0
- package/src/explorer/recipes.js +194 -0
- package/src/explorer/shared/pattern-examples.js +1337 -0
- package/src/explorer/shared/showcase-renderer.js +958 -0
- package/src/explorer/shared/spec-table.js +41 -0
- package/src/explorer/shared/usage-links.js +87 -0
- package/src/explorer/shell-config.js +10 -0
- package/src/explorer/shells.js +551 -0
- package/src/explorer/styles.js +161 -0
- package/src/explorer/tokens.js +262 -0
- package/src/explorer/tools.js +525 -0
- package/src/form/index.js +804 -0
- package/src/i18n/index.js +251 -0
- package/src/icons/essential.js +479 -0
- package/src/icons/index.js +53 -0
- package/src/plugins/index.js +282 -0
- package/src/registry/archetypes/content-site.json +71 -0
- package/src/registry/archetypes/docs-explorer.json +23 -0
- package/src/registry/archetypes/ecommerce.json +104 -0
- package/src/registry/archetypes/financial-dashboard.json +77 -0
- package/src/registry/archetypes/index.json +41 -0
- package/src/registry/archetypes/portfolio.json +82 -0
- package/src/registry/archetypes/recipe-community.json +159 -0
- package/src/registry/archetypes/saas-dashboard.json +86 -0
- package/src/registry/architect/cross-cutting.json +45 -0
- package/src/registry/architect/domains/ecommerce.json +294 -0
- package/src/registry/architect/domains/financial-services.json +302 -0
- package/src/registry/architect/index.json +26 -0
- package/src/registry/architect/traits.json +379 -0
- package/src/registry/atoms.json +16 -0
- package/src/registry/chart-showcase.json +160 -0
- package/src/registry/chart.json +136 -0
- package/src/registry/components.json +8616 -0
- package/src/registry/core.json +216 -0
- package/src/registry/css.json +319 -0
- package/src/registry/data.json +135 -0
- package/src/registry/foundations.json +11 -0
- package/src/registry/icons.json +463 -0
- package/src/registry/index.json +101 -0
- package/src/registry/patterns/activity-feed.json +37 -0
- package/src/registry/patterns/article-content.json +27 -0
- package/src/registry/patterns/auth-form.json +37 -0
- package/src/registry/patterns/author-card.json +20 -0
- package/src/registry/patterns/card-grid.json +127 -0
- package/src/registry/patterns/category-nav.json +26 -0
- package/src/registry/patterns/chart-grid.json +36 -0
- package/src/registry/patterns/chat-interface.json +37 -0
- package/src/registry/patterns/checklist-card.json +55 -0
- package/src/registry/patterns/comparison-panel.json +27 -0
- package/src/registry/patterns/component-showcase.json +24 -0
- package/src/registry/patterns/contact-form.json +31 -0
- package/src/registry/patterns/cta-section.json +20 -0
- package/src/registry/patterns/data-table.json +37 -0
- package/src/registry/patterns/detail-header.json +83 -0
- package/src/registry/patterns/detail-panel.json +27 -0
- package/src/registry/patterns/explorer-shell.json +22 -0
- package/src/registry/patterns/filter-bar.json +33 -0
- package/src/registry/patterns/filter-sidebar.json +27 -0
- package/src/registry/patterns/form-sections.json +110 -0
- package/src/registry/patterns/goal-tracker.json +27 -0
- package/src/registry/patterns/hero.json +107 -0
- package/src/registry/patterns/index.json +47 -0
- package/src/registry/patterns/kpi-grid.json +36 -0
- package/src/registry/patterns/media-gallery.json +20 -0
- package/src/registry/patterns/order-history.json +20 -0
- package/src/registry/patterns/pagination.json +19 -0
- package/src/registry/patterns/photo-to-recipe.json +36 -0
- package/src/registry/patterns/pipeline-tracker.json +28 -0
- package/src/registry/patterns/post-list.json +27 -0
- package/src/registry/patterns/pricing-table.json +32 -0
- package/src/registry/patterns/scorecard.json +28 -0
- package/src/registry/patterns/search-bar.json +20 -0
- package/src/registry/patterns/specimen-grid.json +19 -0
- package/src/registry/patterns/stat-card.json +55 -0
- package/src/registry/patterns/stats-bar.json +55 -0
- package/src/registry/patterns/steps-card.json +55 -0
- package/src/registry/patterns/table-of-contents.json +19 -0
- package/src/registry/patterns/testimonials.json +21 -0
- package/src/registry/patterns/timeline.json +27 -0
- package/src/registry/patterns/token-inspector.json +21 -0
- package/src/registry/patterns/wizard.json +27 -0
- package/src/registry/recipe-auradecantism.json +69 -0
- package/src/registry/recipe-clean.json +65 -0
- package/src/registry/recipe-command-center.json +78 -0
- package/src/registry/router.json +73 -0
- package/src/registry/schema/README.md +197 -0
- package/src/registry/skeletons.json +259 -0
- package/src/registry/state.json +137 -0
- package/src/registry/tokens.json +40 -0
- package/src/router/hash.js +17 -0
- package/src/router/history.js +18 -0
- package/src/router/index.js +598 -0
- package/src/ssr/index.js +922 -0
- package/src/state/arrays.js +181 -0
- package/src/state/devtools.js +647 -0
- package/src/state/index.js +498 -0
- package/src/state/middleware.js +288 -0
- package/src/state/scheduler.js +206 -0
- package/src/state/store.js +300 -0
- package/src/tags/index.js +19 -0
- package/src/tannins/auth.js +396 -0
- package/src/test/dom.js +352 -0
- package/src/test/index.js +62 -0
- package/src/test/state.js +306 -0
- package/tools/a11y-audit.js +487 -0
- package/tools/analyzer.js +315 -0
- package/tools/audit.js +706 -0
- package/tools/builder.js +1422 -0
- package/tools/css-extract.js +188 -0
- package/tools/dev-server.js +316 -0
- package/tools/dts-gen.js +1260 -0
- package/tools/figma-components.js +329 -0
- package/tools/figma-patterns.js +516 -0
- package/tools/figma-plugin/code.js +453 -0
- package/tools/figma-plugin/manifest.json +14 -0
- package/tools/figma-plugin/ui.html +268 -0
- package/tools/figma-render.js +293 -0
- package/tools/figma-tokens.js +712 -0
- package/tools/figma-upload.js +318 -0
- package/tools/generate.js +738 -0
- package/tools/icons.js +133 -0
- package/tools/init-templates.js +265 -0
- package/tools/install-hooks.sh +5 -0
- package/tools/migrations/0.5.0.js +53 -0
- package/tools/migrations/0.6.0.js +95 -0
- package/tools/minify.js +170 -0
- package/tools/pre-commit +4 -0
- package/tools/registry.js +662 -0
- package/tools/reset-playground.js +61 -0
- package/tools/starter-templates/content-site/app.js +49 -0
- package/tools/starter-templates/content-site/essence.js +19 -0
- package/tools/starter-templates/content-site/pages.js +31 -0
- package/tools/starter-templates/ecommerce/app.js +50 -0
- package/tools/starter-templates/ecommerce/essence.js +19 -0
- package/tools/starter-templates/ecommerce/pages.js +31 -0
- package/tools/starter-templates/landing-page/app.js +38 -0
- package/tools/starter-templates/landing-page/essence.js +18 -0
- package/tools/starter-templates/landing-page/pages.js +21 -0
- package/tools/starter-templates/portfolio/app.js +45 -0
- package/tools/starter-templates/portfolio/essence.js +19 -0
- package/tools/starter-templates/portfolio/pages.js +33 -0
- package/tools/starter-templates/saas-dashboard/app.js +70 -0
- package/tools/starter-templates/saas-dashboard/essence.js +19 -0
- package/tools/starter-templates/saas-dashboard/pages.js +31 -0
- package/tools/verify-pack.js +203 -0
- package/types/chart.d.ts +77 -0
- package/types/components.d.ts +587 -0
- package/types/core.d.ts +89 -0
- package/types/css.d.ts +149 -0
- package/types/data.d.ts +238 -0
- package/types/form.d.ts +164 -0
- package/types/i18n.d.ts +51 -0
- package/types/icons.d.ts +27 -0
- package/types/index.d.ts +13 -0
- package/types/router.d.ts +116 -0
- package/types/ssr.d.ts +102 -0
- package/types/state.d.ts +83 -0
- package/types/tags.d.ts +62 -0
- package/types/tannins.d.ts +63 -0
- package/types/test.d.ts +48 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decantr Design System v2 — Style & Mode Registry
|
|
3
|
+
* Manages style (visual personality) and mode (light/dark/auto) independently.
|
|
4
|
+
* Replaces the old theme system with seed-derived, mode-aware styling.
|
|
5
|
+
*
|
|
6
|
+
* @module theme-registry
|
|
7
|
+
*/
|
|
8
|
+
import { createSignal, untrack } from '../state/index.js';
|
|
9
|
+
import { derive, densityCSS, getShapeTokens } from './derive.js';
|
|
10
|
+
import { componentCSS } from './components.js';
|
|
11
|
+
import { auradecantism } from './styles/auradecantism.js';
|
|
12
|
+
|
|
13
|
+
// ============================================================
|
|
14
|
+
// State
|
|
15
|
+
// ============================================================
|
|
16
|
+
|
|
17
|
+
/** @type {Map<string, Object>} Registered styles */
|
|
18
|
+
const styles = new Map();
|
|
19
|
+
|
|
20
|
+
const [_getStyleId, _setStyleId] = createSignal('auradecantism');
|
|
21
|
+
const [_getMode, _setMode] = createSignal('dark');
|
|
22
|
+
const [_getResolvedMode, _setResolvedMode] = createSignal('dark');
|
|
23
|
+
const [_getAnimations, _setAnimations] = createSignal(true);
|
|
24
|
+
const [_getShape, _setShape] = createSignal(null);
|
|
25
|
+
const [_getColorblind, _setColorblind] = createSignal('off');
|
|
26
|
+
|
|
27
|
+
const SHAPES = ['sharp', 'rounded', 'pill'];
|
|
28
|
+
|
|
29
|
+
/** @type {HTMLStyleElement|null} */
|
|
30
|
+
let styleEl = null;
|
|
31
|
+
let densityEl = null;
|
|
32
|
+
let animEl = null;
|
|
33
|
+
let mediaQuery = null;
|
|
34
|
+
let mediaHandler = null;
|
|
35
|
+
|
|
36
|
+
/** @type {Set<Function>} Mode change listeners */
|
|
37
|
+
const modeListeners = new Set();
|
|
38
|
+
|
|
39
|
+
const ANIM_OFF_CSS = '*{animation-duration:0.01ms !important;animation-iteration-count:1 !important;transition-duration:0.01ms !important}';
|
|
40
|
+
|
|
41
|
+
// ============================================================
|
|
42
|
+
// Built-in Styles
|
|
43
|
+
// ============================================================
|
|
44
|
+
|
|
45
|
+
const builtins = [auradecantism];
|
|
46
|
+
for (const s of builtins) styles.set(s.id, s);
|
|
47
|
+
|
|
48
|
+
// ============================================================
|
|
49
|
+
// Legacy Theme ID Mapping
|
|
50
|
+
// ============================================================
|
|
51
|
+
|
|
52
|
+
const LEGACY_THEME_MAP = {
|
|
53
|
+
'light': { style: 'clean', mode: 'light' },
|
|
54
|
+
'dark': { style: 'clean', mode: 'dark' },
|
|
55
|
+
'retro': { style: 'retro', mode: 'auto' },
|
|
56
|
+
'hot-lava': { style: 'clean', mode: 'dark' },
|
|
57
|
+
'stormy-ai': { style: 'clean', mode: 'dark' },
|
|
58
|
+
'ai': { style: 'clean', mode: 'dark' },
|
|
59
|
+
'nature': { style: 'clean', mode: 'light' },
|
|
60
|
+
'pastel': { style: 'clean', mode: 'light' },
|
|
61
|
+
'spice': { style: 'clean', mode: 'dark' },
|
|
62
|
+
'mono': { style: 'clean', mode: 'dark' },
|
|
63
|
+
'lava': { style: 'clean', mode: 'dark' },
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// ============================================================
|
|
67
|
+
// DOM Injection
|
|
68
|
+
// ============================================================
|
|
69
|
+
|
|
70
|
+
function getStyleElement() {
|
|
71
|
+
if (!styleEl && typeof document !== 'undefined') {
|
|
72
|
+
styleEl = document.createElement('style');
|
|
73
|
+
styleEl.setAttribute('data-decantr-style', '');
|
|
74
|
+
document.head.appendChild(styleEl);
|
|
75
|
+
}
|
|
76
|
+
return styleEl;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Apply token map to :root */
|
|
80
|
+
function applyTokens(tokens) {
|
|
81
|
+
if (typeof document === 'undefined') return;
|
|
82
|
+
const el = document.documentElement;
|
|
83
|
+
for (const [key, value] of Object.entries(tokens)) {
|
|
84
|
+
if (el.style.setProperty) el.style.setProperty(key, value);
|
|
85
|
+
else el.style[key] = value;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Clear all custom properties from :root */
|
|
90
|
+
function clearTokens(tokens) {
|
|
91
|
+
if (typeof document === 'undefined') return;
|
|
92
|
+
const el = document.documentElement;
|
|
93
|
+
for (const key of Object.keys(tokens)) {
|
|
94
|
+
el.style.removeProperty(key);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ============================================================
|
|
99
|
+
// Core: Apply Style + Mode
|
|
100
|
+
// ============================================================
|
|
101
|
+
|
|
102
|
+
/** Derive and apply all tokens for the current style + mode.
|
|
103
|
+
* Wrapped in untrack() so internal signal reads don't leak
|
|
104
|
+
* subscriptions into any calling effect (prevents infinite loops
|
|
105
|
+
* when setStyle/setMode/etc. are called from createEffect). */
|
|
106
|
+
function applyCurrentState() {
|
|
107
|
+
untrack(() => {
|
|
108
|
+
const styleId = _getStyleId();
|
|
109
|
+
const style = styles.get(styleId);
|
|
110
|
+
if (!style) return;
|
|
111
|
+
|
|
112
|
+
const resolvedMode = _getResolvedMode();
|
|
113
|
+
|
|
114
|
+
// Derive full token set
|
|
115
|
+
const modeOverrides = style.overrides?.[resolvedMode] || {};
|
|
116
|
+
const cbMode = _getColorblind();
|
|
117
|
+
const tokens = derive(
|
|
118
|
+
style.seed,
|
|
119
|
+
style.personality,
|
|
120
|
+
resolvedMode,
|
|
121
|
+
style.typography,
|
|
122
|
+
modeOverrides,
|
|
123
|
+
cbMode !== 'off' ? { colorblind: cbMode } : undefined,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Apply all tokens to :root
|
|
127
|
+
applyTokens(tokens);
|
|
128
|
+
|
|
129
|
+
// Inject density classes into d.base layer (once)
|
|
130
|
+
if (!densityEl && typeof document !== 'undefined') {
|
|
131
|
+
densityEl = document.createElement('style');
|
|
132
|
+
densityEl.setAttribute('data-decantr-density', '');
|
|
133
|
+
densityEl.textContent = `@layer d.base{${densityCSS()}}`;
|
|
134
|
+
document.head.appendChild(densityEl);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Apply shape override if set (re-apply after derive to win specificity)
|
|
138
|
+
applyShape();
|
|
139
|
+
|
|
140
|
+
// Inject shared + style-specific component CSS into d.theme layer
|
|
141
|
+
const styleCSS = style.components || '';
|
|
142
|
+
const el = getStyleElement();
|
|
143
|
+
if (el) el.textContent = `@layer d.theme{${componentCSS}${styleCSS}}`;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ============================================================
|
|
148
|
+
// Mode System
|
|
149
|
+
// ============================================================
|
|
150
|
+
|
|
151
|
+
/** Resolve 'auto' mode to 'light' or 'dark' based on system preference */
|
|
152
|
+
function resolveAutoMode() {
|
|
153
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return 'light';
|
|
154
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Set up or tear down the prefers-color-scheme listener */
|
|
158
|
+
function updateMediaListener() {
|
|
159
|
+
// Clean up existing listener
|
|
160
|
+
if (mediaQuery && mediaHandler) {
|
|
161
|
+
mediaQuery.removeEventListener('change', mediaHandler);
|
|
162
|
+
mediaQuery = null;
|
|
163
|
+
mediaHandler = null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (_getMode() === 'auto' && typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
|
|
167
|
+
mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
168
|
+
mediaHandler = () => {
|
|
169
|
+
const newMode = resolveAutoMode();
|
|
170
|
+
const prev = _getResolvedMode();
|
|
171
|
+
if (newMode !== prev) {
|
|
172
|
+
_setResolvedMode(newMode);
|
|
173
|
+
applyCurrentState();
|
|
174
|
+
for (const fn of modeListeners) fn(newMode);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
mediaQuery.addEventListener('change', mediaHandler);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ============================================================
|
|
182
|
+
// Public API: Style
|
|
183
|
+
// ============================================================
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Set the visual style (personality).
|
|
187
|
+
* @param {string} id - Style ID ('clean', 'retro', or custom registered style)
|
|
188
|
+
*/
|
|
189
|
+
export function setStyle(id) {
|
|
190
|
+
if (!styles.has(id)) throw new Error(`[decantr] Unknown style: "${id}". Available: ${[...styles.keys()].join(', ')}`);
|
|
191
|
+
_setStyleId(id);
|
|
192
|
+
applyCurrentState();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** @returns {() => string} Signal getter for current style ID */
|
|
196
|
+
export function getStyle() { return _getStyleId; }
|
|
197
|
+
|
|
198
|
+
/** @returns {{ id: string, name: string }[]} List of registered styles */
|
|
199
|
+
export function getStyleList() {
|
|
200
|
+
return [...styles.values()].map(s => ({ id: s.id, name: s.name }));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Register a custom style.
|
|
205
|
+
* @param {Object} style - Style definition { id, name, seed, personality, typography?, overrides?, components? }
|
|
206
|
+
*/
|
|
207
|
+
export function registerStyle(style) {
|
|
208
|
+
if (!style.id || !style.name) throw new Error('[decantr] Style must have id and name');
|
|
209
|
+
if (!style.seed) throw new Error('[decantr] Style must have seed colors');
|
|
210
|
+
if (!style.personality) style.personality = {};
|
|
211
|
+
if (!style.overrides) style.overrides = {};
|
|
212
|
+
styles.set(style.id, style);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Merge a Map of plugin-provided styles into the registry.
|
|
217
|
+
* Used by the plugin system to wire addon styles without individual registerStyle calls.
|
|
218
|
+
* @param {Map<string, Object>} pluginStyles - Map of style id -> style definition
|
|
219
|
+
*/
|
|
220
|
+
export function mergePluginStyles(pluginStyles) {
|
|
221
|
+
for (const [id, style] of pluginStyles) {
|
|
222
|
+
if (!style.id || !style.name) throw new Error(`[decantr] Plugin style must have id and name (got id="${id}")`);
|
|
223
|
+
if (!style.seed) throw new Error(`[decantr] Plugin style "${id}" must have seed colors`);
|
|
224
|
+
if (!style.personality) style.personality = {};
|
|
225
|
+
if (!style.overrides) style.overrides = {};
|
|
226
|
+
styles.set(id, style);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ============================================================
|
|
231
|
+
// Public API: Mode
|
|
232
|
+
// ============================================================
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Set color mode.
|
|
236
|
+
* @param {'light'|'dark'|'auto'} mode
|
|
237
|
+
*/
|
|
238
|
+
export function setMode(mode) {
|
|
239
|
+
if (mode !== 'light' && mode !== 'dark' && mode !== 'auto') {
|
|
240
|
+
throw new Error(`[decantr] Invalid mode: "${mode}". Use 'light', 'dark', or 'auto'.`);
|
|
241
|
+
}
|
|
242
|
+
_setMode(mode);
|
|
243
|
+
const resolved = mode === 'auto' ? resolveAutoMode() : mode;
|
|
244
|
+
const prev = untrack(() => _getResolvedMode());
|
|
245
|
+
_setResolvedMode(resolved);
|
|
246
|
+
untrack(() => updateMediaListener());
|
|
247
|
+
applyCurrentState();
|
|
248
|
+
if (resolved !== prev) {
|
|
249
|
+
for (const fn of modeListeners) fn(resolved);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** @returns {() => string} Signal getter for current mode setting ('light'|'dark'|'auto') */
|
|
254
|
+
export function getMode() { return _getMode; }
|
|
255
|
+
|
|
256
|
+
/** @returns {'light'|'dark'} Resolved mode (never 'auto') */
|
|
257
|
+
export function getResolvedMode() { return _getResolvedMode(); }
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Register a callback for mode changes (fires when resolved mode changes).
|
|
261
|
+
* @param {Function} fn - Callback receiving the new resolved mode ('light'|'dark')
|
|
262
|
+
* @returns {Function} Unsubscribe function
|
|
263
|
+
*/
|
|
264
|
+
export function onModeChange(fn) {
|
|
265
|
+
modeListeners.add(fn);
|
|
266
|
+
return () => modeListeners.delete(fn);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ============================================================
|
|
270
|
+
// Public API: Theme (convenience / backward compat)
|
|
271
|
+
// ============================================================
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Set theme — convenience API combining setStyle + setMode.
|
|
275
|
+
* @param {string} id - Style ID or legacy theme ID
|
|
276
|
+
* @param {'light'|'dark'|'auto'} [mode='auto'] - Color mode
|
|
277
|
+
*/
|
|
278
|
+
export function setTheme(id, mode) {
|
|
279
|
+
// Handle legacy theme IDs
|
|
280
|
+
const legacy = LEGACY_THEME_MAP[id];
|
|
281
|
+
if (legacy && !styles.has(id)) {
|
|
282
|
+
const targetStyle = legacy.style;
|
|
283
|
+
const targetMode = mode || legacy.mode;
|
|
284
|
+
if (_getStyleId() !== targetStyle) _setStyleId(targetStyle);
|
|
285
|
+
setMode(targetMode);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// New-style: id is a style, mode defaults to 'auto'
|
|
290
|
+
if (styles.has(id)) {
|
|
291
|
+
_setStyleId(id);
|
|
292
|
+
setMode(mode || 'auto');
|
|
293
|
+
} else {
|
|
294
|
+
throw new Error(`[decantr] Unknown theme/style: "${id}". Available: ${[...styles.keys()].join(', ')}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** @returns {() => string} Signal getter for current style ID (backward compat alias) */
|
|
299
|
+
export function getTheme() { return _getStyleId; }
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Get metadata about current style + mode.
|
|
303
|
+
* @returns {{ isDark: boolean, style: string, mode: string, resolvedMode: string }}
|
|
304
|
+
*/
|
|
305
|
+
export function getThemeMeta() {
|
|
306
|
+
return {
|
|
307
|
+
isDark: _getResolvedMode() === 'dark',
|
|
308
|
+
style: _getStyleId(),
|
|
309
|
+
mode: _getMode(),
|
|
310
|
+
resolvedMode: _getResolvedMode(),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** @returns {{ id: string, name: string }[]} List of registered styles */
|
|
315
|
+
export function getThemeList() { return getStyleList(); }
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Legacy registerTheme — wraps registerStyle.
|
|
319
|
+
* @param {Object} theme - Old theme object or new style object
|
|
320
|
+
*/
|
|
321
|
+
export function registerTheme(theme) {
|
|
322
|
+
// If it has seed + personality, it's a new-style definition
|
|
323
|
+
if (theme.seed) {
|
|
324
|
+
registerStyle(theme);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
// Legacy theme object — wrap it as a minimal style
|
|
328
|
+
console.warn(`[decantr] registerTheme() with old format is deprecated. Use registerStyle() with seed + personality.`);
|
|
329
|
+
registerStyle({
|
|
330
|
+
id: theme.id,
|
|
331
|
+
name: theme.name || theme.id,
|
|
332
|
+
seed: {},
|
|
333
|
+
personality: {},
|
|
334
|
+
components: theme.global || '',
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ============================================================
|
|
339
|
+
// Public API: Animations
|
|
340
|
+
// ============================================================
|
|
341
|
+
|
|
342
|
+
export function setAnimations(enabled) {
|
|
343
|
+
_setAnimations(!!enabled);
|
|
344
|
+
if (typeof document === 'undefined') return;
|
|
345
|
+
if (!enabled) {
|
|
346
|
+
if (!animEl) {
|
|
347
|
+
animEl = document.createElement('style');
|
|
348
|
+
animEl.setAttribute('data-decantr-anim', '');
|
|
349
|
+
document.head.appendChild(animEl);
|
|
350
|
+
}
|
|
351
|
+
animEl.textContent = ANIM_OFF_CSS;
|
|
352
|
+
} else if (animEl) {
|
|
353
|
+
animEl.textContent = '';
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function getAnimations() { return _getAnimations; }
|
|
358
|
+
|
|
359
|
+
// ============================================================
|
|
360
|
+
// Public API: Shape
|
|
361
|
+
// ============================================================
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Set the shape (border-radius preset), independent of style.
|
|
365
|
+
* @param {string|null} shape - 'sharp', 'rounded', 'pill', or null for style default
|
|
366
|
+
*/
|
|
367
|
+
export function setShape(shape) {
|
|
368
|
+
if (shape !== null && !SHAPES.includes(shape)) {
|
|
369
|
+
throw new Error(`[decantr] Invalid shape: "${shape}". Use 'sharp', 'rounded', 'pill', or null for style default.`);
|
|
370
|
+
}
|
|
371
|
+
_setShape(shape);
|
|
372
|
+
// Re-derive to restore style defaults, then applyShape() overrides if shape is set
|
|
373
|
+
applyCurrentState();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** @returns {() => string|null} Signal getter for current shape (null = style default) */
|
|
377
|
+
export function getShape() { return _getShape; }
|
|
378
|
+
|
|
379
|
+
/** @returns {{ id: string, label: string }[]} List of available shape presets */
|
|
380
|
+
export function getShapeList() {
|
|
381
|
+
return SHAPES.map(s => ({ id: s, label: s.charAt(0).toUpperCase() + s.slice(1) }));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** Apply shape tokens to :root inline styles (overrides derive output) */
|
|
385
|
+
function applyShape() {
|
|
386
|
+
if (typeof document === 'undefined') return;
|
|
387
|
+
const shape = _getShape();
|
|
388
|
+
if (!shape) return; // null = style default, already set by derive()
|
|
389
|
+
const tokens = getShapeTokens(shape);
|
|
390
|
+
if (tokens) applyTokens(tokens);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// ============================================================
|
|
394
|
+
// Public API: Colorblind Mode
|
|
395
|
+
// ============================================================
|
|
396
|
+
|
|
397
|
+
const CB_MODES = ['off', 'protanopia', 'deuteranopia', 'tritanopia'];
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Set colorblind mode. Transforms seed colors and chart palettes for CVD safety.
|
|
401
|
+
* Orthogonal to style × mode × shape — all four axes are independent.
|
|
402
|
+
* @param {'off'|'protanopia'|'deuteranopia'|'tritanopia'} type
|
|
403
|
+
*/
|
|
404
|
+
export function setColorblindMode(type) {
|
|
405
|
+
if (!CB_MODES.includes(type)) {
|
|
406
|
+
throw new Error(`[decantr] Invalid colorblind mode: "${type}". Use ${CB_MODES.map(m => `'${m}'`).join(', ')}.`);
|
|
407
|
+
}
|
|
408
|
+
_setColorblind(type);
|
|
409
|
+
applyCurrentState();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** @returns {() => string} Signal getter for current colorblind mode */
|
|
413
|
+
export function getColorblindMode() { return _getColorblind; }
|
|
414
|
+
|
|
415
|
+
// ============================================================
|
|
416
|
+
// Public API: CSS Extraction & Reset
|
|
417
|
+
// ============================================================
|
|
418
|
+
|
|
419
|
+
/** @returns {string} Active theme layer CSS */
|
|
420
|
+
export function getActiveCSS() {
|
|
421
|
+
const style = styles.get(_getStyleId());
|
|
422
|
+
return style ? `@layer d.theme{${componentCSS}${style.components || ''}}` : '';
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export function resetStyles() {
|
|
426
|
+
if (styleEl && styleEl.parentNode) styleEl.parentNode.removeChild(styleEl);
|
|
427
|
+
styleEl = null;
|
|
428
|
+
if (densityEl && densityEl.parentNode) densityEl.parentNode.removeChild(densityEl);
|
|
429
|
+
densityEl = null;
|
|
430
|
+
if (animEl && animEl.parentNode) animEl.parentNode.removeChild(animEl);
|
|
431
|
+
animEl = null;
|
|
432
|
+
if (mediaQuery && mediaHandler) {
|
|
433
|
+
mediaQuery.removeEventListener('change', mediaHandler);
|
|
434
|
+
mediaQuery = null;
|
|
435
|
+
mediaHandler = null;
|
|
436
|
+
}
|
|
437
|
+
modeListeners.clear();
|
|
438
|
+
_setStyleId('auradecantism');
|
|
439
|
+
_setMode('dark');
|
|
440
|
+
_setResolvedMode('dark');
|
|
441
|
+
_setAnimations(true);
|
|
442
|
+
_setShape(null);
|
|
443
|
+
_setColorblind('off');
|
|
444
|
+
}
|