@vlian/framework 1.2.54 → 1.2.55

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 (35) hide show
  1. package/dist/analytics.umd.js +1 -1
  2. package/dist/index.umd.js +213 -60
  3. package/dist/index.umd.js.map +1 -1
  4. package/dist/kernel/manager/theme/ThemeManager.cjs +86 -0
  5. package/dist/kernel/manager/theme/ThemeManager.cjs.map +1 -0
  6. package/dist/kernel/manager/theme/ThemeManager.d.ts +16 -0
  7. package/dist/kernel/manager/theme/ThemeManager.js +76 -0
  8. package/dist/kernel/manager/theme/ThemeManager.js.map +1 -0
  9. package/dist/kernel/manager/theme/index.cjs +13 -0
  10. package/dist/kernel/manager/theme/index.cjs.map +1 -0
  11. package/dist/kernel/manager/theme/index.d.ts +1 -0
  12. package/dist/kernel/manager/theme/index.js +3 -0
  13. package/dist/kernel/manager/theme/index.js.map +1 -0
  14. package/dist/kernel/manager/theme/theme.dom.cjs +63 -0
  15. package/dist/kernel/manager/theme/theme.dom.cjs.map +1 -0
  16. package/dist/kernel/manager/theme/theme.dom.d.ts +3 -0
  17. package/dist/kernel/manager/theme/theme.dom.js +45 -0
  18. package/dist/kernel/manager/theme/theme.dom.js.map +1 -0
  19. package/dist/kernel/manager/theme/theme.persistence.cjs +59 -0
  20. package/dist/kernel/manager/theme/theme.persistence.cjs.map +1 -0
  21. package/dist/kernel/manager/theme/theme.persistence.d.ts +5 -0
  22. package/dist/kernel/manager/theme/theme.persistence.js +38 -0
  23. package/dist/kernel/manager/theme/theme.persistence.js.map +1 -0
  24. package/dist/kernel/manager/theme/theme.schema.cjs +124 -0
  25. package/dist/kernel/manager/theme/theme.schema.cjs.map +1 -0
  26. package/dist/kernel/manager/theme/theme.schema.d.ts +7 -0
  27. package/dist/kernel/manager/theme/theme.schema.js +97 -0
  28. package/dist/kernel/manager/theme/theme.schema.js.map +1 -0
  29. package/dist/kernel/manager/themeManager.cjs +2 -95
  30. package/dist/kernel/manager/themeManager.cjs.map +1 -1
  31. package/dist/kernel/manager/themeManager.d.ts +1 -15
  32. package/dist/kernel/manager/themeManager.js +1 -94
  33. package/dist/kernel/manager/themeManager.js.map +1 -1
  34. package/dist/state.umd.js +1 -1
  35. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @vlian/framework v1.2.53
2
+ * @vlian/framework v1.2.54
3
3
  * Secra Framework - 一个现代化的低代码框架
4
4
  * (c) 2026 Secra Framework Contributors
5
5
  * Licensed under Apache-2.0
package/dist/index.umd.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @vlian/framework v1.2.53
2
+ * @vlian/framework v1.2.54
3
3
  * Secra Framework - 一个现代化的低代码框架
4
4
  * (c) 2026 Secra Framework Contributors
5
5
  * Licensed under Apache-2.0
@@ -12437,6 +12437,182 @@
12437
12437
  }
12438
12438
  }
12439
12439
 
