orio-ui 1.20.0 → 1.23.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.
Files changed (139) hide show
  1. package/README.md +12 -5
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +5 -2
  4. package/dist/runtime/canvas.d.ts +21 -0
  5. package/dist/runtime/canvas.js +52 -0
  6. package/dist/runtime/components/Canvas/REQUIREMENTS.md +174 -0
  7. package/dist/runtime/components/Canvas/components/Stage.d.vue.ts +3 -0
  8. package/dist/runtime/components/Canvas/components/Stage.vue +150 -0
  9. package/dist/runtime/components/Canvas/components/Stage.vue.d.ts +3 -0
  10. package/dist/runtime/components/Canvas/components/ToolButton.d.vue.ts +24 -0
  11. package/dist/runtime/components/Canvas/components/ToolButton.vue +62 -0
  12. package/dist/runtime/components/Canvas/components/ToolButton.vue.d.ts +24 -0
  13. package/dist/runtime/components/Canvas/components/Toolbar.d.vue.ts +24 -0
  14. package/dist/runtime/components/Canvas/components/Toolbar.vue +48 -0
  15. package/dist/runtime/components/Canvas/components/Toolbar.vue.d.ts +24 -0
  16. package/dist/runtime/components/Canvas/composables/useCanvasHistory.d.ts +17 -0
  17. package/dist/runtime/components/Canvas/composables/useCanvasHistory.js +76 -0
  18. package/dist/runtime/components/Canvas/composables/useCanvasNodes.d.ts +13 -0
  19. package/dist/runtime/components/Canvas/composables/useCanvasNodes.js +60 -0
  20. package/dist/runtime/components/Canvas/composables/useCanvasSetup.d.ts +5 -0
  21. package/dist/runtime/components/Canvas/composables/useCanvasSetup.js +19 -0
  22. package/dist/runtime/components/Canvas/context.d.ts +47 -0
  23. package/dist/runtime/components/Canvas/context.js +11 -0
  24. package/dist/runtime/components/Canvas/index.d.vue.ts +80 -0
  25. package/dist/runtime/components/Canvas/index.vue +225 -0
  26. package/dist/runtime/components/Canvas/index.vue.d.ts +80 -0
  27. package/dist/runtime/components/Canvas/registry.d.ts +1 -0
  28. package/dist/runtime/components/Canvas/registry.js +2 -0
  29. package/dist/runtime/components/Canvas/tools/ColorPickerWidget.d.vue.ts +7 -0
  30. package/dist/runtime/components/Canvas/tools/ColorPickerWidget.vue +32 -0
  31. package/dist/runtime/components/Canvas/tools/ColorPickerWidget.vue.d.ts +7 -0
  32. package/dist/runtime/components/Canvas/tools/clearTool.d.ts +1 -0
  33. package/dist/runtime/components/Canvas/tools/clearTool.js +16 -0
  34. package/dist/runtime/components/Canvas/tools/colorPickerTool.d.ts +6 -0
  35. package/dist/runtime/components/Canvas/tools/colorPickerTool.js +15 -0
  36. package/dist/runtime/components/Canvas/tools/drawTool.d.ts +16 -0
  37. package/dist/runtime/components/Canvas/tools/drawTool.js +92 -0
  38. package/dist/runtime/components/Canvas/tools/eraseTool.d.ts +5 -0
  39. package/dist/runtime/components/Canvas/tools/eraseTool.js +62 -0
  40. package/dist/runtime/components/Canvas/tools/exportTool.d.ts +65 -0
  41. package/dist/runtime/components/Canvas/tools/exportTool.js +136 -0
  42. package/dist/runtime/components/Canvas/tools/highlightTool.d.ts +11 -0
  43. package/dist/runtime/components/Canvas/tools/highlightTool.js +51 -0
  44. package/dist/runtime/components/Canvas/tools/hitTest.d.ts +20 -0
  45. package/dist/runtime/components/Canvas/tools/hitTest.js +111 -0
  46. package/dist/runtime/components/Canvas/tools/imageTool.d.ts +18 -0
  47. package/dist/runtime/components/Canvas/tools/imageTool.js +163 -0
  48. package/dist/runtime/components/Canvas/tools/moveTool.d.ts +5 -0
  49. package/dist/runtime/components/Canvas/tools/moveTool.js +94 -0
  50. package/dist/runtime/components/Canvas/tools/redoTool.d.ts +1 -0
  51. package/dist/runtime/components/Canvas/tools/redoTool.js +17 -0
  52. package/dist/runtime/components/Canvas/tools/resizeTool.d.ts +7 -0
  53. package/dist/runtime/components/Canvas/tools/resizeTool.js +132 -0
  54. package/dist/runtime/components/Canvas/tools/rotateTool.d.ts +5 -0
  55. package/dist/runtime/components/Canvas/tools/rotateTool.js +109 -0
  56. package/dist/runtime/components/Canvas/tools/textTool.d.ts +14 -0
  57. package/dist/runtime/components/Canvas/tools/textTool.js +99 -0
  58. package/dist/runtime/components/Canvas/tools/tooltips/Clear.d.vue.ts +3 -0
  59. package/dist/runtime/components/Canvas/tools/tooltips/Clear.vue +12 -0
  60. package/dist/runtime/components/Canvas/tools/tooltips/Clear.vue.d.ts +3 -0
  61. package/dist/runtime/components/Canvas/tools/tooltips/Draw.d.vue.ts +3 -0
  62. package/dist/runtime/components/Canvas/tools/tooltips/Draw.vue +12 -0
  63. package/dist/runtime/components/Canvas/tools/tooltips/Draw.vue.d.ts +3 -0
  64. package/dist/runtime/components/Canvas/tools/tooltips/Erase.d.vue.ts +3 -0
  65. package/dist/runtime/components/Canvas/tools/tooltips/Erase.vue +12 -0
  66. package/dist/runtime/components/Canvas/tools/tooltips/Erase.vue.d.ts +3 -0
  67. package/dist/runtime/components/Canvas/tools/tooltips/Export.d.vue.ts +3 -0
  68. package/dist/runtime/components/Canvas/tools/tooltips/Export.vue +13 -0
  69. package/dist/runtime/components/Canvas/tools/tooltips/Export.vue.d.ts +3 -0
  70. package/dist/runtime/components/Canvas/tools/tooltips/Highlight.d.vue.ts +3 -0
  71. package/dist/runtime/components/Canvas/tools/tooltips/Highlight.vue +12 -0
  72. package/dist/runtime/components/Canvas/tools/tooltips/Highlight.vue.d.ts +3 -0
  73. package/dist/runtime/components/Canvas/tools/tooltips/Image.d.vue.ts +3 -0
  74. package/dist/runtime/components/Canvas/tools/tooltips/Image.vue +13 -0
  75. package/dist/runtime/components/Canvas/tools/tooltips/Image.vue.d.ts +3 -0
  76. package/dist/runtime/components/Canvas/tools/tooltips/Move.d.vue.ts +3 -0
  77. package/dist/runtime/components/Canvas/tools/tooltips/Move.vue +13 -0
  78. package/dist/runtime/components/Canvas/tools/tooltips/Move.vue.d.ts +3 -0
  79. package/dist/runtime/components/Canvas/tools/tooltips/Redo.d.vue.ts +3 -0
  80. package/dist/runtime/components/Canvas/tools/tooltips/Redo.vue +13 -0
  81. package/dist/runtime/components/Canvas/tools/tooltips/Redo.vue.d.ts +3 -0
  82. package/dist/runtime/components/Canvas/tools/tooltips/Resize.d.vue.ts +3 -0
  83. package/dist/runtime/components/Canvas/tools/tooltips/Resize.vue +13 -0
  84. package/dist/runtime/components/Canvas/tools/tooltips/Resize.vue.d.ts +3 -0
  85. package/dist/runtime/components/Canvas/tools/tooltips/Rotate.d.vue.ts +3 -0
  86. package/dist/runtime/components/Canvas/tools/tooltips/Rotate.vue +13 -0
  87. package/dist/runtime/components/Canvas/tools/tooltips/Rotate.vue.d.ts +3 -0
  88. package/dist/runtime/components/Canvas/tools/tooltips/Text.d.vue.ts +3 -0
  89. package/dist/runtime/components/Canvas/tools/tooltips/Text.vue +13 -0
  90. package/dist/runtime/components/Canvas/tools/tooltips/Text.vue.d.ts +3 -0
  91. package/dist/runtime/components/Canvas/tools/tooltips/Transform.d.vue.ts +3 -0
  92. package/dist/runtime/components/Canvas/tools/tooltips/Transform.vue +14 -0
  93. package/dist/runtime/components/Canvas/tools/tooltips/Transform.vue.d.ts +3 -0
  94. package/dist/runtime/components/Canvas/tools/tooltips/Undo.d.vue.ts +3 -0
  95. package/dist/runtime/components/Canvas/tools/tooltips/Undo.vue +13 -0
  96. package/dist/runtime/components/Canvas/tools/tooltips/Undo.vue.d.ts +3 -0
  97. package/dist/runtime/components/Canvas/tools/transformHandles.d.ts +74 -0
  98. package/dist/runtime/components/Canvas/tools/transformHandles.js +191 -0
  99. package/dist/runtime/components/Canvas/tools/transformTool.d.ts +7 -0
  100. package/dist/runtime/components/Canvas/tools/transformTool.js +210 -0
  101. package/dist/runtime/components/Canvas/tools/undoTool.d.ts +1 -0
  102. package/dist/runtime/components/Canvas/tools/undoTool.js +17 -0
  103. package/dist/runtime/components/Canvas/types.d.ts +125 -0
  104. package/dist/runtime/components/Canvas/types.js +3 -0
  105. package/dist/runtime/components/ControlElement.vue +5 -1
  106. package/dist/runtime/components/DateRangePicker.vue +16 -4
  107. package/dist/runtime/components/Icon.vue +2 -2
  108. package/dist/runtime/components/LocaleSwitcher.d.vue.ts +13 -0
  109. package/dist/runtime/components/LocaleSwitcher.vue +43 -0
  110. package/dist/runtime/components/LocaleSwitcher.vue.d.ts +13 -0
  111. package/dist/runtime/components/Selector.vue +14 -5
  112. package/dist/runtime/components/Tooltip.vue +17 -7
  113. package/dist/runtime/components/ZoomableContainer.d.vue.ts +48 -0
  114. package/dist/runtime/components/ZoomableContainer.vue +238 -0
  115. package/dist/runtime/components/ZoomableContainer.vue.d.ts +48 -0
  116. package/dist/runtime/components/gallery/Carousel.vue +1 -1
  117. package/dist/runtime/components/gallery/CarouselPreview.d.vue.ts +31 -0
  118. package/dist/runtime/components/gallery/CarouselPreview.vue +64 -0
  119. package/dist/runtime/components/gallery/CarouselPreview.vue.d.ts +31 -0
  120. package/dist/runtime/components/view/Dates.vue +5 -3
  121. package/dist/runtime/components/view/KeyBinds.d.vue.ts +7 -0
  122. package/dist/runtime/components/view/KeyBinds.vue +36 -0
  123. package/dist/runtime/components/view/KeyBinds.vue.d.ts +7 -0
  124. package/dist/runtime/components/view/Text.vue +4 -4
  125. package/dist/runtime/composables/useInertia.d.ts +10 -0
  126. package/dist/runtime/composables/useInertia.js +49 -0
  127. package/dist/runtime/composables/usePinchZoom.d.ts +13 -0
  128. package/dist/runtime/composables/usePinchZoom.js +66 -0
  129. package/dist/runtime/composables/useValidation.js +11 -1
  130. package/dist/runtime/i18n/en.json +20 -0
  131. package/dist/runtime/i18n/index.d.ts +11 -0
  132. package/dist/runtime/i18n/index.js +19 -0
  133. package/dist/runtime/i18n/uk.json +20 -0
  134. package/dist/runtime/index.d.ts +5 -0
  135. package/dist/runtime/index.js +16 -0
  136. package/dist/runtime/plugins/i18n.d.ts +2 -0
  137. package/dist/runtime/plugins/i18n.js +18 -0
  138. package/dist/runtime/utils/icon-registry.js +13 -1
  139. package/package.json +9 -4
