mnfst 0.5.163 → 0.5.164
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/lib/manifest.appwrite.auth.js +107 -199
- package/lib/manifest.appwrite.data.js +1 -3
- package/lib/manifest.charts.js +34 -79
- package/lib/manifest.chat.js +515 -0
- package/lib/manifest.code.js +110 -340
- package/lib/manifest.colorpicker.js +252 -551
- package/lib/manifest.combobox.js +73 -91
- package/lib/manifest.components.js +34 -95
- package/lib/manifest.css +0 -132
- package/lib/manifest.data.js +189 -562
- package/lib/manifest.datepicker.js +43 -128
- package/lib/manifest.dropdowns.js +18 -39
- package/lib/manifest.export.js +29 -147
- package/lib/manifest.icons.js +9 -24
- package/lib/manifest.integrity.json +23 -23
- package/lib/manifest.js +62 -126
- package/lib/manifest.localization.js +67 -200
- package/lib/manifest.markdown.js +65 -174
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.payments.js +40 -139
- package/lib/manifest.router.js +24 -73
- package/lib/manifest.status.js +20 -56
- package/lib/manifest.svg.js +12 -30
- package/lib/manifest.tooltips.js +28 -65
- package/lib/manifest.utilities.js +40 -107
- package/lib/manifest.virtual.js +15 -53
- package/package.json +1 -1
- package/lib/manifest.appwrite.presence.js +0 -1650
|
@@ -1,49 +1,15 @@
|
|
|
1
|
-
/* Manifest Color Picker
|
|
2
|
-
|
|
3
|
-
Directive: x-colorpicker[.<modifier>[="value"]]
|
|
4
|
-
|
|
5
|
-
Root (no modifier): declares a picker container.
|
|
6
|
-
Children (with modifier): declare hooks. Three families:
|
|
7
|
-
|
|
8
|
-
• Layout (template OR instance — determined by host element tag)
|
|
9
|
-
.solid canvas + hue/alpha sliders + value + format
|
|
10
|
-
.gradient full gradient panel (layers container + layer template)
|
|
11
|
-
.layer-options one gradient layer's UI
|
|
12
|
-
.gradient-layers container holding cloned .layer-options instances
|
|
13
|
-
.layer-stops-bar stops bar (inside a layer)
|
|
14
|
-
|
|
15
|
-
• Actions (click triggers behavior)
|
|
16
|
-
.add-layer .apply-color .grab-color
|
|
17
|
-
.duplicate-layer .remove-layer
|
|
18
|
-
.flip-layer .rotate-layer
|
|
19
|
-
.duplicate-stop .delete-stop
|
|
20
|
-
.set-gradient-type="linear|radial|conic"
|
|
21
|
-
|
|
22
|
-
• Inputs (state-bound control)
|
|
23
|
-
.set-canvas .set-hue .set-alpha
|
|
24
|
-
.set-alpha-value .set-color-space .set-color-value
|
|
25
|
-
.set-angle .set-gradient-value
|
|
26
|
-
|
|
27
|
-
Magic: $colorpicker — returns the nearest ancestor picker's API
|
|
28
|
-
for programmatic control (addLayer, setHue, layers, activeStop, ...).
|
|
29
|
-
*/
|
|
1
|
+
/* Manifest Color Picker — x-colorpicker directive + $colorpicker magic */
|
|
30
2
|
|
|
31
3
|
function initializeColorpickerPlugin() {
|
|
32
4
|
|
|
33
5
|
// ---- Shared global: ManifestUI (universal `_ui` resolver) ----
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
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.
|
|
6
|
+
// Guarded so the picker can localize its default-menu chrome whether or not
|
|
7
|
+
// the date picker / charts (which also define it) are loaded; byte-identical
|
|
8
|
+
// across those copies. Any loaded source may carry a top-level `_ui` object
|
|
9
|
+
// namespaced per element; resolve() deep-merges `_ui[component]` onto fallbacks.
|
|
43
10
|
if (!window.ManifestUI) {
|
|
44
11
|
window.ManifestUI = {
|
|
45
|
-
// Names of data sources
|
|
46
|
-
// sources only — never force-loads others just to scan them for `_ui`.
|
|
12
|
+
// Names of loaded data sources (current locale); never force-loads others.
|
|
47
13
|
_loadedSourceNames() {
|
|
48
14
|
try {
|
|
49
15
|
const store = window.ManifestDataStore && window.ManifestDataStore.rawDataStore;
|
|
@@ -152,10 +118,8 @@ function initializeColorpickerPlugin() {
|
|
|
152
118
|
|
|
153
119
|
function roundA(a) { const v=Math.round(a*100); return v===100?'1':(v/100).toString(); }
|
|
154
120
|
|
|
155
|
-
// Canonical dedupe key for a color
|
|
156
|
-
// with
|
|
157
|
-
// an `active` class. Parses any CSS color via parseCssColor → 8-digit hex. Gradients
|
|
158
|
-
// are compared as their normalized CSS string.
|
|
121
|
+
// Canonical dedupe key for a color/gradient — 8-digit hex for colors, the CSS
|
|
122
|
+
// string for gradients. Tags library swatches with data-cp-key for active-toggle.
|
|
159
123
|
function _swatchKeyOf(value) {
|
|
160
124
|
if (typeof value !== 'string') return null;
|
|
161
125
|
const v = value.trim();
|
|
@@ -194,8 +158,7 @@ function initializeColorpickerPlugin() {
|
|
|
194
158
|
// ---- Canvas (SV plane) ----
|
|
195
159
|
|
|
196
160
|
function drawSvCanvas(canvas, hue) {
|
|
197
|
-
//
|
|
198
|
-
// we only ever write to this canvas, so we want GPU-accelerated compositing.
|
|
161
|
+
// Cached context; no willReadFrequently (write-only → GPU compositing).
|
|
199
162
|
const ctx = canvas._cpCtx || (canvas._cpCtx = canvas.getContext('2d'));
|
|
200
163
|
const w = canvas.width, h = canvas.height;
|
|
201
164
|
const hr = hsvToRgb(hue, 100, 100);
|
|
@@ -226,11 +189,9 @@ function initializeColorpickerPlugin() {
|
|
|
226
189
|
return layers.map(buildLayerString).join(', ');
|
|
227
190
|
}
|
|
228
191
|
|
|
229
|
-
// ---- Gradient parsing (reverse of
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
// uses internally. Tolerant of whatever the browser serializes from inline
|
|
233
|
-
// styles (rgb(...) and rgba(...) instead of hex, etc.).
|
|
192
|
+
// ---- Gradient parsing (reverse of the builders) ----
|
|
193
|
+
// Parses a CSS gradient string back into layers/stops. Tolerant of whatever
|
|
194
|
+
// the browser serializes from inline styles (rgb()/rgba() instead of hex).
|
|
234
195
|
|
|
235
196
|
// Split a string on top-level commas (commas not inside parentheses).
|
|
236
197
|
function _splitTopLevelCommas(str) {
|
|
@@ -246,9 +207,7 @@ function initializeColorpickerPlugin() {
|
|
|
246
207
|
return out;
|
|
247
208
|
}
|
|
248
209
|
|
|
249
|
-
// Split
|
|
250
|
-
// start with `<type>-gradient(`. Handles multi-layer gradients like
|
|
251
|
-
// "linear-gradient(...), radial-gradient(...)".
|
|
210
|
+
// Split into individual gradient calls (top-level `<type>-gradient(` segments).
|
|
252
211
|
function _splitGradientLayers(str) {
|
|
253
212
|
const segments = _splitTopLevelCommas(str);
|
|
254
213
|
return segments.filter(s => /^(linear|radial|conic)-gradient\s*\(/i.test(s));
|
|
@@ -415,10 +374,8 @@ function initializeColorpickerPlugin() {
|
|
|
415
374
|
function buildIosPreset(labels) {
|
|
416
375
|
const L = labels || {};
|
|
417
376
|
const groupName = L._group || 'iOS';
|
|
418
|
-
//
|
|
419
|
-
//
|
|
420
|
-
// palette context is lost). The `_lightPrefix` / `_darkPrefix` meta keys
|
|
421
|
-
// let devs translate or remove the prefixes.
|
|
377
|
+
// Tone prefix ("Light - " / "Dark - ") keeps swatches distinguishable in
|
|
378
|
+
// Recent, where palette context is lost. `_lightPrefix`/`_darkPrefix` override.
|
|
422
379
|
const lightPrefix = L._lightPrefix != null ? L._lightPrefix : 'Light - ';
|
|
423
380
|
const darkPrefix = L._darkPrefix != null ? L._darkPrefix : 'Dark - ';
|
|
424
381
|
const baseColorName = (c) => L[c.name] || titleCase(c.name);
|
|
@@ -446,12 +403,10 @@ function initializeColorpickerPlugin() {
|
|
|
446
403
|
|
|
447
404
|
// ---- Library data normalization ----
|
|
448
405
|
|
|
449
|
-
// Normalize into
|
|
450
|
-
//
|
|
451
|
-
//
|
|
452
|
-
//
|
|
453
|
-
// symbol keys, function values, and the few inherited Object.prototype names that can leak
|
|
454
|
-
// through exotic proxies.
|
|
406
|
+
// Normalize into Group[] — Group = { name?, colors?|palettes? }, Palette =
|
|
407
|
+
// { name?, colors }, Swatch = { name?, value }. Hierarchy is strictly
|
|
408
|
+
// Group > Palette > Swatch. Filters $-magic, symbol keys, functions, and
|
|
409
|
+
// Object.prototype names that leak through proxies.
|
|
455
410
|
const _LIB_PROTO_NOISE = new Set(['valueOf', 'toString', 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString']);
|
|
456
411
|
function _cleanLibraryEntries(input) {
|
|
457
412
|
return Object.entries(input).filter(([k, v]) => {
|
|
@@ -519,9 +474,8 @@ function initializeColorpickerPlugin() {
|
|
|
519
474
|
return { name: groupName, colors: [] };
|
|
520
475
|
}
|
|
521
476
|
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
// Optional `labels` is forwarded to the Tailwind / iOS builders for localization.
|
|
477
|
+
// Built-in Recent + Tailwind + iOS default library. Used when
|
|
478
|
+
// x-colorpicker.library has no expression and no ancestor $x data.
|
|
525
479
|
function buildDefaultLibrary(labels) {
|
|
526
480
|
const L = labels || {};
|
|
527
481
|
return {
|
|
@@ -532,32 +486,13 @@ function initializeColorpickerPlugin() {
|
|
|
532
486
|
}
|
|
533
487
|
|
|
534
488
|
// ---- Auto-discovery of colorpicker data sources ----
|
|
535
|
-
//
|
|
536
|
-
//
|
|
537
|
-
//
|
|
538
|
-
//
|
|
539
|
-
// {
|
|
540
|
-
// "data": {
|
|
541
|
-
// "myColors": "/data/colors.yaml" // non-localized
|
|
542
|
-
// "brand": { "en": "/data/brand.en.yaml", "fr": "..." } // per-locale
|
|
543
|
-
// },
|
|
544
|
-
// "colorpicker": "myColors" // single source
|
|
545
|
-
// "colorpicker": ["myColors", "brand"] // multiple sources
|
|
546
|
-
// }
|
|
547
|
-
//
|
|
548
|
-
// The colorpicker plugin scans $x.manifest.data for entries flagged with a
|
|
549
|
-
// `colorpicker:` key, then merges the loaded $x.<name> values in declaration
|
|
550
|
-
// order. Each source may contain:
|
|
551
|
-
// _tailwind: (optional) replaces the built-in Tailwind group entirely
|
|
552
|
-
// _ios: (optional) replaces the built-in iOS group entirely
|
|
553
|
-
// <Any Name>: custom groups appended to the library after built-ins
|
|
554
|
-
//
|
|
555
|
-
// Presence of _tailwind / _ios is "all or nothing" — no merge with built-in data.
|
|
556
|
-
// If multiple flagged sources both include _tailwind, the LAST source wins.
|
|
489
|
+
// manifest.json flags library sources via a top-level `colorpicker` key (one
|
|
490
|
+
// name or an array). Each source may carry `_tailwind` / `_ios` (all-or-nothing
|
|
491
|
+
// replacements of the built-in group, last wins) plus custom named groups.
|
|
492
|
+
// See colorpickers.md for the manifest shape.
|
|
557
493
|
|
|
558
|
-
// Manifest's data proxy returns
|
|
559
|
-
//
|
|
560
|
-
// actually declared the key (`in` operator) AND the value has real content.
|
|
494
|
+
// Manifest's data proxy returns {} for any access, so `src._tailwind` always
|
|
495
|
+
// looks truthy. Only treat an override as real if declared (`in`) with content.
|
|
561
496
|
function _hasRealContent(v) {
|
|
562
497
|
if (v == null || typeof v !== 'object') return false;
|
|
563
498
|
return Object.keys(v).some(k => !k.startsWith('$')
|
|
@@ -565,8 +500,8 @@ function initializeColorpickerPlugin() {
|
|
|
565
500
|
&& k !== 'contentType');
|
|
566
501
|
}
|
|
567
502
|
|
|
568
|
-
//
|
|
569
|
-
//
|
|
503
|
+
// Extract _tailwind / _ios overrides + custom groups from resolved sources,
|
|
504
|
+
// returning the final normalized groups array.
|
|
570
505
|
function composeLibraryFromSources(sources) {
|
|
571
506
|
let tailwindOverride = null;
|
|
572
507
|
let iosOverride = null;
|
|
@@ -593,14 +528,13 @@ function initializeColorpickerPlugin() {
|
|
|
593
528
|
const groups = [];
|
|
594
529
|
if (recent.length) {
|
|
595
530
|
const recentGroup = normalizeGroup(recent, 'Recent');
|
|
596
|
-
recentGroup._isRecent = true; //
|
|
531
|
+
recentGroup._isRecent = true; // renderer picks library-recent-swatch template
|
|
597
532
|
groups.push(recentGroup);
|
|
598
533
|
}
|
|
599
534
|
|
|
600
535
|
groups.push(...customGroups);
|
|
601
536
|
|
|
602
537
|
if (tailwindOverride) {
|
|
603
|
-
// `_group` meta inside the override becomes the group heading; default to "Tailwind"
|
|
604
538
|
groups.push(normalizeGroup(tailwindOverride, tailwindOverride._group || 'Tailwind'));
|
|
605
539
|
} else {
|
|
606
540
|
groups.push(...normalizeLibraryInput(buildTailwindPreset()));
|
|
@@ -615,20 +549,10 @@ function initializeColorpickerPlugin() {
|
|
|
615
549
|
return groups;
|
|
616
550
|
}
|
|
617
551
|
|
|
618
|
-
//
|
|
619
|
-
//
|
|
620
|
-
//
|
|
621
|
-
//
|
|
622
|
-
// • Template: "${$locale.t('brand.primary')}" → Alpine.evaluate(...) as a literal
|
|
623
|
-
// Plain strings pass through unchanged. Failed lookups return the original
|
|
624
|
-
// string so a missing key surfaces as-is rather than rendering empty.
|
|
625
|
-
//
|
|
626
|
-
// Reading via Alpine.evaluate inside the surrounding render effect registers
|
|
627
|
-
// reactive deps on the referenced data — locale switches and content updates
|
|
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.
|
|
552
|
+
// Resolve a `$x.`/`$locale`/`${…}` reference string against document.body scope.
|
|
553
|
+
// Recognizes bare paths and template literals; plain strings and failures pass
|
|
554
|
+
// through unchanged. Alpine.evaluate inside the render effect registers the
|
|
555
|
+
// locale/content dep so switches re-trigger the render.
|
|
632
556
|
function _resolveRefString(val) {
|
|
633
557
|
if (typeof val !== 'string' || val.length === 0) return val;
|
|
634
558
|
const trimmed = val.trim();
|
|
@@ -671,18 +595,16 @@ function initializeColorpickerPlugin() {
|
|
|
671
595
|
return groups;
|
|
672
596
|
}
|
|
673
597
|
|
|
674
|
-
// Returns an object
|
|
675
|
-
//
|
|
676
|
-
// Spreading the returned value exposes the default (unlabeled) preset's top-level keys.
|
|
598
|
+
// Returns an object both callable (for localization) and spreadable (for
|
|
599
|
+
// composition) — spreading exposes the default preset's top-level keys.
|
|
677
600
|
function _makeCallablePreset(builder) {
|
|
678
601
|
const fn = (labels) => builder(labels);
|
|
679
|
-
Object.assign(fn, builder());
|
|
602
|
+
Object.assign(fn, builder());
|
|
680
603
|
return fn;
|
|
681
604
|
}
|
|
682
605
|
|
|
683
|
-
// Render a group by cloning
|
|
684
|
-
//
|
|
685
|
-
// threads down so palette/swatch layers can pick the Recent-specific swatch template.
|
|
606
|
+
// Render a group by cloning its template and expanding nested templates in
|
|
607
|
+
// place. `isRecent` threads down to pick the Recent-specific swatch template.
|
|
686
608
|
function renderLibraryGroup(groupTpl, group) {
|
|
687
609
|
const frag = groupTpl.content.cloneNode(true);
|
|
688
610
|
const primary = frag.firstElementChild;
|
|
@@ -691,7 +613,7 @@ function initializeColorpickerPlugin() {
|
|
|
691
613
|
|
|
692
614
|
const isRecent = !!group._isRecent;
|
|
693
615
|
|
|
694
|
-
//
|
|
616
|
+
// Flat groups become a single unnamed palette so templates work uniformly.
|
|
695
617
|
const palettes = (group.palettes && group.palettes.length)
|
|
696
618
|
? group.palettes
|
|
697
619
|
: (group.colors && group.colors.length ? [{ name: null, colors: group.colors }] : []);
|
|
@@ -733,23 +655,19 @@ function initializeColorpickerPlugin() {
|
|
|
733
655
|
return frag;
|
|
734
656
|
}
|
|
735
657
|
|
|
736
|
-
// Per-clone counter for uniquifying nested dropdown/menu ids
|
|
737
|
-
//
|
|
738
|
-
// get uniquified; menus placed at the library root are left alone (shared).
|
|
658
|
+
// Per-clone counter for uniquifying nested dropdown/menu ids. Only menus
|
|
659
|
+
// inside the swatch template get uniquified; library-root menus stay shared.
|
|
739
660
|
let _swatchCloneCounter = 0;
|
|
740
661
|
|
|
741
662
|
function renderLibrarySwatch(swatchTpl, swatch) {
|
|
742
663
|
const frag = swatchTpl.content.cloneNode(true);
|
|
743
664
|
const primary = frag.firstElementChild;
|
|
744
665
|
if (!primary) return frag;
|
|
745
|
-
//
|
|
746
|
-
//
|
|
747
|
-
// read from the dropdowns plugin's trigger ref.
|
|
666
|
+
// Swatch scope on the primary (the swatch button); nested menus read from
|
|
667
|
+
// the dropdowns plugin's trigger ref instead.
|
|
748
668
|
primary.setAttribute('x-data', '{ swatch: ' + _jsonStringifyForAlpine(swatch) + ' }');
|
|
749
|
-
//
|
|
750
|
-
//
|
|
751
|
-
// `.active` on both, and dev CSS targeting either selector — the wrapper div
|
|
752
|
-
// (for layout effects like `order: -1`) or the button (for box-shadow) — works.
|
|
669
|
+
// Value + key go on both apply-color element and primary wrapper so
|
|
670
|
+
// _updateActiveSwatches can toggle `.active` and dev CSS can target either.
|
|
753
671
|
if (swatch && typeof swatch.value === 'string') {
|
|
754
672
|
const applyEl = frag.querySelector('[x-colorpicker\\.apply-color]');
|
|
755
673
|
const key = _swatchKeyOf(swatch.value);
|
|
@@ -761,14 +679,13 @@ function initializeColorpickerPlugin() {
|
|
|
761
679
|
if (key) t.setAttribute('data-cp-key', key);
|
|
762
680
|
}
|
|
763
681
|
}
|
|
764
|
-
// Uniquify
|
|
765
|
-
// collide. Skipped automatically when the menu lives outside the template.
|
|
682
|
+
// Uniquify nested menu ids so per-swatch context menus don't collide.
|
|
766
683
|
uniquifyDropdownIdsIn(frag, 'swatch-' + (++_swatchCloneCounter));
|
|
767
684
|
return frag;
|
|
768
685
|
}
|
|
769
686
|
|
|
770
|
-
//
|
|
771
|
-
//
|
|
687
|
+
// Fallback when no library template supplied: heading per group + a flex-wrap
|
|
688
|
+
// row of apply-color spans.
|
|
772
689
|
function renderDefaultGroup(group) {
|
|
773
690
|
const root = document.createElement('div');
|
|
774
691
|
root.setAttribute('data-cp-library-group', group.name || '');
|
|
@@ -804,12 +721,10 @@ function initializeColorpickerPlugin() {
|
|
|
804
721
|
if (input == null) return { name: paletteName, colors: [] };
|
|
805
722
|
if (Array.isArray(input)) return { name: paletteName, colors: normalizeColorList(input) };
|
|
806
723
|
if (typeof input === 'object') {
|
|
807
|
-
//
|
|
808
|
-
// across locales while translating visible text).
|
|
724
|
+
// `_name` meta overrides the display name (keeps object keys stable across locales).
|
|
809
725
|
const displayName = (typeof input._name === 'string') ? input._name : (input.name || paletteName);
|
|
810
|
-
// Explicit `_colors
|
|
811
|
-
//
|
|
812
|
-
// object keys and sequence items in the same node).
|
|
726
|
+
// Explicit `_colors`/`colors`/`items` holds the swatch list — pairs
|
|
727
|
+
// `_name` with a bare-hex array (YAML can't mix keys and sequence items).
|
|
813
728
|
if ('_colors' in input || 'colors' in input || 'items' in input) {
|
|
814
729
|
return {
|
|
815
730
|
...input,
|
|
@@ -844,7 +759,7 @@ function initializeColorpickerPlugin() {
|
|
|
844
759
|
return input.map(item => _coerceSwatch(item)).filter(Boolean);
|
|
845
760
|
}
|
|
846
761
|
if (typeof input === 'object') {
|
|
847
|
-
// Filter `_`-prefixed meta keys
|
|
762
|
+
// Filter `_`-prefixed meta keys so they're not mistaken for shades.
|
|
848
763
|
return Object.entries(input)
|
|
849
764
|
.filter(([k]) => !k.startsWith('_'))
|
|
850
765
|
.map(([name, value]) => _coerceSwatch(value, name))
|
|
@@ -896,8 +811,7 @@ function initializeColorpickerPlugin() {
|
|
|
896
811
|
|
|
897
812
|
// ---- Default fallback templates (used when developer doesn't supply their own) ----
|
|
898
813
|
|
|
899
|
-
// Solid-options panel —
|
|
900
|
-
// accordion content under each gradient stop.
|
|
814
|
+
// Solid-options panel — top-level tab body and per-stop accordion content.
|
|
901
815
|
const DEFAULT_SOLID_TEMPLATE_HTML = `
|
|
902
816
|
<div>
|
|
903
817
|
<div class="canvas-wrapper">
|
|
@@ -924,10 +838,8 @@ function initializeColorpickerPlugin() {
|
|
|
924
838
|
</div>
|
|
925
839
|
`;
|
|
926
840
|
|
|
927
|
-
// Gradient layer template
|
|
928
|
-
//
|
|
929
|
-
// with right-click context menu (Duplicate/Delete + inline library), and
|
|
930
|
-
// the per-stop solid-panel accordion.
|
|
841
|
+
// Gradient layer template (one per layer clone) — type dropdown, angle input,
|
|
842
|
+
// stops bar with context menu, and the per-stop solid-panel accordion.
|
|
931
843
|
const DEFAULT_LAYER_TEMPLATE_HTML = `
|
|
932
844
|
<div>
|
|
933
845
|
<div class="layer-options-wrapper">
|
|
@@ -1067,14 +979,9 @@ function initializeColorpickerPlugin() {
|
|
|
1067
979
|
const _defaultLibraryLayoutTpl = parseOnce(DEFAULT_LIBRARY_LAYOUT_HTML);
|
|
1068
980
|
|
|
1069
981
|
// ---- Default-menu text (localizable via `_ui`) ----
|
|
1070
|
-
//
|
|
1071
|
-
// English
|
|
1072
|
-
//
|
|
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.
|
|
982
|
+
// English fallbacks; ManifestUI.resolve deep-overlays project `_ui` overrides.
|
|
983
|
+
// The same English also lives verbatim in the templates above, so the menu
|
|
984
|
+
// reads correctly even if the resolver never runs.
|
|
1078
985
|
const UI_FALLBACK = {
|
|
1079
986
|
tabs: { solid: 'Solid', gradient: 'Gradient', library: 'Library' },
|
|
1080
987
|
grabColor: 'Grab color',
|
|
@@ -1107,10 +1014,9 @@ function initializeColorpickerPlugin() {
|
|
|
1107
1014
|
return ui;
|
|
1108
1015
|
}
|
|
1109
1016
|
|
|
1110
|
-
// Stamp a resolved `_ui` object onto a
|
|
1111
|
-
//
|
|
1112
|
-
//
|
|
1113
|
-
// custom templates have no markers, so this is a no-op for them.
|
|
1017
|
+
// Stamp a resolved `_ui` object onto a cloned default-template subtree via
|
|
1018
|
+
// data-cp-ui-text (textContent) / data-cp-ui-label (aria+tooltip) markers.
|
|
1019
|
+
// No-op for dev-authored templates (no markers).
|
|
1114
1020
|
function _applyDefaultUiText(scopeEl, ui) {
|
|
1115
1021
|
if (!scopeEl || !ui) return;
|
|
1116
1022
|
scopeEl.querySelectorAll('[data-cp-ui-text]').forEach(el => {
|
|
@@ -1130,12 +1036,11 @@ function initializeColorpickerPlugin() {
|
|
|
1130
1036
|
|
|
1131
1037
|
let pickerCounter = 0;
|
|
1132
1038
|
|
|
1133
|
-
// Reactive registry of pickers keyed by element ID.
|
|
1134
|
-
//
|
|
1135
|
-
// so bindings resolve correctly when a picker mounts later in the DOM.
|
|
1039
|
+
// Reactive registry of pickers keyed by element ID. Reads track unregistered
|
|
1040
|
+
// IDs too, so bindings resolve when a picker mounts later.
|
|
1136
1041
|
const _pickerRegistry = window.Alpine?.reactive ? Alpine.reactive({}) : {};
|
|
1137
1042
|
|
|
1138
|
-
// Fallback API
|
|
1043
|
+
// Fallback API for an unregistered picker ID; coerces to empty string.
|
|
1139
1044
|
const _nullApi = (() => {
|
|
1140
1045
|
const noop = () => {};
|
|
1141
1046
|
const empty = () => '';
|
|
@@ -1174,17 +1079,13 @@ function initializeColorpickerPlugin() {
|
|
|
1174
1079
|
pickerMode: 'solid',
|
|
1175
1080
|
openStop: null, // { layerIndex, stopIndex } | null
|
|
1176
1081
|
|
|
1177
|
-
//
|
|
1178
|
-
//
|
|
1179
|
-
// injected UI is filtered + reordered to match. null/empty = all panels.
|
|
1082
|
+
// Panel filter from an array-literal root expression (e.g.
|
|
1083
|
+
// x-colorpicker="['solid','gradient']"); null = all panels.
|
|
1180
1084
|
allowedPanels: null,
|
|
1181
1085
|
|
|
1182
|
-
//
|
|
1183
|
-
// dropdown button whose text reads "Hex"/"RGB"/"HSL"/"OKLCH"). Refreshed
|
|
1184
|
-
// whenever the format changes via _refreshFormatLabels().
|
|
1086
|
+
// Live format-label elements (dropdown button text "Hex"/"RGB"/...).
|
|
1185
1087
|
formatLabelEls: [],
|
|
1186
|
-
// Elements bound to a specific format value (
|
|
1187
|
-
// Refreshed alongside the labels to toggle an `active` class on the current.
|
|
1088
|
+
// Elements bound to a specific format value (toggle `active` on current).
|
|
1188
1089
|
formatChoiceEls: [],
|
|
1189
1090
|
|
|
1190
1091
|
// Element registry (populated by child directive handlers)
|
|
@@ -1199,27 +1100,23 @@ function initializeColorpickerPlugin() {
|
|
|
1199
1100
|
gradientValueInputs: [], // <textarea x-colorpicker.set-gradient-value>
|
|
1200
1101
|
|
|
1201
1102
|
// Library
|
|
1202
|
-
libraryContainers: [], // <div x-colorpicker.library> — every registered target
|
|
1203
|
-
libraryTemplate: null, // <template x-colorpicker.library> — dev layout
|
|
1204
|
-
libraryRootValue: null, //
|
|
1205
|
-
|
|
1206
|
-
// Recent-list commit tracking.
|
|
1207
|
-
//
|
|
1208
|
-
//
|
|
1209
|
-
// a preset swatch — those don't count as "recent" even if committed.
|
|
1210
|
-
// `_hasUserChange` is true if any non-library user interaction has happened
|
|
1211
|
-
// since the baseline was set.
|
|
1103
|
+
libraryContainers: [], // <div x-colorpicker.library> — every registered target
|
|
1104
|
+
libraryTemplate: null, // <template x-colorpicker.library> — dev layout
|
|
1105
|
+
libraryRootValue: null, // x-colorpicker.library="..." data source expression
|
|
1106
|
+
|
|
1107
|
+
// Recent-list commit tracking. Baseline = color at cycle start;
|
|
1108
|
+
// _lastChangeFromLibrary = preset swatch (doesn't count as recent);
|
|
1109
|
+
// _hasUserChange = any non-library interaction since baseline.
|
|
1212
1110
|
_recentBaseline: null,
|
|
1213
1111
|
_lastChangeFromLibrary: false,
|
|
1214
1112
|
_hasUserChange: false,
|
|
1215
1113
|
|
|
1216
|
-
//
|
|
1114
|
+
// Active solid-controls refs (Solid tab or open-stop accordion)
|
|
1217
1115
|
activeControls: null,
|
|
1218
1116
|
solidTabRefs: null,
|
|
1219
1117
|
|
|
1220
|
-
// Reactive version counter — bumped on every
|
|
1221
|
-
//
|
|
1222
|
-
// values lazily. No upfront work when nothing is bound to $colorpicker(id).
|
|
1118
|
+
// Reactive version counter — bumped on every change; API getters read
|
|
1119
|
+
// it to establish a dep, then compute lazily.
|
|
1223
1120
|
snapshot: window.Alpine?.reactive ? Alpine.reactive({ version: 0 }) : { version: 0 },
|
|
1224
1121
|
|
|
1225
1122
|
// ---- Active target accessors ----
|
|
@@ -1276,8 +1173,7 @@ function initializeColorpickerPlugin() {
|
|
|
1276
1173
|
this.activeLayerIndex = 0;
|
|
1277
1174
|
this.activeStopIndex = 0;
|
|
1278
1175
|
this.openStop = null;
|
|
1279
|
-
// Seed the
|
|
1280
|
-
// syncUI / hex output / canvas reflect something coherent.
|
|
1176
|
+
// Seed the working solid color from the first stop.
|
|
1281
1177
|
const firstStop = layers[0].stops[0];
|
|
1282
1178
|
if (firstStop) {
|
|
1283
1179
|
this.h = firstStop.color.h; this.s = firstStop.color.s;
|
|
@@ -1308,8 +1204,8 @@ function initializeColorpickerPlugin() {
|
|
|
1308
1204
|
this.activeLayerIndex = layerIndex;
|
|
1309
1205
|
this.activeStopIndex = stopIndex;
|
|
1310
1206
|
this.renderLayers();
|
|
1311
|
-
//
|
|
1312
|
-
// (CSS
|
|
1207
|
+
// Library gradient swatches disabled while a stop is open
|
|
1208
|
+
// (CSS can't nest gradients as stop colors).
|
|
1313
1209
|
this._syncStopGradientDisable();
|
|
1314
1210
|
},
|
|
1315
1211
|
|
|
@@ -1428,8 +1324,7 @@ function initializeColorpickerPlugin() {
|
|
|
1428
1324
|
},
|
|
1429
1325
|
|
|
1430
1326
|
applyColor(str) {
|
|
1431
|
-
// Pure setter — Recent
|
|
1432
|
-
// (popover close / inline focusout), not on every call.
|
|
1327
|
+
// Pure setter — Recent commits happen at the close boundary, not here.
|
|
1433
1328
|
if (this.setFromString(str)) {
|
|
1434
1329
|
this.syncUI();
|
|
1435
1330
|
this.syncToInput();
|
|
@@ -1437,10 +1332,8 @@ function initializeColorpickerPlugin() {
|
|
|
1437
1332
|
}
|
|
1438
1333
|
},
|
|
1439
1334
|
|
|
1440
|
-
// Toggle `.active` on library swatches
|
|
1441
|
-
//
|
|
1442
|
-
// solids compare as 8-digit hex. Runs across every registered library
|
|
1443
|
-
// container (tab + any nested containers such as stop context menus).
|
|
1335
|
+
// Toggle `.active` on library swatches matching the current color's key,
|
|
1336
|
+
// across every registered library container.
|
|
1444
1337
|
_updateActiveSwatches() {
|
|
1445
1338
|
if (!this.libraryContainers.length) return;
|
|
1446
1339
|
const current = this.isGradient
|
|
@@ -1458,14 +1351,9 @@ function initializeColorpickerPlugin() {
|
|
|
1458
1351
|
this._syncStopGradientDisable();
|
|
1459
1352
|
},
|
|
1460
1353
|
|
|
1461
|
-
//
|
|
1462
|
-
//
|
|
1463
|
-
//
|
|
1464
|
-
// • a stop accordion is currently open (openStop set), OR
|
|
1465
|
-
// • the library container itself is nested inside a stop-context-menu —
|
|
1466
|
-
// every click in such a menu targets the right-clicked stop's color.
|
|
1467
|
-
// Dev CSS styles [disabled] on apply-color elements; the click handler
|
|
1468
|
-
// also short-circuits as belt-and-braces.
|
|
1354
|
+
// Disable gradient library swatches when a click would apply them as a
|
|
1355
|
+
// stop color (can't nest gradients): a stop accordion is open, or the
|
|
1356
|
+
// container is inside a stop-context-menu. Dev CSS styles [disabled].
|
|
1469
1357
|
_syncStopGradientDisable() {
|
|
1470
1358
|
const stopIsOpen = !!this.openStop;
|
|
1471
1359
|
for (const c of this.libraryContainers) {
|
|
@@ -1484,14 +1372,10 @@ function initializeColorpickerPlugin() {
|
|
|
1484
1372
|
},
|
|
1485
1373
|
|
|
1486
1374
|
// ---- Recent-list commit cycle ----
|
|
1487
|
-
//
|
|
1488
|
-
//
|
|
1489
|
-
//
|
|
1490
|
-
//
|
|
1491
|
-
// • A commit pushes the current color to Recent IFF the user made a
|
|
1492
|
-
// non-library change since the last baseline and the color actually differs.
|
|
1493
|
-
// • Gradient mode never commits (stops are part of a gradient, not standalone).
|
|
1494
|
-
// • Programmatic api.applyColor calls don't mark user changes — only UI paths do.
|
|
1375
|
+
// Popover commits on close, inline on focusout past rootEl. A commit
|
|
1376
|
+
// pushes to Recent only if a non-library user change since baseline
|
|
1377
|
+
// actually changed the color. Gradient mode never commits; programmatic
|
|
1378
|
+
// applyColor doesn't mark a user change.
|
|
1495
1379
|
|
|
1496
1380
|
_startCommitCycle() {
|
|
1497
1381
|
this._recentBaseline = this._currentCommitValue();
|
|
@@ -1521,10 +1405,8 @@ function initializeColorpickerPlugin() {
|
|
|
1521
1405
|
this._startCommitCycle();
|
|
1522
1406
|
},
|
|
1523
1407
|
|
|
1524
|
-
//
|
|
1525
|
-
//
|
|
1526
|
-
// Recent, this runs even when the change came from a library click — the user
|
|
1527
|
-
// clicked a library swatch intending to assign that color to their field.
|
|
1408
|
+
// Write the current color back through the trigger's x-model setter on
|
|
1409
|
+
// commit. Runs even for library clicks (the user meant to assign it).
|
|
1528
1410
|
_commitToTrigger() {
|
|
1529
1411
|
const trigger = this.triggerBtn;
|
|
1530
1412
|
if (!trigger || typeof trigger._cpModelSetter !== 'function') return;
|
|
@@ -1537,10 +1419,7 @@ function initializeColorpickerPlugin() {
|
|
|
1537
1419
|
if (!window.EyeDropper) return;
|
|
1538
1420
|
try {
|
|
1539
1421
|
const result = await new EyeDropper().open();
|
|
1540
|
-
//
|
|
1541
|
-
// to Solid mode and surface the solid controls. Mark as a
|
|
1542
|
-
// non-library user change; the usual commit boundary decides
|
|
1543
|
-
// whether it lands in Recent (user may tweak before closing).
|
|
1422
|
+
// Single-color op — route to Solid mode, mark a non-library change.
|
|
1544
1423
|
this._switchToSolidMode();
|
|
1545
1424
|
this._markUserChange(false);
|
|
1546
1425
|
this.applyColor(result.sRGBHex);
|
|
@@ -1560,9 +1439,8 @@ function initializeColorpickerPlugin() {
|
|
|
1560
1439
|
if (this.layersContainer) this.renderLayers();
|
|
1561
1440
|
},
|
|
1562
1441
|
|
|
1563
|
-
// Switch
|
|
1564
|
-
//
|
|
1565
|
-
// shouldn't be flipped off the Library tab when they pick one.
|
|
1442
|
+
// Switch mode without touching the dev's `tab` data — library-tab
|
|
1443
|
+
// applies shouldn't flip the user off the Library tab.
|
|
1566
1444
|
_setPickerMode(mode) {
|
|
1567
1445
|
if (this.pickerMode === mode) return;
|
|
1568
1446
|
this.pickerMode = mode;
|
|
@@ -1603,9 +1481,8 @@ function initializeColorpickerPlugin() {
|
|
|
1603
1481
|
}
|
|
1604
1482
|
},
|
|
1605
1483
|
|
|
1606
|
-
// Sync
|
|
1607
|
-
//
|
|
1608
|
-
// style the active option without writing reactive bindings.
|
|
1484
|
+
// Sync registered format-label elements and toggle `active` on each
|
|
1485
|
+
// format-choice element for the current format.
|
|
1609
1486
|
_refreshFormatLabels() {
|
|
1610
1487
|
const current = this.format;
|
|
1611
1488
|
const label = this._formatLabel(current);
|
|
@@ -1641,10 +1518,8 @@ function initializeColorpickerPlugin() {
|
|
|
1641
1518
|
syncToInput() {
|
|
1642
1519
|
const swatchVal = this.toSwatchColor();
|
|
1643
1520
|
if (this.hiddenInput) {
|
|
1644
|
-
// Native <input type=
|
|
1645
|
-
//
|
|
1646
|
-
// the full CSS string — solid color or gradient — so gradients
|
|
1647
|
-
// and non-hex formats round-trip without lossy conversion.
|
|
1521
|
+
// Native <input type=color> only accepts #rrggbb; hidden inputs
|
|
1522
|
+
// get the full CSS string so gradients/non-hex round-trip losslessly.
|
|
1648
1523
|
const isNativeColorInput = this.hiddenInput.type === 'color';
|
|
1649
1524
|
this.hiddenInput.value = isNativeColorInput ? this.toHex() : swatchVal;
|
|
1650
1525
|
this.hiddenInput.dispatchEvent(new Event('input', { bubbles: true }));
|
|
@@ -1652,15 +1527,10 @@ function initializeColorpickerPlugin() {
|
|
|
1652
1527
|
}
|
|
1653
1528
|
if (this.triggerBtn) this.triggerBtn.style.setProperty('--color-picker-swatch', swatchVal);
|
|
1654
1529
|
this.updateGradientValue();
|
|
1655
|
-
// Reflect current color in the library: any swatch whose canonical key
|
|
1656
|
-
// matches gets an `.active` class, all others lose it.
|
|
1657
1530
|
this._updateActiveSwatches();
|
|
1658
|
-
// Refresh any custom format labels / choice highlights — covers paths
|
|
1659
|
-
// where format changes implicitly (setFromString parsing a new format).
|
|
1660
1531
|
this._refreshFormatLabels();
|
|
1661
1532
|
|
|
1662
|
-
// Bump reactive version —
|
|
1663
|
-
// No eager computation of hex/css/etc. unless somebody is actually bound to them.
|
|
1533
|
+
// Bump reactive version — $colorpicker(id).* readers re-run (lazily).
|
|
1664
1534
|
this.snapshot.version++;
|
|
1665
1535
|
},
|
|
1666
1536
|
|
|
@@ -1676,11 +1546,11 @@ function initializeColorpickerPlugin() {
|
|
|
1676
1546
|
const canvas = this.activeControls?.canvas;
|
|
1677
1547
|
if (!canvas) return;
|
|
1678
1548
|
const rect = canvas.getBoundingClientRect();
|
|
1679
|
-
if (rect.width <= 0) return; // not visible
|
|
1549
|
+
if (rect.width <= 0) return; // not visible; ResizeObserver redraws later
|
|
1680
1550
|
let dimsChanged = false;
|
|
1681
1551
|
if (canvas.width !== rect.width) { canvas.width = rect.width; dimsChanged = true; }
|
|
1682
1552
|
if (canvas.height !== rect.height) { canvas.height = rect.height; dimsChanged = true; }
|
|
1683
|
-
// Skip repaint if hue
|
|
1553
|
+
// Skip repaint if hue and dims unchanged (setting dims clears pixels).
|
|
1684
1554
|
if (!dimsChanged && canvas._cpLastHue === this.h) return;
|
|
1685
1555
|
drawSvCanvas(canvas, this.h);
|
|
1686
1556
|
canvas._cpLastHue = this.h;
|
|
@@ -1722,7 +1592,7 @@ function initializeColorpickerPlugin() {
|
|
|
1722
1592
|
|
|
1723
1593
|
// ---- Library rendering ----
|
|
1724
1594
|
//
|
|
1725
|
-
// Templates
|
|
1595
|
+
// Templates nest as group > palette > swatch:
|
|
1726
1596
|
// <template x-colorpicker.library>
|
|
1727
1597
|
// <template x-colorpicker.library-group> <!-- scope: group -->
|
|
1728
1598
|
// <template x-colorpicker.library-palette> <!-- scope: palette -->
|
|
@@ -1732,69 +1602,46 @@ function initializeColorpickerPlugin() {
|
|
|
1732
1602
|
// </template>
|
|
1733
1603
|
// </template>
|
|
1734
1604
|
//
|
|
1735
|
-
// Each inner <template> is replaced in
|
|
1736
|
-
//
|
|
1737
|
-
// If a group has only `colors` (flat, e.g. Recent), it's auto-wrapped as a single
|
|
1738
|
-
// unnamed palette so nested templates still work uniformly.
|
|
1605
|
+
// Each inner <template> is replaced in place by clones. Flat groups (Recent)
|
|
1606
|
+
// are auto-wrapped as one unnamed palette so nesting works uniformly.
|
|
1739
1607
|
|
|
1740
1608
|
renderLibrary() {
|
|
1741
1609
|
if (this._libraryEffectBound) return;
|
|
1742
1610
|
this._libraryEffectBound = true;
|
|
1743
1611
|
if (!this.libraryContainers.length) return;
|
|
1744
|
-
//
|
|
1745
|
-
// the first for any Alpine scope evaluation that needs an element.
|
|
1612
|
+
// Containers share one data source; use the first for scope eval.
|
|
1746
1613
|
const evalHost = this.libraryContainers[0];
|
|
1747
1614
|
|
|
1748
1615
|
if (this.libraryRootValue && window.Alpine?.effect && window.Alpine?.evaluateLater) {
|
|
1749
|
-
// Explicit expression — bypass discovery, re-render
|
|
1616
|
+
// Explicit expression — bypass discovery, re-render on dep change.
|
|
1750
1617
|
const evalFn = Alpine.evaluateLater(evalHost, this.libraryRootValue);
|
|
1751
1618
|
Alpine.effect(() => {
|
|
1752
1619
|
evalFn(v => { this._libraryResolvedData = v; this._doRenderLibrary(); });
|
|
1753
1620
|
});
|
|
1754
1621
|
} else if (window.Alpine?.effect) {
|
|
1755
|
-
// Zero-config — scan
|
|
1756
|
-
//
|
|
1757
|
-
//
|
|
1758
|
-
//
|
|
1759
|
-
//
|
|
1760
|
-
//
|
|
1761
|
-
// Multiple sources are supported — split your default-palette overrides
|
|
1762
|
-
// (`_tailwind`, `_ios`) and your custom palettes across as many files
|
|
1763
|
-
// as you like. Order in `manifest.data` determines render order.
|
|
1764
|
-
//
|
|
1765
|
-
// Everything happens synchronously inside the effect so Alpine tracks
|
|
1766
|
-
// all reactive deps ($locale, $x.manifest, each $x.<name>) and re-runs
|
|
1767
|
-
// the full pass on any change. Evaluate against document.body so $x
|
|
1768
|
-
// magic is always in scope (the container may be detached/popover
|
|
1769
|
-
// content without its own scope chain).
|
|
1622
|
+
// Zero-config — scan $x.manifest.data for `colorpicker:`-flagged
|
|
1623
|
+
// entries, collect their $x.<name> values, merge in declaration
|
|
1624
|
+
// order. Runs synchronously in the effect so Alpine tracks all deps.
|
|
1625
|
+
// Eval against document.body so $x magic is in scope (popover content
|
|
1626
|
+
// may be detached).
|
|
1770
1627
|
const evalCtx = document.body;
|
|
1771
1628
|
|
|
1772
|
-
//
|
|
1773
|
-
//
|
|
1774
|
-
//
|
|
1775
|
-
//
|
|
1776
|
-
//
|
|
1777
|
-
// poll tick would tear down and rebuild the whole library, thrashing the
|
|
1778
|
-
// DOM (and invalidating x-dropdown.context menu id lookups mid-flight).
|
|
1629
|
+
// The data plugin REPLACES the $x.<source> proxy on load rather than
|
|
1630
|
+
// mutating, which Alpine.effect can't catch by property tracking — so a
|
|
1631
|
+
// short poller re-reads until loaded. Re-render only when serialized
|
|
1632
|
+
// content changed (else each tick rebuilds the whole library, thrashing
|
|
1633
|
+
// the DOM and invalidating x-dropdown.context id lookups mid-flight).
|
|
1779
1634
|
const keyOf = (names, collected) => {
|
|
1780
|
-
//
|
|
1781
|
-
//
|
|
1782
|
-
// establishes reactivity on it, so cookie mutations (pushRecent /
|
|
1783
|
-
// removeRecent) fire the effect and change the key.
|
|
1635
|
+
// Recent list is in the key (and read here for reactivity) so
|
|
1636
|
+
// push/removeRecent fire the effect and change the key.
|
|
1784
1637
|
const recentKey = _recentStore.list.slice(0, _recentMax).join(',');
|
|
1785
1638
|
try { return recentKey + '#' + names.join('|') + '::' + JSON.stringify(collected); }
|
|
1786
1639
|
catch { return recentKey + '#' + names.join('|') + '::[unserializable]'; }
|
|
1787
1640
|
};
|
|
1788
1641
|
const readSources = () => {
|
|
1789
|
-
//
|
|
1790
|
-
//
|
|
1791
|
-
//
|
|
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.
|
|
1642
|
+
// Two discovery paths: (1) preferred top-level manifest
|
|
1643
|
+
// "colorpicker" pointer naming normal data sources; (2) legacy
|
|
1644
|
+
// `colorpicker` key flagged on the data entry itself (back-compat).
|
|
1798
1645
|
const names = [];
|
|
1799
1646
|
try {
|
|
1800
1647
|
const ptr = Alpine.evaluate(evalCtx, '$x && $x.manifest && $x.manifest.colorpicker');
|
|
@@ -1834,8 +1681,7 @@ function initializeColorpickerPlugin() {
|
|
|
1834
1681
|
this._libraryDiscoveredKey = key;
|
|
1835
1682
|
this._libraryDiscoveredData = collected;
|
|
1836
1683
|
this._doRenderLibrary();
|
|
1837
|
-
// A `_ui` source
|
|
1838
|
-
// $locale, so re-localize the rest of the picker here too.
|
|
1684
|
+
// A late-loading `_ui` source won't change $locale — re-localize here.
|
|
1839
1685
|
this._applyUi(this.rootEl);
|
|
1840
1686
|
}
|
|
1841
1687
|
return ready;
|
|
@@ -1846,12 +1692,9 @@ function initializeColorpickerPlugin() {
|
|
|
1846
1692
|
try { Alpine.evaluate(evalCtx, '$locale && $locale.current'); } catch {}
|
|
1847
1693
|
try { Alpine.evaluate(evalCtx, '$x && $x.manifest && $x.manifest._loadedFrom'); } catch {}
|
|
1848
1694
|
runDiscovery();
|
|
1849
|
-
// Re-localize the whole picker
|
|
1850
|
-
// locale switch — this effect re-runs when $locale.current changes,
|
|
1851
|
-
// and _applyUi re-resolves $x/$locale references inside it.
|
|
1695
|
+
// Re-localize the whole picker on locale switch.
|
|
1852
1696
|
this._applyUi(this.rootEl);
|
|
1853
|
-
//
|
|
1854
|
-
// but skips actual re-render when the content is unchanged.
|
|
1697
|
+
// Poll (150ms) only until data is ready; skips re-render when unchanged.
|
|
1855
1698
|
if (!this._libraryPollTimer) {
|
|
1856
1699
|
let attempts = 0;
|
|
1857
1700
|
this._libraryPollTimer = setInterval(() => {
|
|
@@ -1884,17 +1727,13 @@ function initializeColorpickerPlugin() {
|
|
|
1884
1727
|
} else {
|
|
1885
1728
|
groups = composeLibraryFromSources(this._libraryDiscoveredData || []);
|
|
1886
1729
|
}
|
|
1887
|
-
// Resolve
|
|
1888
|
-
//
|
|
1889
|
-
// chain into a separate localization data source without dev-side
|
|
1890
|
-
// template tweaks. Reactive reads inside `Alpine.evaluate` register
|
|
1891
|
-
// deps on the surrounding render effect → locale switches re-render.
|
|
1730
|
+
// Resolve $x/${...} references in group/palette/swatch names (reactive
|
|
1731
|
+
// reads register deps → locale switches re-render).
|
|
1892
1732
|
return _resolveLibraryRefs(groups);
|
|
1893
1733
|
},
|
|
1894
1734
|
|
|
1895
|
-
// Render ONE container (
|
|
1896
|
-
//
|
|
1897
|
-
// down every other container's x-dropdown.context init timers.
|
|
1735
|
+
// Render ONE container (new post-mount container, e.g. a layer clone's
|
|
1736
|
+
// library div) without tearing down others' x-dropdown.context timers.
|
|
1898
1737
|
_renderIntoContainer(container) {
|
|
1899
1738
|
if (!container || !container.isConnected) return;
|
|
1900
1739
|
const groups = this._resolveLibraryGroups();
|
|
@@ -1958,11 +1797,8 @@ function initializeColorpickerPlugin() {
|
|
|
1958
1797
|
return this.layersContainer.querySelectorAll(':scope > [data-cp-layer-clone]')[li] || null;
|
|
1959
1798
|
},
|
|
1960
1799
|
|
|
1961
|
-
// Resolve
|
|
1962
|
-
//
|
|
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.
|
|
1800
|
+
// Resolve default-menu chrome text (fallbacks + project `_ui` overrides)
|
|
1801
|
+
// and stamp onto a cloned default-template subtree. No-op for custom templates.
|
|
1966
1802
|
_applyUi(scopeEl) {
|
|
1967
1803
|
if (!scopeEl || !window.ManifestUI) return;
|
|
1968
1804
|
let ui;
|
|
@@ -1999,24 +1835,15 @@ function initializeColorpickerPlugin() {
|
|
|
1999
1835
|
root.setAttribute('data-gradient-type', layer.type);
|
|
2000
1836
|
root._cpLayerIndex = li;
|
|
2001
1837
|
|
|
2002
|
-
// Expose
|
|
2003
|
-
//
|
|
2004
|
-
// layerType — 'linear' | 'radial' | 'conic'
|
|
2005
|
-
// layerIndex — 0-based position of this layer
|
|
2006
|
-
// layerCount — total number of layers in the picker
|
|
2007
|
-
// Examples:
|
|
2008
|
-
// :class="'layer-type-' + layerType"
|
|
2009
|
-
// :disabled="layerIndex === 0" (Move Up)
|
|
2010
|
-
// :disabled="layerIndex === layerCount - 1" (Move Down)
|
|
2011
|
-
// :disabled="layerCount === 1" (Remove)
|
|
1838
|
+
// Expose layerType / layerIndex / layerCount to the clone's Alpine
|
|
1839
|
+
// scope for reactive :class / :disabled bindings.
|
|
2012
1840
|
root.setAttribute('x-data', '{ '
|
|
2013
1841
|
+ 'layerType: ' + JSON.stringify(layer.type) + ', '
|
|
2014
1842
|
+ 'layerIndex: ' + li + ', '
|
|
2015
1843
|
+ 'layerCount: ' + this.layers.length
|
|
2016
1844
|
+ ' }');
|
|
2017
1845
|
|
|
2018
|
-
// Uniquify x-dropdown
|
|
2019
|
-
// within this clone so per-layer dropdowns don't collide.
|
|
1846
|
+
// Uniquify x-dropdown IDs so per-layer dropdowns don't collide.
|
|
2020
1847
|
uniquifyDropdownIdsIn(root, this.pickerUid + '-layer-' + li);
|
|
2021
1848
|
|
|
2022
1849
|
// Render stops bar content
|
|
@@ -2036,11 +1863,9 @@ function initializeColorpickerPlugin() {
|
|
|
2036
1863
|
|
|
2037
1864
|
this.layersContainer.appendChild(root);
|
|
2038
1865
|
|
|
2039
|
-
// Localize this clone's default menu chrome (gradient types, layer
|
|
2040
|
-
// actions, stop actions). No-op for dev-supplied layer templates.
|
|
2041
1866
|
this._applyUi(root);
|
|
2042
1867
|
|
|
2043
|
-
// Let Alpine
|
|
1868
|
+
// Let Alpine process the clone (x-dropdown, x-icon, nested x-colorpicker).
|
|
2044
1869
|
if (window.Alpine?.initTree) {
|
|
2045
1870
|
requestAnimationFrame(() => Alpine.initTree(root));
|
|
2046
1871
|
}
|
|
@@ -2067,7 +1892,7 @@ function initializeColorpickerPlugin() {
|
|
|
2067
1892
|
handle.setAttribute('data-cp-stop-handle', '');
|
|
2068
1893
|
handle.style.left = stop.position + '%';
|
|
2069
1894
|
handle.style.backgroundColor = colorToRgba(stop.color);
|
|
2070
|
-
// .active
|
|
1895
|
+
// .active = this stop's accordion is open
|
|
2071
1896
|
if (this.openStop && this.openStop.layerIndex === layerIndex && this.openStop.stopIndex === si) {
|
|
2072
1897
|
handle.classList.add('active');
|
|
2073
1898
|
}
|
|
@@ -2087,8 +1912,7 @@ function initializeColorpickerPlugin() {
|
|
|
2087
1912
|
if (e.button !== 0) return;
|
|
2088
1913
|
e.stopPropagation();
|
|
2089
1914
|
self.selectStop(layerIndex, si);
|
|
2090
|
-
// .active is set by the re-render after toggleStop
|
|
2091
|
-
// don't preemptively toggle here (would be wrong for drag-without-toggle).
|
|
1915
|
+
// .active is set by the re-render after toggleStop — don't preempt it.
|
|
2092
1916
|
dragging = true; moved = false; startX = e.clientX;
|
|
2093
1917
|
cachedBarRect = barEl.getBoundingClientRect();
|
|
2094
1918
|
handle.setPointerCapture(e.pointerId);
|
|
@@ -2099,10 +1923,9 @@ function initializeColorpickerPlugin() {
|
|
|
2099
1923
|
if (moved) throttledDrag(e);
|
|
2100
1924
|
});
|
|
2101
1925
|
handle.addEventListener('pointerup', () => {
|
|
2102
|
-
// Only
|
|
2103
|
-
//
|
|
2104
|
-
//
|
|
2105
|
-
// destroy the layer clone — killing the context menu that just opened.
|
|
1926
|
+
// Only act on a real left-drag session. Right-click never sets
|
|
1927
|
+
// dragging, so bailing here keeps its context menu from being
|
|
1928
|
+
// destroyed by a stray toggleStop() re-render.
|
|
2106
1929
|
if (!dragging) return;
|
|
2107
1930
|
const wasMoved = moved;
|
|
2108
1931
|
dragging = false; moved = false; cachedBarRect = null;
|
|
@@ -2119,13 +1942,10 @@ function initializeColorpickerPlugin() {
|
|
|
2119
1942
|
containerEl.innerHTML = '';
|
|
2120
1943
|
const source = this.solidTemplate || _defaultSolidTpl;
|
|
2121
1944
|
const frag = source.content.cloneNode(true);
|
|
2122
|
-
// Uniquify
|
|
2123
|
-
// (e.g. the `color-space-menu` from the default template) so two
|
|
2124
|
-
// pickers on the same page don't share the same popover element.
|
|
1945
|
+
// Uniquify x-dropdown menu ids so two pickers don't share a popover.
|
|
2125
1946
|
uniquifyDropdownIdsIn(frag, this.pickerUid + '-solid');
|
|
2126
1947
|
containerEl.appendChild(frag);
|
|
2127
1948
|
|
|
2128
|
-
// Localize the default solid panel's chrome (the "Grab color" item).
|
|
2129
1949
|
this._applyUi(containerEl);
|
|
2130
1950
|
|
|
2131
1951
|
const refs = this._collectSolidRefs(containerEl);
|
|
@@ -2139,8 +1959,7 @@ function initializeColorpickerPlugin() {
|
|
|
2139
1959
|
return refs;
|
|
2140
1960
|
},
|
|
2141
1961
|
|
|
2142
|
-
// Mount the full gradient panel into
|
|
2143
|
-
// Uses <template x-colorpicker.gradient> if provided, otherwise the default.
|
|
1962
|
+
// Mount the full gradient panel (dev template or default) into a container.
|
|
2144
1963
|
_mountGradientInstance(containerEl) {
|
|
2145
1964
|
if (!containerEl) return;
|
|
2146
1965
|
containerEl.innerHTML = '';
|
|
@@ -2148,9 +1967,8 @@ function initializeColorpickerPlugin() {
|
|
|
2148
1967
|
const frag = source.content.cloneNode(true);
|
|
2149
1968
|
containerEl.appendChild(frag);
|
|
2150
1969
|
|
|
2151
|
-
// Alpine processes the inner x-colorpicker.* directives
|
|
2152
|
-
//
|
|
2153
|
-
// register with THIS state via ancestor traversal.
|
|
1970
|
+
// Alpine processes the inner x-colorpicker.* directives, which register
|
|
1971
|
+
// with THIS state via ancestor traversal.
|
|
2154
1972
|
if (window.Alpine?.initTree) Alpine.initTree(containerEl);
|
|
2155
1973
|
},
|
|
2156
1974
|
|
|
@@ -2176,7 +1994,7 @@ function initializeColorpickerPlugin() {
|
|
|
2176
1994
|
let dragging = false;
|
|
2177
1995
|
let cachedRect = null;
|
|
2178
1996
|
const pick = (e) => {
|
|
2179
|
-
//
|
|
1997
|
+
// Rect cached during drag, invalidated on pointerdown.
|
|
2180
1998
|
if (!cachedRect) cachedRect = refs.canvas.getBoundingClientRect();
|
|
2181
1999
|
const rect = cachedRect;
|
|
2182
2000
|
self.s = Math.max(0, Math.min(100, ((e.clientX - rect.left) / rect.width) * 100));
|
|
@@ -2190,7 +2008,7 @@ function initializeColorpickerPlugin() {
|
|
|
2190
2008
|
cachedRect = refs.canvas.getBoundingClientRect();
|
|
2191
2009
|
refs.wrapper.setPointerCapture(e.pointerId);
|
|
2192
2010
|
self._markUserChange(false);
|
|
2193
|
-
pick(e); // immediate on first click
|
|
2011
|
+
pick(e); // immediate on first click
|
|
2194
2012
|
});
|
|
2195
2013
|
refs.wrapper.addEventListener('pointermove', (e) => { if (dragging) throttledPick(e); });
|
|
2196
2014
|
refs.wrapper.addEventListener('pointerup', () => { dragging = false; cachedRect = null; });
|
|
@@ -2245,8 +2063,7 @@ function initializeColorpickerPlugin() {
|
|
|
2245
2063
|
// ---- Picker mount (after all children registered) ----
|
|
2246
2064
|
|
|
2247
2065
|
mount() {
|
|
2248
|
-
//
|
|
2249
|
-
// inject the full default UI. Templates alone don't count as "declared UI".
|
|
2066
|
+
// No declared UI (templates alone don't count) → inject the default UI.
|
|
2250
2067
|
const noDeclared = !this.solidTemplate && !this.layerTemplate && !this.gradientTemplate
|
|
2251
2068
|
&& this.solidInstances.length === 0 && this.gradientInstances.length === 0
|
|
2252
2069
|
&& !this.layersContainer && this.gradientValueInputs.length === 0
|
|
@@ -2256,23 +2073,16 @@ function initializeColorpickerPlugin() {
|
|
|
2256
2073
|
this._injectDefaultUI();
|
|
2257
2074
|
}
|
|
2258
2075
|
|
|
2259
|
-
// Auto-popovers register
|
|
2260
|
-
//
|
|
2261
|
-
// triggerBtn lookup misses. By mount-time (setTimeout 0) the swatch
|
|
2262
|
-
// wiring has finished, so re-query if we don't have one yet.
|
|
2076
|
+
// Auto-popovers register before the swatch hook sets x-dropdown on the
|
|
2077
|
+
// trigger, so the early lookup misses. Re-query now that wiring's done.
|
|
2263
2078
|
if (!this.triggerBtn && this.rootEl.id) {
|
|
2264
2079
|
this.triggerBtn = document.querySelector(`[x-dropdown="${this.rootEl.id}"]`);
|
|
2265
2080
|
}
|
|
2266
2081
|
|
|
2267
|
-
//
|
|
2268
|
-
//
|
|
2269
|
-
//
|
|
2270
|
-
//
|
|
2271
|
-
// 1. trigger swatch's x-model getter
|
|
2272
|
-
// 2. trigger swatch's paired hidden input (form-participation flow)
|
|
2273
|
-
// 3. trigger swatch's `value` attribute
|
|
2274
|
-
// 4. picker container's own hidden input child
|
|
2275
|
-
// 5. fallback '#000000'
|
|
2082
|
+
// Init from the first source with a real value (matches the swatch-click
|
|
2083
|
+
// retarget priority so value reads are correct from first paint):
|
|
2084
|
+
// 1. trigger x-model getter 2. trigger's hidden input
|
|
2085
|
+
// 3. trigger `value` attr 4. container hidden input 5. '#000000'
|
|
2276
2086
|
let initVal = '';
|
|
2277
2087
|
const tb = this.triggerBtn;
|
|
2278
2088
|
if (tb) {
|
|
@@ -2285,8 +2095,7 @@ function initializeColorpickerPlugin() {
|
|
|
2285
2095
|
if (!initVal && this.hiddenInput) initVal = this.hiddenInput.value;
|
|
2286
2096
|
this.setFromString(initVal || '#000000');
|
|
2287
2097
|
|
|
2288
|
-
// Seed trigger swatch CSS var so
|
|
2289
|
-
// correctly before any interaction.
|
|
2098
|
+
// Seed trigger swatch CSS var so border-color paints before interaction.
|
|
2290
2099
|
if (this.triggerBtn) this.triggerBtn.style.setProperty('--color-picker-swatch', this.toSwatchColor());
|
|
2291
2100
|
|
|
2292
2101
|
// Mount all solid-panel instances (Solid tab + any others)
|
|
@@ -2297,9 +2106,8 @@ function initializeColorpickerPlugin() {
|
|
|
2297
2106
|
}
|
|
2298
2107
|
this.solidTabRefs = firstSolidRefs;
|
|
2299
2108
|
|
|
2300
|
-
// Mount
|
|
2301
|
-
//
|
|
2302
|
-
// registers gradient-layers / layer-options / set-gradient-value with this state.
|
|
2109
|
+
// Mount gradient-panel instances (registers gradient-layers /
|
|
2110
|
+
// layer-options / set-gradient-value with this state).
|
|
2303
2111
|
for (const inst of this.gradientInstances) this._mountGradientInstance(inst);
|
|
2304
2112
|
|
|
2305
2113
|
// Render gradient layers (only if a container is declared)
|
|
@@ -2319,42 +2127,35 @@ function initializeColorpickerPlugin() {
|
|
|
2319
2127
|
// Initial sync
|
|
2320
2128
|
this.syncToInput();
|
|
2321
2129
|
|
|
2322
|
-
// Render the library — _doRenderLibrary clones the (optional) library template
|
|
2323
|
-
// into the container and expands nested group/palette/swatch templates in-place.
|
|
2324
2130
|
if (this.libraryContainers.length) this.renderLibrary();
|
|
2325
2131
|
|
|
2326
2132
|
// ---- Recent-list commit wiring ----
|
|
2327
|
-
// Seed the
|
|
2133
|
+
// Seed the baseline; popover pickers re-seed on toggle→open.
|
|
2328
2134
|
this._startCommitCycle();
|
|
2329
2135
|
|
|
2330
|
-
// Broad user-interaction detector
|
|
2331
|
-
//
|
|
2332
|
-
// controls (add-layer, set-angle, stop drags, textarea edits, etc.) without
|
|
2333
|
-
// having to instrument each handler. Library swatches are detected by
|
|
2334
|
-
// scope ancestry so a preset pick is correctly flagged as library-sourced.
|
|
2136
|
+
// Broad user-interaction detector — any pointerdown/input marks the cycle
|
|
2137
|
+
// touched (covers all controls). Library picks flagged by scope ancestry.
|
|
2335
2138
|
this.rootEl.addEventListener('pointerdown', (e) => {
|
|
2336
2139
|
const fromLibrary = !!e.target.closest('[x-data*="swatch:"]');
|
|
2337
2140
|
this._markUserChange(fromLibrary);
|
|
2338
2141
|
});
|
|
2339
2142
|
this.rootEl.addEventListener('input', () => {
|
|
2340
|
-
// 'input' on form controls = user typing / dragging sliders
|
|
2341
2143
|
this._markUserChange(false);
|
|
2342
2144
|
});
|
|
2343
2145
|
|
|
2344
2146
|
if (this.rootEl.hasAttribute('popover')) {
|
|
2345
|
-
// Popover
|
|
2147
|
+
// Popover: open/close are the commit boundaries
|
|
2346
2148
|
this.rootEl.addEventListener('toggle', (e) => {
|
|
2347
2149
|
if (e.newState === 'open') this._startCommitCycle();
|
|
2348
2150
|
if (e.newState === 'closed') {
|
|
2349
|
-
//
|
|
2350
|
-
//
|
|
2351
|
-
// short-circuit _commitToTrigger.
|
|
2151
|
+
// Commit to trigger FIRST — _tryCommitRecent resets the
|
|
2152
|
+
// user-change flag, which would short-circuit _commitToTrigger.
|
|
2352
2153
|
this._commitToTrigger();
|
|
2353
2154
|
this._tryCommitRecent();
|
|
2354
2155
|
}
|
|
2355
2156
|
});
|
|
2356
2157
|
} else {
|
|
2357
|
-
// Inline
|
|
2158
|
+
// Inline: commit when focus leaves the picker
|
|
2358
2159
|
this.rootEl.addEventListener('focusout', (e) => {
|
|
2359
2160
|
const moved = e.relatedTarget;
|
|
2360
2161
|
if (!moved || !this.rootEl.contains(moved)) {
|
|
@@ -2412,17 +2213,15 @@ function initializeColorpickerPlugin() {
|
|
|
2412
2213
|
}
|
|
2413
2214
|
}
|
|
2414
2215
|
|
|
2415
|
-
// Localize
|
|
2416
|
-
// resolved label rather than the English default.
|
|
2216
|
+
// Localize before initTree so x-tooltip caches the resolved label.
|
|
2417
2217
|
this._applyUi(this.rootEl);
|
|
2418
2218
|
|
|
2419
|
-
//
|
|
2420
|
-
//
|
|
2421
|
-
// against THIS state (since ancestor traversal finds this.rootEl).
|
|
2219
|
+
// initTree fires the injected x-* directives, registering the templates
|
|
2220
|
+
// and instances against THIS state via ancestor traversal.
|
|
2422
2221
|
if (window.Alpine?.initTree) {
|
|
2423
2222
|
Alpine.initTree(this.rootEl);
|
|
2424
2223
|
}
|
|
2425
|
-
// Stash the auto-injected tab
|
|
2224
|
+
// Stash the auto-injected tab wrapper for _syncPickerModeFromTab.
|
|
2426
2225
|
this._autoTabScope = this.rootEl.firstElementChild;
|
|
2427
2226
|
},
|
|
2428
2227
|
|
|
@@ -2431,10 +2230,8 @@ function initializeColorpickerPlugin() {
|
|
|
2431
2230
|
const autoTab = this._autoTabScope?._x_dataStack?.[0]?.tab;
|
|
2432
2231
|
const tab = rootTab || autoTab;
|
|
2433
2232
|
if (!tab) return;
|
|
2434
|
-
// Only Solid/Gradient tabs drive
|
|
2435
|
-
//
|
|
2436
|
-
// editing a gradient would silently demote the picker to solid, breaking
|
|
2437
|
-
// the "active" indicator on gradient Recent swatches.
|
|
2233
|
+
// Only Solid/Gradient tabs drive edit mode; Library preserves it (else
|
|
2234
|
+
// opening Library mid-gradient would demote to solid).
|
|
2438
2235
|
let newMode = null;
|
|
2439
2236
|
if (tab === 'gradient') newMode = 'gradient';
|
|
2440
2237
|
else if (tab === 'solid') newMode = 'solid';
|
|
@@ -2453,10 +2250,8 @@ function initializeColorpickerPlugin() {
|
|
|
2453
2250
|
|
|
2454
2251
|
// ---- Public API exposed via $colorpicker magic ----
|
|
2455
2252
|
|
|
2456
|
-
//
|
|
2457
|
-
//
|
|
2458
|
-
// read these getters re-runs — and only then do we compute the value.
|
|
2459
|
-
// Zero computation if nothing is bound.
|
|
2253
|
+
// track() reads snapshot.version to register a reactive dep; syncToInput
|
|
2254
|
+
// bumps it, re-running bound effects. Zero computation if nothing is bound.
|
|
2460
2255
|
const track = () => state.snapshot.version;
|
|
2461
2256
|
state.api = {
|
|
2462
2257
|
// Reactive reads — lazily computed on demand
|
|
@@ -2470,10 +2265,8 @@ function initializeColorpickerPlugin() {
|
|
|
2470
2265
|
get format() { track(); return state.format; },
|
|
2471
2266
|
get pickerMode() { track(); return state.pickerMode; },
|
|
2472
2267
|
|
|
2473
|
-
// Default string coercion → current CSS value.
|
|
2474
|
-
//
|
|
2475
|
-
// x-text="$colorpicker('id')"
|
|
2476
|
-
// and get the color string directly without picking a specific property.
|
|
2268
|
+
// Default string coercion → current CSS value (e.g. x-text or :style
|
|
2269
|
+
// interpolation of $colorpicker('id') without naming a property).
|
|
2477
2270
|
[Symbol.toPrimitive]() { track(); return state.toFormattedString(); },
|
|
2478
2271
|
toString() { track(); return state.toFormattedString(); },
|
|
2479
2272
|
valueOf() { track(); return state.toFormattedString(); },
|
|
@@ -2543,8 +2336,8 @@ function initializeColorpickerPlugin() {
|
|
|
2543
2336
|
return name.replace(/\./g, '\\.');
|
|
2544
2337
|
}
|
|
2545
2338
|
|
|
2546
|
-
// Rewrite x-dropdown*
|
|
2547
|
-
//
|
|
2339
|
+
// Rewrite x-dropdown* triggers + matching <menu id> in a clone so clones don't
|
|
2340
|
+
// share popover IDs.
|
|
2548
2341
|
function uniquifyDropdownIdsIn(root, suffix) {
|
|
2549
2342
|
const attrs = ['x-dropdown', 'x-dropdown.context', 'x-dropdown.hover'];
|
|
2550
2343
|
for (const attr of attrs) {
|
|
@@ -2564,8 +2357,7 @@ function initializeColorpickerPlugin() {
|
|
|
2564
2357
|
}
|
|
2565
2358
|
}
|
|
2566
2359
|
|
|
2567
|
-
// Coalesce rapid-fire calls
|
|
2568
|
-
// The latest args win. Essential for keeping the main thread responsive on busy devices.
|
|
2360
|
+
// Coalesce rapid-fire calls into at most one per animation frame; latest args win.
|
|
2569
2361
|
function rafThrottle(fn) {
|
|
2570
2362
|
let scheduled = false;
|
|
2571
2363
|
let lastArgs;
|
|
@@ -2582,9 +2374,8 @@ function initializeColorpickerPlugin() {
|
|
|
2582
2374
|
};
|
|
2583
2375
|
}
|
|
2584
2376
|
|
|
2585
|
-
// JSON-serialize
|
|
2586
|
-
//
|
|
2587
|
-
// already valid JS literal syntax that Alpine can parse directly.
|
|
2377
|
+
// JSON-serialize for an Alpine x-data attribute — setAttribute doesn't decode
|
|
2378
|
+
// entities, so JSON is directly parseable, no escaping needed.
|
|
2588
2379
|
function _jsonStringifyForAlpine(v) {
|
|
2589
2380
|
try { return JSON.stringify(v); } catch { return '{}'; }
|
|
2590
2381
|
}
|
|
@@ -2625,15 +2416,12 @@ function initializeColorpickerPlugin() {
|
|
|
2625
2416
|
Alpine.directive('colorpicker', (el, { modifiers, expression }, { cleanup, evaluateLater }) => {
|
|
2626
2417
|
// Root: no modifiers
|
|
2627
2418
|
if (!modifiers || modifiers.length === 0) {
|
|
2628
|
-
// <template x-colorpicker> →
|
|
2629
|
-
//
|
|
2630
|
-
// would otherwise auto-create an empty popover instead clones from
|
|
2631
|
-
// this template. Only one default per page; first declaration wins.
|
|
2419
|
+
// <template x-colorpicker> → page-wide default; bare swatches clone
|
|
2420
|
+
// from it instead of auto-creating an empty popover. First wins.
|
|
2632
2421
|
if (el.tagName === 'TEMPLATE') {
|
|
2633
2422
|
if (expression || el.id) {
|
|
2634
|
-
// Id-keyed templates
|
|
2635
|
-
//
|
|
2636
|
-
// wrap it in a Manifest HTML component for reuse.
|
|
2423
|
+
// Id-keyed templates no longer supported — use an inline element
|
|
2424
|
+
// with the same id, or an HTML component.
|
|
2637
2425
|
try { console.warn('[colorpicker] Id-keyed <template x-colorpicker> is no longer supported. Use a live inline element with the same id, or wrap the picker in an HTML component.'); } catch {}
|
|
2638
2426
|
return;
|
|
2639
2427
|
}
|
|
@@ -2644,15 +2432,14 @@ function initializeColorpickerPlugin() {
|
|
|
2644
2432
|
const state = createPickerState(el);
|
|
2645
2433
|
el._colorpickerState = state;
|
|
2646
2434
|
|
|
2647
|
-
// Panel-list expression
|
|
2648
|
-
//
|
|
2649
|
-
// the default UI's tabs and panels. Anything else is ignored.
|
|
2435
|
+
// Panel-list expression x-colorpicker="['solid','gradient']" —
|
|
2436
|
+
// filters + reorders the default UI's tabs/panels.
|
|
2650
2437
|
state.allowedPanels = parsePanelsExpression(expression);
|
|
2651
2438
|
|
|
2652
2439
|
// Find the form-participation input
|
|
2653
2440
|
state.hiddenInput = el.querySelector('input[type=color], input[type=hidden]');
|
|
2654
2441
|
|
|
2655
|
-
// Find trigger button (
|
|
2442
|
+
// Find trigger button (x-dropdown pointing at this element's ID)
|
|
2656
2443
|
const id = el.id;
|
|
2657
2444
|
state.triggerBtn = id ? document.querySelector(`[x-dropdown="${id}"]`) : null;
|
|
2658
2445
|
if (state.triggerBtn) {
|
|
@@ -2661,10 +2448,8 @@ function initializeColorpickerPlugin() {
|
|
|
2661
2448
|
});
|
|
2662
2449
|
}
|
|
2663
2450
|
|
|
2664
|
-
// Defer mount so
|
|
2665
|
-
//
|
|
2666
|
-
// backgrounded or rAF is throttled — mount must happen for the
|
|
2667
|
-
// picker to work, and it doesn't need to be sync'd with paint.
|
|
2451
|
+
// Defer mount so child directives register first. setTimeout (not rAF)
|
|
2452
|
+
// so it still fires when backgrounded.
|
|
2668
2453
|
setTimeout(() => state.mount(), 0);
|
|
2669
2454
|
|
|
2670
2455
|
cleanup(() => {
|
|
@@ -2677,20 +2462,15 @@ function initializeColorpickerPlugin() {
|
|
|
2677
2462
|
// Child hook
|
|
2678
2463
|
const role = modifiers[0];
|
|
2679
2464
|
|
|
2680
|
-
// Swatches
|
|
2681
|
-
// We
|
|
2682
|
-
// owns popover mechanics, anchor positioning, and transitions.
|
|
2465
|
+
// Swatches trigger OUTSIDE any picker — handle before the ancestor check.
|
|
2466
|
+
// We assign IDs + the popover tag; x-dropdown owns popover mechanics.
|
|
2683
2467
|
if (role === 'swatch') {
|
|
2684
2468
|
if (el._cpSwatchWired) return; // guard against re-firing via initTree
|
|
2685
2469
|
el._cpSwatchWired = true;
|
|
2686
2470
|
|
|
2687
2471
|
// ---- Optional x-model binding ----
|
|
2688
|
-
//
|
|
2689
|
-
//
|
|
2690
|
-
// • reactively shows the model value as the swatch background (via CSS var)
|
|
2691
|
-
// • exposes a read accessor the picker uses on open to load the value
|
|
2692
|
-
// • exposes a write accessor the picker uses on close to persist changes
|
|
2693
|
-
// The dev can still apply inline style / class overrides on top.
|
|
2472
|
+
// x-model is the swatch's source of truth: reactive background preview,
|
|
2473
|
+
// plus read (open) / write (close) accessors the picker uses.
|
|
2694
2474
|
const modelExpr = el.getAttribute('x-model');
|
|
2695
2475
|
if (modelExpr && window.Alpine?.evaluateLater && window.Alpine?.effect) {
|
|
2696
2476
|
try {
|
|
@@ -2705,9 +2485,7 @@ function initializeColorpickerPlugin() {
|
|
|
2705
2485
|
});
|
|
2706
2486
|
});
|
|
2707
2487
|
el._cpModelGetter = (cb) => readFn(cb);
|
|
2708
|
-
// Writer:
|
|
2709
|
-
// ensures the value is safely serialized (color strings + gradient CSS
|
|
2710
|
-
// are all JSON-safe).
|
|
2488
|
+
// Writer: `<modelExpr> = <JSON value>` (color/gradient strings are JSON-safe).
|
|
2711
2489
|
el._cpModelSetter = (v) => {
|
|
2712
2490
|
try { Alpine.evaluate(el, `${modelExpr} = ${JSON.stringify(v)}`); } catch {}
|
|
2713
2491
|
};
|
|
@@ -2715,21 +2493,16 @@ function initializeColorpickerPlugin() {
|
|
|
2715
2493
|
}
|
|
2716
2494
|
|
|
2717
2495
|
// ---- Initial color via `value` attribute ----
|
|
2718
|
-
//
|
|
2719
|
-
//
|
|
2720
|
-
// and the swatch's CSS var so the border-color derivation paints
|
|
2721
|
-
// correctly before any interaction.
|
|
2496
|
+
// Mirrors native <input type=color>: seeds the picker on first open and
|
|
2497
|
+
// the swatch CSS var so border-color paints before interaction.
|
|
2722
2498
|
const valueAttr = el.getAttribute('value');
|
|
2723
2499
|
if (valueAttr && !el.style.getPropertyValue('--color-picker-swatch')) {
|
|
2724
2500
|
el.style.setProperty('--color-picker-swatch', valueAttr);
|
|
2725
2501
|
}
|
|
2726
2502
|
|
|
2727
2503
|
// ---- Form participation via `name` attribute ----
|
|
2728
|
-
//
|
|
2729
|
-
//
|
|
2730
|
-
// already in the DOM). syncToInput then writes the picker's hex
|
|
2731
|
-
// value to it, dispatching input/change events for form code.
|
|
2732
|
-
// No `name` → no synthesized input — purely decorative swatch.
|
|
2504
|
+
// With `name=`, synthesize (or adopt) a sibling <input type=hidden>;
|
|
2505
|
+
// syncToInput writes the value + dispatches input/change. No name → decorative.
|
|
2733
2506
|
const nameAttr = el.getAttribute('name');
|
|
2734
2507
|
if (nameAttr) {
|
|
2735
2508
|
let hidden = el.parentElement?.querySelector?.(
|
|
@@ -2744,8 +2517,7 @@ function initializeColorpickerPlugin() {
|
|
|
2744
2517
|
el._cpSynthesizedHidden = hidden;
|
|
2745
2518
|
}
|
|
2746
2519
|
el._cpHiddenInput = hidden;
|
|
2747
|
-
// Drop `name` from the
|
|
2748
|
-
// both the (typically empty) button and the hidden input.
|
|
2520
|
+
// Drop `name` from the button so the form doesn't submit it too.
|
|
2749
2521
|
if (el.tagName === 'BUTTON') el.removeAttribute('name');
|
|
2750
2522
|
}
|
|
2751
2523
|
|
|
@@ -2758,11 +2530,9 @@ function initializeColorpickerPlugin() {
|
|
|
2758
2530
|
const wireSwatchTo = (target) => {
|
|
2759
2531
|
if (!target) return;
|
|
2760
2532
|
|
|
2761
|
-
// Alias the
|
|
2762
|
-
//
|
|
2763
|
-
//
|
|
2764
|
-
// and the consumer effect's tracked dep on `_pickerRegistry['<swatch-id>']`
|
|
2765
|
-
// would never fire). Run after mount so the api exists.
|
|
2533
|
+
// Alias the api under the swatch's id so $colorpicker('<swatch-id>')
|
|
2534
|
+
// tracks the right key (auto-popover otherwise registers under
|
|
2535
|
+
// colorpicker-swatch-N). Runs after mount so the api exists.
|
|
2766
2536
|
const aliasToSwatchId = () => {
|
|
2767
2537
|
if (!el.id) return;
|
|
2768
2538
|
const st = target._colorpickerState
|
|
@@ -2774,12 +2544,9 @@ function initializeColorpickerPlugin() {
|
|
|
2774
2544
|
|
|
2775
2545
|
const isDialog = target.tagName === 'DIALOG';
|
|
2776
2546
|
|
|
2777
|
-
//
|
|
2778
|
-
//
|
|
2779
|
-
//
|
|
2780
|
-
// <dialog> targets are NOT routed through x-dropdown — dialogs are
|
|
2781
|
-
// modal/centered surfaces, not anchored to a trigger. We open them
|
|
2782
|
-
// imperatively on click via showPopover() or showModal().
|
|
2547
|
+
// Non-dialog popover targets route through x-dropdown (anchored like
|
|
2548
|
+
// a dropdown). <dialog> targets open imperatively on click instead
|
|
2549
|
+
// (modal/centered, not trigger-anchored).
|
|
2783
2550
|
if (!isDialog
|
|
2784
2551
|
&& target.hasAttribute('popover')
|
|
2785
2552
|
&& !el.hasAttribute('popovertarget')
|
|
@@ -2794,6 +2561,7 @@ function initializeColorpickerPlugin() {
|
|
|
2794
2561
|
// dialog has a `popover` attribute (light-dismiss); otherwise
|
|
2795
2562
|
// open as a true modal with backdrop and focus trap.
|
|
2796
2563
|
if (isDialog) {
|
|
2564
|
+
// Popover-attr dialogs light-dismiss; others open as true modals.
|
|
2797
2565
|
e.preventDefault();
|
|
2798
2566
|
try {
|
|
2799
2567
|
if (target.hasAttribute('popover')) {
|
|
@@ -2805,9 +2573,8 @@ function initializeColorpickerPlugin() {
|
|
|
2805
2573
|
}
|
|
2806
2574
|
|
|
2807
2575
|
const retarget = () => {
|
|
2808
|
-
//
|
|
2809
|
-
//
|
|
2810
|
-
// as <dialog> hosting <div x-colorpicker> inside.
|
|
2576
|
+
// State lives on `target` or a descendant (e.g. <dialog> wrapping
|
|
2577
|
+
// <div x-colorpicker>).
|
|
2811
2578
|
let st = target._colorpickerState;
|
|
2812
2579
|
if (!st) {
|
|
2813
2580
|
const inner = target.querySelector?.('[x-colorpicker]');
|
|
@@ -2815,17 +2582,10 @@ function initializeColorpickerPlugin() {
|
|
|
2815
2582
|
}
|
|
2816
2583
|
if (!st) { setTimeout(retarget, 0); return; }
|
|
2817
2584
|
st.triggerBtn = el;
|
|
2818
|
-
// Route form
|
|
2819
|
-
// input (synthesized from `name`, or dev-supplied sibling).
|
|
2820
|
-
// Falls back to whatever the picker container already had
|
|
2821
|
-
// — preserves the existing inline-input flow.
|
|
2585
|
+
// Route form writes to this swatch's hidden input if it has one.
|
|
2822
2586
|
if (el._cpHiddenInput) st.hiddenInput = el._cpHiddenInput;
|
|
2823
|
-
// Load
|
|
2824
|
-
//
|
|
2825
|
-
// 2. paired hidden input value (form-participation flow)
|
|
2826
|
-
// 3. `value` attribute on the swatch
|
|
2827
|
-
// 4. --color-picker-swatch CSS var (auto / inline-style flow)
|
|
2828
|
-
// 5. fallback '#000000'
|
|
2587
|
+
// Load trigger value by priority: x-model getter, hidden input,
|
|
2588
|
+
// `value` attr, --color-picker-swatch var, then '#000000'.
|
|
2829
2589
|
if (el._cpModelGetter) {
|
|
2830
2590
|
el._cpModelGetter(v => { if (typeof v === 'string' && v.length) st.setFromString(v); });
|
|
2831
2591
|
} else {
|
|
@@ -2836,7 +2596,7 @@ function initializeColorpickerPlugin() {
|
|
|
2836
2596
|
|| '#000000';
|
|
2837
2597
|
if (current) st.setFromString(current);
|
|
2838
2598
|
}
|
|
2839
|
-
// Defer
|
|
2599
|
+
// Defer UI sync so the popover's entry transition runs unimpeded.
|
|
2840
2600
|
setTimeout(() => st.syncUI(), 0);
|
|
2841
2601
|
};
|
|
2842
2602
|
retarget();
|
|
@@ -2913,27 +2673,20 @@ function initializeColorpickerPlugin() {
|
|
|
2913
2673
|
|
|
2914
2674
|
case 'apply-color': {
|
|
2915
2675
|
el.addEventListener('click', () => {
|
|
2916
|
-
//
|
|
2917
|
-
// and the user is editing a gradient stop — CSS doesn't allow
|
|
2918
|
-
// gradients as color stop values).
|
|
2676
|
+
// Disabled = gradient swatch while editing a stop (can't nest gradients).
|
|
2919
2677
|
if (el.hasAttribute('disabled')) return;
|
|
2920
2678
|
const cs = window.getComputedStyle(el);
|
|
2921
2679
|
const raw = el.style.background || el.style.backgroundColor || cs.backgroundColor;
|
|
2922
2680
|
if (!raw) return;
|
|
2923
|
-
//
|
|
2924
|
-
// to record them as "recent" even if the picker closes afterwards.
|
|
2681
|
+
// Mark library clicks so the commit cycle won't record them as recent.
|
|
2925
2682
|
const fromLibrary = !!el.closest('[x-data*="swatch:"]');
|
|
2926
2683
|
const fromStopMenu = !!el.closest('menu[id^="stop-context-menu"]');
|
|
2927
2684
|
state._markUserChange(fromLibrary);
|
|
2928
2685
|
|
|
2929
|
-
// Top-level library picks
|
|
2930
|
-
// the
|
|
2931
|
-
//
|
|
2932
|
-
//
|
|
2933
|
-
// stop's color. Stop-menu picks intentionally write to the right-
|
|
2934
|
-
// clicked stop and stay in gradient mode.
|
|
2935
|
-
// Use _setPickerMode (not _switchToSolidMode) — we don't want to
|
|
2936
|
-
// flip the user off whatever tab they're on (typically Library).
|
|
2686
|
+
// Top-level library picks replace the whole field — switch mode to
|
|
2687
|
+
// match the swatch's value type. Stop-menu picks stay in gradient
|
|
2688
|
+
// mode (they target the right-clicked stop). _setPickerMode keeps
|
|
2689
|
+
// the user on their current tab.
|
|
2937
2690
|
const valueIsGradient = raw.includes('gradient(');
|
|
2938
2691
|
if (fromLibrary && !fromStopMenu) {
|
|
2939
2692
|
state._setPickerMode(valueIsGradient ? 'gradient' : 'solid');
|
|
@@ -2945,10 +2698,8 @@ function initializeColorpickerPlugin() {
|
|
|
2945
2698
|
}
|
|
2946
2699
|
|
|
2947
2700
|
case 'remove-recent': {
|
|
2948
|
-
//
|
|
2949
|
-
//
|
|
2950
|
-
// triggering element on `menu._triggerEl`. We read its `data-cp-value`
|
|
2951
|
-
// (the raw stored form) and remove that entry from the Recent cookie.
|
|
2701
|
+
// Menu item in a Recent swatch's context menu. The dropdowns plugin
|
|
2702
|
+
// stashes the trigger on menu._triggerEl; read its data-cp-value to remove.
|
|
2952
2703
|
el.addEventListener('click', () => {
|
|
2953
2704
|
const menu = el.closest('[popover]');
|
|
2954
2705
|
const trigger = menu?._triggerEl || menu?._triggerHost;
|
|
@@ -2970,9 +2721,7 @@ function initializeColorpickerPlugin() {
|
|
|
2970
2721
|
case 'delete-stop':
|
|
2971
2722
|
case 'set-gradient-type': {
|
|
2972
2723
|
el.addEventListener('click', () => {
|
|
2973
|
-
//
|
|
2974
|
-
// element; a disabled menu item shouldn't fire its action.
|
|
2975
|
-
if (el.hasAttribute('disabled')) return;
|
|
2724
|
+
if (el.hasAttribute('disabled')) return; // respect :disabled bindings
|
|
2976
2725
|
const ctx = findLayerContext(el);
|
|
2977
2726
|
const li = ctx ? ctx.layerIndex : state.activeLayerIndex;
|
|
2978
2727
|
switch (role) {
|
|
@@ -2987,7 +2736,6 @@ function initializeColorpickerPlugin() {
|
|
|
2987
2736
|
case 'duplicate-stop': state.duplicateStop(li, state.activeStopIndex); break;
|
|
2988
2737
|
case 'delete-stop': state.deleteStop(li, state.activeStopIndex); break;
|
|
2989
2738
|
case 'set-gradient-type': {
|
|
2990
|
-
// Value comes from the expression (Alpine parsed) or the attribute
|
|
2991
2739
|
const type = expression || el.getAttribute('x-colorpicker.set-gradient-type');
|
|
2992
2740
|
state.setGradientType(li, (type || '').replace(/['"]/g, ''));
|
|
2993
2741
|
break;
|
|
@@ -3044,27 +2792,17 @@ function initializeColorpickerPlugin() {
|
|
|
3044
2792
|
});
|
|
3045
2793
|
return;
|
|
3046
2794
|
|
|
3047
|
-
// Solid-tab controls
|
|
3048
|
-
// If the developer places them OUTSIDE a solid-panel instance, wire individually:
|
|
2795
|
+
// Solid-tab controls — normally handled inside _mountSolidInstance.
|
|
3049
2796
|
case 'set-canvas':
|
|
3050
2797
|
case 'set-hue':
|
|
3051
2798
|
case 'set-alpha':
|
|
3052
2799
|
case 'set-alpha-value':
|
|
3053
2800
|
case 'set-color-value': {
|
|
3054
|
-
// These are typically inside a solid-panel template/instance.
|
|
3055
|
-
// (The usual path handles them inside _mountSolidInstance.)
|
|
3056
2801
|
return;
|
|
3057
2802
|
}
|
|
3058
2803
|
|
|
3059
|
-
// set-color-space
|
|
3060
|
-
//
|
|
3061
|
-
// click sets that format. Tracked so we can toggle .active on the
|
|
3062
|
-
// current choice.
|
|
3063
|
-
// • Without expression (`<button x-colorpicker.set-color-space>`) →
|
|
3064
|
-
// reactive label whose text reflects the active format. Useful as
|
|
3065
|
-
// a dropdown trigger that shows the current format.
|
|
3066
|
-
// • <select x-colorpicker.set-color-space> still works through the
|
|
3067
|
-
// legacy _wireSolidControls flow inside a solid-panel instance.
|
|
2804
|
+
// set-color-space: with expression = format choice (click sets + tracks
|
|
2805
|
+
// for .active); without = reactive label; <select> uses the legacy flow.
|
|
3068
2806
|
case 'set-color-space': {
|
|
3069
2807
|
if (el.tagName === 'SELECT') return; // legacy flow handles it
|
|
3070
2808
|
const raw = (expression || '').replace(/['"`]/g, '').trim().toLowerCase();
|
|
@@ -3087,21 +2825,16 @@ function initializeColorpickerPlugin() {
|
|
|
3087
2825
|
|
|
3088
2826
|
case 'library': {
|
|
3089
2827
|
if (el.tagName === 'TEMPLATE') {
|
|
3090
|
-
// Dev-defined library layout
|
|
3091
|
-
// Nested <template x-colorpicker.library-group/palette/swatch> are resolved
|
|
3092
|
-
// in-place during rendering (x-for style).
|
|
2828
|
+
// Dev-defined library layout, cloned into the container at render time.
|
|
3093
2829
|
state.libraryTemplate = el;
|
|
3094
2830
|
} else {
|
|
3095
|
-
//
|
|
3096
|
-
//
|
|
3097
|
-
//
|
|
3098
|
-
// from the FIRST container wins as the data source; others reuse it.
|
|
2831
|
+
// Render container. Multiple are supported (tab + inline stop menus);
|
|
2832
|
+
// each gets its own clone. The first container's expression is the
|
|
2833
|
+
// data source; others reuse it.
|
|
3099
2834
|
if (!state.libraryContainers.includes(el)) {
|
|
3100
2835
|
state.libraryContainers.push(el);
|
|
3101
|
-
//
|
|
3102
|
-
//
|
|
3103
|
-
// (x-dropdown.context menu lookups, tooltips, etc.) aren't
|
|
3104
|
-
// torn down mid-init.
|
|
2836
|
+
// Post-mount: render only this new container so others' in-flight
|
|
2837
|
+
// directive inits aren't torn down mid-init.
|
|
3105
2838
|
if (state._mounted) state._renderIntoContainer(el);
|
|
3106
2839
|
}
|
|
3107
2840
|
if (expression && !state.libraryRootValue) state.libraryRootValue = expression;
|
|
@@ -3113,28 +2846,24 @@ function initializeColorpickerPlugin() {
|
|
|
3113
2846
|
case 'library-palette':
|
|
3114
2847
|
case 'library-swatch':
|
|
3115
2848
|
case 'library-recent-swatch': {
|
|
3116
|
-
// Nested templates —
|
|
3117
|
-
//
|
|
3118
|
-
// `library-recent-swatch` is an optional alternate template used only for
|
|
3119
|
-
// swatches inside the Recent group (typically wires up x-dropdown.context).
|
|
2849
|
+
// Nested templates — resolved via querySelector at render time, no
|
|
2850
|
+
// registration. library-recent-swatch is an optional Recent-only variant.
|
|
3120
2851
|
return;
|
|
3121
2852
|
}
|
|
3122
2853
|
|
|
3123
2854
|
}
|
|
3124
2855
|
});
|
|
3125
2856
|
|
|
3126
|
-
// $colorpicker —
|
|
3127
|
-
// (
|
|
2857
|
+
// $colorpicker — callable ($colorpicker('id')) and property-readable
|
|
2858
|
+
// ($colorpicker.hex, nearest ancestor picker).
|
|
3128
2859
|
Alpine.magic('colorpicker', (el) => {
|
|
3129
2860
|
const localState = findAncestorState(el);
|
|
3130
2861
|
const localApi = localState?.api || null;
|
|
3131
2862
|
|
|
3132
|
-
//
|
|
3133
|
-
//
|
|
3134
|
-
// picker is declared later in the DOM than its consumer.
|
|
2863
|
+
// $colorpicker('id') → that picker's API. Reactive registry read so it
|
|
2864
|
+
// resolves even when the picker is declared after its consumer.
|
|
3135
2865
|
const byId = (id) => {
|
|
3136
2866
|
if (!id) return localApi;
|
|
3137
|
-
// Reactive read: tracks the key even if not yet registered
|
|
3138
2867
|
const api = _pickerRegistry[id];
|
|
3139
2868
|
if (api) return api;
|
|
3140
2869
|
// Allow lookup by swatch button ID (resolve through its popovertarget)
|
|
@@ -3149,15 +2878,11 @@ function initializeColorpickerPlugin() {
|
|
|
3149
2878
|
|
|
3150
2879
|
return new Proxy(byId, {
|
|
3151
2880
|
get(fn, prop) {
|
|
3152
|
-
// Coerce `${$colorpicker}` (no call) to the local
|
|
2881
|
+
// Coerce `${$colorpicker}` (no call) to the local CSS string.
|
|
3153
2882
|
if (prop === Symbol.toPrimitive || prop === 'toString' || prop === 'valueOf') {
|
|
3154
2883
|
return () => (localApi ? localApi.css : '');
|
|
3155
2884
|
}
|
|
3156
|
-
// Global helpers
|
|
3157
|
-
// Each is BOTH callable AND spreadable:
|
|
3158
|
-
// {...$colorpicker.tailwind} → default English preset
|
|
3159
|
-
// $colorpicker.tailwind(labels) → localized preset (same values, translated names)
|
|
3160
|
-
// {...$colorpicker.tailwind(labels)} → spread the localized result
|
|
2885
|
+
// Global preset helpers — each both callable (localized) and spreadable.
|
|
3161
2886
|
if (prop === 'presets') return _makeCallablePreset(buildDefaultLibrary);
|
|
3162
2887
|
if (prop === 'tailwind') return _makeCallablePreset(buildTailwindPreset);
|
|
3163
2888
|
if (prop === 'ios') return _makeCallablePreset(buildIosPreset);
|
|
@@ -3175,32 +2900,19 @@ function initializeColorpickerPlugin() {
|
|
|
3175
2900
|
|
|
3176
2901
|
// ---- Picker resolution: inline / default-template / auto-generated ----
|
|
3177
2902
|
//
|
|
3178
|
-
// Two ways
|
|
3179
|
-
//
|
|
3180
|
-
//
|
|
3181
|
-
//
|
|
3182
|
-
// 2. Auto-created (bare): <button x-colorpicker.swatch> generates its own popover.
|
|
3183
|
-
// By default, the popover is filled with the plugin's
|
|
3184
|
-
// hardcoded fallback UI. Devs can override it page-wide
|
|
3185
|
-
// by adding a single `<template x-colorpicker>` (no id)
|
|
3186
|
-
// anywhere in the markup — the auto-creator clones from
|
|
3187
|
-
// that instead.
|
|
3188
|
-
//
|
|
3189
|
-
// For "componentize and reuse" use cases that previously needed an id-keyed template,
|
|
3190
|
-
// wrap the picker in a Manifest HTML component (`<x-my-picker>`) and drop it wherever
|
|
3191
|
-
// it's needed. This keeps the plugin's resolution model deliberately small.
|
|
2903
|
+
// Two ways to declare a picker: (1) a live inline element with x-colorpicker;
|
|
2904
|
+
// (2) a bare <button x-colorpicker.swatch> that auto-creates its own popover
|
|
2905
|
+
// (fallback UI, or a clone of the page-wide <template x-colorpicker> if present).
|
|
2906
|
+
// For reuse, wrap the picker in a Manifest HTML component.
|
|
3192
2907
|
|
|
3193
2908
|
let _defaultColorpickerTemplate = null; // <template x-colorpicker> (no id)
|
|
3194
2909
|
|
|
3195
2910
|
function registerDefaultColorpickerTemplate(tpl) {
|
|
3196
|
-
|
|
3197
|
-
if (!tpl || _defaultColorpickerTemplate) return;
|
|
2911
|
+
if (!tpl || _defaultColorpickerTemplate) return; // first wins
|
|
3198
2912
|
_defaultColorpickerTemplate = tpl;
|
|
3199
2913
|
}
|
|
3200
2914
|
|
|
3201
|
-
// Resolve a swatch's target by id
|
|
3202
|
-
// looked up by id anymore (use a `<menu id="X" x-colorpicker>` or wrap in an
|
|
3203
|
-
// HTML component for that pattern).
|
|
2915
|
+
// Resolve a swatch's target by id — inline elements only, never templates.
|
|
3204
2916
|
function resolvePickerById(id) {
|
|
3205
2917
|
if (!id) return null;
|
|
3206
2918
|
const live = document.getElementById(id);
|
|
@@ -3220,14 +2932,9 @@ function initializeColorpickerPlugin() {
|
|
|
3220
2932
|
function createSwatchPopover(customId, panelsExpr) {
|
|
3221
2933
|
const id = customId || nextAutoSwatchId();
|
|
3222
2934
|
|
|
3223
|
-
//
|
|
3224
|
-
//
|
|
3225
|
-
//
|
|
3226
|
-
// The dev's content inside the template is rendered verbatim (mount's
|
|
3227
|
-
// noDeclared check sees real children and skips _injectDefaultUI).
|
|
3228
|
-
// If the template exists in the DOM but its directive hasn't fired yet
|
|
3229
|
-
// (source-order race), scan for it now so swatches earlier in the tree
|
|
3230
|
-
// still pick it up.
|
|
2935
|
+
// Clone the default <template x-colorpicker> if registered (preserves the
|
|
2936
|
+
// dev's wrapper + content; mount skips _injectDefaultUI when it sees real
|
|
2937
|
+
// children). Scan for it now in case its directive hasn't fired yet (race).
|
|
3231
2938
|
if (!_defaultColorpickerTemplate) {
|
|
3232
2939
|
const candidates = document.querySelectorAll('template[x-colorpicker]');
|
|
3233
2940
|
for (const t of candidates) {
|
|
@@ -3255,8 +2962,7 @@ function initializeColorpickerPlugin() {
|
|
|
3255
2962
|
// No default template → empty <menu> populated by _injectDefaultUI on mount.
|
|
3256
2963
|
const menu = document.createElement('menu');
|
|
3257
2964
|
menu.setAttribute('popover', '');
|
|
3258
|
-
// Pass the panel-list expression through
|
|
3259
|
-
// so the auto-created popover only shows the panels the swatch requested.
|
|
2965
|
+
// Pass the panel-list expression through so the popover shows only requested panels.
|
|
3260
2966
|
menu.setAttribute('x-colorpicker', panelsExpr || '');
|
|
3261
2967
|
menu.id = id;
|
|
3262
2968
|
menu.className = 'colorpicker dropdown-menu';
|
|
@@ -3274,8 +2980,7 @@ function initializeColorpickerPlugin() {
|
|
|
3274
2980
|
const trimmed = expr.trim();
|
|
3275
2981
|
if (!trimmed.startsWith('[') || !trimmed.endsWith(']')) return null;
|
|
3276
2982
|
try {
|
|
3277
|
-
//
|
|
3278
|
-
// are simple string-array literals, so a quote swap is safe.
|
|
2983
|
+
// Simple string-array literals only, so a quote swap before JSON.parse is safe.
|
|
3279
2984
|
const arr = JSON.parse(trimmed.replace(/'/g, '"'));
|
|
3280
2985
|
if (!Array.isArray(arr)) return null;
|
|
3281
2986
|
const out = arr
|
|
@@ -3293,8 +2998,7 @@ function initializeColorpickerPlugin() {
|
|
|
3293
2998
|
// Track initialization
|
|
3294
2999
|
let colorpickerPluginInitialized = false;
|
|
3295
3000
|
|
|
3296
|
-
// True once Alpine
|
|
3297
|
-
// module load so we never miss the event, whatever the script order.
|
|
3001
|
+
// True once Alpine finished its initial DOM walk; listener bound at module load.
|
|
3298
3002
|
let colorpickerAlpineHasWalked = false;
|
|
3299
3003
|
document.addEventListener('alpine:initialized', () => { colorpickerAlpineHasWalked = true; });
|
|
3300
3004
|
|
|
@@ -3303,11 +3007,8 @@ function ensureColorpickerPluginInitialized() {
|
|
|
3303
3007
|
if (!window.Alpine || typeof window.Alpine.directive !== 'function') return;
|
|
3304
3008
|
colorpickerPluginInitialized = true;
|
|
3305
3009
|
initializeColorpickerPlugin();
|
|
3306
|
-
// Only walk existing
|
|
3307
|
-
//
|
|
3308
|
-
// Otherwise the directive is registered during `alpine:init` and Alpine's
|
|
3309
|
-
// one boot walk processes every element with all sibling directives present;
|
|
3310
|
-
// walking here during boot would drop nested plugin content.
|
|
3010
|
+
// Only walk existing subtrees ourselves when Alpine already finished its boot
|
|
3011
|
+
// walk (late load). Walking during boot would drop nested plugin content.
|
|
3311
3012
|
if (colorpickerAlpineHasWalked && typeof window.Alpine.initTree === 'function') {
|
|
3312
3013
|
document.querySelectorAll('[x-colorpicker]').forEach(el => { if (!el.__x) window.Alpine.initTree(el); });
|
|
3313
3014
|
}
|