@leavepulse/ui 0.29.5 → 0.30.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.
Files changed (39) hide show
  1. package/dist/chunks/LpActionBar-CxmBZpGu.js +173 -0
  2. package/dist/chunks/{LpAppShell-BJk3b-u4.js → LpAppShell-DnocEJAu.js} +1 -1
  3. package/dist/chunks/{LpCodeBlock-rS_6mO3D.js → LpCodeBlock-BWX1KZwk.js} +7 -13
  4. package/dist/chunks/LpColorPicker-CFC6iaNK.js +491 -0
  5. package/dist/chunks/{LpDisclosure-BnwP4Q-b.js → LpDisclosure-C1_YkMrd.js} +8 -2
  6. package/dist/chunks/{LpLogViewer-CVaGfTF_.js → LpLogViewer-TSRyt3t9.js} +3 -6
  7. package/dist/chunks/{LpSidebar-C_Mr-JPI.js → LpSidebar-CWSbczWi.js} +11 -2
  8. package/dist/component-names.d.ts +1 -1
  9. package/dist/components/LpActionBar.vue.d.ts +84 -0
  10. package/dist/components/LpActionBar.vue.js +4 -0
  11. package/dist/components/LpAppShell.vue.js +1 -1
  12. package/dist/components/LpCodeBlock.vue.js +1 -1
  13. package/dist/components/LpColorPicker.vue.d.ts +124 -0
  14. package/dist/components/LpColorPicker.vue.js +4 -0
  15. package/dist/components/LpDisclosure.vue.js +1 -1
  16. package/dist/components/LpLogViewer.vue.js +1 -1
  17. package/dist/components/LpNotificationBell.vue.d.ts +2 -2
  18. package/dist/components/LpSidebar.vue.d.ts +21 -0
  19. package/dist/components/LpSidebar.vue.js +1 -1
  20. package/dist/components/actionBar.d.ts +19 -0
  21. package/dist/components/actionBar.js +31 -0
  22. package/dist/components/color.d.ts +65 -0
  23. package/dist/components/color.js +158 -0
  24. package/dist/composables/useClipboard.d.ts +46 -0
  25. package/dist/composables/useClipboard.js +28 -0
  26. package/dist/index.d.ts +8 -0
  27. package/dist/index.js +145 -126
  28. package/package.json +1 -1
  29. package/src/component-names.ts +2 -0
  30. package/src/components/LpActionBar.vue +268 -0
  31. package/src/components/LpCodeBlock.vue +5 -10
  32. package/src/components/LpColorPicker.vue +784 -0
  33. package/src/components/LpDisclosure.vue +17 -2
  34. package/src/components/LpLogViewer.vue +5 -8
  35. package/src/components/LpSidebar.vue +42 -6
  36. package/src/components/actionBar.ts +73 -0
  37. package/src/components/color.ts +257 -0
  38. package/src/composables/useClipboard.ts +107 -0
  39. package/src/index.ts +21 -0