@@ -0,0 +1,43 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ import { useI18n } from "vue-i18n";
4
+ const props = defineProps({
5
+ locales: { type: Array, required: false, default: () => [
6
+ { code: "en", flag: "\u{1F1EC}\u{1F1E7}", label: "English" },
7
+ { code: "uk", flag: "\u{1F1FA}\u{1F1E6}", label: "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430" }
8
+ ] }
9
+ });
10
+ const { locale } = useI18n();
11
+ const selected = computed({
12
+ get: () => props.locales.find((l) => l.code === locale.value) ?? props.locales[0],
13
+ set: (val) => {
14
+ locale.value = val.code;
15
+ }
16
+ });
17
+ </script>
18
+
19
+ <template>
20
+ <orio-selector
21
+ v-model="selected"
22
+ :options="locales"
23
+ field="code"
24
+ option-name="label"
25
+ >
26
+ <template #trigger-label>
27
+ <span class="locale-trigger">
28
+ <span class="locale-flag">{{ selected.flag }}</span>
29
+ <span class="locale-label">{{ selected.label }}</span>
30
+ </span>
31
+ </template>
32
+ <template #option="{ option }">
33
+ <span class="locale-option">
34
+ <span class="locale-flag">{{ option.flag }}</span>
35
+ <span>{{ option.label }}</span>
36
+ </span>
37
+ </template>
38
+ </orio-selector>
39
+ </template>
40
+
41
+ <style scoped>
42
+ .locale-option,.locale-trigger{align-items:center;display:flex;gap:.5rem}.locale-flag{font-size:1.25em;line-height:1}
43
+ </style>
@@ -0,0 +1,13 @@
1
+ export interface LocaleOption {
2
+ code: string;
3
+ flag: string;
4
+ label: string;
5
+ }
6
+ interface Props {
7
+ locales?: LocaleOption[];
8
+ }
9
+ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
10
+ locales: LocaleOption[];
11
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
12
+ declare const _default: typeof __VLS_export;
13
+ export default _default;
@@ -2,12 +2,14 @@
2
2
  import { computed, ref, toRef, toRefs } from "vue";
