@leavepulse/ui 0.28.1 → 0.28.3
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/dist/assets/LpDropzone-DVDC-ABU.css +52 -0
- package/dist/assets/LpImageEditor-DqP5JKCy.css +25 -0
- package/dist/chunks/{LpChart-tNXs1ttr.js → LpChart-UiU4RhXP.js} +5 -4
- package/dist/chunks/{LpLogViewer-CcQ3s3Fp.js → LpLogViewer-CKmEQYnm.js} +66 -8
- package/dist/chunks/{LpProgress-DoIP4jQR.js → LpProgress-Btq5W4Ut.js} +14 -14
- package/dist/component-names.d.ts +1 -1
- package/dist/components/LpChart.vue.js +1 -1
- package/dist/components/LpDropzone.vue.d.ts +66 -0
- package/dist/components/LpDropzone.vue.js +163 -0
- package/dist/components/LpImageEditor.vue.d.ts +58 -0
- package/dist/components/LpImageEditor.vue.js +357 -0
- package/dist/components/LpLogViewer.vue.d.ts +4 -0
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpProgress.vue.js +1 -1
- package/dist/composables/useHotkeys.d.ts +16 -1
- package/dist/composables/useHotkeys.js +8 -1
- package/dist/composables/useZoomPan.d.ts +24 -0
- package/dist/composables/useZoomPan.js +11 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -5
- package/package.json +1 -1
- package/src/component-names.ts +2 -0
- package/src/components/LpChart.vue +23 -4
- package/src/components/LpDropzone.vue +299 -0
- package/src/components/LpImageEditor.vue +515 -0
- package/src/components/LpLogViewer.vue +114 -2
- package/src/components/LpProgress.vue +30 -21
- package/src/composables/useHotkeys.ts +30 -1
- package/src/composables/useZoomPan.ts +30 -0
- package/src/index.ts +2 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
|
|
2
|
+
.lp-dropzone__frame rect[data-v-996dbce9] {
|
|
3
|
+
stroke: var(--color-line);
|
|
4
|
+
transition: stroke var(--duration-fast) linear;
|
|
5
|
+
}
|
|
6
|
+
.lp-dropzone:hover .lp-dropzone__frame rect[data-v-996dbce9] {
|
|
7
|
+
stroke: var(--color-line-strong);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* Marching ants, and only while a drag is in flight. At rest this would be a
|
|
11
|
+
loop nobody asked for, running for as long as the page is open and competing
|
|
12
|
+
with every other thing on it; under a drag it is the answer to "can I let go
|
|
13
|
+
here". One dash period per cycle, so the travel is continuous rather than
|
|
14
|
+
jumping back at the seam. */
|
|
15
|
+
.lp-dropzone--over .lp-dropzone__frame rect[data-v-996dbce9] {
|
|
16
|
+
stroke: var(--color-brand);
|
|
17
|
+
animation: lp-dropzone-ants-996dbce9 500ms linear infinite;
|
|
18
|
+
}
|
|
19
|
+
@keyframes lp-dropzone-ants-996dbce9 {
|
|
20
|
+
to {
|
|
21
|
+
stroke-dashoffset: -11;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* The dashes fade in and the block settles up a few pixels. */
|
|
26
|
+
.lp-dropzone[data-v-996dbce9] {
|
|
27
|
+
animation: lp-dropzone-in-996dbce9 var(--duration-slow) var(--ease-settle) both;
|
|
28
|
+
}
|
|
29
|
+
.lp-dropzone--waiting[data-v-996dbce9] {
|
|
30
|
+
opacity: 0;
|
|
31
|
+
animation: none;
|
|
32
|
+
}
|
|
33
|
+
@keyframes lp-dropzone-in-996dbce9 {
|
|
34
|
+
from {
|
|
35
|
+
opacity: 0;
|
|
36
|
+
transform: translateY(6px);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
@media (prefers-reduced-motion: reduce) {
|
|
40
|
+
.lp-dropzone[data-v-996dbce9],
|
|
41
|
+
.lp-dropzone--waiting[data-v-996dbce9] {
|
|
42
|
+
opacity: 1;
|
|
43
|
+
transform: none;
|
|
44
|
+
animation: none;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* The ants stop, the colour stays: the brand stroke is what says "drop
|
|
48
|
+
here", and it says it standing still. */
|
|
49
|
+
.lp-dropzone--over .lp-dropzone__frame rect[data-v-996dbce9] {
|
|
50
|
+
animation: none;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
|
|
2
|
+
/* The picture settles into the frame instead of appearing in it. Scale rather
|
|
3
|
+
than a slide: the frame is the fixed thing here and the image lives inside
|
|
4
|
+
it, so anything that moved would read as a crop that had shifted. */
|
|
5
|
+
.lp-editor__image[data-v-7d404124] {
|
|
6
|
+
animation: lp-editor-in-7d404124 var(--duration-slow) var(--ease-settle) both;
|
|
7
|
+
}
|
|
8
|
+
.lp-editor__image--waiting[data-v-7d404124] {
|
|
9
|
+
opacity: 0;
|
|
10
|
+
animation: none;
|
|
11
|
+
}
|
|
12
|
+
@keyframes lp-editor-in-7d404124 {
|
|
13
|
+
from {
|
|
14
|
+
opacity: 0;
|
|
15
|
+
scale: 1.04;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
@media (prefers-reduced-motion: reduce) {
|
|
19
|
+
.lp-editor__image[data-v-7d404124],
|
|
20
|
+
.lp-editor__image--waiting[data-v-7d404124] {
|
|
21
|
+
opacity: 1;
|
|
22
|
+
scale: 1;
|
|
23
|
+
animation: none;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -379,6 +379,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
379
379
|
{ deep: true }
|
|
380
380
|
);
|
|
381
381
|
const clipId = `lp-chart-${Math.random().toString(36).slice(2, 9)}`;
|
|
382
|
+
const gradientId = (name, index) => `${clipId}-g-${index}-${name.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
|
|
382
383
|
return (_ctx, _cache) => {
|
|
383
384
|
return openBlock(), createElementBlock("div", {
|
|
384
385
|
ref_key: "root",
|
|
@@ -435,9 +436,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
435
436
|
onContextmenu: onContextMenu
|
|
436
437
|
}, [
|
|
437
438
|
createElementVNode("defs", null, [
|
|
438
|
-
(openBlock(true), createElementBlock(Fragment, null, renderList(lines.value, (s) => {
|
|
439
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(lines.value, (s, i) => {
|
|
439
440
|
return openBlock(), createElementBlock("linearGradient", {
|
|
440
|
-
id:
|
|
441
|
+
id: gradientId(s.name, i),
|
|
441
442
|
key: s.name,
|
|
442
443
|
x1: "0",
|
|
443
444
|
y1: "0",
|
|
@@ -543,11 +544,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
543
544
|
opacity: active.value < 0 || active.value === b.index ? 1 : 0.45
|
|
544
545
|
}, null, 8, _hoisted_25);
|
|
545
546
|
}), 128)),
|
|
546
|
-
(openBlock(true), createElementBlock(Fragment, null, renderList(lines.value, (s) => {
|
|
547
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(lines.value, (s, i) => {
|
|
547
548
|
return withDirectives((openBlock(), createElementBlock("path", {
|
|
548
549
|
key: `f-${s.name}`,
|
|
549
550
|
d: s.fill,
|
|
550
|
-
fill: `url(#${
|
|
551
|
+
fill: `url(#${gradientId(s.name, i)})`
|
|
551
552
|
}, null, 8, _hoisted_26)), [
|
|
552
553
|
[vShow, __props.type === "area"]
|
|
553
554
|
]);
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { defineComponent,
|
|
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 { useHotkeys } from "../composables/useHotkeys.js";
|
|
3
4
|
import { _ as _sfc_main$3 } from "./LpContextMenu-D0mzXqA7.js";
|
|
4
5
|
import { _ as _sfc_main$2 } from "./LpIcon-CCnX5_2j.js";
|
|
5
6
|
import { _ as _sfc_main$1 } from "./LpScrollArea-D8xyKXcF.js";
|
|
6
|
-
const _hoisted_1 = {
|
|
7
|
+
const _hoisted_1 = {
|
|
8
|
+
key: 0,
|
|
9
|
+
class: "pointer-events-none absolute top-2 left-1/2 z-10 -translate-x-1/2 rounded-pill bg-brand px-2.5 py-1 text-[11px] font-medium text-ink-inverse"
|
|
10
|
+
};
|
|
7
11
|
const _hoisted_2 = {
|
|
8
12
|
key: 0,
|
|
9
13
|
class: "flex h-full flex-col items-center justify-center gap-1 px-4 py-10 text-center"
|
|
@@ -58,6 +62,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
58
62
|
tail: { type: Boolean, default: true },
|
|
59
63
|
height: { default: "20rem" },
|
|
60
64
|
emptyLabel: { default: "No logs yet" },
|
|
65
|
+
frozenLabel: { default: "Held — release Ctrl to resume" },
|
|
61
66
|
loadingOlder: { type: Boolean },
|
|
62
67
|
loadOlder: { type: Boolean },
|
|
63
68
|
rowMenu: { type: Boolean, default: true },
|
|
@@ -132,8 +137,43 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
132
137
|
}
|
|
133
138
|
return msg === line.message && time === line.time && level === line.level && source === line.source ? line : { ...line, message: msg, time, level, source };
|
|
134
139
|
}
|
|
140
|
+
const root = ref(null);
|
|
141
|
+
const frozen = ref(false);
|
|
142
|
+
const frozenLines = shallowRef(null);
|
|
143
|
+
const sourceLines = computed(() => frozenLines.value ?? props.lines);
|
|
144
|
+
function freeze() {
|
|
145
|
+
if (!hovered.value && !focusWithin()) return false;
|
|
146
|
+
if (frozen.value) return true;
|
|
147
|
+
frozenLines.value = props.lines;
|
|
148
|
+
frozen.value = true;
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
function thaw() {
|
|
152
|
+
frozen.value = false;
|
|
153
|
+
frozenLines.value = null;
|
|
154
|
+
}
|
|
155
|
+
const hovered = ref(false);
|
|
156
|
+
function focusWithin() {
|
|
157
|
+
const el = root.value;
|
|
158
|
+
return !!el && el.contains(document.activeElement);
|
|
159
|
+
}
|
|
160
|
+
useHotkeys(() => [
|
|
161
|
+
{ key: "control", allowInInput: true, handler: freeze },
|
|
162
|
+
{ key: "meta", allowInInput: true, handler: freeze }
|
|
163
|
+
]);
|
|
164
|
+
function onKeyup(event) {
|
|
165
|
+
if (event.key === "Control" || event.key === "Meta") thaw();
|
|
166
|
+
}
|
|
167
|
+
onMounted(() => {
|
|
168
|
+
window.addEventListener("keyup", onKeyup);
|
|
169
|
+
window.addEventListener("blur", thaw);
|
|
170
|
+
});
|
|
171
|
+
onBeforeUnmount(() => {
|
|
172
|
+
window.removeEventListener("keyup", onKeyup);
|
|
173
|
+
window.removeEventListener("blur", thaw);
|
|
174
|
+
});
|
|
135
175
|
const parsedLines = computed(
|
|
136
|
-
() => props.parse ?
|
|
176
|
+
() => props.parse ? sourceLines.value.map(parseLine) : sourceLines.value
|
|
137
177
|
);
|
|
138
178
|
const visibleLines = computed(() => {
|
|
139
179
|
const term = props.filterMatches ? props.highlight?.trim().toLowerCase() : "";
|
|
@@ -350,7 +390,25 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
350
390
|
"move-class": "transition-transform duration-200 ease-[var(--ease-emphasized)]"
|
|
351
391
|
};
|
|
352
392
|
return (_ctx, _cache) => {
|
|
353
|
-
return openBlock(), createElementBlock("div",
|
|
393
|
+
return openBlock(), createElementBlock("div", {
|
|
394
|
+
ref_key: "root",
|
|
395
|
+
ref: root,
|
|
396
|
+
tabindex: "-1",
|
|
397
|
+
class: normalizeClass(["relative overflow-hidden rounded-card border border-line bg-surface font-mono text-xs leading-relaxed", frozen.value ? "ring-1 ring-brand" : ""]),
|
|
398
|
+
onPointerenter: _cache[0] || (_cache[0] = ($event) => hovered.value = true),
|
|
399
|
+
onPointerleave: _cache[1] || (_cache[1] = ($event) => hovered.value = false)
|
|
400
|
+
}, [
|
|
401
|
+
createVNode(Transition, {
|
|
402
|
+
"enter-active-class": "transition duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] motion-reduce:transition-none",
|
|
403
|
+
"leave-active-class": "transition duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] motion-reduce:transition-none",
|
|
404
|
+
"enter-from-class": "-translate-y-1 opacity-0",
|
|
405
|
+
"leave-to-class": "-translate-y-1 opacity-0"
|
|
406
|
+
}, {
|
|
407
|
+
default: withCtx(() => [
|
|
408
|
+
frozen.value ? (openBlock(), createElementBlock("div", _hoisted_1, toDisplayString(__props.frozenLabel), 1)) : createCommentVNode("", true)
|
|
409
|
+
]),
|
|
410
|
+
_: 1
|
|
411
|
+
}),
|
|
354
412
|
createVNode(_sfc_main$1, {
|
|
355
413
|
ref_key: "scrollRef",
|
|
356
414
|
ref: scrollRef,
|
|
@@ -370,7 +428,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
370
428
|
size: 14,
|
|
371
429
|
class: "animate-spin"
|
|
372
430
|
}),
|
|
373
|
-
_cache[
|
|
431
|
+
_cache[2] || (_cache[2] = createElementVNode("span", { class: "text-[11px]" }, "loading older…", -1))
|
|
374
432
|
])) : createCommentVNode("", true),
|
|
375
433
|
createVNode(_sfc_main$3, {
|
|
376
434
|
items: menuItems.value,
|
|
@@ -398,7 +456,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
398
456
|
class: normalizeClass(["absolute inset-y-0 left-0 w-0.5", metaFor(line.level).rail]),
|
|
399
457
|
"aria-hidden": "true"
|
|
400
458
|
}, null, 2),
|
|
401
|
-
_cache[
|
|
459
|
+
_cache[3] || (_cache[3] = createElementVNode("span", {
|
|
402
460
|
class: "mr-2 w-0.5 shrink-0",
|
|
403
461
|
"aria-hidden": "true"
|
|
404
462
|
}, null, -1)),
|
|
@@ -468,13 +526,13 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
468
526
|
size: 14,
|
|
469
527
|
class: "transition-transform duration-200 ease-[var(--ease-emphasized)] group-hover:translate-y-0.5"
|
|
470
528
|
}),
|
|
471
|
-
_cache[
|
|
529
|
+
_cache[4] || (_cache[4] = createTextVNode(" Jump to latest ", -1))
|
|
472
530
|
])
|
|
473
531
|
])) : createCommentVNode("", true)
|
|
474
532
|
]),
|
|
475
533
|
_: 1
|
|
476
534
|
})
|
|
477
|
-
]);
|
|
535
|
+
], 34);
|
|
478
536
|
};
|
|
479
537
|
}
|
|
480
538
|
});
|
|
@@ -36,19 +36,19 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
36
36
|
},
|
|
37
37
|
setup(__props) {
|
|
38
38
|
const props = __props;
|
|
39
|
-
const
|
|
39
|
+
const shown = ref(props.animateOnMount ? 0 : props.value);
|
|
40
40
|
let raf = 0;
|
|
41
|
-
|
|
42
|
-
const runSweep = (ms) => {
|
|
41
|
+
const runSweep = (to, ms) => {
|
|
43
42
|
if (raf) cancelAnimationFrame(raf);
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const from = shown.value;
|
|
44
|
+
if (prefersReducedMotion() || ms <= 0 || from === to) {
|
|
45
|
+
shown.value = to;
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
const t0 = performance.now();
|
|
49
49
|
const step = (now) => {
|
|
50
50
|
const t = Math.min(1, (now - t0) / ms);
|
|
51
|
-
|
|
51
|
+
shown.value = from + (to - from) * easeOut(t);
|
|
52
52
|
raf = t < 1 ? requestAnimationFrame(step) : 0;
|
|
53
53
|
};
|
|
54
54
|
raf = requestAnimationFrame(step);
|
|
@@ -60,17 +60,17 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
60
60
|
const bindReveal = (instance) => {
|
|
61
61
|
revealAnchor.value = instance?.$el ?? null;
|
|
62
62
|
};
|
|
63
|
-
watch(
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
watch(
|
|
64
|
+
revealed,
|
|
65
|
+
(visible) => {
|
|
66
|
+
if (visible && props.animateOnMount) runSweep(props.value, props.duration);
|
|
67
|
+
},
|
|
68
|
+
{ immediate: true }
|
|
69
|
+
);
|
|
66
70
|
watch(
|
|
67
71
|
() => [props.value, props.max],
|
|
68
|
-
() =>
|
|
69
|
-
from = shown.value;
|
|
70
|
-
runSweep(props.duration);
|
|
71
|
-
}
|
|
72
|
+
() => runSweep(props.value, props.duration)
|
|
72
73
|
);
|
|
73
|
-
const shown = computed(() => from + (props.value - from) * eased.value);
|
|
74
74
|
const pct = computed(
|
|
75
75
|
() => Math.max(0, Math.min(100, shown.value / props.max * 100))
|
|
76
76
|
);
|
|
@@ -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", "LpEmptyState", "LpFileTree", "LpFileTreeNode", "LpFormField", "LpIcon", "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", "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"];
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
/** Same syntax as the input's: "image/*", ".yaml,.yml". */
|
|
3
|
+
accept?: string;
|
|
4
|
+
/** Take more than one at a time. */
|
|
5
|
+
multiple?: boolean;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Also accept a paste anywhere on the page, not only while focused.
|
|
9
|
+
* Off by default: two zones both listening would each take the same
|
|
10
|
+
* screenshot, and a page with one obvious target is the case for it.
|
|
11
|
+
*/
|
|
12
|
+
pasteTarget?: boolean;
|
|
13
|
+
/** Largest file accepted, in bytes. Bigger ones are rejected, not silently
|
|
14
|
+
* dropped — a picture that vanishes reads as a broken uploader. */
|
|
15
|
+
maxBytes?: number;
|
|
16
|
+
icon?: string;
|
|
17
|
+
title?: string;
|
|
18
|
+
hint?: string;
|
|
19
|
+
/**
|
|
20
|
+
* How the well sits against what holds it. A drop target reads as a hole
|
|
21
|
+
* in the surface, not a tile on top of it, so the default is darker than
|
|
22
|
+
* the card — which is what a dashed border is already suggesting.
|
|
23
|
+
* `flush` for a zone that fills a panel of its own, `soft` when it sits
|
|
24
|
+
* directly on the page background and needs to lift off it instead.
|
|
25
|
+
*/
|
|
26
|
+
tone?: "sunken" | "flush" | "soft";
|
|
27
|
+
/** Draw the entrance when the zone first comes into view. */
|
|
28
|
+
animate?: boolean;
|
|
29
|
+
};
|
|
30
|
+
declare var __VLS_1: {
|
|
31
|
+
over: boolean;
|
|
32
|
+
};
|
|
33
|
+
type __VLS_Slots = {} & {
|
|
34
|
+
default?: (props: typeof __VLS_1) => any;
|
|
35
|
+
};
|
|
36
|
+
declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
|
|
37
|
+
browse: () => void | undefined;
|
|
38
|
+
}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
|
|
39
|
+
files: (files: File[]) => any;
|
|
40
|
+
rejected: (reason: {
|
|
41
|
+
file: File;
|
|
42
|
+
cause: "type" | "size";
|
|
43
|
+
}[]) => any;
|
|
44
|
+
}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
45
|
+
onFiles?: ((files: File[]) => any) | undefined;
|
|
46
|
+
onRejected?: ((reason: {
|
|
47
|
+
file: File;
|
|
48
|
+
cause: "type" | "size";
|
|
49
|
+
}[]) => any) | undefined;
|
|
50
|
+
}>, {
|
|
51
|
+
title: string;
|
|
52
|
+
animate: boolean;
|
|
53
|
+
icon: string;
|
|
54
|
+
accept: string;
|
|
55
|
+
multiple: boolean;
|
|
56
|
+
hint: string;
|
|
57
|
+
tone: "sunken" | "flush" | "soft";
|
|
58
|
+
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
59
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
60
|
+
declare const _default: typeof __VLS_export;
|
|
61
|
+
export default _default;
|
|
62
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
63
|
+
new (): {
|
|
64
|
+
$slots: S;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { defineComponent, ref, onMounted, onBeforeUnmount, openBlock, createElementBlock, withModifiers, normalizeClass, unref, createElementVNode, renderSlot, createVNode, toDisplayString, createCommentVNode } from "vue";
|
|
2
|
+
import { useReveal } from "../composables/useReveal.js";
|
|
3
|
+
import { _ as _sfc_main$1 } from "../chunks/LpIcon-CCnX5_2j.js";
|
|
4
|
+
import { _ as _export_sfc } from "../chunks/_plugin-vue_export-helper-1tPrXgE0.js";
|
|
5
|
+
import '../assets/LpDropzone-DVDC-ABU.css';const _hoisted_1 = ["disabled"];
|
|
6
|
+
const _hoisted_2 = {
|
|
7
|
+
key: 0,
|
|
8
|
+
class: "text-sm font-medium text-ink"
|
|
9
|
+
};
|
|
10
|
+
const _hoisted_3 = {
|
|
11
|
+
key: 1,
|
|
12
|
+
class: "text-xs text-muted"
|
|
13
|
+
};
|
|
14
|
+
const _hoisted_4 = ["accept", "multiple", "disabled"];
|
|
15
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
16
|
+
__name: "LpDropzone",
|
|
17
|
+
props: {
|
|
18
|
+
accept: { default: "" },
|
|
19
|
+
multiple: { type: Boolean, default: false },
|
|
20
|
+
disabled: { type: Boolean },
|
|
21
|
+
pasteTarget: { type: Boolean },
|
|
22
|
+
maxBytes: {},
|
|
23
|
+
icon: { default: "lucide:upload" },
|
|
24
|
+
title: { default: "" },
|
|
25
|
+
hint: { default: "" },
|
|
26
|
+
tone: { default: "sunken" },
|
|
27
|
+
animate: { type: Boolean, default: true }
|
|
28
|
+
},
|
|
29
|
+
emits: ["files", "rejected"],
|
|
30
|
+
setup(__props, { expose: __expose, emit: __emit }) {
|
|
31
|
+
const props = __props;
|
|
32
|
+
const TONES = {
|
|
33
|
+
sunken: "bg-surface",
|
|
34
|
+
flush: "bg-surface-raised",
|
|
35
|
+
soft: "bg-surface-soft"
|
|
36
|
+
};
|
|
37
|
+
const { el: revealAnchor, revealed } = useReveal({ immediate: () => !props.animate });
|
|
38
|
+
const emit = __emit;
|
|
39
|
+
const input = ref(null);
|
|
40
|
+
const over = ref(false);
|
|
41
|
+
function onDragOver(event) {
|
|
42
|
+
if (props.disabled) return;
|
|
43
|
+
over.value = true;
|
|
44
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = "copy";
|
|
45
|
+
}
|
|
46
|
+
function onDragLeave(event) {
|
|
47
|
+
const to = event.relatedTarget;
|
|
48
|
+
if (to && event.currentTarget.contains(to)) return;
|
|
49
|
+
over.value = false;
|
|
50
|
+
}
|
|
51
|
+
function matches(file) {
|
|
52
|
+
if (!props.accept) return true;
|
|
53
|
+
return props.accept.split(",").some((rule) => {
|
|
54
|
+
const pattern = rule.trim().toLowerCase();
|
|
55
|
+
if (!pattern) return false;
|
|
56
|
+
if (pattern.startsWith(".")) return file.name.toLowerCase().endsWith(pattern);
|
|
57
|
+
if (pattern.endsWith("/*")) return file.type.startsWith(pattern.slice(0, -1));
|
|
58
|
+
return file.type.toLowerCase() === pattern;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function take(list) {
|
|
62
|
+
if (props.disabled || !list) return;
|
|
63
|
+
const all = Array.from(list);
|
|
64
|
+
const kept = [];
|
|
65
|
+
const refused = [];
|
|
66
|
+
for (const file of all) {
|
|
67
|
+
if (!matches(file)) refused.push({ file, cause: "type" });
|
|
68
|
+
else if (props.maxBytes && file.size > props.maxBytes) {
|
|
69
|
+
refused.push({ file, cause: "size" });
|
|
70
|
+
} else kept.push(file);
|
|
71
|
+
}
|
|
72
|
+
const bounded = props.multiple ? kept : kept.slice(0, 1);
|
|
73
|
+
if (refused.length) emit("rejected", refused);
|
|
74
|
+
if (bounded.length) emit("files", bounded);
|
|
75
|
+
}
|
|
76
|
+
function onDrop(event) {
|
|
77
|
+
over.value = false;
|
|
78
|
+
take(event.dataTransfer?.files);
|
|
79
|
+
}
|
|
80
|
+
function onPaste(event) {
|
|
81
|
+
const files = Array.from(event.clipboardData?.files ?? []);
|
|
82
|
+
if (!files.length) return;
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
take(files);
|
|
85
|
+
}
|
|
86
|
+
function onPicked(event) {
|
|
87
|
+
const el = event.target;
|
|
88
|
+
take(el.files);
|
|
89
|
+
el.value = "";
|
|
90
|
+
}
|
|
91
|
+
function windowPaste(event) {
|
|
92
|
+
if (props.disabled) return;
|
|
93
|
+
const target = event.target;
|
|
94
|
+
if (target?.closest("input, textarea, [contenteditable]")) return;
|
|
95
|
+
onPaste(event);
|
|
96
|
+
}
|
|
97
|
+
onMounted(() => {
|
|
98
|
+
if (props.pasteTarget) window.addEventListener("paste", windowPaste);
|
|
99
|
+
});
|
|
100
|
+
onBeforeUnmount(() => window.removeEventListener("paste", windowPaste));
|
|
101
|
+
__expose({ browse: () => input.value?.click() });
|
|
102
|
+
return (_ctx, _cache) => {
|
|
103
|
+
return openBlock(), createElementBlock("button", {
|
|
104
|
+
ref_key: "revealAnchor",
|
|
105
|
+
ref: revealAnchor,
|
|
106
|
+
type: "button",
|
|
107
|
+
disabled: __props.disabled,
|
|
108
|
+
class: normalizeClass(["lp-dropzone relative flex w-full flex-col items-center gap-2 rounded-card px-6 py-8 text-center transition-colors duration-[var(--duration-fast)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-55", [
|
|
109
|
+
over.value ? "bg-brand-soft" : TONES[__props.tone],
|
|
110
|
+
over.value ? "lp-dropzone--over" : "",
|
|
111
|
+
__props.animate && !unref(revealed) ? "lp-dropzone--waiting" : ""
|
|
112
|
+
]]),
|
|
113
|
+
onClick: _cache[1] || (_cache[1] = ($event) => input.value?.click()),
|
|
114
|
+
onPaste,
|
|
115
|
+
onDragenter: withModifiers(onDragOver, ["prevent"]),
|
|
116
|
+
onDragover: withModifiers(onDragOver, ["prevent"]),
|
|
117
|
+
onDragleave: onDragLeave,
|
|
118
|
+
onDrop: withModifiers(onDrop, ["prevent"])
|
|
119
|
+
}, [
|
|
120
|
+
_cache[2] || (_cache[2] = createElementVNode("svg", {
|
|
121
|
+
"aria-hidden": "true",
|
|
122
|
+
class: "lp-dropzone__frame pointer-events-none absolute inset-0 size-full"
|
|
123
|
+
}, [
|
|
124
|
+
createElementVNode("rect", {
|
|
125
|
+
x: "0.5",
|
|
126
|
+
y: "0.5",
|
|
127
|
+
width: "calc(100% - 1px)",
|
|
128
|
+
height: "calc(100% - 1px)",
|
|
129
|
+
rx: "var(--radius-card)",
|
|
130
|
+
fill: "none",
|
|
131
|
+
"stroke-width": "1",
|
|
132
|
+
"stroke-dasharray": "6 5"
|
|
133
|
+
})
|
|
134
|
+
], -1)),
|
|
135
|
+
renderSlot(_ctx.$slots, "default", { over: over.value }, () => [
|
|
136
|
+
createVNode(_sfc_main$1, {
|
|
137
|
+
name: __props.icon,
|
|
138
|
+
size: 22,
|
|
139
|
+
class: normalizeClass(["text-muted transition-transform duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] motion-reduce:transition-none", over.value ? "scale-110 text-brand" : ""])
|
|
140
|
+
}, null, 8, ["name", "class"]),
|
|
141
|
+
__props.title ? (openBlock(), createElementBlock("span", _hoisted_2, toDisplayString(__props.title), 1)) : createCommentVNode("", true),
|
|
142
|
+
__props.hint ? (openBlock(), createElementBlock("span", _hoisted_3, toDisplayString(__props.hint), 1)) : createCommentVNode("", true)
|
|
143
|
+
], true),
|
|
144
|
+
createElementVNode("input", {
|
|
145
|
+
ref_key: "input",
|
|
146
|
+
ref: input,
|
|
147
|
+
type: "file",
|
|
148
|
+
class: "hidden",
|
|
149
|
+
accept: __props.accept || void 0,
|
|
150
|
+
multiple: __props.multiple,
|
|
151
|
+
disabled: __props.disabled,
|
|
152
|
+
onChange: onPicked,
|
|
153
|
+
onClick: _cache[0] || (_cache[0] = withModifiers(() => {
|
|
154
|
+
}, ["stop"]))
|
|
155
|
+
}, null, 40, _hoisted_4)
|
|
156
|
+
], 42, _hoisted_1);
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
const LpDropzone = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-996dbce9"]]);
|
|
161
|
+
export {
|
|
162
|
+
LpDropzone as default
|
|
163
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
/** The picture to work on. */
|
|
3
|
+
file: File | Blob;
|
|
4
|
+
/**
|
|
5
|
+
* Shape of the crop box.
|
|
6
|
+
*
|
|
7
|
+
* The named ones are shorthand for the common cases; a NUMBER is the real
|
|
8
|
+
* interface — `:aspect="2.35"` for anamorphic, `:aspect="0.8"` for a
|
|
9
|
+
* shelf label — so a consumer whose shape is not on the list is not stuck
|
|
10
|
+
* choosing the nearest wrong one. "free" keeps the source's own
|
|
11
|
+
* proportions and crops only what panning and zoom leave outside.
|
|
12
|
+
*/
|
|
13
|
+
aspect?: "free" | "square" | "4:3" | "3:4" | "16:9" | number;
|
|
14
|
+
/** Longest edge of the exported image. Larger sources are scaled down. */
|
|
15
|
+
maxEdge?: number;
|
|
16
|
+
/** JPEG quality of the export. */
|
|
17
|
+
quality?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Labels for the toolbar. Defaulted in English rather than left blank: an
|
|
20
|
+
* icon button with no accessible name is announced as "button", and the
|
|
21
|
+
* kit cannot reach the consumer's translations. Apps with i18n pass their
|
|
22
|
+
* own strings.
|
|
23
|
+
*/
|
|
24
|
+
labels?: Partial<Record<"rotateLeft" | "rotateRight" | "zoomIn" | "zoomOut" | "reset" | "canvas", string>>;
|
|
25
|
+
/** Fade the picture in when the editor first comes into view. */
|
|
26
|
+
animate?: boolean;
|
|
27
|
+
};
|
|
28
|
+
declare function reset(): void;
|
|
29
|
+
declare function exportImage(): Promise<Blob | null>;
|
|
30
|
+
declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {
|
|
31
|
+
export: typeof exportImage;
|
|
32
|
+
reset: typeof reset;
|
|
33
|
+
edited: import('vue').ComputedRef<boolean>;
|
|
34
|
+
shortcuts: readonly [{
|
|
35
|
+
readonly keys: readonly ["←", "→", "↑", "↓"];
|
|
36
|
+
readonly label: "Pan (Shift for a bigger step)";
|
|
37
|
+
}, {
|
|
38
|
+
readonly keys: readonly ["+", "−"];
|
|
39
|
+
readonly label: "Zoom";
|
|
40
|
+
}, {
|
|
41
|
+
readonly keys: readonly ["R"];
|
|
42
|
+
readonly label: "Rotate a quarter turn (Shift reverses)";
|
|
43
|
+
}, {
|
|
44
|
+
readonly keys: readonly ["Alt", "wheel"];
|
|
45
|
+
readonly label: "Turn freely (Shift snaps to 15°)";
|
|
46
|
+
}, {
|
|
47
|
+
readonly keys: readonly ["0"];
|
|
48
|
+
readonly label: "Reset";
|
|
49
|
+
}];
|
|
50
|
+
}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
51
|
+
animate: boolean;
|
|
52
|
+
labels: Partial<Record<"rotateLeft" | "rotateRight" | "zoomIn" | "zoomOut" | "reset" | "canvas", string>>;
|
|
53
|
+
aspect: "free" | "square" | "4:3" | "3:4" | "16:9" | number;
|
|
54
|
+
maxEdge: number;
|
|
55
|
+
quality: number;
|
|
56
|
+
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
57
|
+
declare const _default: typeof __VLS_export;
|
|
58
|
+
export default _default;
|