@revenexx/studio-integrations 3.8.0 → 3.8.1
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/CHANGELOG.md +6 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/GroupFrameView.vue +17 -6
- package/dist/runtime/components/Integrations/StickyNoteView.vue +22 -9
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +3 -2
- package/dist/runtime/composables/useCanvasTarget.d.ts +15 -0
- package/dist/runtime/composables/useCanvasTarget.js +10 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @revenexx/studio-integrations
|
|
2
2
|
|
|
3
|
+
## 3.8.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f5c169c: The insert and delete buttons on a connection, and the corners a sticky note and a group frame are resized by, are now at least 24 pixels on screen at every zoom level. They used to shrink with the canvas: a workflow of eighteen steps opens at 58 %, and there they measured 14, 11.6 and 9.3 pixels. The connection's buttons now keep their size on screen, as the node's toolbar already did. The resize corners look the same as before, and the area that reacts to the pointer reaches outward past the note or frame, so a bigger target covers none of the content. The pencil and the note badge on a node card are not changed here.
|
|
8
|
+
|
|
3
9
|
## 3.8.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/dist/module.json
CHANGED
|
@@ -6,6 +6,7 @@ import { ChevronsDownUp, ChevronsUpDown, Ungroup } from "lucide-vue-next";
|
|
|
6
6
|
import { TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
7
7
|
import { inputHandleId, outputHandleId } from "../../data/handleId";
|
|
8
8
|
import { resolveDisplayName } from "../../utils/localize";
|
|
9
|
+
import { useCanvasTarget } from "../../composables/useCanvasTarget";
|
|
9
10
|
const props = defineProps({
|
|
10
11
|
id: { type: String, required: true },
|
|
11
12
|
data: { type: Object, required: true },
|
|
@@ -65,6 +66,8 @@ const resizing = ref(false);
|
|
|
65
66
|
const liveW = ref(0);
|
|
66
67
|
const liveH = ref(0);
|
|
67
68
|
let resizeStart = { x: 0, y: 0, w: 0, h: 0 };
|
|
69
|
+
const RESIZE_CORNER = 20;
|
|
70
|
+
const resizeTarget = useCanvasTarget(RESIZE_CORNER);
|
|
68
71
|
const frameW = computed(() => resizing.value ? liveW.value : props.data.rect.width);
|
|
69
72
|
const frameH = computed(() => resizing.value ? liveH.value : props.data.rect.height);
|
|
70
73
|
function onResizeStart(e) {
|
|
@@ -215,18 +218,26 @@ function onResizeEnd() {
|
|
|
215
218
|
<!-- Bottom-right resize handle (editable frames only). -->
|
|
216
219
|
<div
|
|
217
220
|
v-if="editable"
|
|
218
|
-
class="nodrag pointer-events-auto absolute
|
|
219
|
-
:style="{
|
|
221
|
+
class="nodrag pointer-events-auto absolute cursor-se-resize"
|
|
222
|
+
:style="{
|
|
223
|
+
color: color.chip,
|
|
224
|
+
width: `${resizeTarget}px`,
|
|
225
|
+
height: `${resizeTarget}px`,
|
|
226
|
+
right: `${RESIZE_CORNER - resizeTarget}px`,
|
|
227
|
+
bottom: `${RESIZE_CORNER - resizeTarget}px`
|
|
228
|
+
}"
|
|
220
229
|
:title="t('integration.canvas.group.action.resize')"
|
|
221
230
|
@pointerdown="onResizeStart"
|
|
222
231
|
@pointermove="onResizeMove"
|
|
223
232
|
@pointerup="onResizeEnd"
|
|
224
233
|
@pointercancel="onResizeEnd"
|
|
225
234
|
>
|
|
226
|
-
<
|
|
227
|
-
<
|
|
228
|
-
|
|
229
|
-
|
|
235
|
+
<div class="flex size-5 items-end justify-end p-1">
|
|
236
|
+
<svg viewBox="0 0 8 8" class="size-2.5" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round">
|
|
237
|
+
<path d="M7 1 1 7" />
|
|
238
|
+
<path d="M7 4.5 4.5 7" />
|
|
239
|
+
</svg>
|
|
240
|
+
</div>
|
|
230
241
|
</div>
|
|
231
242
|
</div>
|
|
232
243
|
</div>
|
|
@@ -3,6 +3,7 @@ import { computed, nextTick, ref, watch } from "vue";
|
|
|
3
3
|
import { Position, useVueFlow } from "@vue-flow/core";
|
|
4
4
|
import { NodeToolbar } from "@vue-flow/node-toolbar";
|
|
5
5
|
import { TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
6
|
+
import { useCanvasTarget } from "../../composables/useCanvasTarget";
|
|
6
7
|
const props = defineProps({
|
|
7
8
|
id: { type: String, required: true },
|
|
8
9
|
data: { type: Object, required: true },
|
|
@@ -34,6 +35,8 @@ const MIN_HEIGHT = 80;
|
|
|
34
35
|
const width = ref(props.data.sticky.size?.width ?? DEFAULT_WIDTH);
|
|
35
36
|
const height = ref(props.data.sticky.size?.height ?? DEFAULT_HEIGHT);
|
|
36
37
|
const resizing = ref(false);
|
|
38
|
+
const RESIZE_CORNER = 16;
|
|
39
|
+
const resizeTarget = useCanvasTarget(RESIZE_CORNER);
|
|
37
40
|
watch(() => props.data.sticky.size, (s) => {
|
|
38
41
|
if (resizing.value) return;
|
|
39
42
|
width.value = s?.width ?? DEFAULT_WIDTH;
|
|
@@ -191,7 +194,7 @@ function onViewMouseDown(e) {
|
|
|
191
194
|
|
|
192
195
|
<!-- Card -->
|
|
193
196
|
<div
|
|
194
|
-
class="relative flex flex-col
|
|
197
|
+
class="relative flex flex-col rounded-lg border text-sm leading-snug text-stone-700 shadow-sm"
|
|
195
198
|
:class="[
|
|
196
199
|
selected ? 'ring-2 ring-emerald-400/60' : '',
|
|
197
200
|
editing ? '' : 'cursor-grab active:cursor-grabbing'
|
|
@@ -209,7 +212,7 @@ function onViewMouseDown(e) {
|
|
|
209
212
|
<!-- View mode -->
|
|
210
213
|
<div
|
|
211
214
|
v-if="!editing"
|
|
212
|
-
class="h-full w-full overflow-hidden whitespace-pre-wrap break-words px-3 py-2"
|
|
215
|
+
class="h-full w-full overflow-hidden rounded-[inherit] whitespace-pre-wrap break-words px-3 py-2"
|
|
213
216
|
@mousedown="onViewMouseDown"
|
|
214
217
|
>
|
|
215
218
|
<!-- eslint-disable-next-line vue/no-v-html -- renderMarkdownLite escapes the note before it adds a tag -->
|
|
@@ -228,7 +231,7 @@ function onViewMouseDown(e) {
|
|
|
228
231
|
ref="textareaEl"
|
|
229
232
|
v-model="draft"
|
|
230
233
|
:aria-label="t('integration.canvas.sticky.textarea.aria')"
|
|
231
|
-
class="nodrag nowheel h-full w-full resize-none bg-transparent px-3 py-2 text-sm leading-snug text-stone-700 outline-none placeholder:text-stone-400 focus:ring-2 focus:ring-inset focus:ring-stone-500/40"
|
|
234
|
+
class="nodrag nowheel h-full w-full resize-none rounded-[inherit] bg-transparent px-3 py-2 text-sm leading-snug text-stone-700 outline-none placeholder:text-stone-400 focus:ring-2 focus:ring-inset focus:ring-stone-500/40"
|
|
232
235
|
:placeholder="t('integration.canvas.sticky.textarea.placeholder')"
|
|
233
236
|
spellcheck="false"
|
|
234
237
|
@blur="commitEdit"
|
|
@@ -239,20 +242,30 @@ function onViewMouseDown(e) {
|
|
|
239
242
|
|
|
240
243
|
<!-- Bottom-right resize handle (hand-rolled; min 150x80). Gone on a
|
|
241
244
|
read-only canvas: a handle that cannot resize is the same lie as a bin
|
|
242
|
-
that cannot delete.
|
|
245
|
+
that cannot delete. The hit area reaches past the note's corner (see
|
|
246
|
+
`resizeTarget`) — which is why the card no longer clips its overflow —
|
|
247
|
+
and the drawn corner sits in its top-left, exactly where it always sat. -->
|
|
243
248
|
<div
|
|
244
249
|
v-if="editable"
|
|
245
|
-
class="nodrag absolute
|
|
250
|
+
class="nodrag absolute cursor-se-resize"
|
|
251
|
+
:style="{
|
|
252
|
+
width: `${resizeTarget}px`,
|
|
253
|
+
height: `${resizeTarget}px`,
|
|
254
|
+
right: `${RESIZE_CORNER - resizeTarget}px`,
|
|
255
|
+
bottom: `${RESIZE_CORNER - resizeTarget}px`
|
|
256
|
+
}"
|
|
246
257
|
:title="t('integration.canvas.sticky.action.resize')"
|
|
247
258
|
@pointerdown="onResizeStart"
|
|
248
259
|
@pointermove="onResizeMove"
|
|
249
260
|
@pointerup="onResizeEnd"
|
|
250
261
|
@pointercancel="onResizeEnd"
|
|
251
262
|
>
|
|
252
|
-
<
|
|
253
|
-
<
|
|
254
|
-
|
|
255
|
-
|
|
263
|
+
<div class="flex size-4 items-end justify-end p-0.5 text-stone-400/80">
|
|
264
|
+
<svg viewBox="0 0 8 8" class="size-2.5" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round">
|
|
265
|
+
<path d="M7 1 1 7" />
|
|
266
|
+
<path d="M7 4.5 4.5 7" />
|
|
267
|
+
</svg>
|
|
268
|
+
</div>
|
|
256
269
|
</div>
|
|
257
270
|
</div>
|
|
258
271
|
</template>
|
|
@@ -92,6 +92,7 @@ function onHoverLeave() {
|
|
|
92
92
|
}, 200);
|
|
93
93
|
}
|
|
94
94
|
const toolsVisible = computed(() => edgeActions !== null && !props.data?.replay && (hovered.value || isSelected.value));
|
|
95
|
+
const unzoom = computed(() => 1 / (flow.viewport.value.zoom || 1));
|
|
95
96
|
function removeEdge() {
|
|
96
97
|
flow.removeEdges([props.id]);
|
|
97
98
|
}
|
|
@@ -128,7 +129,7 @@ function spliceNode() {
|
|
|
128
129
|
<div
|
|
129
130
|
class="absolute pointer-events-none select-none rounded-md border px-1.5 py-0.5 text-mono shadow-sm"
|
|
130
131
|
:style="{
|
|
131
|
-
transform: `translate(-50%, -50%) translate(${path[1]}px, ${path[2] - (toolsVisible ? 16 : 0)}px)`,
|
|
132
|
+
transform: `translate(-50%, -50%) translate(${path[1]}px, ${path[2] - (toolsVisible ? 16 * unzoom : 0)}px)`,
|
|
132
133
|
color: color.label,
|
|
133
134
|
background: color.bg,
|
|
134
135
|
borderColor: color.stroke + '55'
|
|
@@ -142,7 +143,7 @@ function spliceNode() {
|
|
|
142
143
|
<EdgeLabelRenderer v-if="toolsVisible">
|
|
143
144
|
<div
|
|
144
145
|
class="absolute z-20 flex items-center gap-0.5 rounded-md border border-black/[0.08] bg-white p-0.5 shadow-md"
|
|
145
|
-
:style="{ transform: `translate(-50%, -50%) translate(${path[1]}px, ${path[2] + (props.data?.label ? 14 : 0)}px)`, pointerEvents: 'all' }"
|
|
146
|
+
:style="{ transform: `translate(-50%, -50%) translate(${path[1]}px, ${path[2] + (props.data?.label ? 14 * unzoom : 0)}px) scale(${unzoom})`, pointerEvents: 'all' }"
|
|
146
147
|
@mouseenter="onHoverEnter"
|
|
147
148
|
@mouseleave="onHoverLeave"
|
|
148
149
|
>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ComputedRef } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* The smallest a pointer target may be on screen — WCAG 2.2 SC 2.5.8, promised in
|
|
4
|
+
* `specs/accessibility.md` AC-7.
|
|
5
|
+
*/
|
|
6
|
+
export declare const MIN_TARGET_PX = 24;
|
|
7
|
+
/**
|
|
8
|
+
* The canvas-pixel size a control drawn **inside** the zoom transform needs so that it
|
|
9
|
+
* still measures `MIN_TARGET_PX` on screen, and never less than the `drawn` size it
|
|
10
|
+
* has at full zoom. Zoomed out to 50 %, a target has to be 48 canvas pixels to be hit
|
|
11
|
+
* as 24 — which is the whole problem with a control the canvas scales (PO-388).
|
|
12
|
+
*/
|
|
13
|
+
export declare function targetSize(drawn: number, zoom: number): number;
|
|
14
|
+
/** `targetSize` against the live viewport of the canvas this component sits on. */
|
|
15
|
+
export declare function useCanvasTarget(drawn: number): ComputedRef<number>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { computed } from "vue";
|
|
2
|
+
import { useVueFlow } from "@vue-flow/core";
|
|
3
|
+
export const MIN_TARGET_PX = 24;
|
|
4
|
+
export function targetSize(drawn, zoom) {
|
|
5
|
+
return Math.max(drawn, MIN_TARGET_PX / (zoom || 1));
|
|
6
|
+
}
|
|
7
|
+
export function useCanvasTarget(drawn) {
|
|
8
|
+
const flow = useVueFlow();
|
|
9
|
+
return computed(() => targetSize(drawn, flow.viewport.value.zoom));
|
|
10
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revenexx/studio-integrations",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Integration Studio (core: workflows, node editor, runs, schedules, credentials, secrets, audit) as a standalone, publishable Nuxt module.",
|
|
6
6
|
"repository": {
|