@leavepulse/ui 0.28.0 → 0.28.2

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 (31) hide show
  1. package/dist/assets/LpDropzone-DVDC-ABU.css +52 -0
  2. package/dist/assets/LpImageEditor-DqP5JKCy.css +25 -0
  3. package/dist/chunks/{LpLogViewer-CcQ3s3Fp.js → LpLogViewer-CKmEQYnm.js} +66 -8
  4. package/dist/chunks/{LpNumberField-B3JuQFol.js → LpNumberField-NPE8Bumx.js} +14 -3
  5. package/dist/chunks/{LpProgress-DoIP4jQR.js → LpProgress-Btq5W4Ut.js} +14 -14
  6. package/dist/component-names.d.ts +1 -1
  7. package/dist/components/LpDropzone.vue.d.ts +66 -0
  8. package/dist/components/LpDropzone.vue.js +163 -0
  9. package/dist/components/LpImageEditor.vue.d.ts +58 -0
  10. package/dist/components/LpImageEditor.vue.js +357 -0
  11. package/dist/components/LpLogViewer.vue.d.ts +4 -0
  12. package/dist/components/LpLogViewer.vue.js +1 -1
  13. package/dist/components/LpNumberField.vue.d.ts +1 -0
  14. package/dist/components/LpNumberField.vue.js +1 -1
  15. package/dist/components/LpProgress.vue.js +1 -1
  16. package/dist/composables/useHotkeys.d.ts +16 -1
  17. package/dist/composables/useHotkeys.js +8 -1
  18. package/dist/composables/useZoomPan.d.ts +24 -0
  19. package/dist/composables/useZoomPan.js +11 -0
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.js +9 -5
  22. package/package.json +1 -1
  23. package/src/component-names.ts +2 -0
  24. package/src/components/LpDropzone.vue +299 -0
  25. package/src/components/LpImageEditor.vue +515 -0
  26. package/src/components/LpLogViewer.vue +114 -2
  27. package/src/components/LpNumberField.vue +16 -0
  28. package/src/components/LpProgress.vue +30 -21
  29. package/src/composables/useHotkeys.ts +30 -1
  30. package/src/composables/useZoomPan.ts +30 -0
  31. 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
+ }
@@ -1,9 +1,13 @@
1
- import { defineComponent, computed, ref, watch, nextTick, onMounted, onBeforeUnmount, openBlock, createElementBlock, createVNode, normalizeStyle, withCtx, createElementVNode, toDisplayString, createCommentVNode, Fragment, createBlock, resolveDynamicComponent, TransitionGroup, mergeProps, renderList, normalizeClass, createTextVNode, Transition } from "vue";
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 = { class: "relative overflow-hidden rounded-card border border-line bg-surface font-mono text-xs leading-relaxed" };
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 ? props.lines.map(parseLine) : props.lines
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", _hoisted_1, [
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[0] || (_cache[0] = createElementVNode("span", { class: "text-[11px]" }, "loading older…", -1))
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[1] || (_cache[1] = createElementVNode("span", {
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[2] || (_cache[2] = createTextVNode(" Jump to latest ", -1))
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
  });
@@ -1,6 +1,7 @@
1
- import { defineComponent, openBlock, createBlock, unref, withCtx, createVNode } from "vue";
1
+ import { defineComponent, useId, openBlock, createBlock, unref, withCtx, createVNode, createElementBlock, toDisplayString, createCommentVNode } from "vue";
2
2
  import { NumberFieldRoot, NumberFieldDecrement, NumberFieldInput, NumberFieldIncrement } from "reka-ui";
3
3
  import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
4
+ const _hoisted_1 = ["id"];
4
5
  const _sfc_main = /* @__PURE__ */ defineComponent({
5
6
  __name: "LpNumberField",
6
7
  props: {
@@ -8,10 +9,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
8
9
  min: {},
9
10
  max: {},
10
11
  step: {},
11
- disabled: { type: Boolean }
12
+ disabled: { type: Boolean },
13
+ unit: {}
12
14
  },
13
15
  emits: ["update:modelValue"],
14
16
  setup(__props) {
17
+ const unitId = useId();
15
18
  return (_ctx, _cache) => {
16
19
  return openBlock(), createBlock(unref(NumberFieldRoot), {
17
20
  "model-value": __props.modelValue ?? void 0,
@@ -34,7 +37,15 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
34
37
  ]),
35
38
  _: 1
36
39
  }),
37
- createVNode(unref(NumberFieldInput), { class: "min-w-0 flex-1 bg-transparent text-center text-sm text-ink outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none" }),
40
+ createVNode(unref(NumberFieldInput), {
41
+ "aria-describedby": __props.unit ? unref(unitId) : void 0,
42
+ class: "min-w-0 flex-1 bg-transparent text-center text-sm text-ink outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none"
43
+ }, null, 8, ["aria-describedby"]),
44
+ __props.unit ? (openBlock(), createElementBlock("span", {
45
+ key: 0,
46
+ id: unref(unitId),
47
+ class: "shrink-0 pr-1 text-xs text-muted"
48
+ }, toDisplayString(__props.unit), 9, _hoisted_1)) : createCommentVNode("", true),
38
49
  createVNode(unref(NumberFieldIncrement), { class: "group grid h-full w-8 place-items-center text-muted transition-colors duration-[var(--duration-fast)] hover:text-ink disabled:opacity-40" }, {
39
50
  default: withCtx(() => [
40
51
  createVNode(_sfc_main$1, {
@@ -36,19 +36,19 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
36
36
  },
37
37
  setup(__props) {
38
38
  const props = __props;
39
- const eased = ref(props.animateOnMount ? 0 : 1);
39
+ const shown = ref(props.animateOnMount ? 0 : props.value);
40
40
  let raf = 0;
41
- let from = props.animateOnMount ? 0 : props.value;
42
- const runSweep = (ms) => {
41
+ const runSweep = (to, ms) => {
43
42
  if (raf) cancelAnimationFrame(raf);
44
- if (prefersReducedMotion() || ms <= 0) {
45
- eased.value = 1;
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
- eased.value = easeOut(t);
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(revealed, (visible) => {
64
- if (visible && props.animateOnMount) runSweep(props.duration);
65
- }, { immediate: true });
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;