3
3
  import { useControlTokens } from "../composables/useControlSize";
4
4
  import { useListKeyboard } from "../composables/useListKeyboard";
5
+ import { useI18n } from "vue-i18n";
6
+ const { t } = useI18n();
5
7
  const props = defineProps({
6
8
  options: { type: Array, required: true },
7
9
  multiple: { type: Boolean, required: false },
8
10
  field: { type: String, required: false, default: "id" },
9
11
  optionName: { type: String, required: false },
10
- placeholder: { type: String, required: false, default: "Select an option" },
12
+ placeholder: { type: String, required: false },
11
13
  appearance: { type: String, required: false },
12
14
  error: { type: [String, null], required: false },
13
15
  group: { type: Boolean, required: false },
@@ -17,7 +19,10 @@ const props = defineProps({
17
19
  size: { type: String, required: false },
18
20
  fill: { type: Boolean, required: false }
19
21
  });
20
- const { field, optionName, placeholder } = toRefs(props);
22
+ const { field, optionName } = toRefs(props);
23
+ const resolvedPlaceholder = computed(
24
+ () => props.placeholder ?? t("selector.placeholder")
25
+ );
21
26
  const modelValue = defineModel({ type: null, ...{
22
27
  required: true
23
28
  } });
@@ -60,7 +65,7 @@ function isOptionSelected(option) {
60
65
  }
61
66
  }
62
67
  function getOptionLabel(option) {
63
- if (!option) return placeholder.value;
68
+ if (!option) return resolvedPlaceholder.value;
64
69
  if (typeof option === "string") return option;
65
70
  if (optionName.value) return String(option[label.value]);
66
71
  return JSON.stringify(option);
@@ -134,7 +139,11 @@ const {
134
139
  {{ getOptionLabel(modelValue) }}
135
140
  </template>
136
141
  <template v-else-if="Array.isArray(modelValue)">
137
- <span> {{ modelValue.length }} selected </span>
142
+ <span>
143
+ {{
144
+ t("selector.selected", { count: modelValue.length })
145
+ }}
146
+ </span>
138
147
  </template>
139
148
  </slot>
140
149
  </div>
@@ -175,7 +184,7 @@ const {
175
184
  </orio-list-item>
176
185
  </ul>
177
186
  <slot v-else name="no-options">
178
- <orio-empty-state title="No options found" size="small" />
187
+ <orio-empty-state :title="t('selector.noOptions')" size="small" />
179
188
  </slot>
180
189
  <slot name="options-addon" />
181
190
  </div>
@@ -1,5 +1,5 @@
1
1
  <script setup>
2
- import { ref, computed, nextTick } from "vue";
2
+ import { ref, computed, nextTick, toRefs, watch } from "vue";
3
3
  import { useEventListener } from "@vueuse/core";
4
4
  const props = defineProps({
5
5
  text: { type: String, required: false, default: void 0 },
@@ -8,6 +8,10 @@ const props = defineProps({
8
8
  disabled: { type: Boolean, required: false, default: false },
9
9
  offset: { type: Number, required: false, default: 8 }
10
10
  });
11
+ const { disabled } = toRefs(props);
12
+ watch(disabled, () => {
13
+ if (disabled.value) hideTooltip();
14
+ });
11
15
  const isVisible = ref(false);
12
16
  const trigger = ref(null);
13
17
  const tooltip = ref(null);
@@ -42,7 +46,7 @@ function calculatePosition() {
42
46
  position.value = { top, left };
43
47
  }
44
48
  function showTooltip() {
45
- if (props.disabled) return;
49
+ if (disabled.value) return;
46
50
  if (props.delay > 0) {
47
51
  showTimeout = setTimeout(() => {
48
52
  isVisible.value = true;
@@ -75,7 +79,9 @@ const tooltipStyle = computed(() => ({
75
79
  top: `${position.value.top}px`,
76
80
  left: `${position.value.left}px`
77
81
  }));
78
- const arrowClass = computed(() => `arrow-${props.placement}`);
82
+ const arrowClass = computed(
83
+ () => `orio-tooltip-arrow orio-tooltip-arrow-${props.placement}`
84
+ );
79
85
  </script>
80
86
 
81
87
  <template>
@@ -91,22 +97,26 @@ const arrowClass = computed(() => `arrow-${props.placement}`);
91
97
  <Teleport v-if="isVisible" to="body">
92
98
  <div
93
99
  ref="tooltip"
94
- class="tooltip"
100
+ class="orio-tooltip"
95
101
  :style="tooltipStyle"
96
102
  role="tooltip"
97
103
  :aria-hidden="!isVisible"
98
104
  >
99
- <div class="content">
105
+ <div class="orio-tooltip-content">
100
106
  <slot name="content">
101
107
  {{ text }}
102
108
  </slot>
103
109
  </div>
104
- <div :class="['arrow', arrowClass]" />
110
+ <div :class="arrowClass" />
105
111
  </div>
106
112
  </Teleport>
107
113
  </div>
108
114
  </template>
109
115
 
110
116
  <style scoped>
111
- .trigger{align-items:center;display:inline-flex;justify-content:center}.tooltip{pointer-events:none;position:absolute;z-index:9999}.content{background-color:rgba(0,0,0,.9);border-radius:var(--border-radius-sm,4px);box-shadow:0 2px 8px rgba(0,0,0,.15);color:#fff;font-size:var(--font-md);padding:.5rem .75rem;white-space:nowrap}.arrow{border-style:solid;height:0;position:absolute;width:0}.arrow-top{border-color:rgba(0,0,0,.9) transparent transparent;border-width:4px 4px 0;bottom:-4px}.arrow-bottom,.arrow-top{left:50%;transform:translateX(-50%)}.arrow-bottom{border-color:transparent transparent rgba(0,0,0,.9);border-width:0 4px 4px;top:-4px}.arrow-left{border-color:transparent transparent transparent rgba(0,0,0,.9);border-width:4px 0 4px 4px;right:-4px}.arrow-left,.arrow-right{top:50%;transform:translateY(-50%)}.arrow-right{border-color:transparent rgba(0,0,0,.9) transparent transparent;border-width:4px 4px 4px 0;left:-4px}
117
+ .trigger{align-items:center;display:inline-flex;justify-content:center}
118
+ </style>
119
+
120
+ <style>
121
+ .orio-tooltip{pointer-events:none;position:absolute;z-index:9999}.orio-tooltip-content{background-color:rgba(0,0,0,.9);border-radius:var(--border-radius-sm,4px);box-shadow:0 2px 8px rgba(0,0,0,.15);color:#fff;font-size:var(--font-md);padding:.5rem .75rem;white-space:nowrap}.orio-tooltip-arrow{border-style:solid;height:0;position:absolute;width:0}.orio-tooltip-arrow-top{border-color:rgba(0,0,0,.9) transparent transparent;border-width:4px 4px 0;bottom:-4px;left:50%;transform:translateX(-50%)}.orio-tooltip-arrow-bottom{border-color:transparent transparent rgba(0,0,0,.9);border-width:0 4px 4px;left:50%;top:-4px;transform:translateX(-50%)}.orio-tooltip-arrow-left{border-color:transparent transparent transparent rgba(0,0,0,.9);border-width:4px 0 4px 4px;right:-4px;top:50%;transform:translateY(-50%)}.orio-tooltip-arrow-right{border-color:transparent rgba(0,0,0,.9) transparent transparent;border-width:4px 4px 4px 0;left:-4px;top:50%;transform:translateY(-50%)}
112
122
  </style>
@@ -0,0 +1,48 @@
1
+ export interface ZoomableContainerProps {
2
+ minScale?: number;
3
+ maxScale?: number;
4
+ initialScale?: number;
5
+ zoomSpeed?: number;
6
+ wheelPanSpeed?: number;
7
+ }
8
+ declare function setScaleAt(target: number, px: number, py: number): void;
9
+ declare function panBy(dx: number, dy: number): void;
10
+ declare function centerWorld(): void;
11
+ declare function resetView(): void;
12
+ declare var __VLS_1: {
13
+ scale: number;
14
+ tx: number;
15
+ ty: number;
16
+ };
17
+ type __VLS_Slots = {} & {
18
+ default?: (props: typeof __VLS_1) => any;
19
+ };
20
+ declare const __VLS_base: import("vue").DefineComponent<ZoomableContainerProps, {
21
+ scale: import("vue").Ref<number, number>;
22
+ tx: import("vue").Ref<number, number>;
23
+ ty: import("vue").Ref<number, number>;
24
+ setScaleAt: typeof setScaleAt;
25
+ panBy: typeof panBy;
26
+ resetView: typeof resetView;
27
+ centerWorld: typeof centerWorld;
28
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
29
+ "update:scale": (value: number) => any;
30
+ "update:translate": (x: number, y: number) => any;
31
+ }, string, import("vue").PublicProps, Readonly<ZoomableContainerProps> & Readonly<{
32
+ "onUpdate:scale"?: ((value: number) => any) | undefined;
33
+ "onUpdate:translate"?: ((x: number, y: number) => any) | undefined;
34
+ }>, {
35
+ minScale: number;
36
+ maxScale: number;
37
+ initialScale: number;
38
+ zoomSpeed: number;
39
+ wheelPanSpeed: number;
40
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
41
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
42
+ declare const _default: typeof __VLS_export;
43
+ export default _default;
44
+ type __VLS_WithSlots<T, S> = T & {
45
+ new (): {
46
+ $slots: S;
47
+ };
48
+ };
@@ -0,0 +1,238 @@
1
+ <script setup>
2
+ import { computed, ref, onMounted, onUnmounted, useTemplateRef } from "vue";
3
+ import { useResizeObserver, useEventListener } from "@vueuse/core";
4
+ import { usePinchZoom } from "../composables/usePinchZoom";
5
+ import { useInertia } from "../composables/useInertia";
6
+ const props = defineProps({
7
+ minScale: { type: Number, required: false, default: 0.1 },
8
+ maxScale: { type: Number, required: false, default: 10 },
9
+ initialScale: { type: Number, required: false, default: 1 },
10
+ zoomSpeed: { type: Number, required: false, default: 15e-4 },
11
+ wheelPanSpeed: { type: Number, required: false, default: 1 }
12
+ });
13
+ const emit = defineEmits(["update:scale", "update:translate"]);
14
+ const viewportEl = useTemplateRef("viewportEl");
15
+ const worldEl = useTemplateRef("worldEl");
16
+ const scale = ref(props.initialScale);
17
+ const tx = ref(0);
18
+ const ty = ref(0);
19
+ const viewport = ref({ w: 0, h: 0 });
20
+ const world = ref({ w: 0, h: 0 });
21
+ const spaceHeld = ref(false);
22
+ let centered = false;
23
+ function clamp(v, a, b) {
24
+ const lo = Math.min(a, b);
25
+ const hi = Math.max(a, b);
26
+ return Math.max(lo, Math.min(hi, v));
27
+ }
28
+ function applyBounds() {
29
+ const vw = viewport.value.w;
30
+ const vh = viewport.value.h;
31
+ const ww = world.value.w * scale.value;
32
+ const wh = world.value.h * scale.value;
33
+ tx.value = clamp(tx.value, vw / 2 - ww, vw / 2);
34
+ ty.value = clamp(ty.value, vh / 2 - wh, vh / 2);
35
+ }
36
+ function emitState() {
37
+ emit("update:scale", scale.value);
38
+ emit("update:translate", tx.value, ty.value);
39
+ }
40
+ function setScaleAt(target, px, py) {
41
+ const next = clamp(target, props.minScale, props.maxScale);
42
+ const k = next / scale.value;
43
+ tx.value = px - (px - tx.value) * k;
44
+ ty.value = py - (py - ty.value) * k;
45
+ scale.value = next;
46
+ applyBounds();
47
+ emitState();
48
+ }
49
+ function panBy(dx, dy) {
50
+ tx.value += dx;
51
+ ty.value += dy;
52
+ applyBounds();
53
+ emitState();
54
+ }
55
+ function centerWorld() {
56
+ const { w: vw, h: vh } = viewport.value;
57
+ const { w: ww, h: wh } = world.value;
58
+ tx.value = (vw - ww * scale.value) / 2;
59
+ ty.value = (vh - wh * scale.value) / 2;
60
+ applyBounds();
61
+ emitState();
62
+ }
63
+ function resetView() {
64
+ scale.value = props.initialScale;
65
+ centerWorld();
66
+ }
67
+ const inertia = useInertia(panBy);
68
+ let pinchBaseScale = 1;
69
+ const pinch = usePinchZoom({
70
+ onPinchStart() {
71
+ dragId = null;
72
+ pinchBaseScale = scale.value;
73
+ },
74
+ onPinchMove(scaleFactor, midX, midY, dx, dy) {
75
+ const rect = viewportEl.value.getBoundingClientRect();
76
+ setScaleAt(pinchBaseScale * scaleFactor, midX - rect.left, midY - rect.top);
77
+ if (dx !== 0 || dy !== 0) panBy(dx, dy);
78
+ },
79
+ onSingleDown(e) {
80
+ e.preventDefault();
81
+ dragId = e.pointerId;
82
+ lastX = e.clientX;
83
+ lastY = e.clientY;
84
+ inertia.resetTime();
85
+ },
86
+ onSingleUp(id, x, y) {
87
+ dragId = id;
88
+ lastX = x;
89
+ lastY = y;
90
+ inertia.resetTime();
91
+ inertia.stop();
92
+ }
93
+ });
94
+ function onWheel(e) {
95
+ e.preventDefault();
96
+ if (e.ctrlKey || e.metaKey) {
97
+ const rect = viewportEl.value.getBoundingClientRect();
98
+ const px = e.clientX - rect.left;
99
+ const py = e.clientY - rect.top;
100
+ const factor = Math.exp(-e.deltaY * props.zoomSpeed);
101
+ setScaleAt(scale.value * factor, px, py);
102
+ return;
103
+ }
104
+ let dx = -e.deltaX * props.wheelPanSpeed;
105
+ let dy = -e.deltaY * props.wheelPanSpeed;
106
+ if (e.shiftKey && dx === 0) {
107
+ dx = dy;
108
+ dy = 0;
109
+ }
110
+ panBy(dx, dy);
111
+ }
112
+ let dragId = null;
113
+ let lastX = 0;
114
+ let lastY = 0;
115
+ function shouldPan(e) {
116
+ if (e.button === 1) return true;
117
+ if (spaceHeld.value) return true;
118
+ if (e.target === viewportEl.value) return true;
119
+ return false;
120
+ }
121
+ function onPointerDown(e) {
122
+ inertia.stop();
123
+ if (e.pointerType === "touch") {
124
+ pinch.onPointerDown(e);
125
+ return;
126
+ }
127
+ if (!shouldPan(e)) return;
128
+ e.preventDefault();
129
+ dragId = e.pointerId;
130
+ lastX = e.clientX;
131
+ lastY = e.clientY;
132
+ inertia.resetTime();
133
+ e.currentTarget.setPointerCapture(e.pointerId);
134
+ }
135
+ function onPointerMove(e) {
136
+ if (e.pointerType === "touch") {
137
+ if (pinch.onPointerMove(e)) return;
138
+ }
139
+ if (dragId !== e.pointerId) return;
140
+ const dx = e.clientX - lastX;
141
+ const dy = e.clientY - lastY;
142
+ lastX = e.clientX;
143
+ lastY = e.clientY;
144
+ inertia.trackMove(dx, dy);
145
+ panBy(dx, dy);
146
+ }
147
+ function onPointerUp(e) {
148
+ if (e.pointerType === "touch") {
149
+ if (pinch.onPointerUp(e)) return;
150
+ }
151
+ if (dragId !== e.pointerId) return;
152
+ dragId = null;
153
+ inertia.release();
154
+ try {
155
+ e.currentTarget.releasePointerCapture(e.pointerId);
156
+ } catch {
157
+ }
158
+ }
159
+ useEventListener("keydown", (e) => {
160
+ if (e.code === "Space") spaceHeld.value = true;
161
+ });
162
+ useEventListener("keyup", (e) => {
163
+ if (e.code === "Space") spaceHeld.value = false;
164
+ });
165
+ useResizeObserver(viewportEl, (entries) => {
166
+ const r = entries[0].contentRect;
167
+ viewport.value = { w: r.width, h: r.height };
168
+ if (!centered && world.value.w && world.value.h) {
169
+ centered = true;
170
+ centerWorld();
171
+ } else {
172
+ applyBounds();
173
+ }
174
+ });
175
+ useResizeObserver(worldEl, (entries) => {
176
+ const r = entries[0].contentRect;
177
+ world.value = { w: r.width, h: r.height };
178
+ if (!centered && viewport.value.w && viewport.value.h) {
179
+ centered = true;
180
+ centerWorld();
181
+ } else {
182
+ applyBounds();
183
+ }
184
+ });
185
+ onMounted(() => {
186
+ const vEl = viewportEl.value;
187
+ const wEl = worldEl.value;
188
+ if (!vEl || !wEl) return;
189
+ viewport.value = { w: vEl.clientWidth, h: vEl.clientHeight };
190
+ world.value = { w: wEl.offsetWidth, h: wEl.offsetHeight };
191
+ if (viewport.value.w && world.value.w) {
192
+ centered = true;
193
+ centerWorld();
194
+ }
195
+ });
196
+ onUnmounted(() => {
197
+ inertia.stop();
198
+ });
199
+ const transform = computed(
200
+ () => `translate(${tx.value}px, ${ty.value}px) scale(${scale.value})`
201
+ );
202
+ const cursor = computed(() => {
203
+ if (dragId !== null) return "grabbing";
204
+ if (spaceHeld.value) return "grab";
205
+ return "default";
206
+ });
207
+ defineExpose({
208
+ scale,
209
+ tx,
210
+ ty,
211
+ setScaleAt,
212
+ panBy,
213
+ resetView,
214
+ centerWorld
215
+ });
216
+ </script>
217
+
218
+ <template>
219
+ <div
220
+ ref="viewportEl"
221
+ class="zoomable-viewport"
222
+ :style="{ cursor }"
223
+ @wheel="onWheel"
224
+ @pointerdown="onPointerDown"
225
+ @pointermove="onPointerMove"
226
+ @pointerup="onPointerUp"
227
+ @pointercancel="onPointerUp"
228
+ @contextmenu.prevent
229
+ >
230
+ <div ref="worldEl" class="zoomable-world" :style="{ transform }">
231
+ <slot :scale="scale" :tx="tx" :ty="ty" />
232
+ </div>
233
+ </div>
234
+ </template>
235
+
236
+ <style scoped>
237
+ .zoomable-viewport{height:100%;overflow:hidden;position:relative;touch-action:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.zoomable-world{left:0;position:absolute;top:0;transform-origin:0 0;will-change:transform}
238
+ </style>
@@ -0,0 +1,48 @@
1
+ export interface ZoomableContainerProps {
2
+ minScale?: number;
3
+ maxScale?: number;
4
+ initialScale?: number;
5
+ zoomSpeed?: number;
6
+ wheelPanSpeed?: number;
7
+ }
8
+ declare function setScaleAt(target: number, px: number, py: number): void;
9
+ declare function panBy(dx: number, dy: number): void;
10
+ declare function centerWorld(): void;
11
+ declare function resetView(): void;
12
+ declare var __VLS_1: {
13
+ scale: number;
14
+ tx: number;
15
+ ty: number;
16
+ };
17
+ type __VLS_Slots = {} & {
18
+ default?: (props: typeof __VLS_1) => any;
19
+ };
20
+ declare const __VLS_base: import("vue").DefineComponent<ZoomableContainerProps, {
21
+ scale: import("vue").Ref<number, number>;
22
+ tx: import("vue").Ref<number, number>;
23
+ ty: import("vue").Ref<number, number>;
24
+ setScaleAt: typeof setScaleAt;
25
+ panBy: typeof panBy;
26
+ resetView: typeof resetView;
27
+ centerWorld: typeof centerWorld;
28
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
29
+ "update:scale": (value: number) => any;
30
+ "update:translate": (x: number, y: number) => any;
31
+ }, string, import("vue").PublicProps, Readonly<ZoomableContainerProps> & Readonly<{
32
+ "onUpdate:scale"?: ((value: number) => any) | undefined;
33
+ "onUpdate:translate"?: ((x: number, y: number) => any) | undefined;
34
+ }>, {
35
+ minScale: number;
36
+ maxScale: number;
37
+ initialScale: number;
38
+ zoomSpeed: number;
39
+ wheelPanSpeed: number;
40
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
41
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
42
+ declare const _default: typeof __VLS_export;
43
+ export default _default;
44
+ type __VLS_WithSlots<T, S> = T & {
45
+ new (): {
46
+ $slots: S;
47
+ };
48
+ };
@@ -157,5 +157,5 @@ onMounted(() => {
157
157
  </template>
158
158
 
159
159
  <style scoped>
160
- .carousel-wrapper{display:block;position:relative}.carousel-measure{height:v-bind(measureHeight);left:-9999px;pointer-events:none;position:fixed;visibility:hidden;width:v-bind(measureWidth)}.carousel-measure :deep(>*){display:block;height:100%;width:100%}.carousel{border:1px solid var(--color-border);border-radius:var(--border-radius-lg);height:v-bind("calculatedSize.height");max-height:v-bind(maxHeight);max-width:100%;overflow:hidden;transition:width .3s ease,height .3s ease;width:v-bind("calculatedSize.width")}.carousel-track{align-items:center;cursor:grab;display:flex;gap:.75rem;height:100%;position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.carousel-track:active{cursor:grabbing}.carousel-item{background:var(--color-surface);border-radius:var(--border-radius-sm);color:var(--color-text);height:100%;inset:0;opacity:0;overflow:hidden;padding:.5rem .75rem;pointer-events:none;position:absolute;transition:opacity .5s ease-in-out,transform .5s ease-in-out;white-space:nowrap;width:100%}.carousel-item.previous-image{transform:translateX(-100%)}.carousel-item.next-image{transform:translateX(100%)}.carousel-item.active-image{opacity:1;pointer-events:auto;transform:translateX(0)}.carousel-item :deep(>*){height:100%;-o-object-fit:v-bind(fit);object-fit:v-bind(fit);width:100%}.carousel-empty{color:var(--color-muted)}.switch-button{position:absolute}.switch-button :deep(button){background:transparent!important;border:none!important;color:transparent!important}.switch-button :deep(button:hover){color:transparent!important}.switch-button :deep(.orio-icon){color:#fff!important;fill:#fff!important;filter:drop-shadow(0 0 2px rgba(0,0,0,.8)) drop-shadow(0 0 4px rgba(0,0,0,.6))}@supports (mix-blend-mode:difference) and (not (-webkit-hyphens:none)){.switch-button :deep(.orio-icon){color:#000!important;fill:#000!important;filter:grayscale(1) contrast(9) invert(1) drop-shadow(0 0 1px black) drop-shadow(0 0 2px black);mix-blend-mode:difference}}.switch-button.previous-button{left:0}.switch-button.next-button{right:0}.carousel--minimal{background:none;border:none}.carousel--minimal .carousel-item{background:none}.carousel--minimal .switch-button{opacity:0;transition:opacity .2s ease}.carousel--minimal:hover .switch-button{opacity:1}
160
+ .carousel-wrapper{display:block;position:relative}.carousel-measure{height:v-bind(measureHeight);left:-9999px;pointer-events:none;position:fixed;visibility:hidden;width:v-bind(measureWidth)}.carousel-measure :deep(>*){display:block;height:100%;width:100%}.carousel{border:1px solid var(--color-border);border-radius:var(--border-radius-lg);height:v-bind("calculatedSize.height");max-height:v-bind(maxHeight);max-width:100%;overflow:hidden;transition:width .3s ease,height .3s ease;width:v-bind("calculatedSize.width")}.carousel-track{align-items:center;cursor:grab;display:flex;gap:.75rem;height:100%;position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.carousel-track:active{cursor:grabbing}.carousel-item{background:var(--color-surface);border-radius:var(--border-radius-sm);color:var(--color-text);height:100%;inset:0;opacity:0;overflow:hidden;padding:.5rem .75rem;pointer-events:none;position:absolute;transition:opacity .5s ease-in-out,transform .5s ease-in-out;white-space:nowrap;width:100%}.carousel-item.previous-image{transform:translateX(-100%)}.carousel-item.next-image{transform:translateX(100%)}.carousel-item.active-image{opacity:1;pointer-events:auto;transform:translateX(0)}.carousel-item :deep(>*){height:100%;-o-object-fit:v-bind(fit);object-fit:v-bind(fit);width:100%}.carousel-empty{color:var(--color-muted)}.switch-button{position:absolute}.switch-button :deep(button){background:transparent!important;border:none!important;color:transparent!important}.switch-button :deep(button:hover){color:transparent!important}.switch-button :deep(.icon){color:#fff!important;fill:#fff!important;filter:drop-shadow(0 0 2px rgba(0,0,0,.8)) drop-shadow(0 0 4px rgba(0,0,0,.6))}@supports (mix-blend-mode:difference) and (not (-webkit-hyphens:none)){.switch-button :deep(.icon){color:#000!important;fill:#000!important;filter:grayscale(1) contrast(9) invert(1) drop-shadow(0 0 1px black) drop-shadow(0 0 2px black);mix-blend-mode:difference}}.switch-button.previous-button{left:0}.switch-button.next-button{right:0}.carousel--minimal{background:none;border:none}.carousel--minimal .carousel-item{background:none}.carousel--minimal .switch-button{opacity:0;transition:opacity .2s ease}.carousel--minimal:hover .switch-button{opacity:1}
161
161
  </style>
@@ -0,0 +1,31 @@
1
+ interface Props {
2
+ images?: string[];
3
+ fit?: "fill" | "cover" | "contain" | "scale-down";
4
+ }
5
+ type __VLS_Props = Props;
6
+ type __VLS_ModelProps = {
7
+ "activeImage"?: string;
8
+ };
9
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
10
+ declare var __VLS_1: {
11
+ image: string;
12
+ };
13
+ type __VLS_Slots = {} & {
14
+ image?: (props: typeof __VLS_1) => any;
15
+ };
16
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
17
+ "update:activeImage": (value: string | undefined) => any;
18
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
19
+ "onUpdate:activeImage"?: ((value: string | undefined) => any) | undefined;
20
+ }>, {
21
+ images: string[];
22
+ fit: "fill" | "cover" | "contain" | "scale-down";
23
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
24
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
25
+ declare const _default: typeof __VLS_export;
26
+ export default _default;
27
+ type __VLS_WithSlots<T, S> = T & {
28
+ new (): {
29
+ $slots: S;
30
+ };
31
+ };
@@ -0,0 +1,64 @@
1
+ <script setup>
2
+ import { toRefs } from "vue";
3
+ const props = defineProps({
4
+ images: { type: Array, required: false, default: () => [] },
5
+ fit: { type: String, required: false, default: "cover" }
6
+ });
7
+ const { images, fit } = toRefs(props);
8
+ const activeImage = defineModel("activeImage", { type: String });
9
+ </script>
10
+
11
+ <template>
12
+ <div v-if="images.length > 1" class="carousel-preview">
13
+ <button
14
+ v-for="(image, index) of images"
15
+ :key="image"
16
+ type="button"
17
+ class="preview-item"
18
+ :class="{ active: image === activeImage }"
19
+ :aria-pressed="image === activeImage"
20
+ :aria-label="`Show image ${index + 1} of ${images.length}`"
21
+ @click="activeImage = image"
22
+ >
23
+ <slot name="image" :image>
24
+ <img :src="image" alt="" draggable="false" />
25
+ </slot>
26
+ </button>
27
+ </div>
28
+ </template>
29
+
30
+ <style scoped>
31
+ .carousel-preview {
32
+ display: flex;
33
+ gap: 0.5rem;
34
+ overflow-x: auto;
35
+ padding: 0.25rem 0;
36
+ }
37
+
38
+ .preview-item {
39
+ flex-shrink: 0;
40
+ width: 3.5rem;
41
+ height: 3.5rem;
42
+ padding: 0;
43
+ border: 2px solid var(--color-border);
44
+ border-radius: var(--border-radius-md);
45
+ background: var(--color-surface);
46
+ cursor: pointer;
47
+ overflow: hidden;
48
+ opacity: 0.6;
49
+ transition: opacity 0.2s ease, border-color 0.2s ease;
50
+ }
51
+ .preview-item:hover {
52
+ opacity: 0.85;
53
+ }
54
+ .preview-item.active {
55
+ opacity: 1;
56
+ border-color: var(--color-accent);
57
+ }
58
+ .preview-item :deep(> *) {
59
+ width: 100%;
60
+ height: 100%;
61
+ object-fit: v-bind(fit);
62
+ display: block;
63
+ }
64
+ </style>
@@ -0,0 +1,31 @@
1
+ interface Props {
2
+ images?: string[];
3
+ fit?: "fill" | "cover" | "contain" | "scale-down";
4
+ }
5
+ type __VLS_Props = Props;
6
+ type __VLS_ModelProps = {
7
+ "activeImage"?: string;
8
+ };
9
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
10
+ declare var __VLS_1: {
11
+ image: string;
12
+ };
13
+ type __VLS_Slots = {} & {
14
+ image?: (props: typeof __VLS_1) => any;
15
+ };
16
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
17
+ "update:activeImage": (value: string | undefined) => any;
18
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
19
+ "onUpdate:activeImage"?: ((value: string | undefined) => any) | undefined;
20
+ }>, {
21
+ images: string[];
22
+ fit: "fill" | "cover" | "contain" | "scale-down";
23
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
24
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
25
+ declare const _default: typeof __VLS_export;
26
+ export default _default;
27
+ type __VLS_WithSlots<T, S> = T & {
28
+ new (): {
29
+ $slots: S;
30
+ };
31
+ };