@muten/shadcn 0.0.5 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +61 -6
- package/README.md +57 -4
- package/globals.css +30 -2
- package/kit.css +2763 -0
- package/motion.css +79 -0
- package/nova.css +1387 -0
- package/package.json +10 -2
- package/registry/accordion.muten +11 -10
- package/registry/agenda.js +35 -0
- package/registry/agenda.muten +10 -0
- package/registry/alert-dialog.muten +9 -12
- package/registry/alert.muten +8 -8
- package/registry/app-shell.js +9 -0
- package/registry/app-shell.muten +12 -0
- package/registry/attachment.muten +13 -9
- package/registry/avatar.muten +24 -5
- package/registry/badge.muten +23 -8
- package/registry/band.muten +16 -0
- package/registry/bar-chart.muten +7 -0
- package/registry/bell.muten +3 -0
- package/registry/bento.muten +6 -9
- package/registry/booking.js +21 -0
- package/registry/booking.muten +13 -0
- package/registry/breadcrumb.muten +9 -7
- package/registry/button-group.muten +13 -4
- package/registry/button.muten +21 -8
- package/registry/calendar.js +28 -93
- package/registry/calendar.muten +9 -8
- package/registry/canvas-viewport.js +12 -5
- package/registry/canvas-viewport.muten +9 -9
- package/registry/card-field.muten +4 -0
- package/registry/card.muten +12 -8
- package/registry/carousel.js +5 -36
- package/registry/carousel.muten +12 -5
- package/registry/chatbot.muten +9 -0
- package/registry/checklist.muten +6 -0
- package/registry/choice-list.muten +16 -0
- package/registry/code.js +14 -12
- package/registry/code.muten +6 -7
- package/registry/collapsible.muten +14 -10
- package/registry/color-picker.js +460 -41
- package/registry/color-picker.muten +7 -6
- package/registry/combobox.js +5 -0
- package/registry/combobox.muten +6 -23
- package/registry/command-palette.muten +8 -12
- package/registry/command.js +32 -0
- package/registry/command.muten +32 -11
- package/registry/context-menu.muten +9 -8
- package/registry/count-up.muten +5 -10
- package/registry/date-field.js +5 -0
- package/registry/date-field.muten +7 -0
- package/registry/device-frame.muten +8 -0
- package/registry/dialog.muten +6 -12
- package/registry/dirty-bar.js +19 -0
- package/registry/dirty-bar.muten +13 -0
- package/registry/drawer.muten +6 -11
- package/registry/dropdown-menu.muten +37 -13
- package/registry/empty.muten +11 -7
- package/registry/feature-grid.muten +10 -12
- package/registry/feature-rows.muten +12 -8
- package/registry/field-behavior.js +33 -0
- package/registry/field-behavior.muten +9 -0
- package/registry/field.muten +26 -7
- package/registry/file-input.js +36 -21
- package/registry/file-input.muten +8 -7
- package/registry/fixed-card.muten +25 -0
- package/registry/folder-picker.js +13 -15
- package/registry/folder-picker.muten +5 -8
- package/registry/gradient-blinds.muten +6 -13
- package/registry/hover-card.muten +7 -9
- package/registry/image-upload.js +5 -0
- package/registry/image-upload.muten +8 -0
- package/registry/indicator.muten +5 -0
- package/registry/input-group.muten +27 -4
- package/registry/input-otp.js +8 -35
- package/registry/input-otp.muten +6 -6
- package/registry/input.muten +8 -7
- package/registry/inspector.muten +13 -15
- package/registry/island.js +7 -0
- package/registry/island.muten +5 -0
- package/registry/item.muten +20 -7
- package/registry/kbd.muten +3 -3
- package/registry/kit/board.js +300 -0
- package/registry/kit/collections.js +148 -0
- package/registry/kit/core.js +989 -0
- package/registry/kit/dates.js +106 -0
- package/registry/kit/menus.js +90 -0
- package/registry/kit/overlays.js +114 -0
- package/registry/kit/paint.js +211 -0
- package/registry/kit/qrcode.js +2291 -0
- package/registry/kit/structure.js +393 -0
- package/registry/kit/table.js +198 -0
- package/registry/kit/values.js +179 -0
- package/registry/kit-layer.js +2 -0
- package/registry/kit-layer.muten +5 -0
- package/registry/kit-skeleton.js +24 -0
- package/registry/kit-skeleton.muten +16 -0
- package/registry/kit-widget.js +79 -0
- package/registry/kit-widget.muten +4 -0
- package/registry/link-btn.muten +9 -7
- package/registry/live-stack.js +29 -0
- package/registry/live-stack.muten +7 -0
- package/registry/logo-cloud.muten +6 -11
- package/registry/member-list.muten +6 -0
- package/registry/menubar.muten +10 -14
- package/registry/message-scroller.js +12 -0
- package/registry/message-scroller.muten +8 -4
- package/registry/message.muten +27 -10
- package/registry/messaging.muten +11 -0
- package/registry/modal-behavior.js +56 -0
- package/registry/modal-behavior.muten +4 -0
- package/registry/modal.muten +29 -0
- package/registry/money-field.js +5 -0
- package/registry/money-field.muten +7 -0
- package/registry/nav-button.muten +14 -0
- package/registry/navigation-menu.muten +16 -14
- package/registry/navpill.muten +15 -9
- package/registry/new-appointment.js +8 -0
- package/registry/new-appointment.muten +10 -0
- package/registry/number-field.js +34 -24
- package/registry/number-field.muten +8 -6
- package/registry/order-board.js +6 -0
- package/registry/order-board.muten +21 -0
- package/registry/overlay-behavior.js +13 -0
- package/registry/overlay-behavior.muten +4 -0
- package/registry/pagination-nav.js +6 -0
- package/registry/pagination-nav.muten +8 -0
- package/registry/pagination.muten +12 -9
- package/registry/password-field.muten +3 -0
- package/registry/permission-matrix.muten +6 -0
- package/registry/phone-field.js +5 -0
- package/registry/phone-field.muten +7 -0
- package/registry/phone-shell.js +7 -0
- package/registry/phone-shell.muten +9 -0
- package/registry/pill.muten +6 -5
- package/registry/plans.muten +4 -0
- package/registry/popover.muten +16 -9
- package/registry/pricing.muten +21 -21
- package/registry/progress.muten +5 -5
- package/registry/queue.muten +3 -0
- package/registry/radio-group.muten +16 -8
- package/registry/range-slider.js +9 -24
- package/registry/range-slider.muten +4 -5
- package/registry/resizable.js +34 -27
- package/registry/resizable.muten +16 -5
- package/registry/roving-behavior.js +16 -0
- package/registry/roving-behavior.muten +3 -0
- package/registry/row-menu.js +5 -0
- package/registry/row-menu.muten +5 -0
- package/registry/save-button.muten +11 -0
- package/registry/scale-slider.js +7 -0
- package/registry/scale-slider.muten +9 -0
- package/registry/scroll-area.muten +8 -4
- package/registry/segmented-control.muten +8 -8
- package/registry/separator.muten +3 -3
- package/registry/share-button.js +7 -0
- package/registry/share-button.muten +10 -0
- package/registry/sheet.muten +6 -10
- package/registry/sidebar.muten +23 -15
- package/registry/skeleton.muten +6 -4
- package/registry/slider.js +73 -27
- package/registry/slider.muten +6 -6
- package/registry/sortable-behavior.js +43 -0
- package/registry/sortable-behavior.muten +2 -0
- package/registry/sortable-list.js +10 -26
- package/registry/sortable-list.muten +6 -7
- package/registry/sortable.muten +15 -0
- package/registry/spark.js +14 -0
- package/registry/spark.muten +3 -0
- package/registry/spinner.muten +4 -3
- package/registry/split-button.muten +12 -10
- package/registry/stamp-card.muten +7 -0
- package/registry/stat-band.muten +6 -9
- package/registry/stat.muten +23 -0
- package/registry/status-bar.muten +7 -6
- package/registry/stepper.muten +17 -14
- package/registry/store-qr.js +5 -0
- package/registry/store-qr.muten +5 -0
- package/registry/swatch.muten +8 -8
- package/registry/switch.muten +14 -6
- package/registry/table-view.muten +10 -0
- package/registry/tabs-behavior.js +27 -0
- package/registry/tabs-behavior.muten +3 -0
- package/registry/tabs.muten +19 -10
- package/registry/tag-input.js +4 -27
- package/registry/tag-input.muten +6 -6
- package/registry/text.muten +7 -0
- package/registry/time-field.js +5 -0
- package/registry/time-field.muten +6 -0
- package/registry/timeline.muten +14 -14
- package/registry/toaster.js +34 -22
- package/registry/toaster.muten +10 -8
- package/registry/toggle-group.muten +11 -6
- package/registry/toggle.muten +8 -5
- package/registry/toolbar.muten +11 -6
- package/registry/tooltip.muten +6 -9
- package/registry/tree-view.js +27 -7
- package/registry/tree-view.muten +8 -13
- package/registry/vector-field.muten +9 -8
- package/registry/virtual-list.js +5 -0
- package/registry/virtual-list.muten +6 -0
- package/registry/weekly-hours.muten +4 -0
- package/registry/wordline.muten +6 -0
- package/registry/zoom-control.muten +9 -9
- package/registry.json +868 -424
- package/themes/bloom.css +53 -0
- package/themes/editorial.css +65 -0
- package/themes/noir.css +54 -0
- package/registry/chart.js +0 -34
- package/registry/chart.muten +0 -7
- package/registry/checkbox.muten +0 -8
- package/registry/context-menu.js +0 -34
- package/registry/select.muten +0 -21
package/registry/color-picker.js
CHANGED
|
@@ -1,48 +1,467 @@
|
|
|
1
|
-
// ColorPicker
|
|
2
|
-
//
|
|
1
|
+
// ColorPicker (muten Custom) - Storio's one colour picker (solid; gradients and animations when allowed), as the
|
|
2
|
+
// reference artifact mounts it. The value is paint text (a hex, or e.g. "linear 150 | #1a0f0a 0 | #c2410c 100 ~flow 14").
|
|
3
|
+
// THE APP'S ONE COLOUR PICKER. Every place where somebody picks a colour uses this component (through the
|
|
4
|
+
// ColorPick / PaintPick parts), so it looks and behaves the same everywhere and a fix lands everywhere.
|
|
5
|
+
//
|
|
6
|
+
// Closed it is a small swatch showing the real colour or gradient (plus a dot when it is animated). Open it
|
|
7
|
+
// is a popover (a bottom sheet on a phone) with:
|
|
8
|
+
// · a hue × lightness map where every colour is a draggable point, and a SATURATION slider for the
|
|
9
|
+
// selected point only (it is not opacity: the slider says which point it acts on);
|
|
10
|
+
// · the hex code, an eyedropper where the browser has one, and a trash can to delete a gradient stop;
|
|
11
|
+
// · OPTIONALLY, gradients: a bar of up to 4 stops (tap the bar to add one, drag a stop along it, drag it
|
|
12
|
+
// off the bar / press Delete / use the trash can to remove it), linear · radial · conic with an angle
|
|
13
|
+
// dial, and animations (flow, breathe, spin, hue) with a speed.
|
|
14
|
+
//
|
|
15
|
+
// Gradients are OPT-IN: not every colour in the app can be a gradient. `modes: "solid"` (the default) is a
|
|
16
|
+
// plain colour picker and only ever emits a hex; `modes: "solid gradient"` adds the gradient controls, and
|
|
17
|
+
// `anims: "flow breathe spin hue"` lists which animations that place allows (none by default).
|
|
18
|
+
//
|
|
19
|
+
// The value is text, read and written by lib/paint.ts: a plain hex is a solid colour, a gradient is e.g.
|
|
20
|
+
// "linear 150 | #1a0f0a 0 | #c2410c 100 ~flow 14". Hosts paint with paintCss() from that same lib.
|
|
21
|
+
//
|
|
22
|
+
// DRAG IS LOCAL, RELEASE COMMITS. While a point or stop is dragged only this popover repaints (and an
|
|
23
|
+
// optional `input` handler fires); `pick` fires on release, so a heavy preview (Design's whole page)
|
|
24
|
+
// repaints once, when you let go. Opening the picker also commits the value it shows, so a swatch that
|
|
25
|
+
// displays a default applies it on a plain click.
|
|
26
|
+
//
|
|
27
|
+
// The popover lives on <body> with fixed coordinates, so a scrolling or clipping panel never hides it, and
|
|
28
|
+
// it opens from anywhere in the swatch's row (the swatch and the hex label next to it).
|
|
3
29
|
export function mount(el, inputs, handlers) {
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
const en = String((inputs && inputs.lang) || document.documentElement.lang || 'es') === 'en';
|
|
31
|
+
const say = (es, english) => (en ? english : es);
|
|
32
|
+
const modes = String((inputs && inputs.modes) || 'solid').split(/\s+/);
|
|
33
|
+
const allowGradient = modes.includes('gradient');
|
|
34
|
+
const allowedAnims = ['none', ...String((inputs && inputs.anims) || '').split(/\s+/).filter(Boolean)];
|
|
35
|
+
|
|
36
|
+
const ICON = {
|
|
37
|
+
linear: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M5 19 19 5M9 5h10v10"/></svg>',
|
|
38
|
+
radial: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3"/></svg>',
|
|
39
|
+
conic: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="8"/><path d="M12 12V4M12 12l6 5"/></svg>',
|
|
40
|
+
drop: '<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m2 22 1-1h3l9-9M3 21v-3l9-9M15 6l3-3 3 3-3 3M13 8l3 3"/></svg>',
|
|
41
|
+
trash: '<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 7h16M10 11v6M14 11v6M6 7l1 13h10l1-13M9 7V4h6v3"/></svg>',
|
|
42
|
+
play: '<svg width="8" height="8" viewBox="0 0 24 24" fill="#1c1c1e"><path d="M8 5v14l11-7z"/></svg>',
|
|
43
|
+
};
|
|
44
|
+
const TYPE_LABEL = { linear: say('Lineal', 'Linear'), radial: say('Radial', 'Radial'), conic: say('Cónico', 'Conic') };
|
|
45
|
+
const PHONE = 560;
|
|
46
|
+
const OFF_BAR = 38;
|
|
47
|
+
const GREY = 6;
|
|
48
|
+
const COLOURFUL = 75;
|
|
49
|
+
const GRAB = 24;
|
|
50
|
+
const GRAB_STOP = 18;
|
|
51
|
+
|
|
52
|
+
el.classList.add('cp-host');
|
|
53
|
+
const well = document.createElement('button');
|
|
54
|
+
well.type = 'button';
|
|
55
|
+
well.className = 'cp-well pk-well';
|
|
56
|
+
well.setAttribute('aria-label', (inputs && inputs.label) || say('Elegir un color', 'Pick a colour'));
|
|
57
|
+
el.appendChild(well);
|
|
58
|
+
|
|
59
|
+
let lib = null; // lib/paint.ts, loaded on mount (a Custom cannot import at the top)
|
|
60
|
+
let pending = String((inputs && inputs.value) || '');
|
|
61
|
+
let v = null; // the paint being edited
|
|
62
|
+
let sel = 0; // the selected stop
|
|
63
|
+
let pop = null;
|
|
64
|
+
let scrim = null;
|
|
65
|
+
|
|
66
|
+
const box = el.parentElement || el;
|
|
67
|
+
const paintWell = () => {
|
|
68
|
+
if (!lib || !v) return;
|
|
69
|
+
const css = lib.paintCss(v);
|
|
70
|
+
well.style.background = css.background;
|
|
71
|
+
well.style.backgroundSize = css.backgroundSize;
|
|
72
|
+
well.style.animation = css.animation;
|
|
73
|
+
well.innerHTML = v.type !== 'solid' && v.anim !== 'none' ? `<span class="pk-dot">${ICON.play}</span>` : '';
|
|
74
|
+
};
|
|
75
|
+
const load = (text) => {
|
|
76
|
+
let next = lib.parsePaint(text);
|
|
77
|
+
if (next.type !== 'solid' && !allowGradient) next = lib.parsePaint(next.stops[0].c);
|
|
78
|
+
v = next;
|
|
79
|
+
sel = 0;
|
|
80
|
+
};
|
|
81
|
+
const emit = (kind) => {
|
|
82
|
+
paintWell();
|
|
83
|
+
const handler = handlers && handlers[kind];
|
|
84
|
+
if (handler) handler(lib.printPaint(v));
|
|
28
85
|
};
|
|
29
|
-
const emit = () => { paint(); if (handlers.input) handlers.input(curHex()); };
|
|
30
86
|
|
|
31
|
-
|
|
32
|
-
|
|
87
|
+
// ── open / close / place ──
|
|
88
|
+
const phone = () => window.innerWidth <= PHONE;
|
|
89
|
+
// The side (below or above the swatch) is chosen ONCE, when it opens. After that the popover stays anchored
|
|
90
|
+
// to that side while its content changes height (gradient on/off, an animation's speed row…): it grows away
|
|
91
|
+
// from the swatch and scrolls inside, instead of jumping to re-fit the screen.
|
|
92
|
+
function place() {
|
|
93
|
+
if (!pop || phone()) return;
|
|
94
|
+
const r = well.getBoundingClientRect();
|
|
95
|
+
const margin = 8;
|
|
96
|
+
const width = pop.offsetWidth;
|
|
97
|
+
let left = Math.min(r.left, window.innerWidth - width - margin);
|
|
98
|
+
if (left < margin) left = margin;
|
|
99
|
+
pop.style.left = `${left}px`;
|
|
100
|
+
if (!pop.dataset.side) {
|
|
101
|
+
const below = window.innerHeight - r.bottom - 2 * margin;
|
|
102
|
+
const above = r.top - 2 * margin;
|
|
103
|
+
pop.dataset.side = below >= 300 || below >= above ? 'below' : 'above'; // below whenever it fits a usable map; it scrolls inside if it grows
|
|
104
|
+
}
|
|
105
|
+
if (pop.dataset.side === 'below') {
|
|
106
|
+
const top = r.bottom + margin;
|
|
107
|
+
pop.style.top = `${top}px`; pop.style.bottom = 'auto';
|
|
108
|
+
pop.style.maxHeight = `${Math.max(160, window.innerHeight - top - margin)}px`;
|
|
109
|
+
} else {
|
|
110
|
+
pop.style.top = 'auto'; pop.style.bottom = `${window.innerHeight - r.top + margin}px`;
|
|
111
|
+
pop.style.maxHeight = `${Math.max(160, r.top - 2 * margin)}px`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function open() {
|
|
115
|
+
if (!lib || pop) return;
|
|
116
|
+
scrim = document.createElement('div');
|
|
117
|
+
scrim.className = 'pk-scrim';
|
|
118
|
+
scrim.addEventListener('pointerdown', close);
|
|
119
|
+
pop = document.createElement('div');
|
|
120
|
+
pop.className = 'pk-pop';
|
|
121
|
+
pop.setAttribute('role', 'dialog');
|
|
122
|
+
pop.setAttribute('aria-label', (inputs && inputs.label) || say('Color', 'Colour'));
|
|
123
|
+
pop.addEventListener('click', onClick);
|
|
124
|
+
pop.addEventListener('keydown', (event) => { if (event.key === 'Escape') close(); });
|
|
125
|
+
(document.getElementById('lib') || document.body).append(scrim, pop); // inside the skinned area, so it wears the skin's tokens
|
|
126
|
+
render();
|
|
127
|
+
place();
|
|
128
|
+
emit('pick');
|
|
129
|
+
}
|
|
130
|
+
function close() {
|
|
131
|
+
if (!pop) return;
|
|
132
|
+
pop.remove();
|
|
133
|
+
scrim.remove();
|
|
134
|
+
pop = null;
|
|
135
|
+
scrim = null;
|
|
136
|
+
}
|
|
137
|
+
box.addEventListener('click', (event) => {
|
|
138
|
+
if (pop && pop.contains(event.target)) return;
|
|
139
|
+
if (pop) close(); else open();
|
|
140
|
+
});
|
|
141
|
+
window.addEventListener('scroll', () => place(), true);
|
|
142
|
+
window.addEventListener('resize', () => { place(); if (pop) drawMap(); });
|
|
33
143
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
144
|
+
// ── the popover ──
|
|
145
|
+
// Lightness 95 (top) to 5 (bottom); pure black or white sit on the edge instead of past it.
|
|
146
|
+
const Y = (light, height) => Math.max(0, Math.min(height, ((95 - light) / 90) * height));
|
|
147
|
+
// The INTENDED saturation of the selected colour lives on the slider, not in the hex: black at saturation 75
|
|
148
|
+
// is still #000000, so reading it back from the hex would drop it to 0 and the map would stay grey.
|
|
149
|
+
const intensity = () => {
|
|
150
|
+
const slider = pop && pop.querySelector('[data-sat]');
|
|
151
|
+
return slider ? Number(slider.value) : lib.hexToHsl(v.stops[sel].c).s;
|
|
152
|
+
};
|
|
153
|
+
function render() {
|
|
154
|
+
if (!pop) return;
|
|
155
|
+
const gradient = v.type !== 'solid';
|
|
156
|
+
const current = v.stops[sel];
|
|
157
|
+
const hsl = lib.hexToHsl(current.c);
|
|
158
|
+
const points = gradient ? v.stops : [v.stops[0]];
|
|
159
|
+
const html = [];
|
|
160
|
+
if (allowGradient) {
|
|
161
|
+
html.push(`<div class="pk-seg" role="tablist"><button type="button" data-mode="solid" class="${gradient ? '' : 'on'}">${say('Sólido', 'Solid')}</button><button type="button" data-mode="gradient" class="${gradient ? 'on' : ''}">${say('Degradado', 'Gradient')}</button></div>`);
|
|
162
|
+
}
|
|
163
|
+
if (gradient) {
|
|
164
|
+
html.push(`<div class="pk-bar" style="background:${lib.barCss(v)}" aria-label="${say('Colores del degradado', 'Gradient colours')}">${v.stops.map((stop, i) => `<span class="pk-stop ${i === sel ? 'on' : ''}" data-stop="${i}" style="left:${stop.p}%;background:${stop.c}" tabindex="0" role="slider" aria-label="${say('Color', 'Colour')} ${i + 1}" aria-valuenow="${Math.round(stop.p)}"></span>`).join('')}</div>`);
|
|
165
|
+
}
|
|
166
|
+
html.push(`<div class="pk-map" aria-label="${say('Mapa de tonos', 'Colour map')}"><canvas></canvas><svg>${gradient ? '<polyline class="pk-ln" fill="none" stroke="#fff" stroke-width="1.6" stroke-dasharray="3 4" opacity=".9"/>' : ''}</svg>${points.map((_, i) => `<span class="pk-pt ${i === sel ? 'on' : ''}">${gradient ? i + 1 : ''}</span>`).join('')}</div>`);
|
|
167
|
+
// Saturation of the SELECTED point, and it says so: in a gradient the label carries that point's number and
|
|
168
|
+
// colour, so nobody takes it for opacity or for the whole gradient.
|
|
169
|
+
const satName = say('Saturación', 'Saturation');
|
|
170
|
+
const satOf = gradient ? `${satName} ${sel + 1}` : satName;
|
|
171
|
+
html.push(`<div class="pk-row"><span class="pk-lbl pk-satlbl">${gradient ? `<i class="pk-satdot" style="background:${current.c}">${sel + 1}</i>` : ''}${satName}</span><input class="pk-range" type="range" min="0" max="100" value="${hsl.s}" data-sat aria-label="${satOf}"></div>`);
|
|
172
|
+
html.push(`<div class="pk-row"><label class="pk-hex"><i style="background:${current.c}"></i><input value="${current.c}" maxlength="7" spellcheck="false" data-hex data-clear="on" aria-label="${say('Código del color', 'Colour code')}"></label>${'EyeDropper' in window ? `<button type="button" class="pk-ic" data-drop aria-label="${say('Tomar un color de la pantalla', 'Pick a colour from the screen')}">${ICON.drop}</button>` : ''}${gradient ? `<button type="button" class="pk-ic" data-del aria-label="${say('Quitar este color', 'Remove this colour')}" ${v.stops.length <= 2 ? 'disabled' : ''}>${ICON.trash}</button>` : ''}</div>`);
|
|
173
|
+
if (gradient) {
|
|
174
|
+
html.push(`<div class="pk-row">${['linear', 'radial', 'conic'].map((type) => `<button type="button" class="pk-ic ${v.type === type ? 'on' : ''}" data-type="${type}" aria-label="${TYPE_LABEL[type]}" aria-pressed="${v.type === type}">${ICON[type]}</button>`).join('')}<span class="pk-grow"></span>${v.type !== 'radial' ? `<span class="pk-dial" data-dial tabindex="0" role="slider" aria-label="${say('Ángulo', 'Angle')}" aria-valuenow="${Math.round(v.angle)}"><i style="transform:rotate(${v.angle + 180}deg)"></i></span><span class="pk-deg">${Math.round(v.angle)}°</span>` : ''}</div>`);
|
|
175
|
+
if (allowedAnims.length > 1) {
|
|
176
|
+
html.push(`<div class="pk-sep"></div><div class="pk-anims">${allowedAnims.map((anim) => `<button type="button" data-anim="${anim}" class="${v.anim === anim ? 'on' : ''}" aria-pressed="${v.anim === anim}"><i data-prev="${anim}"></i>${en ? lib.ANIM_LABELS[anim].en : lib.ANIM_LABELS[anim].es}</button>`).join('')}</div>`);
|
|
177
|
+
if (v.anim !== 'none') {
|
|
178
|
+
html.push(`<div class="pk-row"><span class="pk-lbl">${say('Velocidad', 'Speed')}</span><div class="pk-seg pk-grow">${[say('Lenta', 'Slow'), say('Media', 'Medium'), say('Rápida', 'Fast')].map((name, i) => `<button type="button" data-speed="${i}" class="${v.speed === i ? 'on' : ''}">${name}</button>`).join('')}</div></div>`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// Ready-made gradients in a grid (all visible, nothing to scroll), and the recent colours in a row of their own.
|
|
183
|
+
if (gradient) {
|
|
184
|
+
html.push(`<div class="pk-sep"></div><div class="pk-sw">${lib.PAIRS.map((pair) => { const name = en ? pair.name.en : pair.name.es; return `<button type="button" data-pair="${pair.colours.join(',')}" style="background:linear-gradient(135deg,${pair.colours.join(',')})" title="${name}" aria-label="${name}"></button>`; }).join('')}</div>`);
|
|
185
|
+
}
|
|
186
|
+
if (recent.length > 0) {
|
|
187
|
+
html.push(`${gradient ? '' : '<div class="pk-sep"></div>'}<div class="pk-recent"><span class="pk-lbl">${say('Recientes', 'Recent')}</span>${recent.map((colour) => `<button type="button" data-swatch="${colour}" style="background:${colour}" title="${colour}" aria-label="${colour}"></button>`).join('')}</div>`);
|
|
188
|
+
}
|
|
189
|
+
pop.innerHTML = html.join('');
|
|
190
|
+
pop.querySelector('[data-sat]').style.background = `linear-gradient(90deg, hsl(${hsl.h} 0% ${hsl.l}%), hsl(${hsl.h} 100% ${hsl.l}%))`;
|
|
191
|
+
paintPreviews();
|
|
192
|
+
drawMap();
|
|
193
|
+
wire();
|
|
194
|
+
}
|
|
195
|
+
// Each animation button previews the CURRENT gradient moving that way, so it repaints on every change.
|
|
196
|
+
function paintPreviews() {
|
|
197
|
+
pop.querySelectorAll('[data-prev]').forEach((swatch) => {
|
|
198
|
+
const css = lib.paintCss({ ...v, anim: swatch.dataset.prev });
|
|
199
|
+
swatch.style.background = css.background;
|
|
200
|
+
swatch.style.backgroundSize = css.backgroundSize;
|
|
201
|
+
swatch.style.animation = css.animation.replace(/[\d.]+s/, '4s');
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function drawMap() {
|
|
205
|
+
const map = pop && pop.querySelector('.pk-map');
|
|
206
|
+
if (!map) return;
|
|
207
|
+
const canvas = map.querySelector('canvas');
|
|
208
|
+
const width = map.clientWidth;
|
|
209
|
+
const height = map.clientHeight;
|
|
210
|
+
const dpr = Math.min(2, window.devicePixelRatio || 1);
|
|
211
|
+
canvas.width = width * dpr;
|
|
212
|
+
canvas.height = height * dpr;
|
|
213
|
+
const ctx = canvas.getContext('2d');
|
|
214
|
+
ctx.scale(dpr, dpr);
|
|
215
|
+
// A grey shows the map in colour anyway: that is what touching it will give (see the pointerdown).
|
|
216
|
+
const sat = intensity() < GREY ? COLOURFUL : intensity();
|
|
217
|
+
for (let x = 0; x < width; x += 1) {
|
|
218
|
+
const hue = (x / width) * 360;
|
|
219
|
+
const column = ctx.createLinearGradient(0, 0, 0, height);
|
|
220
|
+
column.addColorStop(0, `hsl(${hue} ${sat}% 95%)`);
|
|
221
|
+
column.addColorStop(0.5, `hsl(${hue} ${sat}% 50%)`);
|
|
222
|
+
column.addColorStop(1, `hsl(${hue} ${sat}% 5%)`);
|
|
223
|
+
ctx.fillStyle = column;
|
|
224
|
+
ctx.fillRect(x, 0, 1.5, height);
|
|
225
|
+
}
|
|
226
|
+
placePoints();
|
|
227
|
+
}
|
|
228
|
+
function placePoints() {
|
|
229
|
+
const map = pop.querySelector('.pk-map');
|
|
230
|
+
const width = map.clientWidth;
|
|
231
|
+
const height = map.clientHeight;
|
|
232
|
+
const gradient = v.type !== 'solid';
|
|
233
|
+
const stops = gradient ? v.stops : [v.stops[0]];
|
|
234
|
+
const xy = stops.map((stop) => { const hsl = lib.hexToHsl(stop.c); return [(hsl.h / 360) * width, Y(hsl.l, height)]; });
|
|
235
|
+
map.querySelectorAll('.pk-pt').forEach((point, i) => {
|
|
236
|
+
point.style.left = `${xy[i][0]}px`;
|
|
237
|
+
point.style.top = `${xy[i][1]}px`;
|
|
238
|
+
point.style.background = stops[i].c;
|
|
239
|
+
point.classList.toggle('on', i === sel);
|
|
240
|
+
});
|
|
241
|
+
const line = map.querySelector('.pk-ln');
|
|
242
|
+
if (line) line.setAttribute('points', v.stops.map((stop, i) => [stop.p, i]).sort((a, b) => a[0] - b[0]).map(([, i]) => xy[i].join(',')).join(' '));
|
|
243
|
+
}
|
|
244
|
+
// While dragging: repaint this popover, tell an `input` handler if the host wants live updates.
|
|
245
|
+
function live() {
|
|
246
|
+
const bar = pop.querySelector('.pk-bar');
|
|
247
|
+
if (bar) {
|
|
248
|
+
bar.style.background = lib.barCss(v);
|
|
249
|
+
bar.querySelectorAll('.pk-stop').forEach((stop, i) => { stop.style.left = `${v.stops[i].p}%`; stop.style.background = v.stops[i].c; });
|
|
250
|
+
}
|
|
251
|
+
const current = v.stops[sel];
|
|
252
|
+
const hex = pop.querySelector('[data-hex]');
|
|
253
|
+
if (document.activeElement !== hex) hex.value = current.c;
|
|
254
|
+
pop.querySelector('.pk-hex i').style.background = current.c;
|
|
255
|
+
const hsl = lib.hexToHsl(current.c);
|
|
256
|
+
pop.querySelector('[data-sat]').style.background = `linear-gradient(90deg, hsl(${hsl.h} 0% ${hsl.l}%), hsl(${hsl.h} 100% ${hsl.l}%))`;
|
|
257
|
+
const dot = pop.querySelector('.pk-satdot');
|
|
258
|
+
if (dot) {
|
|
259
|
+
dot.textContent = String(sel + 1);
|
|
260
|
+
dot.style.background = current.c;
|
|
261
|
+
pop.querySelector('[data-sat]').setAttribute('aria-label', `${say('Saturación', 'Saturation')} ${sel + 1}`);
|
|
262
|
+
}
|
|
263
|
+
placePoints();
|
|
264
|
+
paintPreviews();
|
|
265
|
+
emit('input');
|
|
266
|
+
}
|
|
267
|
+
function setColour(i, colour) {
|
|
268
|
+
v.stops[i].c = colour;
|
|
269
|
+
if (v.type === 'solid') v.stops.forEach((stop) => { stop.c = colour; });
|
|
270
|
+
}
|
|
271
|
+
function selectStop(i) {
|
|
272
|
+
sel = i;
|
|
273
|
+
pop.querySelectorAll('.pk-pt').forEach((point, at) => point.classList.toggle('on', at === sel));
|
|
274
|
+
pop.querySelectorAll('.pk-stop').forEach((stop, at) => stop.classList.toggle('on', at === sel));
|
|
275
|
+
pop.querySelector('[data-sat]').value = lib.hexToHsl(v.stops[sel].c).s;
|
|
276
|
+
drawMap();
|
|
277
|
+
live();
|
|
278
|
+
}
|
|
279
|
+
let recent = [];
|
|
280
|
+
function remember(colour) { recent = [colour, ...recent.filter((each) => each !== colour)].slice(0, 8); }
|
|
281
|
+
function commit() { remember(v.stops[sel].c); emit('pick'); }
|
|
282
|
+
|
|
283
|
+
function wire() {
|
|
284
|
+
const map = pop.querySelector('.pk-map');
|
|
285
|
+
let dragging = null;
|
|
286
|
+
const at = (event) => {
|
|
287
|
+
const r = map.getBoundingClientRect();
|
|
288
|
+
return { x: Math.max(0, Math.min(r.width, event.clientX - r.left)), y: Math.max(0, Math.min(r.height, event.clientY - r.top)), w: r.width, h: r.height };
|
|
289
|
+
};
|
|
290
|
+
const moveTo = (event) => {
|
|
291
|
+
if (dragging === null) return;
|
|
292
|
+
const p = at(event);
|
|
293
|
+
const sat = intensity();
|
|
294
|
+
setColour(dragging, lib.hslToHex(Math.round((p.x / p.w) * 360) % 360, sat, Math.round(95 - (p.y / p.h) * 90)));
|
|
295
|
+
live();
|
|
296
|
+
};
|
|
297
|
+
map.addEventListener('pointerdown', (event) => {
|
|
298
|
+
const p = at(event);
|
|
299
|
+
const stops = v.type !== 'solid' ? v.stops : [v.stops[0]];
|
|
300
|
+
// Grab the point under the finger; on a tie (two stops of the same colour sit on the same spot) the
|
|
301
|
+
// one already selected wins, so a drag never jumps to a colour you did not choose.
|
|
302
|
+
const far = (i) => { const hsl = lib.hexToHsl(stops[i].c); return Math.hypot((hsl.h / 360) * p.w - p.x, Y(hsl.l, p.h) - p.y); };
|
|
303
|
+
let nearest = sel;
|
|
304
|
+
stops.forEach((_, i) => { if (far(i) < far(nearest) - 2) nearest = i; });
|
|
305
|
+
if (far(nearest) < GRAB && nearest !== sel) selectStop(nearest);
|
|
306
|
+
// A grey (black, white) has no saturation, so the map is drawn grey and dragging would only give greys.
|
|
307
|
+
// Touching the map means «I want a colour»: bring the saturation up first. The slider can take it back.
|
|
308
|
+
const hsl = lib.hexToHsl(v.stops[sel].c);
|
|
309
|
+
if (hsl.s < GREY) {
|
|
310
|
+
setColour(sel, lib.hslToHex(hsl.h, COLOURFUL, hsl.l));
|
|
311
|
+
pop.querySelector('[data-sat]').value = COLOURFUL;
|
|
312
|
+
drawMap();
|
|
313
|
+
}
|
|
314
|
+
dragging = sel;
|
|
315
|
+
map.setPointerCapture(event.pointerId);
|
|
316
|
+
moveTo(event);
|
|
317
|
+
});
|
|
318
|
+
map.addEventListener('pointermove', moveTo);
|
|
319
|
+
map.addEventListener('pointerup', () => { if (dragging !== null) { dragging = null; commit(); } });
|
|
320
|
+
|
|
321
|
+
const bar = pop.querySelector('.pk-bar');
|
|
322
|
+
if (bar) {
|
|
323
|
+
bar.addEventListener('pointerdown', (event) => {
|
|
324
|
+
const r = bar.getBoundingClientRect();
|
|
325
|
+
// A tap NEAR a stop grabs it: only a tap clear of every stop adds a new one. The handle is small and a
|
|
326
|
+
// near-miss used to create a stop instead of moving the one you reached for.
|
|
327
|
+
const x = event.clientX - r.left;
|
|
328
|
+
let near = -1;
|
|
329
|
+
let nearest = GRAB_STOP;
|
|
330
|
+
v.stops.forEach((each, i) => { const d = Math.abs((each.p / 100) * r.width - x); if (d < nearest) { nearest = d; near = i; } });
|
|
331
|
+
const stop = event.target.closest('[data-stop]');
|
|
332
|
+
let moving;
|
|
333
|
+
if (stop || near >= 0) {
|
|
334
|
+
moving = stop ? Number(stop.dataset.stop) : near;
|
|
335
|
+
if (moving !== sel) selectStop(moving);
|
|
336
|
+
} else if (v.stops.length < lib.MAX_STOPS) {
|
|
337
|
+
// The new stop takes the colour the gradient already has at that spot, so adding one never
|
|
338
|
+
// changes how it looks until you move it, and its point does not sit on top of another one.
|
|
339
|
+
const p = Math.round(((event.clientX - r.left) / r.width) * 100);
|
|
340
|
+
const sorted = [...v.stops].sort((a, b) => a.p - b.p);
|
|
341
|
+
const left = [...sorted].reverse().find((each) => each.p <= p) || sorted[0];
|
|
342
|
+
const right = sorted.find((each) => each.p > p) || sorted[sorted.length - 1];
|
|
343
|
+
const span = right.p - left.p;
|
|
344
|
+
v.stops.push({ c: lib.mixHex(left.c, right.c, span > 0 ? (p - left.p) / span : 0), p });
|
|
345
|
+
sel = v.stops.length - 1;
|
|
346
|
+
render();
|
|
347
|
+
commit();
|
|
348
|
+
return;
|
|
349
|
+
} else return;
|
|
350
|
+
const startY = event.clientY;
|
|
351
|
+
bar.setPointerCapture(event.pointerId);
|
|
352
|
+
bar.classList.add('grabbing');
|
|
353
|
+
const off = (e) => Math.abs(e.clientY - startY) > OFF_BAR && v.stops.length > 2;
|
|
354
|
+
const move = (e) => {
|
|
355
|
+
v.stops[moving].p = Math.max(0, Math.min(100, ((e.clientX - r.left) / r.width) * 100));
|
|
356
|
+
bar.querySelectorAll('.pk-stop')[moving].classList.toggle('gone', off(e));
|
|
357
|
+
live();
|
|
358
|
+
};
|
|
359
|
+
const up = (e) => {
|
|
360
|
+
bar.removeEventListener('pointermove', move);
|
|
361
|
+
bar.classList.remove('grabbing');
|
|
362
|
+
if (off(e)) { v.stops.splice(moving, 1); sel = 0; render(); }
|
|
363
|
+
commit();
|
|
364
|
+
};
|
|
365
|
+
bar.addEventListener('pointermove', move);
|
|
366
|
+
bar.addEventListener('pointerup', up, { once: true });
|
|
367
|
+
bar.addEventListener('pointercancel', up, { once: true });
|
|
368
|
+
});
|
|
369
|
+
bar.addEventListener('keydown', (event) => {
|
|
370
|
+
const stop = event.target.closest('[data-stop]');
|
|
371
|
+
if (!stop) return;
|
|
372
|
+
const i = Number(stop.dataset.stop);
|
|
373
|
+
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
|
|
374
|
+
v.stops[i].p = Math.max(0, Math.min(100, v.stops[i].p + (event.key === 'ArrowRight' ? 2 : -2)));
|
|
375
|
+
live(); commit(); event.preventDefault();
|
|
376
|
+
}
|
|
377
|
+
if ((event.key === 'Delete' || event.key === 'Backspace') && v.stops.length > 2) {
|
|
378
|
+
v.stops.splice(i, 1); sel = 0; render(); commit();
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const dial = pop.querySelector('[data-dial]');
|
|
384
|
+
if (dial) {
|
|
385
|
+
const turn = (event) => {
|
|
386
|
+
const r = dial.getBoundingClientRect();
|
|
387
|
+
const degrees = (Math.atan2(event.clientY - (r.top + r.height / 2), event.clientX - (r.left + r.width / 2)) * 180) / Math.PI + 90;
|
|
388
|
+
v.angle = Math.round((((degrees % 360) + 360) % 360) / 5) * 5;
|
|
389
|
+
dial.querySelector('i').style.transform = `rotate(${v.angle + 180}deg)`;
|
|
390
|
+
pop.querySelector('.pk-deg').textContent = `${v.angle}°`;
|
|
391
|
+
live();
|
|
392
|
+
};
|
|
393
|
+
dial.addEventListener('pointerdown', (event) => {
|
|
394
|
+
dial.setPointerCapture(event.pointerId);
|
|
395
|
+
turn(event);
|
|
396
|
+
dial.addEventListener('pointermove', turn);
|
|
397
|
+
dial.addEventListener('pointerup', () => { dial.removeEventListener('pointermove', turn); commit(); }, { once: true });
|
|
398
|
+
});
|
|
399
|
+
dial.addEventListener('keydown', (event) => {
|
|
400
|
+
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
|
|
401
|
+
v.angle = (v.angle + (event.key === 'ArrowRight' ? 15 : -15) + 360) % 360;
|
|
402
|
+
render();
|
|
403
|
+
pop.querySelector('[data-dial]').focus();
|
|
404
|
+
commit();
|
|
405
|
+
event.preventDefault();
|
|
406
|
+
});
|
|
407
|
+
}
|
|
40
408
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
409
|
+
const sat = pop.querySelector('[data-sat]');
|
|
410
|
+
sat.addEventListener('input', () => {
|
|
411
|
+
const hsl = lib.hexToHsl(v.stops[sel].c);
|
|
412
|
+
setColour(sel, lib.hslToHex(hsl.h, Number(sat.value), hsl.l));
|
|
413
|
+
drawMap();
|
|
414
|
+
live();
|
|
415
|
+
});
|
|
416
|
+
sat.addEventListener('change', commit);
|
|
417
|
+
pop.querySelector('[data-hex]').addEventListener('input', (event) => {
|
|
418
|
+
const typed = event.target.value.trim();
|
|
419
|
+
const hex = typed.startsWith('#') ? typed : `#${typed}`;
|
|
420
|
+
if (!lib.isHex(hex)) return;
|
|
421
|
+
setColour(sel, hex.toLowerCase());
|
|
422
|
+
drawMap();
|
|
423
|
+
live();
|
|
424
|
+
commit();
|
|
425
|
+
});
|
|
426
|
+
}
|
|
46
427
|
|
|
47
|
-
|
|
428
|
+
async function onClick(event) {
|
|
429
|
+
const button = event.target.closest('button');
|
|
430
|
+
if (!button || !pop || !pop.contains(button)) return;
|
|
431
|
+
const data = button.dataset;
|
|
432
|
+
if (data.mode) {
|
|
433
|
+
if (data.mode === 'solid') {
|
|
434
|
+
const colour = v.stops[sel].c;
|
|
435
|
+
v = lib.parsePaint(colour);
|
|
436
|
+
} else if (v.type === 'solid') {
|
|
437
|
+
const hsl = lib.hexToHsl(v.stops[0].c);
|
|
438
|
+
v = { ...v, type: 'linear', stops: [{ c: lib.hslToHex(hsl.h, hsl.s, Math.max(6, hsl.l - 28)), p: 0 }, { c: v.stops[0].c, p: 100 }] };
|
|
439
|
+
}
|
|
440
|
+
sel = v.type === 'solid' ? 0 : 1;
|
|
441
|
+
} else if (data.type) v.type = data.type;
|
|
442
|
+
else if (data.anim) v.anim = data.anim;
|
|
443
|
+
else if (data.speed) v.speed = Number(data.speed);
|
|
444
|
+
else if (data.pair) { const colours = data.pair.split(','); v.stops = colours.map((c, i) => ({ c, p: Math.round((i / (colours.length - 1)) * 100) })); sel = 0; }
|
|
445
|
+
else if (data.swatch) setColour(sel, data.swatch);
|
|
446
|
+
else if ('del' in data) { if (v.stops.length <= 2) return; v.stops.splice(sel, 1); sel = 0; }
|
|
447
|
+
else if ('drop' in data) {
|
|
448
|
+
try { const picked = await new window.EyeDropper().open(); setColour(sel, picked.sRGBHex.toLowerCase()); } catch { return; }
|
|
449
|
+
} else return;
|
|
450
|
+
render();
|
|
451
|
+
place();
|
|
452
|
+
commit();
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
import('@muten/shadcn/registry/kit/paint.js').then((m) => { lib = m.default; load(pending); paintWell(); });
|
|
456
|
+
|
|
457
|
+
// A new value from the host (another preset, a reset): take it unless it is what we already show.
|
|
458
|
+
return (next) => {
|
|
459
|
+
const text = String((next && next.value) || '');
|
|
460
|
+
pending = text;
|
|
461
|
+
if (!lib) return;
|
|
462
|
+
if (v && text === lib.printPaint(v)) return;
|
|
463
|
+
load(text);
|
|
464
|
+
paintWell();
|
|
465
|
+
if (pop) render();
|
|
466
|
+
};
|
|
48
467
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
# ColorPicker(value:
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
# ColorPicker - Storio's one colour picker. Closed: a swatch with the real colour (or gradient). Open: a popover (a
|
|
2
|
+
# sheet on a phone) to pick. modes "solid" (default) or "solid gradient"; anims lists the allowed animations.
|
|
3
|
+
# ColorPicker(value: brand, onInput: setBrand)
|
|
4
|
+
# ColorPicker(value: wall, modes: "solid gradient", anims: "flow breathe spin hue", label: "Fondo de la tienda", onInput: setWall)
|
|
5
|
+
# The value is paint text (a hex, or "linear 150 | #1a0f0a 0 | #c2410c 100 ~flow 14"). shadcn-for-muten · Custom.
|
|
6
|
+
part ColorPicker(value: text, modes: text = "solid", anims: text = "", label: text = "Color", onInput: action) {
|
|
7
|
+
Custom ColorPicker inputs(value: $value, modes: $modes, anims: $anims, label: $label, lang: "es") on(input: $onInput, pick: $onInput) class("cx-cp-host")
|
|
7
8
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Combobox (muten Custom) - the kit's combobox (kit/menus.js).
|
|
2
|
+
export function mount(el, inputs, on) {
|
|
3
|
+
Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/menus.js')]).then(([core, m]) => m.combobox(el, inputs, core, on));
|
|
4
|
+
return () => {};
|
|
5
|
+
}
|
package/registry/combobox.muten
CHANGED
|
@@ -1,24 +1,7 @@
|
|
|
1
|
-
# Combobox -
|
|
2
|
-
# the
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
# }
|
|
8
|
-
# shadcn-for-muten · part · no deps · styles via the semantic .combobox* + .select-* classes in globals.css.
|
|
9
|
-
part Combobox(label: text, open: bool, onToggle: action) {
|
|
10
|
-
Stack class("select-wrap") {
|
|
11
|
-
Button -> $onToggle aria(expanded: $open) class("combobox-trigger") {
|
|
12
|
-
Span "{$label}"
|
|
13
|
-
Icon "lucide:chevrons-up-down" class("size-4 opacity-50 shrink-0")
|
|
14
|
-
}
|
|
15
|
-
when $open { Button -> $onToggle class("select-backdrop") }
|
|
16
|
-
Stack class("combobox-content", "combobox-content-open" when $open) { slot }
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
part ComboboxItem(value: text, current: text, label: text, onSelect: action) {
|
|
20
|
-
Button -> $onSelect($value) class("select-item") {
|
|
21
|
-
Span "{$label}"
|
|
22
|
-
when $value == $current { Icon "lucide:check" class("size-4") }
|
|
23
|
-
}
|
|
1
|
+
# Combobox - type and the list filters (accent-insensitive) and marks the match; arrows, Enter, Esc; «Usar «…»» to
|
|
2
|
+
# keep something that is not in the list (allowNew "off" to forbid it). options is comma-joined text.
|
|
3
|
+
# Combobox(label: "Ciudad", options: "Bogotá,Medellín,Cali", placeholder: "Escribe tu ciudad", onChange: setCity)
|
|
4
|
+
# shadcn-for-muten · Custom (kit menus) · host JS combobox.js.
|
|
5
|
+
part Combobox(label: text = "", options: text, value: text = "", placeholder: text = "", allowNew: text = "on", hint: text = "on", onChange: action) {
|
|
6
|
+
Custom Combobox inputs(label: $label, options: $options, value: $value, placeholder: $placeholder, allowNew: $allowNew, hint: $hint) on(change: $onChange) class("cx-cb-host")
|
|
24
7
|
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
part CommandPalette(open: bool, onClose: action) {
|
|
10
|
-
Stack class("dialog-overlay command-palette", "dialog-overlay-open" when $open) {
|
|
11
|
-
Button -> $onClose class("dialog-backdrop")
|
|
12
|
-
Stack class("command-palette-content") { slot }
|
|
1
|
+
# CommandPalette - the ⌘K search: a Command inside the adaptive modal. Open it with a Hotkey and a search button; an
|
|
2
|
+
# item's action does its thing and closes the palette (the page sets open back to false).
|
|
3
|
+
# Hotkey(keys: "k", onPress: openPalette)
|
|
4
|
+
# CommandPalette(open: palette, onClose: closePalette, query: q) { CommandGroup(label: "Ir a") { CommandItem(label: "Agenda", onSelect: goAgenda) { Icon "lucide:calendar" } } }
|
|
5
|
+
# shadcn-for-muten · part over Modal + Command.
|
|
6
|
+
part CommandPalette(open: bool, onClose: action, query: text, placeholder: text = "Busca o salta a…", title: text = "Buscar") {
|
|
7
|
+
Modal(open: $open, onClose: $onClose, title: $title, mode: "dialog") class("cx-palette") {
|
|
8
|
+
Command(query: $query, placeholder: $placeholder) { slot }
|
|
13
9
|
}
|
|
14
10
|
}
|