12440
+ function canUseDom() {
12441
+ return typeof document !== 'undefined';
12442
+ }
12443
+ function resolveThemeMode(theme) {
12444
+ if (theme.mode !== 'system') {
12445
+ return theme.mode;
12446
+ }
12447
+ if (typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
12448
+ return 'dark';
12449
+ }
12450
+ return 'light';
12451
+ }
12452
+ function applyThemeToDocument(nextTheme, prevTheme) {
12453
+ if (!canUseDom()) {
12454
+ return;
12455
+ }
12456
+ const root = document.documentElement;
12457
+ const nextMode = resolveThemeMode(nextTheme);
12458
+ const prevMode = prevTheme ? resolveThemeMode(prevTheme) : undefined;
12459
+ if (!prevMode || prevMode !== nextMode) {
12460
+ root.classList.remove('light', 'dark');
12461
+ root.classList.add(nextMode);
12462
+ }
12463
+ if (!prevTheme || prevTheme.primaryColor !== nextTheme.primaryColor) {
12464
+ if (nextTheme.primaryColor) {
12465
+ root.style.setProperty('--app-primary-color', nextTheme.primaryColor);
12466
+ } else {
12467
+ root.style.removeProperty('--app-primary-color');
12468
+ }
12469
+ }
12470
+ const prevTokens = prevTheme?.tokens || {};
12471
+ const nextTokens = nextTheme.tokens || {};
12472
+ for (const token of Object.keys(prevTokens)){
12473
+ if (!(token in nextTokens)) {
12474
+ root.style.removeProperty(`--${token}`);
12475
+ }
12476
+ }
12477
+ for (const [token, value] of Object.entries(nextTokens)){
12478
+ if (!prevTheme || prevTokens[token] !== value) {
12479
+ root.style.setProperty(`--${token}`, String(value));
12480
+ }
12481
+ }
12482
+ }
12483
+
12484
+ const VALID_THEME_MODES = new Set([
12485
+ 'light',
12486
+ 'dark',
12487
+ 'system'
12488
+ ]);
12489
+ const TOKEN_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_-]*$/;
12490
+ function isPlainObject(value) {
12491
+ return Object.prototype.toString.call(value) === '[object Object]';
12492
+ }
12493
+ function hasOwnProperty(value, key) {
12494
+ return Object.prototype.hasOwnProperty.call(value, key);
12495
+ }
12496
+ function cloneTheme(theme) {
12497
+ return {
12498
+ ...theme,
12499
+ ...theme.tokens ? {
12500
+ tokens: {
12501
+ ...theme.tokens
12502
+ }
12503
+ } : {}
12504
+ };
12505
+ }
12506
+ function sanitizeTokens(tokens) {
12507
+ if (!isPlainObject(tokens)) {
12508
+ return undefined;
12509
+ }
12510
+ const sanitizedTokens = {};
12511
+ for (const [token, tokenValue] of Object.entries(tokens)){
12512
+ if (!TOKEN_NAME_PATTERN.test(token)) {
12513
+ continue;
12514
+ }
12515
+ if (typeof tokenValue === 'string' || typeof tokenValue === 'number') {
12516
+ sanitizedTokens[token] = tokenValue;
12517
+ }
12518
+ }
12519
+ return Object.keys(sanitizedTokens).length > 0 ? sanitizedTokens : undefined;
12520
+ }
12521
+ function normalizeTheme(value, fallback) {
12522
+ const source = isPlainObject(value) ? value : {};
12523
+ const mode = VALID_THEME_MODES.has(source.mode) ? source.mode : fallback.mode;
12524
+ const primaryColor = typeof source.primaryColor === 'string' && source.primaryColor.trim() ? source.primaryColor : fallback.primaryColor;
12525
+ const fallbackTokens = sanitizeTokens(fallback.tokens);
12526
+ const incomingTokens = sanitizeTokens(source.tokens);
12527
+ return {
12528
+ mode,
12529
+ ...primaryColor ? {
12530
+ primaryColor
12531
+ } : {},
12532
+ ...incomingTokens || fallbackTokens ? {
12533
+ tokens: {
12534
+ ...fallbackTokens || {},
12535
+ ...incomingTokens || {}
12536
+ }
12537
+ } : {}
12538
+ };
12539
+ }
12540
+ function mergeTheme(current, next) {
12541
+ const mergedTheme = {
12542
+ mode: hasOwnProperty(next, 'mode') && VALID_THEME_MODES.has(next.mode) ? next.mode : current.mode,
12543
+ ...hasOwnProperty(next, 'primaryColor') ? typeof next.primaryColor === 'string' && next.primaryColor.trim() ? {
12544
+ primaryColor: next.primaryColor
12545
+ } : {} : current.primaryColor ? {
12546
+ primaryColor: current.primaryColor
12547
+ } : {},
12548
+ ...hasOwnProperty(next, 'tokens') ? sanitizeTokens(next.tokens) ? {
12549
+ tokens: sanitizeTokens(next.tokens)
12550
+ } : {} : current.tokens ? {
12551
+ tokens: {
12552
+ ...current.tokens
12553
+ }
12554
+ } : {}
12555
+ };
12556
+ return normalizeTheme(mergedTheme, DEFAULT_EMPTY_THEME);
12557
+ }
12558
+ const DEFAULT_EMPTY_THEME = {
12559
+ mode: 'light'
12560
+ };
12561
+ function isThemeEqual(left, right) {
12562
+ if (left.mode !== right.mode || left.primaryColor !== right.primaryColor) {
12563
+ return false;
12564
+ }
12565
+ const leftTokens = left.tokens || {};
12566
+ const rightTokens = right.tokens || {};
12567
+ const leftKeys = Object.keys(leftTokens);
12568
+ const rightKeys = Object.keys(rightTokens);
12569
+ if (leftKeys.length !== rightKeys.length) {
12570
+ return false;
12571
+ }
12572
+ for (const key of leftKeys){
12573
+ if (leftTokens[key] !== rightTokens[key]) {
12574
+ return false;
12575
+ }
12576
+ }
12577
+ return true;
12578
+ }
12579
+
12580
+ const DEFAULT_THEME_CACHE_KEY = 'vlian:kernel:theme';
12581
+ function resolvePersistenceContext(persistence) {
12582
+ if (persistence?.enabled !== true) {
12583
+ return null;
12584
+ }
12585
+ return {
12586
+ key: persistence.key || DEFAULT_THEME_CACHE_KEY
12587
+ };
12588
+ }
12589
+ async function loadThemeFromCache(cacheManager, persistence, fallback) {
12590
+ const persistenceContext = resolvePersistenceContext(persistence);
12591
+ const safeFallback = cloneTheme(fallback);
12592
+ if (!persistenceContext) {
12593
+ return safeFallback;
12594
+ }
12595
+ try {
12596
+ const cached = await cacheManager.get(persistenceContext.key, {
12597
+ defaultValue: safeFallback
12598
+ });
12599
+ return normalizeTheme(cached, safeFallback);
12600
+ } catch {
12601
+ return safeFallback;
12602
+ }
12603
+ }
12604
+ async function saveThemeToCache(cacheManager, persistence, theme) {
12605
+ const persistenceContext = resolvePersistenceContext(persistence);
12606
+ if (!persistenceContext) {
12607
+ return;
12608
+ }
12609
+ try {
12610
+ await cacheManager.set(persistenceContext.key, cloneTheme(theme));
12611
+ } catch {
12612
+ // Ignore persistence failures and keep theme updates in memory.
12613
+ }
12614
+ }
12615
+
12440
12616
  function _define_property$2(obj, key, value) {
12441
12617
  if (key in obj) {
12442
12618
  Object.defineProperty(obj, key, {
@@ -12450,49 +12626,20 @@
12450
12626
  }
12451
12627
  return obj;
12452
12628
  }
12453
- function applyThemeToDocument(theme) {
12454
- if (typeof document === 'undefined') {
12455
- return;
12456
- }
12457
- const root = document.documentElement;
12458
- root.classList.remove('light', 'dark');
12459
- const resolvedMode = theme.mode === 'system' ? typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' : theme.mode;
12460
- root.classList.add(resolvedMode);
12461
- if (theme.primaryColor) {
12462
- root.style.setProperty('--app-primary-color', theme.primaryColor);
12463
- }
12464
- if (theme.tokens) {
12465
- Object.entries(theme.tokens).forEach(([token, tokenValue])=>{
12466
- root.style.setProperty(`--${token}`, String(tokenValue));
12467
- });
12468
- }
12469
- }
12470
12629
  class ThemeManager {
12471
12630
  async initialize(context) {
12472
12631
  this.config = context.config.theme;
12473
- this.theme = {
12474
- ...DEFAULT_THEME,
12475
- ...this.config.initial || {}
12476
- };
12477
- this.cache = context.cacheManager;
12478
- const { key = "vlian:kernel:theme" } = this.config.persistence || {};
12479
- const persisted = await this.cache.get(key, {
12480
- defaultValue: this.theme
12481
- });
12482
- if (persisted !== null) {
12483
- this.theme = {
12484
- ...this.theme,
12485
- ...persisted
12486
- };
12487
- } else {
12488
- await this.cache.set(key, persisted);
12489
- }
12632
+ this.cacheManager = context.cacheManager;
12633
+ const initialTheme = normalizeTheme(this.config.initial || {}, DEFAULT_THEME);
12634
+ this.theme = await loadThemeFromCache(this.cacheManager, this.config.persistence, initialTheme);
12490
12635
  applyThemeToDocument(this.theme);
12636
+ this.initialized = true;
12491
12637
  }
12492
12638
  getTheme() {
12493
- return {
12494
- ...this.theme
12495
- };
12639
+ return cloneTheme(this.theme);
12640
+ }
12641
+ getSnapshot() {
12642
+ return this.getTheme();
12496
12643
  }
12497
12644
  subscribe(listener) {
12498
12645
  this.listeners.add(listener);
@@ -12500,34 +12647,40 @@
12500
12647
  this.listeners.delete(listener);
12501
12648
  };
12502
12649
  }
12503
- emit(next, prev) {
12504
- this.listeners.forEach((listener)=>{
12505
- listener({
12506
- ...next
12507
- }, {
12508
- ...prev
12509
- });
12510
- });
12511
- }
12512
12650
  async setTheme(nextTheme) {
12513
- const prev = this.theme;
12514
- this.theme = {
12515
- ...this.theme,
12516
- ...nextTheme
12517
- };
12518
- applyThemeToDocument(this.theme);
12519
- const { key = "vlian:kernel:theme" } = this.config.persistence || {};
12520
- await this.cache?.set(key, this.theme);
12521
- this.emit(this.theme, prev);
12651
+ this.ensureInitialized();
12652
+ const prevTheme = this.theme;
12653
+ const mergedTheme = mergeTheme(prevTheme, nextTheme);
12654
+ if (isThemeEqual(prevTheme, mergedTheme)) {
12655
+ return;
12656
+ }
12657
+ this.theme = mergedTheme;
12658
+ applyThemeToDocument(this.theme, prevTheme);
12659
+ if (this.cacheManager) {
12660
+ await saveThemeToCache(this.cacheManager, this.config.persistence, this.theme);
12661
+ }
12662
+ this.emit(this.theme, prevTheme);
12522
12663
  }
12523
- getSnapshot() {
12524
- return this.getTheme();
12664
+ ensureInitialized() {
12665
+ if (!this.initialized) {
12666
+ throw new Error('ThemeManager must be initialized before use.');
12667
+ }
12668
+ }
12669
+ emit(next, prev) {
12670
+ for (const listener of this.listeners){
12671
+ try {
12672
+ listener(cloneTheme(next), cloneTheme(prev));
12673
+ } catch {
12674
+ // Keep notifying remaining listeners even if one fails.
12675
+ }
12676
+ }
12525
12677
  }
12526
12678
  constructor(){
12527
- _define_property$2(this, "theme", DEFAULT_THEME);
12679
+ _define_property$2(this, "theme", cloneTheme(DEFAULT_THEME));
12528
12680
  _define_property$2(this, "config", DEFAULT_CONFIG.theme);
12529
12681
  _define_property$2(this, "listeners", new Set());
12530
- _define_property$2(this, "cache", undefined);
12682
+ _define_property$2(this, "cacheManager", null);
12683
+ _define_property$2(this, "initialized", false);
12531
12684
  }
12532
12685
  }
12533
12686