@@ -0,0 +1,491 @@
1
+ import { defineComponent, computed, ref, watch, openBlock, createBlock, unref, withCtx, createVNode, createElementVNode, normalizeClass, normalizeStyle, toDisplayString, createElementBlock, createCommentVNode, Fragment, renderList, renderSlot } from "vue";
2
+ import { PopoverRoot, PopoverAnchor, PopoverTrigger, PopoverPortal, PopoverContent } from "reka-ui";
3
+ import { DEFAULT_SWATCHES, formatColor, opaqueCss, parseColor, rgbToHsv, isLight, clampHsv } from "../components/color.js";
4
+ import { useClipboard } from "../composables/useClipboard.js";
5
+ import { POPOVER_PANEL } from "../components/dropdown.js";
6
+ import { _ as _sfc_main$1 } from "./LpContextMenu-B6WBLleS.js";
7
+ import { _ as _sfc_main$2 } from "./LpIcon-CCnX5_2j.js";
8
+ const _hoisted_1 = ["disabled", "aria-label"];
9
+ const _hoisted_2 = { class: "min-w-0 flex-1 truncate font-mono text-xs text-muted-strong" };
10
+ const _hoisted_3 = { class: "flex flex-col gap-3" };
11
+ const _hoisted_4 = ["tabindex", "aria-label", "aria-valuetext"];
12
+ const _hoisted_5 = ["aria-label", "aria-valuenow", "tabindex"];
13
+ const _hoisted_6 = ["aria-label", "aria-valuenow", "tabindex"];
14
+ const _hoisted_7 = {
15
+ key: 1,
16
+ "data-lp-ring-owner": "",
17
+ class: "flex h-(--size-control-sm) items-center gap-2 overflow-hidden rounded-control border border-line bg-surface-soft px-2 transition-colors duration-[var(--duration-fast)] focus-within:border-brand focus-within:ring-2 focus-within:ring-ring"
18
+ };
19
+ const _hoisted_8 = ["value", "disabled", "aria-label"];
20
+ const _hoisted_9 = {
21
+ key: 0,
22
+ class: "text-[11px] font-medium uppercase tracking-wide text-muted"
23
+ };
24
+ const _hoisted_10 = ["disabled", "aria-label", "aria-pressed", "onClick"];
25
+ const CHECKERBOARD = "repeating-conic-gradient(var(--color-line-strong) 0% 25%, transparent 0% 50%) 0 0 / 12px 12px";
26
+ const _sfc_main = /* @__PURE__ */ defineComponent({
27
+ __name: "LpColorPicker",
28
+ props: {
29
+ modelValue: { default: "#3b82f6" },
30
+ format: { default: "hex" },
31
+ alpha: { type: Boolean, default: false },
32
+ swatches: { default: () => DEFAULT_SWATCHES },
33
+ hideInput: { type: Boolean },
34
+ disabled: { type: Boolean },
35
+ invalid: { type: Boolean },
36
+ size: { default: "md" },
37
+ label: {},
38
+ ariaLabel: {},
39
+ side: { default: "bottom" },
40
+ align: { default: "start" },
41
+ menu: {},
42
+ menuItems: {},
43
+ recent: { type: Boolean, default: false },
44
+ recentKey: { default: "lp.color.recent" },
45
+ recentLimit: { default: 8 },
46
+ labels: {},
47
+ copyToast: { type: [String, Boolean], default: "Copied {value}" }
48
+ },
49
+ emits: ["update:modelValue", "change"],
50
+ setup(__props, { expose: __expose, emit: __emit }) {
51
+ const props = __props;
52
+ const emit = __emit;
53
+ function fill(tpl, vars) {
54
+ return tpl.replace(/\{(\w+)\}/g, (_, k) => String(vars[k] ?? ""));
55
+ }
56
+ const l = computed(() => ({
57
+ saturation: props.labels?.saturation ?? "Saturation and brightness",
58
+ hue: props.labels?.hue ?? "Hue",
59
+ opacity: props.labels?.opacity ?? "Opacity",
60
+ value: props.labels?.value ?? "Colour value",
61
+ recent: props.labels?.recent ?? "Recent",
62
+ copyHex: props.labels?.copyHex ?? "Copy hex",
63
+ copyRgb: props.labels?.copyRgb ?? "Copy rgb",
64
+ copyHsl: props.labels?.copyHsl ?? "Copy hsl",
65
+ paste: props.labels?.paste ?? "Paste colour",
66
+ formatAs: props.labels?.formatAs ?? "Show as",
67
+ format_hex: props.labels?.format_hex ?? "HEX",
68
+ format_rgb: props.labels?.format_rgb ?? "RGB",
69
+ format_hsl: props.labels?.format_hsl ?? "HSL",
70
+ saturationValueText: props.labels?.saturationValueText ?? "saturation {s}%, brightness {v}%"
71
+ }));
72
+ const { copy } = useClipboard();
73
+ const open = ref(false);
74
+ const hsv = ref({ h: 217, s: 0.76, v: 0.96, a: 1 });
75
+ function adopt(value) {
76
+ const rgb = parseColor(value ?? "");
77
+ if (!rgb) return;
78
+ const next = rgbToHsv(rgb);
79
+ if (next.s === 0) next.h = hsv.value.h;
80
+ if (next.v === 0) next.s = hsv.value.s;
81
+ hsv.value = next;
82
+ }
83
+ adopt(props.modelValue);
84
+ watch(
85
+ () => props.modelValue,
86
+ (v) => {
87
+ if (v !== current.value) adopt(v);
88
+ }
89
+ );
90
+ const activeFormat = ref(props.format);
91
+ watch(() => props.format, (f) => activeFormat.value = f);
92
+ const current = computed(
93
+ () => formatColor(props.alpha ? hsv.value : { ...hsv.value, a: 1 }, activeFormat.value)
94
+ );
95
+ function set(patch, commit = false) {
96
+ if (props.disabled) return;
97
+ hsv.value = clampHsv({ ...hsv.value, ...patch });
98
+ emit("update:modelValue", current.value);
99
+ if (commit) emit("change", current.value);
100
+ }
101
+ function trackPointer(e, onMove) {
102
+ if (props.disabled) return;
103
+ if (e.button !== 0) return;
104
+ e.preventDefault();
105
+ const el = e.currentTarget;
106
+ const rect = el.getBoundingClientRect();
107
+ const at = (ev) => {
108
+ const x = rect.width === 0 ? 0 : (ev.clientX - rect.left) / rect.width;
109
+ const y = rect.height === 0 ? 0 : (ev.clientY - rect.top) / rect.height;
110
+ onMove(Math.min(1, Math.max(0, x)), Math.min(1, Math.max(0, y)));
111
+ };
112
+ el.setPointerCapture(e.pointerId);
113
+ at(e);
114
+ const move = (ev) => at(ev);
115
+ const up = () => {
116
+ el.removeEventListener("pointermove", move);
117
+ el.removeEventListener("pointerup", up);
118
+ el.removeEventListener("pointercancel", up);
119
+ emit("change", current.value);
120
+ };
121
+ el.addEventListener("pointermove", move);
122
+ el.addEventListener("pointerup", up);
123
+ el.addEventListener("pointercancel", up);
124
+ }
125
+ const onFieldPointer = (e) => trackPointer(e, (x, y) => set({ s: x, v: 1 - y }));
126
+ const onHuePointer = (e) => trackPointer(e, (x) => set({ h: x * 360 }));
127
+ const onAlphaPointer = (e) => trackPointer(e, (x) => set({ a: x }));
128
+ function step(e, axis) {
129
+ const big = e.shiftKey;
130
+ const k = e.key;
131
+ const arrows = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"];
132
+ if (!arrows.includes(k)) return;
133
+ e.preventDefault();
134
+ const dx = (k === "ArrowRight" ? 1 : k === "ArrowLeft" ? -1 : 0) * (big ? 10 : 1);
135
+ const dy = (k === "ArrowUp" ? 1 : k === "ArrowDown" ? -1 : 0) * (big ? 10 : 1);
136
+ if (axis === "h") {
137
+ if (k === "Home") return set({ h: 0 }, true);
138
+ if (k === "End") return set({ h: 359 }, true);
139
+ return set({ h: hsv.value.h + (dx || dy) }, true);
140
+ }
141
+ if (axis === "a") {
142
+ if (k === "Home") return set({ a: 0 }, true);
143
+ if (k === "End") return set({ a: 1 }, true);
144
+ return set({ a: hsv.value.a + (dx || dy) / 100 }, true);
145
+ }
146
+ if (k === "Home") return set({ s: 0 }, true);
147
+ if (k === "End") return set({ s: 1 }, true);
148
+ set({ s: hsv.value.s + dx / 100, v: hsv.value.v + dy / 100 }, true);
149
+ }
150
+ const typed = ref(null);
151
+ const inputValue = computed(() => typed.value ?? current.value);
152
+ function onInput(e) {
153
+ const value = e.target.value;
154
+ typed.value = value;
155
+ const rgb = parseColor(value);
156
+ if (!rgb) return;
157
+ const next = rgbToHsv(rgb);
158
+ if (next.s === 0) next.h = hsv.value.h;
159
+ if (next.v === 0) next.s = hsv.value.s;
160
+ set(props.alpha ? next : { ...next, a: 1 }, true);
161
+ }
162
+ function onInputBlur() {
163
+ typed.value = null;
164
+ }
165
+ function pickSwatch(value) {
166
+ const rgb = parseColor(value);
167
+ if (!rgb) return;
168
+ typed.value = null;
169
+ set(rgbToHsv(rgb), true);
170
+ }
171
+ function isActiveSwatch(value) {
172
+ const rgb = parseColor(value);
173
+ return !!rgb && formatColor(rgbToHsv(rgb), "hex") === formatColor({ ...hsv.value, a: 1 }, "hex");
174
+ }
175
+ function tickClass(value) {
176
+ const rgb = parseColor(value);
177
+ return rgb && isLight(rgbToHsv(rgb)) ? "text-black" : "text-white";
178
+ }
179
+ const triggerSize = {
180
+ sm: "h-(--size-control-sm) text-xs",
181
+ md: "h-(--size-control-md) text-sm",
182
+ lg: "h-(--size-control-lg) text-sm"
183
+ };
184
+ const previewCss = computed(() => formatColor(props.alpha ? hsv.value : { ...hsv.value, a: 1 }, "rgb"));
185
+ const hueCss = computed(() => `hsl(${Math.round(hsv.value.h)}, 100%, 50%)`);
186
+ const alphaRampStart = computed(() => formatColor({ ...hsv.value, a: 0 }, "rgb"));
187
+ const fieldCss = computed(() => opaqueCss({ ...hsv.value, s: 1, v: 1 }));
188
+ const fieldStyle = computed(() => ({
189
+ background: [
190
+ "linear-gradient(to top, rgba(0, 0, 0, 1), rgba(0, 0, 0, 0))",
191
+ "linear-gradient(to right, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0))",
192
+ fieldCss.value
193
+ ].join(", ")
194
+ }));
195
+ function storage() {
196
+ try {
197
+ if (typeof window === "undefined") return null;
198
+ return window.localStorage;
199
+ } catch {
200
+ return null;
201
+ }
202
+ }
203
+ function readRecent() {
204
+ const s = storage();
205
+ if (!s) return [];
206
+ try {
207
+ const raw = JSON.parse(s.getItem(props.recentKey) ?? "[]");
208
+ return Array.isArray(raw) ? raw.filter((v) => typeof v === "string" && !!parseColor(v)).slice(0, props.recentLimit) : [];
209
+ } catch {
210
+ return [];
211
+ }
212
+ }
213
+ const recentColors = ref(props.recent ? readRecent() : []);
214
+ function remember(value) {
215
+ if (!props.recent) return;
216
+ const key = formatColor({ ...hsv.value, a: 1 }, "hex");
217
+ const without = recentColors.value.filter(
218
+ (c) => formatColor(rgbToHsv(parseColor(c) ?? { r: 0, g: 0, b: 0, a: 1 }), "hex") !== key
219
+ );
220
+ recentColors.value = [value, ...without].slice(0, props.recentLimit);
221
+ try {
222
+ storage()?.setItem(props.recentKey, JSON.stringify(recentColors.value));
223
+ } catch {
224
+ }
225
+ }
226
+ watch(open, (isOpen, was) => {
227
+ if (was && !isOpen) remember(current.value);
228
+ });
229
+ const swatchRows = computed(
230
+ () => [
231
+ {
232
+ key: "recent",
233
+ heading: l.value.recent,
234
+ colors: props.recent ? recentColors.value : [],
235
+ cols: "grid-cols-8",
236
+ size: "size-6"
237
+ },
238
+ {
239
+ key: "presets",
240
+ heading: "",
241
+ colors: [...props.swatches],
242
+ cols: "grid-cols-6",
243
+ size: "size-7"
244
+ }
245
+ ].filter((r) => r.colors.length)
246
+ );
247
+ function onPanelOpen(e) {
248
+ e.preventDefault();
249
+ e.currentTarget?.focus?.({ preventScroll: true });
250
+ }
251
+ function copyAs(fmt) {
252
+ const text = formatColor(props.alpha ? hsv.value : { ...hsv.value, a: 1 }, fmt);
253
+ const toast = props.copyToast;
254
+ void copy(text, { toast: toast === false ? false : fill(toast, { value: text }) });
255
+ }
256
+ async function pasteColor() {
257
+ try {
258
+ const text = await navigator.clipboard.readText();
259
+ const rgb = parseColor(text);
260
+ if (!rgb) return;
261
+ typed.value = null;
262
+ const next = rgbToHsv(rgb);
263
+ set(props.alpha ? next : { ...next, a: 1 }, true);
264
+ } catch {
265
+ }
266
+ }
267
+ function setFormat(f) {
268
+ if (activeFormat.value === f) return;
269
+ activeFormat.value = f;
270
+ typed.value = null;
271
+ emit("update:modelValue", current.value);
272
+ emit("change", current.value);
273
+ }
274
+ const menuItems = computed(() => {
275
+ const own = props.menuItems ?? [];
276
+ if (props.menu === "none") return own;
277
+ const builtin = [
278
+ { label: l.value.copyHex, icon: "lucide:copy", onSelect: () => copyAs("hex") },
279
+ { label: l.value.copyRgb, icon: "lucide:copy", onSelect: () => copyAs("rgb") },
280
+ { label: l.value.copyHsl, icon: "lucide:copy", onSelect: () => copyAs("hsl") },
281
+ { label: l.value.paste, icon: "lucide:clipboard", onSelect: pasteColor },
282
+ {
283
+ // Ticks rather than a plain list: this is a setting with a current value,
284
+ // and the menu should say which one is on without being opened twice.
285
+ label: l.value.formatAs,
286
+ icon: "lucide:code",
287
+ separatorBefore: true,
288
+ children: ["hex", "rgb", "hsl"].map((f) => ({
289
+ label: l.value[`format_${f}`],
290
+ checked: activeFormat.value === f,
291
+ onSelect: () => setFormat(f)
292
+ }))
293
+ }
294
+ ];
295
+ return own.length ? [...builtin, { ...own[0], separatorBefore: true }, ...own.slice(1)] : builtin;
296
+ });
297
+ __expose({ open });
298
+ return (_ctx, _cache) => {
299
+ return openBlock(), createBlock(unref(PopoverRoot), {
300
+ open: open.value,
301
+ "onUpdate:open": _cache[3] || (_cache[3] = ($event) => open.value = $event)
302
+ }, {
303
+ default: withCtx(() => [
304
+ createVNode(unref(PopoverAnchor), { class: "block w-full" }, {
305
+ default: withCtx(() => [
306
+ createVNode(_sfc_main$1, { items: menuItems.value }, {
307
+ default: withCtx(() => [
308
+ createVNode(unref(PopoverTrigger), { "as-child": "" }, {
309
+ default: withCtx(() => [
310
+ createElementVNode("button", {
311
+ type: "button",
312
+ disabled: __props.disabled,
313
+ "aria-label": __props.ariaLabel,
314
+ class: normalizeClass(["group inline-flex w-full items-center gap-2 rounded-control border bg-surface-soft px-2 text-left text-ink outline-none transition-colors duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-ring data-[state=open]:border-brand disabled:cursor-not-allowed disabled:opacity-55", [
315
+ triggerSize[__props.size],
316
+ __props.invalid ? "border-danger focus-visible:ring-danger-soft" : "border-line focus-visible:border-brand"
317
+ ]])
318
+ }, [
319
+ createElementVNode("span", {
320
+ class: "relative size-5 shrink-0 overflow-hidden rounded-md",
321
+ style: normalizeStyle({ background: CHECKERBOARD })
322
+ }, [
323
+ createElementVNode("span", {
324
+ class: "block size-full",
325
+ style: normalizeStyle({ backgroundColor: previewCss.value })
326
+ }, null, 4),
327
+ _cache[4] || (_cache[4] = createElementVNode("span", { class: "pointer-events-none absolute inset-0 rounded-md ring-1 ring-inset ring-line" }, null, -1))
328
+ ], 4),
329
+ createElementVNode("span", _hoisted_2, toDisplayString(__props.label ?? current.value), 1),
330
+ createVNode(_sfc_main$2, {
331
+ name: "lucide:chevron-down",
332
+ size: 15,
333
+ class: "shrink-0 text-muted transition-transform duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] group-data-[state=open]:rotate-180"
334
+ })
335
+ ], 10, _hoisted_1)
336
+ ]),
337
+ _: 1
338
+ })
339
+ ]),
340
+ _: 1
341
+ }, 8, ["items"])
342
+ ]),
343
+ _: 1
344
+ }),
345
+ createVNode(unref(PopoverPortal), null, {
346
+ default: withCtx(() => [
347
+ createVNode(unref(PopoverContent), {
348
+ side: __props.side,
349
+ align: __props.align,
350
+ "side-offset": 6,
351
+ class: normalizeClass([unref(POPOVER_PANEL), "z-(--z-popover) w-64 select-none rounded-card p-3 outline-none"]),
352
+ onOpenAutoFocus: onPanelOpen
353
+ }, {
354
+ default: withCtx(() => [
355
+ createVNode(_sfc_main$1, { items: menuItems.value }, {
356
+ default: withCtx(() => [
357
+ createElementVNode("div", _hoisted_3, [
358
+ createElementVNode("div", {
359
+ role: "slider",
360
+ tabindex: __props.disabled ? -1 : 0,
361
+ "aria-label": l.value.saturation,
362
+ "aria-valuetext": fill(l.value.saturationValueText, { s: Math.round(hsv.value.s * 100), v: Math.round(hsv.value.v * 100) }),
363
+ class: "relative h-36 w-full cursor-crosshair touch-none overflow-hidden rounded-control outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
364
+ style: normalizeStyle(fieldStyle.value),
365
+ onPointerdown: onFieldPointer,
366
+ onKeydown: _cache[0] || (_cache[0] = ($event) => step($event, "sv"))
367
+ }, [
368
+ createElementVNode("span", {
369
+ class: "pointer-events-none absolute size-3.5 -translate-x-1/2 -translate-y-1/2 rounded-pill border-2 border-white shadow ring-1 ring-black/30",
370
+ style: normalizeStyle({
371
+ left: `${hsv.value.s * 100}%`,
372
+ top: `${(1 - hsv.value.v) * 100}%`,
373
+ backgroundColor: previewCss.value
374
+ })
375
+ }, null, 4)
376
+ ], 44, _hoisted_4),
377
+ createElementVNode("div", {
378
+ role: "slider",
379
+ "aria-label": l.value.hue,
380
+ "aria-valuemin": 0,
381
+ "aria-valuemax": 360,
382
+ "aria-valuenow": Math.round(hsv.value.h),
383
+ tabindex: __props.disabled ? -1 : 0,
384
+ class: "relative h-3 w-full cursor-pointer touch-none rounded-pill outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
385
+ style: { "background": "linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00)" },
386
+ onPointerdown: onHuePointer,
387
+ onKeydown: _cache[1] || (_cache[1] = ($event) => step($event, "h"))
388
+ }, [
389
+ createElementVNode("span", {
390
+ class: "pointer-events-none absolute top-1/2 size-4 -translate-x-1/2 -translate-y-1/2 rounded-pill border-2 border-white shadow ring-1 ring-black/30",
391
+ style: normalizeStyle({ left: `${hsv.value.h / 360 * 100}%`, backgroundColor: hueCss.value })
392
+ }, null, 4)
393
+ ], 40, _hoisted_5),
394
+ __props.alpha ? (openBlock(), createElementBlock("div", {
395
+ key: 0,
396
+ role: "slider",
397
+ "aria-label": l.value.opacity,
398
+ "aria-valuemin": 0,
399
+ "aria-valuemax": 100,
400
+ "aria-valuenow": Math.round(hsv.value.a * 100),
401
+ tabindex: __props.disabled ? -1 : 0,
402
+ class: "relative h-3 w-full cursor-pointer touch-none rounded-pill outline-none ring-1 ring-inset ring-line focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
403
+ style: normalizeStyle({ background: CHECKERBOARD }),
404
+ onPointerdown: onAlphaPointer,
405
+ onKeydown: _cache[2] || (_cache[2] = ($event) => step($event, "a"))
406
+ }, [
407
+ createElementVNode("div", {
408
+ class: "absolute inset-0 overflow-hidden rounded-pill",
409
+ style: normalizeStyle({ backgroundImage: `linear-gradient(to right, ${alphaRampStart.value}, ${unref(opaqueCss)(hsv.value)})` })
410
+ }, null, 4),
411
+ createElementVNode("span", {
412
+ class: "pointer-events-none absolute top-1/2 size-4 -translate-x-1/2 -translate-y-1/2 rounded-pill border-2 border-white shadow ring-1 ring-black/30",
413
+ style: normalizeStyle({ left: `${hsv.value.a * 100}%`, backgroundColor: previewCss.value })
414
+ }, null, 4)
415
+ ], 44, _hoisted_6)) : createCommentVNode("", true),
416
+ !__props.hideInput ? (openBlock(), createElementBlock("div", _hoisted_7, [
417
+ createElementVNode("span", {
418
+ class: "size-4 shrink-0 rounded-[5px] border border-line",
419
+ style: normalizeStyle({ backgroundColor: previewCss.value })
420
+ }, null, 4),
421
+ createElementVNode("input", {
422
+ value: inputValue.value,
423
+ disabled: __props.disabled,
424
+ spellcheck: "false",
425
+ autocapitalize: "off",
426
+ autocomplete: "off",
427
+ "aria-label": l.value.value,
428
+ class: "min-w-0 flex-1 select-text bg-transparent font-mono text-xs text-ink outline-none placeholder:text-muted",
429
+ onInput,
430
+ onBlur: onInputBlur
431
+ }, null, 40, _hoisted_8)
432
+ ])) : createCommentVNode("", true),
433
+ (openBlock(true), createElementBlock(Fragment, null, renderList(swatchRows.value, (row) => {
434
+ return openBlock(), createElementBlock("div", {
435
+ key: row.key,
436
+ class: "flex flex-col gap-1.5"
437
+ }, [
438
+ row.heading ? (openBlock(), createElementBlock("span", _hoisted_9, toDisplayString(row.heading), 1)) : createCommentVNode("", true),
439
+ createElementVNode("div", {
440
+ class: normalizeClass(["grid gap-1.5", row.cols])
441
+ }, [
442
+ (openBlock(true), createElementBlock(Fragment, null, renderList(row.colors, (c) => {
443
+ return openBlock(), createElementBlock("button", {
444
+ key: c,
445
+ type: "button",
446
+ disabled: __props.disabled,
447
+ "aria-label": c,
448
+ "aria-pressed": isActiveSwatch(c),
449
+ class: normalizeClass(["relative flex items-center justify-center overflow-hidden rounded-md outline-none transition-transform duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] hover:scale-110 active:scale-95 focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed motion-reduce:hover:scale-100 motion-reduce:active:scale-95", row.size]),
450
+ style: normalizeStyle({ background: CHECKERBOARD }),
451
+ onClick: ($event) => pickSwatch(c)
452
+ }, [
453
+ createElementVNode("span", {
454
+ class: "absolute inset-0",
455
+ style: normalizeStyle({ backgroundColor: c })
456
+ }, null, 4),
457
+ _cache[5] || (_cache[5] = createElementVNode("span", { class: "pointer-events-none absolute inset-0 rounded-md ring-1 ring-inset ring-line" }, null, -1)),
458
+ isActiveSwatch(c) ? (openBlock(), createBlock(_sfc_main$2, {
459
+ key: 0,
460
+ name: "lucide:check",
461
+ size: 13,
462
+ class: normalizeClass(["relative", tickClass(c)])
463
+ }, null, 8, ["class"])) : createCommentVNode("", true)
464
+ ], 14, _hoisted_10);
465
+ }), 128))
466
+ ], 2)
467
+ ]);
468
+ }), 128)),
469
+ renderSlot(_ctx.$slots, "default", {
470
+ color: current.value,
471
+ hsv: hsv.value
472
+ })
473
+ ])
474
+ ]),
475
+ _: 3
476
+ }, 8, ["items"])
477
+ ]),
478
+ _: 3
479
+ }, 8, ["side", "align", "class"])
480
+ ]),
481
+ _: 3
482
+ })
483
+ ]),
484
+ _: 3
485
+ }, 8, ["open"]);
486
+ };
487
+ }
488
+ });
489
+ export {
490
+ _sfc_main as _
491
+ };
@@ -1,4 +1,4 @@
1
- import { defineComponent, ref, openBlock, createBlock, unref, withCtx, createVNode, createCommentVNode, createElementVNode, renderSlot, createTextVNode, toDisplayString, nextTick } from "vue";
1
+ import { defineComponent, getCurrentInstance, computed, ref, openBlock, createBlock, unref, withCtx, createVNode, createCommentVNode, createElementVNode, renderSlot, createTextVNode, toDisplayString, nextTick } from "vue";
2
2
  import { CollapsibleRoot, CollapsibleTrigger, CollapsibleContent } from "reka-ui";
