@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 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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@revenexx/studio-integrations",
3
3
  "configKey": "studioIntegrations",
4
- "version": "3.8.0",
4
+ "version": "3.8.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.3",
7
7
  "unbuild": "unknown"
@@ -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 bottom-0 right-0 flex size-5 cursor-se-resize items-end justify-end p-1"
219
- :style="{ color: color.chip }"
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
- <svg viewBox="0 0 8 8" class="size-2.5" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round">
227
- <path d="M7 1 1 7" />
228
- <path d="M7 4.5 4.5 7" />
229
- </svg>
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 overflow-hidden rounded-lg border text-sm leading-snug text-stone-700 shadow-sm"
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 bottom-0 right-0 flex size-4 cursor-se-resize items-end justify-end p-0.5 text-stone-400/80"
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
- <svg viewBox="0 0 8 8" class="size-2.5" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round">
253
- <path d="M7 1 1 7" />
254
- <path d="M7 4.5 4.5 7" />
255
- </svg>
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.0",
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": {