mnfst 0.5.121 → 0.5.123

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.
@@ -30,6 +30,54 @@
30
30
 
31
31
  function initializeColorpickerPlugin() {
32
32
 
33
+ // ---- Shared global: ManifestUI (universal `_ui` resolver) ----
34
+ // Defined guarded so the color picker localizes its default-menu chrome
35
+ // whether or not the date picker / charts (which also define it) are loaded.
36
+ // Kept byte-identical across those copies.
37
+ //
38
+ // `_ui` is a reserved, self-identifying key: any loaded data source may carry a
39
+ // top-level `_ui` object, namespaced per element (`_ui.colorpicker`, `_ui.datepicker`).
40
+ // No manifest flag — overrides piggyback on the normal local-data/localization model
41
+ // and can be colocated with author content. resolve() deep-merges every loaded
42
+ // source's `_ui[component]` onto the caller's English fallbacks.
43
+ if (!window.ManifestUI) {
44
+ window.ManifestUI = {
45
+ // Names of data sources that have loaded (current locale). Enumerates loaded
46
+ // sources only — never force-loads others just to scan them for `_ui`.
47
+ _loadedSourceNames() {
48
+ try {
49
+ const store = window.ManifestDataStore && window.ManifestDataStore.rawDataStore;
50
+ if (store && typeof store.keys === 'function') return [...store.keys()];
51
+ } catch (_) { }
52
+ return [];
53
+ },
54
+ // Deep-merge every loaded source's `_ui[component]` onto `fallbacks`.
55
+ // Read inside the caller's Alpine effect so $x/$locale stay reactive.
56
+ resolve(component, fallbacks) {
57
+ const merged = JSON.parse(JSON.stringify(fallbacks || {}));
58
+ try {
59
+ if (!window.Alpine || typeof Alpine.evaluate !== 'function') return merged;
60
+ try { Alpine.evaluate(document.body, '$locale && $locale.current'); } catch (_) { } // dep → re-resolve on locale switch
61
+ for (const name of this._loadedSourceNames()) {
62
+ let ui;
63
+ try { ui = Alpine.evaluate(document.body, `$x['${name}'] && $x['${name}']._ui && $x['${name}']._ui['${component}']`); } catch (_) { ui = null; }
64
+ if (ui && typeof ui === 'object' && !Array.isArray(ui)) this._deepOverlay(merged, ui);
65
+ }
66
+ } catch (_) { }
67
+ return merged;
68
+ },
69
+ _deepOverlay(target, src) {
70
+ for (const k of Object.keys(src)) {
71
+ if (k.startsWith('$') || k === 'contentType' || k === 'valueOf' || k === 'toString') continue;
72
+ const v = src[k];
73
+ if (typeof v === 'function') continue;
74
+ if (v && typeof v === 'object' && !Array.isArray(v)) { if (!target[k] || typeof target[k] !== 'object') target[k] = {}; this._deepOverlay(target[k], v); }
75
+ else if (v !== undefined && v !== null && v !== '') target[k] = v;
76
+ }
77
+ }
78
+ };
79
+ }
80
+
33
81
  // ---- Parse any CSS color string via the browser ----
34
82
 
35
83
  const parseCtx = document.createElement('canvas').getContext('2d', { willReadFrequently: true });
@@ -578,26 +626,30 @@ function initializeColorpickerPlugin() {
578
626
  // Reading via Alpine.evaluate inside the surrounding render effect registers
579
627
  // reactive deps on the referenced data — locale switches and content updates
580
628
  // re-trigger the render automatically.
629
+ // Resolve a single `$x.`/`$locale`/`${…}` reference string against document.body
630
+ // scope. Plain strings pass through unchanged; returns the original on any failure.
631
+ // Reading via Alpine.evaluate inside a render effect registers the locale dep.
632
+ function _resolveRefString(val) {
633
+ if (typeof val !== 'string' || val.length === 0) return val;
634
+ const trimmed = val.trim();
635
+ const isBareRef = trimmed.startsWith('$x.') || trimmed.startsWith('$locale')
636
+ || trimmed.startsWith('$x[') || trimmed.startsWith('$locale[');
637
+ const hasInterp = /\$\{[^}]+\}/.test(trimmed);
638
+ if (!isBareRef && !hasInterp) return val;
639
+ try {
640
+ if (window.Alpine?.evaluate) {
641
+ const expr = isBareRef && !hasInterp ? trimmed : '`' + trimmed + '`';
642
+ const out = Alpine.evaluate(document.body, expr);
643
+ if (out == null) return val;
644
+ return typeof out === 'string' ? out : String(out);
645
+ }
646
+ } catch {}
647
+ return val;
648
+ }
649
+
581
650
  function _resolveLibraryRefs(groups) {
582
651
  if (!Array.isArray(groups) || groups.length === 0) return groups;
583
- const ctx = document.body;
584
- const resolve = (val) => {
585
- if (typeof val !== 'string' || val.length === 0) return val;
586
- const trimmed = val.trim();
587
- const isBareRef = trimmed.startsWith('$x.') || trimmed.startsWith('$locale')
588
- || trimmed.startsWith('$x[') || trimmed.startsWith('$locale[');
589
- const hasInterp = /\$\{[^}]+\}/.test(trimmed);
590
- if (!isBareRef && !hasInterp) return val;
591
- try {
592
- if (window.Alpine?.evaluate) {
593
- const expr = isBareRef && !hasInterp ? trimmed : '`' + trimmed + '`';
594
- const out = Alpine.evaluate(ctx, expr);
595
- if (out == null) return val;
596
- return typeof out === 'string' ? out : String(out);
597
- }
598
- } catch {}
599
- return val;
600
- };
652
+ const resolve = _resolveRefString;
601
653
  for (const g of groups) {
602
654
  if (g && typeof g.name === 'string') g.name = resolve(g.name);
603
655
  if (Array.isArray(g?.palettes)) {
@@ -863,7 +915,7 @@ function initializeColorpickerPlugin() {
863
915
  <li x-colorpicker.set-color-space="hsl">HSL</li>
864
916
  <li x-colorpicker.set-color-space="oklch">OKLCH</li>
865
917
  <hr>
866
- <li x-colorpicker.grab-color><span x-icon class="color-icon-grab"></span><span>Grab color</span></li>
918
+ <li x-colorpicker.grab-color><span x-icon class="color-icon-grab"></span><span data-cp-ui-text="grabColor">Grab color</span></li>
867
919
  </menu>
868
920
  <input type="text" x-colorpicker.set-color-value class="ghost sm" onClick="this.select()" />
869
921
  <input type="number" x-colorpicker.set-alpha-value class="ghost sm no-spinner" min="0" max="100" step="1" onClick="this.select()" />
@@ -883,26 +935,26 @@ function initializeColorpickerPlugin() {
883
935
  <button class="ghost sm" x-dropdown="gradient-layer-options" aria-label="Layer options"><span :class="'gradient-layer-icon-' + layerType" x-icon></span></button>
884
936
  <menu popover id="gradient-layer-options">
885
937
  <li x-colorpicker.set-gradient-type="linear">
886
- <span x-icon class="gradient-layer-icon-linear"></span><span>Linear Gradient</span>
938
+ <span x-icon class="gradient-layer-icon-linear"></span><span data-cp-ui-text="gradientTypes.linear">Linear Gradient</span>
887
939
  </li>
888
940
  <li x-colorpicker.set-gradient-type="radial">
889
- <span x-icon class="gradient-layer-icon-radial"></span><span>Radial Gradient</span>
941
+ <span x-icon class="gradient-layer-icon-radial"></span><span data-cp-ui-text="gradientTypes.radial">Radial Gradient</span>
890
942
  </li>
891
943
  <li x-colorpicker.set-gradient-type="conic">
892
- <span x-icon class="gradient-layer-icon-conic"></span><span>Conic Gradient</span>
944
+ <span x-icon class="gradient-layer-icon-conic"></span><span data-cp-ui-text="gradientTypes.conic">Conic Gradient</span>
893
945
  </li>
894
946
  <hr>
895
- <li x-colorpicker.rotate-layer>Rotate 90°</li>
896
- <li x-colorpicker.flip-layer>Flip Direction</li>
947
+ <li x-colorpicker.rotate-layer data-cp-ui-text="layerActions.rotate">Rotate 90°</li>
948
+ <li x-colorpicker.flip-layer data-cp-ui-text="layerActions.flip">Flip Direction</li>
897
949
  <hr>
898
- <li x-colorpicker.add-layer-above>Add Above</li>
899
- <li x-colorpicker.add-layer-below>Add Below</li>
900
- <li x-colorpicker.duplicate-layer>Duplicate</li>
950
+ <li x-colorpicker.add-layer-above data-cp-ui-text="layerActions.addAbove">Add Above</li>
951
+ <li x-colorpicker.add-layer-below data-cp-ui-text="layerActions.addBelow">Add Below</li>
952
+ <li x-colorpicker.duplicate-layer data-cp-ui-text="layerActions.duplicate">Duplicate</li>
901
953
  <hr>
902
- <li x-colorpicker.move-layer-up :disabled="layerIndex === 0">Move Up</li>
903
- <li x-colorpicker.move-layer-down :disabled="layerIndex === layerCount - 1">Move Down</li>
954
+ <li x-colorpicker.move-layer-up :disabled="layerIndex === 0" data-cp-ui-text="layerActions.moveUp">Move Up</li>
955
+ <li x-colorpicker.move-layer-down :disabled="layerIndex === layerCount - 1" data-cp-ui-text="layerActions.moveDown">Move Down</li>
904
956
  <hr>
905
- <li x-colorpicker.remove-layer :disabled="layerCount === 1" class="negative">Remove</li>
957
+ <li x-colorpicker.remove-layer :disabled="layerCount === 1" class="negative" data-cp-ui-text="layerActions.remove">Remove</li>
906
958
  </menu>
907
959
 
908
960
  <div class="layer-angle-wrapper">
@@ -912,8 +964,8 @@ function initializeColorpickerPlugin() {
912
964
 
913
965
  <div x-colorpicker.layer-stops-bar class="gradient-layer" x-dropdown.context="stop-context-menu"></div>
914
966
  <menu popover id="stop-context-menu" class="stop-context-menu">
915
- <li x-colorpicker.duplicate-stop>Duplicate</li>
916
- <li x-colorpicker.delete-stop class="negative">Delete</li>
967
+ <li x-colorpicker.duplicate-stop data-cp-ui-text="stopActions.duplicate">Duplicate</li>
968
+ <li x-colorpicker.delete-stop class="negative" data-cp-ui-text="stopActions.delete">Delete</li>
917
969
  <hr>
918
970
  <div x-colorpicker.library></div>
919
971
  </menu>
@@ -945,9 +997,9 @@ function initializeColorpickerPlugin() {
945
997
  <div x-data="{ tab: 'solid' }">
946
998
 
947
999
  <div class="tabs-wrapper" data-cp-tabs>
948
- <button data-cp-tab="solid" class="ghost sm" :class="tab === 'solid' && 'selected'" @click="tab = 'solid'" x-tooltip="Solid" aria-label="Solid"><span x-icon class="color-icon-solid"></span></button>
949
- <button data-cp-tab="gradient" class="ghost sm" :class="tab === 'gradient' && 'selected'" @click="tab = 'gradient'" x-tooltip="Gradient" aria-label="Gradient"><span x-icon class="color-icon-gradient"></span></button>
950
- <button data-cp-tab="library" class="ghost sm" :class="tab === 'library' && 'selected'" @click="tab = 'library'" x-tooltip="Library" aria-label="Library"><span x-icon class="color-icon-library"></span></button>
1000
+ <button data-cp-tab="solid" data-cp-ui-label="tabs.solid" class="ghost sm" :class="tab === 'solid' && 'selected'" @click="tab = 'solid'" x-tooltip="Solid" aria-label="Solid"><span x-icon class="color-icon-solid"></span></button>
1001
+ <button data-cp-tab="gradient" data-cp-ui-label="tabs.gradient" class="ghost sm" :class="tab === 'gradient' && 'selected'" @click="tab = 'gradient'" x-tooltip="Gradient" aria-label="Gradient"><span x-icon class="color-icon-gradient"></span></button>
1002
+ <button data-cp-tab="library" data-cp-ui-label="tabs.library" class="ghost sm" :class="tab === 'library' && 'selected'" @click="tab = 'library'" x-tooltip="Library" aria-label="Library"><span x-icon class="color-icon-library"></span></button>
951
1003
  </div>
952
1004
 
953
1005
  <div data-cp-panel="solid" x-colorpicker.solid x-show="tab === 'solid'"></div>
@@ -1000,7 +1052,7 @@ function initializeColorpickerPlugin() {
1000
1052
  <div>
1001
1053
  <button x-colorpicker.apply-color x-dropdown.context="recent-menu" :style="\`background: \${swatch.value}\`" x-tooltip="\`\${swatch.name || swatch.value}\`"></button>
1002
1054
  <menu popover id="recent-menu">
1003
- <li x-colorpicker.remove-recent>Remove</li>
1055
+ <li x-colorpicker.remove-recent data-cp-ui-text="recent.remove">Remove</li>
1004
1056
  </menu>
1005
1057
  </div>
1006
1058
  </template>
@@ -1014,6 +1066,66 @@ function initializeColorpickerPlugin() {
1014
1066
  `;
1015
1067
  const _defaultLibraryLayoutTpl = parseOnce(DEFAULT_LIBRARY_LAYOUT_HTML);
1016
1068
 
1069
+ // ---- Default-menu text (localizable via `_ui`) ----
1070
+ //
1071
+ // English defaults for the chrome baked into the default menu. A project
1072
+ // overrides any subset by flagging a data source with a `colorpicker` key and
1073
+ // giving it a top-level `_ui` object (see colorpickers.md → Text & Localization).
1074
+ // window.ManifestUI.resolve deep-overlays those overrides onto these fallbacks;
1075
+ // values may be plain strings or `$x`/`$locale` references. The English text also
1076
+ // lives verbatim in the templates above, so the menu still reads correctly if the
1077
+ // resolver never runs (e.g. ManifestUI absent). Key shape matches the docs exactly.
1078
+ const UI_FALLBACK = {
1079
+ tabs: { solid: 'Solid', gradient: 'Gradient', library: 'Library' },
1080
+ grabColor: 'Grab color',
1081
+ gradientTypes: { linear: 'Linear Gradient', radial: 'Radial Gradient', conic: 'Conic Gradient' },
1082
+ layerActions: {
1083
+ rotate: 'Rotate 90°', flip: 'Flip Direction',
1084
+ addAbove: 'Add Above', addBelow: 'Add Below', duplicate: 'Duplicate',
1085
+ moveUp: 'Move Up', moveDown: 'Move Down', remove: 'Remove'
1086
+ },
1087
+ stopActions: { duplicate: 'Duplicate', delete: 'Delete' },
1088
+ recent: { remove: 'Remove' }
1089
+ };
1090
+
1091
+ function _uiByPath(obj, path) {
1092
+ let o = obj;
1093
+ for (const k of path.split('.')) { if (o == null) return undefined; o = o[k]; }
1094
+ return o;
1095
+ }
1096
+
1097
+ // Deep-resolve `$x`/`$locale`/`${…}` reference strings in a resolved `_ui`
1098
+ // object (same treatment as library swatch names). Mutates in place; reading
1099
+ // via _resolveRefString inside a render effect registers the locale dep.
1100
+ function _deepResolveUiRefs(ui) {
1101
+ if (!ui || typeof ui !== 'object') return ui;
1102
+ for (const k of Object.keys(ui)) {
1103
+ const v = ui[k];
1104
+ if (v && typeof v === 'object') _deepResolveUiRefs(v);
1105
+ else if (typeof v === 'string') ui[k] = _resolveRefString(v);
1106
+ }
1107
+ return ui;
1108
+ }
1109
+
1110
+ // Stamp a resolved `_ui` object onto a freshly-cloned default-template subtree.
1111
+ // Text nodes carry data-cp-ui-text="dotted.path"; icon-only buttons whose only
1112
+ // label is a tooltip/aria carry data-cp-ui-label="dotted.path". Dev-authored
1113
+ // custom templates have no markers, so this is a no-op for them.
1114
+ function _applyDefaultUiText(scopeEl, ui) {
1115
+ if (!scopeEl || !ui) return;
1116
+ scopeEl.querySelectorAll('[data-cp-ui-text]').forEach(el => {
1117
+ const v = _uiByPath(ui, el.getAttribute('data-cp-ui-text'));
1118
+ if (typeof v === 'string' && v) el.textContent = v;
1119
+ });
1120
+ scopeEl.querySelectorAll('[data-cp-ui-label]').forEach(el => {
1121
+ const v = _uiByPath(ui, el.getAttribute('data-cp-ui-label'));
1122
+ if (typeof v === 'string' && v) {
1123
+ el.setAttribute('aria-label', v);
1124
+ if (el.hasAttribute('x-tooltip')) el.setAttribute('x-tooltip', v);
1125
+ }
1126
+ });
1127
+ }
1128
+
1017
1129
  // ---- Per-picker state ----
1018
1130
 
1019
1131
  let pickerCounter = 0;
@@ -1674,17 +1786,28 @@ function initializeColorpickerPlugin() {
1674
1786
  catch { return recentKey + '#' + names.join('|') + '::[unserializable]'; }
1675
1787
  };
1676
1788
  const readSources = () => {
1677
- // Discover names by scanning manifest.data for entries with a
1678
- // `colorpicker` key. The key may hold a path string or a locale
1679
- // map the data plugin handles loading either shape; we just
1680
- // need the set of names whose loaded data should feed the library.
1789
+ // Discover palette sources two ways:
1790
+ // 1. Preferred: a top-level manifest `"colorpicker"` pointer
1791
+ // naming one or more NORMAL data sources ("colors" or
1792
+ // ["colors","brand"]). The sources register as plain
1793
+ // (optionally localized) data — loading, locale reloads,
1794
+ // and `_ui` piggybacking all work untouched.
1795
+ // 2. Legacy: a `colorpicker` key flagged on the data entry
1796
+ // itself. Kept for back-compat; note the flag-wrapped
1797
+ // locale-map shape is NOT loadable by the data plugin.
1798
+ const names = [];
1799
+ try {
1800
+ const ptr = Alpine.evaluate(evalCtx, '$x && $x.manifest && $x.manifest.colorpicker');
1801
+ if (typeof ptr === 'string' && ptr) names.push(ptr);
1802
+ else if (Array.isArray(ptr)) ptr.forEach(n => { if (typeof n === 'string' && n) names.push(n); });
1803
+ } catch {}
1681
1804
  let dataMap = null;
1682
1805
  try { dataMap = Alpine.evaluate(evalCtx, '$x && $x.manifest && $x.manifest.data'); } catch {}
1683
- const names = [];
1684
1806
  if (dataMap && typeof dataMap === 'object') {
1685
1807
  for (const name of Object.keys(dataMap)) {
1686
1808
  if (name.startsWith('$') || name.startsWith('_')) continue;
1687
1809
  if (name === 'valueOf' || name === 'toString' || name === 'contentType') continue;
1810
+ if (names.includes(name)) continue;
1688
1811
  const entry = dataMap[name];
1689
1812
  if (entry && typeof entry === 'object' && !Array.isArray(entry)
1690
1813
  && entry.colorpicker !== undefined) {
@@ -1711,6 +1834,9 @@ function initializeColorpickerPlugin() {
1711
1834
  this._libraryDiscoveredKey = key;
1712
1835
  this._libraryDiscoveredData = collected;
1713
1836
  this._doRenderLibrary();
1837
+ // A `_ui` source loading late (poller-driven) won't change
1838
+ // $locale, so re-localize the rest of the picker here too.
1839
+ this._applyUi(this.rootEl);
1714
1840
  }
1715
1841
  return ready;
1716
1842
  };
@@ -1720,6 +1846,10 @@ function initializeColorpickerPlugin() {
1720
1846
  try { Alpine.evaluate(evalCtx, '$locale && $locale.current'); } catch {}
1721
1847
  try { Alpine.evaluate(evalCtx, '$x && $x.manifest && $x.manifest._loadedFrom'); } catch {}
1722
1848
  runDiscovery();
1849
+ // Re-localize the whole picker (tabs + any mounted menus) on
1850
+ // locale switch — this effect re-runs when $locale.current changes,
1851
+ // and _applyUi re-resolves $x/$locale references inside it.
1852
+ this._applyUi(this.rootEl);
1723
1853
  // Kick the poller only until data is ready — it re-checks every 150ms
1724
1854
  // but skips actual re-render when the content is unchanged.
1725
1855
  if (!this._libraryPollTimer) {
@@ -1780,6 +1910,9 @@ function initializeColorpickerPlugin() {
1780
1910
  for (const g of groups) container.appendChild(renderDefaultGroup(g));
1781
1911
  }
1782
1912
  if (window.Alpine?.initTree) Alpine.initTree(container);
1913
+ // Localize default chrome inside freshly-cloned swatches (Recent's
1914
+ // "Remove" context-menu item).
1915
+ this._applyUi(container);
1783
1916
  // Newly-rendered swatches need active + gradient-disable state applied.
1784
1917
  this._updateActiveSwatches();
1785
1918
  },
@@ -1825,6 +1958,19 @@ function initializeColorpickerPlugin() {
1825
1958
  return this.layersContainer.querySelectorAll(':scope > [data-cp-layer-clone]')[li] || null;
1826
1959
  },
1827
1960
 
1961
+ // Resolve the default-menu chrome text (English fallbacks overlaid with
1962
+ // any project `_ui` overrides) and stamp it onto a cloned default-template
1963
+ // subtree. Called at each clone site and re-run inside the library locale
1964
+ // effect, so switching locale (or a late data load) re-renders the labels.
1965
+ // Custom dev templates carry no markers → no-op.
1966
+ _applyUi(scopeEl) {
1967
+ if (!scopeEl || !window.ManifestUI) return;
1968
+ let ui;
1969
+ try { ui = _deepResolveUiRefs(window.ManifestUI.resolve('colorpicker', UI_FALLBACK)); }
1970
+ catch { ui = UI_FALLBACK; }
1971
+ _applyDefaultUiText(scopeEl, ui);
1972
+ },
1973
+
1828
1974
  // ---- Layer rendering ----
1829
1975
 
1830
1976
  renderLayers() {
@@ -1890,6 +2036,10 @@ function initializeColorpickerPlugin() {
1890
2036
 
1891
2037
  this.layersContainer.appendChild(root);
1892
2038
 
2039
+ // Localize this clone's default menu chrome (gradient types, layer
2040
+ // actions, stop actions). No-op for dev-supplied layer templates.
2041
+ this._applyUi(root);
2042
+
1893
2043
  // Let Alpine/Manifest process the clone (x-dropdown, x-icon, and nested x-colorpicker directives)
1894
2044
  if (window.Alpine?.initTree) {
1895
2045
  requestAnimationFrame(() => Alpine.initTree(root));
@@ -1975,6 +2125,9 @@ function initializeColorpickerPlugin() {
1975
2125
  uniquifyDropdownIdsIn(frag, this.pickerUid + '-solid');
1976
2126
  containerEl.appendChild(frag);
1977
2127
 
2128
+ // Localize the default solid panel's chrome (the "Grab color" item).
2129
+ this._applyUi(containerEl);
2130
+
1978
2131
  const refs = this._collectSolidRefs(containerEl);
1979
2132
  this._wireSolidControls(refs);
1980
2133
 
@@ -2259,6 +2412,10 @@ function initializeColorpickerPlugin() {
2259
2412
  }
2260
2413
  }
2261
2414
 
2415
+ // Localize the tab chrome before initTree so x-tooltip caches the
2416
+ // resolved label rather than the English default.
2417
+ this._applyUi(this.rootEl);
2418
+
2262
2419
  // Alpine.initTree fires all x-* directives in the newly injected content,
2263
2420
  // which will register solidTemplate/layerTemplate/solidInstances/layersContainer
2264
2421
  // against THIS state (since ancestor traversal finds this.rootEl).