3
3
  import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
4
4
  const _hoisted_1 = { class: "min-w-0 flex-1 text-left" };
@@ -16,6 +16,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
16
16
  setup(__props, { emit: __emit }) {
17
17
  const props = __props;
18
18
  const emit = __emit;
19
+ const instance = getCurrentInstance();
20
+ const controlled = computed(() => {
21
+ const raw = instance?.vnode.props;
22
+ return Boolean(raw && ("open" in raw || "onUpdate:open" in raw));
23
+ });
24
+ const rootOpen = computed(() => controlled.value ? props.open : void 0);
19
25
  const root = ref(null);
20
26
  function onToggle(value) {
21
27
  emit("update:open", value);
@@ -30,7 +36,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
30
36
  return openBlock(), createBlock(unref(CollapsibleRoot), {
31
37
  ref_key: "root",
32
38
  ref: root,
33
- open: __props.open,
39
+ open: rootOpen.value,
34
40
  "default-open": __props.defaultOpen,
35
41
  class: "overflow-hidden rounded-control border border-line bg-surface-soft",
36
42
  "onUpdate:open": onToggle
@@ -1,5 +1,6 @@
1
1
  import { defineComponent, ref, shallowRef, computed, onMounted, onBeforeUnmount, watch, nextTick, openBlock, createElementBlock, normalizeClass, createVNode, Transition, withCtx, toDisplayString, createCommentVNode, normalizeStyle, createElementVNode, Fragment, createBlock, resolveDynamicComponent, TransitionGroup, mergeProps, renderList, createTextVNode } from "vue";
2
2
  import { prefersReducedMotion } from "../composables/easing.js";
3
+ import { useClipboard } from "../composables/useClipboard.js";
3
4
  import { useHotkeys } from "../composables/useHotkeys.js";
4
5
  import { _ as _sfc_main$3 } from "./LpContextMenu-B6WBLleS.js";
5
6
  import { _ as _sfc_main$2 } from "./LpIcon-CCnX5_2j.js";
@@ -202,12 +203,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
202
203
  }
203
204
  return time;
204
205
  }
205
- async function copy(text) {
206
- try {
207
- await navigator.clipboard?.writeText(text);
208
- } catch {
209
- }
210
- }
206
+ const { copy: writeClipboard } = useClipboard();
207
+ const copy = (text) => writeClipboard(text, { toast: "Copied to clipboard" });
211
208
  function lineText(line) {
212
209
  return [fmtTime(line.time), line.level?.toUpperCase(), line.source && `[${line.source}]`, line.message].filter(Boolean).join(" ");
213
210
  }
@@ -34,6 +34,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
34
34
  mobileBreakpoint: { default: "md" },
35
35
  edgeOpen: { type: Boolean },
36
36
  divider: { type: Boolean, default: true },
37
+ variant: { default: "rail" },
38
+ width: {},
37
39
  collapsed: {},
38
40
  autoCollapseAfter: { default: 14 }
39
41
  },
@@ -85,15 +87,22 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
85
87
  const railClass = computed(
86
88
  () => props.responsive ? RAIL_VISIBILITY[props.mobileBreakpoint] : "flex"
87
89
  );
90
+ const chromeClass = computed(
91
+ () => props.variant === "panel" ? "rounded-card border border-line bg-surface-raised" : "border-r border-line bg-surface-raised"
92
+ );
88
93
  return (_ctx, _cache) => {
89
94
  return openBlock(), createElementBlock(Fragment, null, [
90
95
  createElementVNode("nav", mergeProps({
91
- class: ["h-full w-60 flex-col gap-1 border-r border-line bg-surface-raised p-3", railClass.value],
96
+ class: ["h-full flex-col gap-1 p-3", [railClass.value, chromeClass.value, __props.width ? "" : "w-60"]],
97
+ style: __props.width ? { width: __props.width } : void 0,
92
98
  "aria-label": "Sidebar"
93
99
  }, _ctx.$attrs), [
94
100
  _ctx.$slots.header ? (openBlock(), createElementBlock("div", {
95
101
  key: 0,
96
- class: normalizeClass(["-mx-3 -mt-3 mb-3 flex h-16 shrink-0 items-center px-3", __props.divider ? "border-b border-line" : ""])
102
+ class: normalizeClass(["mb-3 flex shrink-0 items-center", [
103
+ __props.variant === "panel" ? "pb-3" : "-mx-3 -mt-3 h-16 px-3",
104
+ __props.divider ? "border-b border-line" : ""
105
+ ]])
97
106
  }, [
98
107
  renderSlot(_ctx.$slots, "header")
99
108
  ], 2)) : createCommentVNode("", true),
@@ -1 +1 @@
1
- export declare const COMPONENT_NAMES: readonly ["LayoutCanvas", "LayoutNode", "LpAddressInput", "LpAlert", "LpAppShell", "LpAutocomplete", "LpAvatar", "LpBadge", "LpBreadcrumbs", "LpButton", "LpCalendar", "LpCard", "LpChart", "LpCheckbox", "LpCodeBlock", "LpCommandPalette", "LpConfirmDialog", "LpContextMenu", "LpDatePicker", "LpDisclosure", "LpDivider", "LpDrawer", "LpDropdownMenu", "LpDropzone", "LpEmptyState", "LpFileTree", "LpFileTreeNode", "LpFormField", "LpIcon", "LpImageEditor", "LpInfraNode", "LpInput", "LpLaneNode", "LpLightbox", "LpLink", "LpLogViewer", "LpMapPicker", "LpModal", "LpNotificationBell", "LpNumberField", "LpNumberFlow", "LpOtpInput", "LpPagination", "LpPasswordInput", "LpPhoneInput", "LpPie", "LpPopover", "LpProgress", "LpRadio", "LpRadioGroup", "LpRollingText", "LpScrollArea", "LpSection", "LpSegmented", "LpSelect", "LpServiceNode", "LpShift", "LpShortcutsDialog", "LpSidebar", "LpSidebarNav", "LpSkeleton", "LpSlider", "LpSparkline", "LpStat", "LpStepper", "LpSwitch", "LpTable", "LpTableOfContents", "LpTabs", "LpTextarea", "LpThemeSwitcher", "LpTilt", "LpToaster", "LpTooltip", "LpTopologyCanvas", "LpUptimeBar", "LpWaypointEdge"];
1
+ export declare const COMPONENT_NAMES: readonly ["LayoutCanvas", "LayoutNode", "LpActionBar", "LpAddressInput", "LpAlert", "LpAppShell", "LpAutocomplete", "LpAvatar", "LpBadge", "LpBreadcrumbs", "LpButton", "LpCalendar", "LpCard", "LpChart", "LpCheckbox", "LpCodeBlock", "LpColorPicker", "LpCommandPalette", "LpConfirmDialog", "LpContextMenu", "LpDatePicker", "LpDisclosure", "LpDivider", "LpDrawer", "LpDropdownMenu", "LpDropzone", "LpEmptyState", "LpFileTree", "LpFileTreeNode", "LpFormField", "LpIcon", "LpImageEditor", "LpInfraNode", "LpInput", "LpLaneNode", "LpLightbox", "LpLink", "LpLogViewer", "LpMapPicker", "LpModal", "LpNotificationBell", "LpNumberField", "LpNumberFlow", "LpOtpInput", "LpPagination", "LpPasswordInput", "LpPhoneInput", "LpPie", "LpPopover", "LpProgress", "LpRadio", "LpRadioGroup", "LpRollingText", "LpScrollArea", "LpSection", "LpSegmented", "LpSelect", "LpServiceNode", "LpShift", "LpShortcutsDialog", "LpSidebar", "LpSidebarNav", "LpSkeleton", "LpSlider", "LpSparkline", "LpStat", "LpStepper", "LpSwitch", "LpTable", "LpTableOfContents", "LpTabs", "LpTextarea", "LpThemeSwitcher", "LpTilt", "LpToaster", "LpTooltip", "LpTopologyCanvas", "LpUptimeBar", "LpWaypointEdge"];