@revenexx/studio-integrations 0.1.4 → 0.3.0

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 (188) hide show
  1. package/README.md +31 -0
  2. package/dist/module.d.mts +2 -2
  3. package/dist/module.json +1 -1
  4. package/dist/runtime/components/Integrations/CredentialCreateDialog.d.vue.ts +13 -0
  5. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +279 -0
  6. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue.d.ts +13 -0
  7. package/dist/runtime/components/Integrations/CredentialDetailDialog.d.vue.ts +20 -0
  8. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +342 -0
  9. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue.d.ts +20 -0
  10. package/dist/runtime/components/Integrations/CredentialLogo.d.vue.ts +14 -0
  11. package/dist/runtime/components/Integrations/CredentialLogo.vue +37 -0
  12. package/dist/runtime/components/Integrations/CredentialLogo.vue.d.ts +14 -0
  13. package/dist/runtime/components/Integrations/CredentialRevenexxSource.d.vue.ts +12 -0
  14. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +364 -0
  15. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue.d.ts +12 -0
  16. package/dist/runtime/components/Integrations/CredentialTypePicker.d.vue.ts +15 -0
  17. package/dist/runtime/components/Integrations/CredentialTypePicker.vue +71 -0
  18. package/dist/runtime/components/Integrations/CredentialTypePicker.vue.d.ts +15 -0
  19. package/dist/runtime/components/Integrations/DeadLettersPanel.d.vue.ts +5 -0
  20. package/dist/runtime/components/Integrations/DeadLettersPanel.vue +151 -0
  21. package/dist/runtime/components/Integrations/DeadLettersPanel.vue.d.ts +5 -0
  22. package/dist/runtime/components/Integrations/EditorTip.d.vue.ts +17 -0
  23. package/dist/runtime/components/Integrations/EditorTip.vue +46 -0
  24. package/dist/runtime/components/Integrations/EditorTip.vue.d.ts +17 -0
  25. package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +9 -0
  26. package/dist/runtime/components/Integrations/EventTypeCombobox.vue +64 -0
  27. package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +9 -0
  28. package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +19 -0
  29. package/dist/runtime/components/Integrations/ExpressionInput.vue +236 -0
  30. package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +19 -0
  31. package/dist/runtime/components/Integrations/GroupFrameView.d.vue.ts +28 -0
  32. package/dist/runtime/components/Integrations/GroupFrameView.vue +222 -0
  33. package/dist/runtime/components/Integrations/GroupFrameView.vue.d.ts +28 -0
  34. package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +14 -0
  35. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +184 -0
  36. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +14 -0
  37. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.d.vue.ts +16 -0
  38. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +102 -0
  39. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue.d.ts +16 -0
  40. package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +23 -0
  41. package/dist/runtime/components/Integrations/NodeConfigField.vue +587 -0
  42. package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +23 -0
  43. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.d.vue.ts +14 -0
  44. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue +80 -0
  45. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue.d.ts +14 -0
  46. package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +22 -3
  47. package/dist/runtime/components/Integrations/NodeInspector.vue +1074 -294
  48. package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +22 -3
  49. package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +9 -0
  50. package/dist/runtime/components/Integrations/NodeLogo.vue +38 -0
  51. package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +9 -0
  52. package/dist/runtime/components/Integrations/NodePalette.d.vue.ts +22 -1
  53. package/dist/runtime/components/Integrations/NodePalette.vue +411 -90
  54. package/dist/runtime/components/Integrations/NodePalette.vue.d.ts +22 -1
  55. package/dist/runtime/components/Integrations/QueryBuilder.d.vue.ts +9 -0
  56. package/dist/runtime/components/Integrations/QueryBuilder.vue +239 -0
  57. package/dist/runtime/components/Integrations/QueryBuilder.vue.d.ts +9 -0
  58. package/dist/runtime/components/Integrations/{RunDetailDialog.d.vue.ts → RunActionsMenu.d.vue.ts} +3 -4
  59. package/dist/runtime/components/Integrations/RunActionsMenu.vue +86 -0
  60. package/dist/runtime/components/Integrations/{RunDetailDialog.vue.d.ts → RunActionsMenu.vue.d.ts} +3 -4
  61. package/dist/runtime/components/Integrations/RunDetailPanel.d.vue.ts +14 -0
  62. package/dist/runtime/components/Integrations/RunDetailPanel.vue +777 -0
  63. package/dist/runtime/components/Integrations/RunDetailPanel.vue.d.ts +14 -0
  64. package/dist/runtime/components/Integrations/RunPayloadInput.d.vue.ts +13 -0
  65. package/dist/runtime/components/Integrations/RunPayloadInput.vue +175 -0
  66. package/dist/runtime/components/Integrations/RunPayloadInput.vue.d.ts +13 -0
  67. package/dist/runtime/components/Integrations/RunResponseModal.d.vue.ts +27 -0
  68. package/dist/runtime/components/Integrations/RunResponseModal.vue +84 -0
  69. package/dist/runtime/components/Integrations/RunResponseModal.vue.d.ts +27 -0
  70. package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +21 -0
  71. package/dist/runtime/components/Integrations/RunsTable.vue +114 -0
  72. package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +21 -0
  73. package/dist/runtime/components/Integrations/SftpAccountImportDialog.d.vue.ts +11 -0
  74. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +91 -0
  75. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue.d.ts +11 -0
  76. package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +28 -0
  77. package/dist/runtime/components/Integrations/StickyNoteView.vue +208 -0
  78. package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +28 -0
  79. package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +29 -0
  80. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +201 -0
  81. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +29 -0
  82. package/dist/runtime/components/Integrations/WorkflowContextMenu.d.vue.ts +32 -0
  83. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +138 -0
  84. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue.d.ts +32 -0
  85. package/dist/runtime/components/Integrations/WorkflowCreateDialog.d.vue.ts +9 -0
  86. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +322 -0
  87. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue.d.ts +9 -0
  88. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.d.vue.ts +18 -0
  89. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +63 -0
  90. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue.d.ts +18 -0
  91. package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +12 -1
  92. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +68 -6
  93. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +12 -1
  94. package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +8 -1
  95. package/dist/runtime/components/Integrations/WorkflowEditor.vue +1424 -144
  96. package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +8 -1
  97. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +12 -0
  98. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +417 -0
  99. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +12 -0
  100. package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +10 -0
  101. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +89 -0
  102. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +10 -0
  103. package/dist/runtime/components/Integrations/WorkflowIconView.d.vue.ts +10 -0
  104. package/dist/runtime/components/Integrations/WorkflowIconView.vue +35 -0
  105. package/dist/runtime/components/Integrations/WorkflowIconView.vue.d.ts +10 -0
  106. package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +5 -1
  107. package/dist/runtime/components/Integrations/WorkflowNodeView.vue +114 -84
  108. package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +5 -1
  109. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.d.vue.ts +12 -0
  110. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +654 -0
  111. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue.d.ts +12 -0
  112. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +36 -0
  113. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +136 -0
  114. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +36 -0
  115. package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +14 -0
  116. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +63 -0
  117. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +14 -0
  118. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +35 -15
  119. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.d.vue.ts +13 -0
  120. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +265 -0
  121. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue.d.ts +13 -0
  122. package/dist/runtime/components/Integrations/WorkflowsTable.d.vue.ts +4 -1
  123. package/dist/runtime/components/Integrations/WorkflowsTable.vue +122 -39
  124. package/dist/runtime/components/Integrations/WorkflowsTable.vue.d.ts +4 -1
  125. package/dist/runtime/components/Integrations/workflowLayout.d.ts +5 -0
  126. package/dist/runtime/components/Integrations/workflowLayout.js +5 -3
  127. package/dist/runtime/composables/integrationsApi.d.ts +6 -7
  128. package/dist/runtime/composables/integrationsApi.js +6 -12
  129. package/dist/runtime/composables/useCanvasShortcuts.d.ts +37 -0
  130. package/dist/runtime/composables/useCanvasShortcuts.js +62 -0
  131. package/dist/runtime/composables/useCredentialTypes.js +2 -1
  132. package/dist/runtime/composables/useIntegrationsAudit.js +2 -1
  133. package/dist/runtime/composables/useIntegrationsCredentials.js +2 -1
  134. package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +22 -0
  135. package/dist/runtime/composables/useIntegrationsDeadLetters.js +50 -0
  136. package/dist/runtime/composables/useIntegrationsNodes.d.ts +4 -0
  137. package/dist/runtime/composables/useIntegrationsNodes.js +8 -4
  138. package/dist/runtime/composables/useIntegrationsRevisions.d.ts +40 -0
  139. package/dist/runtime/composables/useIntegrationsRevisions.js +52 -0
  140. package/dist/runtime/composables/useIntegrationsRuns.d.ts +21 -1
  141. package/dist/runtime/composables/useIntegrationsRuns.js +94 -5
  142. package/dist/runtime/composables/useIntegrationsSecrets.js +2 -1
  143. package/dist/runtime/composables/useIntegrationsTemplates.d.ts +1 -0
  144. package/dist/runtime/composables/useIntegrationsTemplates.js +34 -5
  145. package/dist/runtime/composables/useIntegrationsTriggers.js +2 -1
  146. package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +1 -0
  147. package/dist/runtime/composables/useIntegrationsWorkflows.js +59 -2
  148. package/dist/runtime/composables/useMiniMapLayout.d.ts +34 -0
  149. package/dist/runtime/composables/useMiniMapLayout.js +119 -0
  150. package/dist/runtime/composables/useNodeConfigResolve.d.ts +43 -0
  151. package/dist/runtime/composables/useNodeConfigResolve.js +124 -0
  152. package/dist/runtime/composables/useNodeDrag.d.ts +22 -0
  153. package/dist/runtime/composables/useNodeDrag.js +10 -0
  154. package/dist/runtime/composables/usePaletteLayout.d.ts +41 -0
  155. package/dist/runtime/composables/usePaletteLayout.js +113 -0
  156. package/dist/runtime/data/expressionEditor.d.ts +22 -0
  157. package/dist/runtime/data/expressionEditor.js +40 -0
  158. package/dist/runtime/data/integrationNodeCatalog.js +8 -8
  159. package/dist/runtime/data/nodeInputData.d.ts +37 -0
  160. package/dist/runtime/data/nodeInputData.js +62 -0
  161. package/dist/runtime/data/nodeRunData.d.ts +56 -0
  162. package/dist/runtime/data/nodeRunData.js +91 -0
  163. package/dist/runtime/data/payloadForm.d.ts +44 -0
  164. package/dist/runtime/data/payloadForm.js +126 -0
  165. package/dist/runtime/data/workflowIconCatalog.d.ts +34 -0
  166. package/dist/runtime/data/workflowIconCatalog.js +116 -0
  167. package/dist/runtime/pages/integrations/audit.vue +25 -35
  168. package/dist/runtime/pages/integrations/credentials.vue +117 -122
  169. package/dist/runtime/pages/integrations/index.vue +133 -36
  170. package/dist/runtime/pages/integrations/runs.vue +125 -75
  171. package/dist/runtime/pages/integrations/schedules.vue +5 -16
  172. package/dist/runtime/pages/integrations/secrets.vue +1 -3
  173. package/dist/runtime/pages/integrations/workflows/[id].vue +599 -194
  174. package/dist/runtime/pages/integrations/workflows/index.vue +10 -8
  175. package/dist/runtime/types/integrations.d.ts +190 -4
  176. package/dist/runtime/utils/apiError.d.ts +17 -0
  177. package/dist/runtime/utils/apiError.js +8 -0
  178. package/dist/runtime/utils/credentialState.d.ts +28 -0
  179. package/dist/runtime/utils/credentialState.js +29 -0
  180. package/dist/runtime/utils/localize.d.ts +3 -1
  181. package/dist/runtime/utils/localize.js +1 -2
  182. package/dist/runtime/utils/statusStyle.d.ts +6 -0
  183. package/dist/runtime/utils/statusStyle.js +15 -0
  184. package/package.json +20 -3
  185. package/dist/runtime/components/Integrations/RunDetailDialog.vue +0 -169
  186. package/dist/runtime/pages/integrations/workflows/new.d.vue.ts +0 -2
  187. package/dist/runtime/pages/integrations/workflows/new.vue +0 -153
  188. package/dist/runtime/pages/integrations/workflows/new.vue.d.ts +0 -2
@@ -0,0 +1,46 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ import { Tooltip, TooltipContent, TooltipTrigger } from "@revenexx/studio";
4
+ const props = defineProps({
5
+ label: { type: String, required: true },
6
+ shortcut: { type: String, required: false }
7
+ });
8
+ const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "");
9
+ const keys = computed(() => {
10
+ if (!props.shortcut) return "";
11
+ const out = [];
12
+ for (const raw of props.shortcut.split("+")) {
13
+ const t = raw.toLowerCase();
14
+ if (t === "mod") out.push(isMac ? "\u2318" : "Ctrl");
15
+ else if (t === "shift") out.push("\u21E7");
16
+ else if (t === "alt") out.push(isMac ? "\u2325" : "Alt");
17
+ else if (t === "enter") out.push("\u21B5");
18
+ else if (t === "escape") out.push("Esc");
19
+ else out.push(t.toUpperCase());
20
+ }
21
+ return isMac ? out.join("") : out.join("+");
22
+ });
23
+ </script>
24
+
25
+ <template>
26
+ <Tooltip :delay-duration="250">
27
+ <TooltipTrigger as-child>
28
+ <slot />
29
+ </TooltipTrigger>
30
+ <TooltipContent
31
+ side="top"
32
+ :side-offset="8"
33
+ class="editor-tip flex items-center gap-1.5 rounded-md border border-black/[0.06] bg-white px-2 py-1 text-xs text-foreground shadow-md"
34
+ >
35
+ <span>{{ label }}</span>
36
+ <kbd
37
+ v-if="keys"
38
+ class="rounded border border-black/[0.08] bg-muted/60 px-1 py-px font-mono text-[10px] tabular-nums text-muted-foreground"
39
+ >{{ keys }}</kbd>
40
+ </TooltipContent>
41
+ </Tooltip>
42
+ </template>
43
+
44
+ <style>
45
+ .editor-tip svg{display:none!important}
46
+ </style>
@@ -0,0 +1,17 @@
1
+ type __VLS_Props = {
2
+ label: string;
3
+ /** e.g. 'mod+enter', 'shift+alt+t', 'v', '1'. Omit for label-only tips. */
4
+ shortcut?: string;
5
+ };
6
+ declare var __VLS_10: {};
7
+ type __VLS_Slots = {} & {
8
+ default?: (props: typeof __VLS_10) => any;
9
+ };
10
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
11
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
12
+ export default _default;
13
+ type __VLS_WithSlots<T, S> = T & {
14
+ new (): {
15
+ $slots: S;
16
+ };
17
+ };
@@ -0,0 +1,9 @@
1
+ type __VLS_Props = {
2
+ modelValue: string;
3
+ };
4
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
5
+ "update:modelValue": (value: string) => any;
6
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
7
+ "onUpdate:modelValue"?: ((value: string) => any) | undefined;
8
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ export default _default;
@@ -0,0 +1,64 @@
1
+ <script setup>
2
+ import { computed, onMounted, ref } from "vue";
3
+ import { Input } from "@revenexx/studio";
4
+ const props = defineProps({
5
+ modelValue: { type: String, required: true }
6
+ });
7
+ const emit = defineEmits(["update:modelValue"]);
8
+ const catalog = useDefinedEvents();
9
+ onMounted(() => {
10
+ void catalog.load();
11
+ });
12
+ const focused = ref(false);
13
+ const options = computed(() => {
14
+ const out = /* @__PURE__ */ new Map();
15
+ for (const e of catalog.emits.value) out.set(e.topic, { topic: e.topic, source: e.app });
16
+ for (const t of catalog.listens.value) {
17
+ if (!out.has(t)) out.set(t, { topic: t, source: null });
18
+ }
19
+ return [...out.values()].sort((a, b) => a.topic.localeCompare(b.topic));
20
+ });
21
+ const filtered = computed(() => {
22
+ const q = (props.modelValue ?? "").trim().toLowerCase();
23
+ const list = q ? options.value.filter((o) => o.topic.toLowerCase().includes(q)) : options.value;
24
+ return list.slice(0, 40);
25
+ });
26
+ const open = computed(() => focused.value && (filtered.value.length > 0 || catalog.loading.value));
27
+ function pick(topic) {
28
+ emit("update:modelValue", topic);
29
+ focused.value = false;
30
+ }
31
+ </script>
32
+
33
+ <template>
34
+ <div class="relative">
35
+ <Input
36
+ :model-value="modelValue"
37
+ placeholder="customer.created"
38
+ class="h-8 text-sm bg-white font-mono"
39
+ @update:model-value="(v) => emit('update:modelValue', String(v))"
40
+ @focus="focused = true"
41
+ @blur="focused = false"
42
+ />
43
+ <div
44
+ v-if="open"
45
+ class="absolute left-0 right-0 top-full z-30 mt-1 max-h-56 overflow-y-auto rounded-md border border-black/[0.08] bg-white py-1 shadow-md"
46
+ >
47
+ <div v-if="catalog.loading.value && !filtered.length" class="px-2.5 py-1.5 text-sm text-muted-foreground">
48
+ Loading event catalog…
49
+ </div>
50
+ <button
51
+ v-for="o in filtered"
52
+ :key="o.topic"
53
+ type="button"
54
+ class="flex w-full items-center gap-2 px-2.5 py-1.5 text-left hover:bg-muted/50"
55
+ @mousedown.prevent="pick(o.topic)"
56
+ >
57
+ <code class="min-w-0 flex-1 truncate font-mono text-[13px]">{{ o.topic }}</code>
58
+ <span v-if="o.source" class="shrink-0 rounded-full bg-slate-100 px-1.5 py-px text-[11px] font-medium uppercase text-slate-700">
59
+ {{ o.source }}
60
+ </span>
61
+ </button>
62
+ </div>
63
+ </div>
64
+ </template>
@@ -0,0 +1,9 @@
1
+ type __VLS_Props = {
2
+ modelValue: string;
3
+ };
4
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
5
+ "update:modelValue": (value: string) => any;
6
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
7
+ "onUpdate:modelValue"?: ((value: string) => any) | undefined;
8
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ export default _default;
@@ -0,0 +1,19 @@
1
+ import type { WorkflowVariable } from '../../types/integrations.js';
2
+ type UpstreamOutput = {
3
+ nodeId: string;
4
+ nodeName?: string;
5
+ output: string;
6
+ fields?: string[];
7
+ };
8
+ type __VLS_Props = {
9
+ modelValue: unknown;
10
+ placeholder?: string;
11
+ variables?: Record<string, WorkflowVariable>;
12
+ upstreamOutputs?: UpstreamOutput[];
13
+ };
14
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
+ "update:modelValue": (value: string) => any;
16
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
17
+ "onUpdate:modelValue"?: ((value: string) => any) | undefined;
18
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ export default _default;
@@ -0,0 +1,236 @@
1
+ <script setup>
2
+ import { computed, inject, onBeforeUnmount, onMounted, ref, watch } from "vue";
3
+ import { Editor, EditorContent } from "@tiptap/vue-3";
4
+ import { Node } from "@tiptap/core";
5
+ import Document from "@tiptap/extension-document";
6
+ import Paragraph from "@tiptap/extension-paragraph";
7
+ import Text from "@tiptap/extension-text";
8
+ import History from "@tiptap/extension-history";
9
+ import { resolveExpressionText } from "../../data/nodeRunData";
10
+ import { EXPR_TOKEN_NODE, docToText, hasRawTokenText, textToDoc } from "../../data/expressionEditor";
11
+ const props = defineProps({
12
+ modelValue: { type: null, required: true },
13
+ placeholder: { type: String, required: false },
14
+ variables: { type: Object, required: false },
15
+ upstreamOutputs: { type: Array, required: false }
16
+ });
17
+ const emit = defineEmits(["update:modelValue"]);
18
+ const text = computed(() => String(props.modelValue ?? ""));
19
+ const OPEN = "${{ ";
20
+ const CLOSE = " }}";
21
+ function tok(inner) {
22
+ return OPEN + inner + CLOSE;
23
+ }
24
+ const triggerLabel = "{ }";
25
+ const nodeNames = inject("integrationsNodeNames", null);
26
+ function pillLabel(expr) {
27
+ const path = expr.split(".");
28
+ if (path[0] === "nodes" && path[2] === "outputs") {
29
+ const name = nodeNames?.value[path[1] ?? ""] ?? path[1] ?? "";
30
+ const rest = path.slice(3).filter((seg, i) => !(i === 0 && seg === "default")).join(".");
31
+ return rest ? name + " \xB7 " + rest : name;
32
+ }
33
+ if (path[0] === "trigger" && path[1] === "payload") {
34
+ const rest = path.slice(2).join(".");
35
+ return rest ? "Trigger \xB7 " + rest : "Trigger payload";
36
+ }
37
+ if (path[0] === "vars") return "Variable \xB7 " + path.slice(1).join(".");
38
+ if (path[0] === "in") {
39
+ const rest = path.slice(1).join(".");
40
+ return rest ? "Input \xB7 " + rest : "Input payload";
41
+ }
42
+ return expr;
43
+ }
44
+ function tokenLabel(token) {
45
+ const inner = token.replace(/^\$\{\{\s*/, "").replace(/\s*\}\}$/, "");
46
+ return pillLabel(inner);
47
+ }
48
+ const ExprToken = Node.create({
49
+ name: EXPR_TOKEN_NODE,
50
+ group: "inline",
51
+ inline: true,
52
+ atom: true,
53
+ selectable: true,
54
+ addAttributes() {
55
+ return { token: { default: "" } };
56
+ },
57
+ parseHTML() {
58
+ return [{
59
+ tag: "span[data-expr-token]",
60
+ getAttrs: (el) => ({ token: el.getAttribute("data-expr-token") ?? "" })
61
+ }];
62
+ },
63
+ renderHTML({ node }) {
64
+ return ["span", { "data-expr-token": String(node.attrs.token) }, String(node.attrs.token)];
65
+ },
66
+ addNodeView() {
67
+ return ({ node }) => {
68
+ const dom = document.createElement("span");
69
+ dom.className = "expr-pill";
70
+ dom.textContent = tokenLabel(String(node.attrs.token));
71
+ dom.title = String(node.attrs.token);
72
+ return { dom };
73
+ };
74
+ }
75
+ });
76
+ const editor = new Editor({
77
+ extensions: [Document, Paragraph, Text, History, ExprToken],
78
+ content: textToDoc(text.value),
79
+ editorProps: {
80
+ // Single line: swallow Enter (paragraph splits would break the field).
81
+ handleKeyDown: (_view, event) => event.key === "Enter" && !event.isComposing
82
+ },
83
+ onUpdate({ editor: ed }) {
84
+ const json = ed.getJSON();
85
+ let raw = docToText(json);
86
+ if (hasRawTokenText(json)) {
87
+ ed.commands.setContent(textToDoc(raw));
88
+ raw = docToText(ed.getJSON());
89
+ }
90
+ if (raw !== text.value) emit("update:modelValue", raw);
91
+ },
92
+ onFocus() {
93
+ if (insertHandler) insertHandler.value = insertToken;
94
+ if (hasSuggestions.value) open.value = true;
95
+ }
96
+ });
97
+ onBeforeUnmount(() => {
98
+ if (insertHandler?.value === insertToken) insertHandler.value = null;
99
+ editor.destroy();
100
+ });
101
+ watch(text, (t) => {
102
+ if (docToText(editor.getJSON()) !== t) {
103
+ editor.commands.setContent(textToDoc(t));
104
+ }
105
+ });
106
+ function insertToken(expr) {
107
+ if (editor.isDestroyed) return;
108
+ editor.chain().focus().insertContent({ type: EXPR_TOKEN_NODE, attrs: { token: expr } }).run();
109
+ }
110
+ const insertHandler = inject("integrationsExpressionInsert", null);
111
+ const sampleContext = inject("integrationsSampleContext", null);
112
+ const preview = computed(() => {
113
+ if (!sampleContext) return null;
114
+ return resolveExpressionText(text.value, sampleContext.value);
115
+ });
116
+ const sections = computed(() => {
117
+ const out = [];
118
+ const vars = props.variables ?? {};
119
+ const varItems = Object.keys(vars).map((key) => ({
120
+ label: key,
121
+ expr: tok("vars." + key)
122
+ }));
123
+ if (varItems.length) out.push({ heading: "VARIABLES", items: varItems });
124
+ const upstream = props.upstreamOutputs ?? [];
125
+ const nodeItems = upstream.flatMap((u) => {
126
+ const base = "nodes." + u.nodeId + ".outputs." + u.output;
127
+ const rows = [{
128
+ label: (u.nodeName || u.nodeId) + " \u2192 " + u.output,
129
+ expr: tok(base)
130
+ }];
131
+ for (const f of u.fields ?? []) {
132
+ rows.push({ label: (u.nodeName || u.nodeId) + " \u2192 " + u.output + "." + f, expr: tok(base + "." + f) });
133
+ }
134
+ return rows;
135
+ });
136
+ if (nodeItems.length) out.push({ heading: "NODE OUTPUTS", items: nodeItems });
137
+ const primary = upstream[0];
138
+ const inItems = [{ label: "input payload", expr: tok("in") }];
139
+ for (const f of primary?.fields ?? []) {
140
+ inItems.push({ label: "input." + f, expr: tok("in." + f) });
141
+ }
142
+ out.push({ heading: "INPUT", items: inItems });
143
+ return out;
144
+ });
145
+ const hasSuggestions = computed(() => sections.value.some((s) => s.items.length > 0));
146
+ const open = ref(false);
147
+ const wrapRef = ref(null);
148
+ function toggle() {
149
+ if (!open.value && !hasSuggestions.value) return;
150
+ open.value = !open.value;
151
+ }
152
+ function onFocusOut(e) {
153
+ if (wrapRef.value && e.relatedTarget instanceof globalThis.Node && wrapRef.value.contains(e.relatedTarget)) return;
154
+ open.value = false;
155
+ }
156
+ function onKeydown(e) {
157
+ if (e.key === "Escape" && open.value) {
158
+ e.stopPropagation();
159
+ e.preventDefault();
160
+ open.value = false;
161
+ }
162
+ }
163
+ function onDocMousedown(e) {
164
+ if (wrapRef.value && !wrapRef.value.contains(e.target)) open.value = false;
165
+ }
166
+ onMounted(() => document.addEventListener("mousedown", onDocMousedown));
167
+ onBeforeUnmount(() => document.removeEventListener("mousedown", onDocMousedown));
168
+ function insert(expr) {
169
+ insertToken(expr);
170
+ open.value = false;
171
+ }
172
+ </script>
173
+
174
+ <template>
175
+ <div ref="wrapRef" class="relative" @focusout="onFocusOut">
176
+ <div class="flex items-center gap-1">
177
+ <div class="expr-editor relative h-8 min-w-0 flex-1 overflow-hidden rounded-md border border-black/[0.08] bg-white px-2 font-mono text-sm transition focus-within:border-black/[0.16]">
178
+ <span
179
+ v-if="!text"
180
+ class="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 truncate text-muted-foreground/60"
181
+ >{{ placeholder }}</span>
182
+ <EditorContent :editor="editor" class="h-full" @keydown="onKeydown" />
183
+ </div>
184
+ <button
185
+ type="button"
186
+ title="Insert expression"
187
+ class="flex h-8 shrink-0 items-center rounded-md border border-black/[0.08] bg-white px-1.5 font-mono text-sm text-muted-foreground transition hover:text-foreground hover:bg-muted/30"
188
+ :class="open ? 'ring-2 ring-emerald-500/40' : ''"
189
+ @mousedown.prevent="toggle"
190
+ >
191
+ {{ triggerLabel }}
192
+ </button>
193
+ </div>
194
+
195
+ <p
196
+ v-if="preview"
197
+ class="mt-1 flex min-w-0 items-baseline gap-1 text-[11px]"
198
+ :title="preview.misses ? 'Parts of this expression have no sample data yet' : preview.resolved"
199
+ >
200
+ <span class="shrink-0 text-muted-foreground">→</span>
201
+ <span class="min-w-0 truncate font-mono" :class="preview.misses ? 'text-amber-600' : 'text-emerald-700'">{{ preview.resolved }}</span>
202
+ </p>
203
+
204
+ <div
205
+ v-if="open && hasSuggestions"
206
+ class="absolute right-0 top-full z-50 mt-1 max-h-64 w-72 overflow-y-auto rounded-md border border-black/[0.08] bg-white py-1 text-sm shadow-lg"
207
+ >
208
+ <template v-for="section in sections" :key="section.heading">
209
+ <div
210
+ v-if="section.items.length"
211
+ class="px-2 pb-0.5 pt-1.5 text-[10px] font-medium uppercase tracking-wider text-muted-foreground"
212
+ >
213
+ {{ section.heading }}
214
+ </div>
215
+ <button
216
+ v-for="item in section.items"
217
+ :key="item.expr"
218
+ type="button"
219
+ class="block w-full px-2 py-1 text-left transition hover:bg-muted/50"
220
+ @mousedown.prevent="insert(item.expr)"
221
+ >
222
+ <div class="truncate text-muted-foreground">
223
+ {{ item.label }}
224
+ </div>
225
+ <div class="truncate font-mono text-emerald-700">
226
+ {{ item.expr }}
227
+ </div>
228
+ </button>
229
+ </template>
230
+ </div>
231
+ </div>
232
+ </template>
233
+
234
+ <style>
235
+ .expr-editor .ProseMirror{align-items:center;display:flex;height:100%;outline:none;overflow-x:auto;scrollbar-width:none}.expr-editor .ProseMirror p{line-height:30px;margin:0;white-space:pre}.expr-pill{background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.25);border-radius:6px;color:#047857;display:inline-block;font-family:ui-sans-serif,system-ui,sans-serif;font-size:11px;line-height:18px;margin:0 1px;padding:0 6px;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:baseline;white-space:nowrap}.expr-pill.ProseMirror-selectednode{outline:2px solid rgba(16,185,129,.5)}
236
+ </style>
@@ -0,0 +1,19 @@
1
+ import type { WorkflowVariable } from '../../types/integrations.js';
2
+ type UpstreamOutput = {
3
+ nodeId: string;
4
+ nodeName?: string;
5
+ output: string;
6
+ fields?: string[];
7
+ };
8
+ type __VLS_Props = {
9
+ modelValue: unknown;
10
+ placeholder?: string;
11
+ variables?: Record<string, WorkflowVariable>;
12
+ upstreamOutputs?: UpstreamOutput[];
13
+ };
14
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
+ "update:modelValue": (value: string) => any;
16
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
17
+ "onUpdate:modelValue"?: ((value: string) => any) | undefined;
18
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ export default _default;
@@ -0,0 +1,28 @@
1
+ import type { WorkflowGroup } from '../../types/integrations.js';
2
+ interface GroupNodeData {
3
+ group: WorkflowGroup;
4
+ /** Derived frame size (expanded mode). */
5
+ rect: {
6
+ width: number;
7
+ height: number;
8
+ };
9
+ memberCount: number;
10
+ /** Absent in read-only previews — no toolbar, no editing. */
11
+ actions?: {
12
+ rename: (label: string) => void;
13
+ setColor: (i: number) => void;
14
+ toggleCollapse: () => void;
15
+ ungroup: () => void;
16
+ resize: (size: {
17
+ width: number;
18
+ height: number;
19
+ }) => void;
20
+ };
21
+ }
22
+ type __VLS_Props = {
23
+ id: string;
24
+ data: GroupNodeData;
25
+ selected?: boolean;
26
+ };
27
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, any, string, import("vue").PublicProps, any, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
28
+ export default _default;
@@ -0,0 +1,222 @@
1
+ <script setup>
2
+ import { computed, nextTick, ref } from "vue";
3
+ import { Handle, Position, useVueFlow } from "@vue-flow/core";
4
+ import { NodeToolbar } from "@vue-flow/node-toolbar";
5
+ import { ChevronsDownUp, ChevronsUpDown, Trash2, Ungroup } from "lucide-vue-next";
6
+ const props = defineProps({
7
+ id: { type: String, required: true },
8
+ data: { type: Object, required: true },
9
+ selected: { type: Boolean, required: false }
10
+ });
11
+ defineEmits(["updateNodeInternals"]);
12
+ const GROUP_COLORS = [
13
+ { bg: "rgba(254,243,199,0.45)", border: "#fcd34d", chip: "#f59e0b" },
14
+ { bg: "rgba(255,237,213,0.45)", border: "#fdba74", chip: "#f97316" },
15
+ { bg: "rgba(255,228,230,0.45)", border: "#fda4af", chip: "#f43f5e" },
16
+ { bg: "rgba(220,252,231,0.45)", border: "#86efac", chip: "#10b981" },
17
+ { bg: "rgba(219,234,254,0.45)", border: "#93c5fd", chip: "#3b82f6" },
18
+ { bg: "rgba(237,233,254,0.45)", border: "#c4b5fd", chip: "#8b5cf6" },
19
+ { bg: "rgba(245,245,244,0.6)", border: "#d6d3d1", chip: "#78716c" }
20
+ ];
21
+ const color = computed(() => GROUP_COLORS[props.data.group.color ?? 4] ?? GROUP_COLORS[4]);
22
+ function loc(v) {
23
+ if (!v) return "";
24
+ return typeof v === "string" ? v : v.en ?? Object.values(v)[0] ?? "";
25
+ }
26
+ const label = computed(() => loc(props.data.group.label) || "Group");
27
+ const collapsed = computed(() => props.data.group.collapsed === true);
28
+ const editable = computed(() => !!props.data.actions);
29
+ const editing = ref(false);
30
+ const draft = ref("");
31
+ const inputEl = ref(null);
32
+ function startRename() {
33
+ if (!editable.value) return;
34
+ draft.value = label.value;
35
+ editing.value = true;
36
+ void nextTick(() => inputEl.value?.focus());
37
+ }
38
+ function commitRename() {
39
+ if (!editing.value) return;
40
+ editing.value = false;
41
+ const next = draft.value.trim();
42
+ if (next && next !== label.value) props.data.actions?.rename(next);
43
+ }
44
+ const hovered = ref(false);
45
+ let hoverTimer = null;
46
+ function onHoverEnter() {
47
+ if (hoverTimer) clearTimeout(hoverTimer);
48
+ hovered.value = true;
49
+ }
50
+ function onHoverLeave() {
51
+ if (hoverTimer) clearTimeout(hoverTimer);
52
+ hoverTimer = setTimeout(() => {
53
+ hovered.value = false;
54
+ }, 250);
55
+ }
56
+ const toolbarVisible = computed(() => editable.value && !editing.value && (hovered.value || !!props.selected));
57
+ const memberCountText = computed(() => `${props.data.memberCount} node${props.data.memberCount === 1 ? "" : "s"}`);
58
+ const flow = useVueFlow();
59
+ const resizing = ref(false);
60
+ const liveW = ref(0);
61
+ const liveH = ref(0);
62
+ let resizeStart = { x: 0, y: 0, w: 0, h: 0 };
63
+ const frameW = computed(() => resizing.value ? liveW.value : props.data.rect.width);
64
+ const frameH = computed(() => resizing.value ? liveH.value : props.data.rect.height);
65
+ function onResizeStart(e) {
66
+ e.stopPropagation();
67
+ e.preventDefault();
68
+ resizing.value = true;
69
+ liveW.value = props.data.rect.width;
70
+ liveH.value = props.data.rect.height;
71
+ resizeStart = { x: e.clientX, y: e.clientY, w: liveW.value, h: liveH.value };
72
+ e.target.setPointerCapture(e.pointerId);
73
+ }
74
+ function onResizeMove(e) {
75
+ if (!resizing.value) return;
76
+ const zoom = flow.viewport.value.zoom || 1;
77
+ liveW.value = Math.max(150, Math.round(resizeStart.w + (e.clientX - resizeStart.x) / zoom));
78
+ liveH.value = Math.max(90, Math.round(resizeStart.h + (e.clientY - resizeStart.y) / zoom));
79
+ }
80
+ function onResizeEnd() {
81
+ if (!resizing.value) return;
82
+ resizing.value = false;
83
+ props.data.actions?.resize({ width: liveW.value, height: liveH.value });
84
+ }
85
+ </script>
86
+
87
+ <template>
88
+ <div class="relative">
89
+ <NodeToolbar :is-visible="toolbarVisible" :position="Position.Top" :offset="8">
90
+ <div
91
+ class="flex items-center gap-1 rounded-lg border border-black/[0.06] bg-white px-1.5 py-1 shadow-md"
92
+ @mouseenter="onHoverEnter"
93
+ @mouseleave="onHoverLeave"
94
+ >
95
+ <button
96
+ v-for="(c, i) in GROUP_COLORS"
97
+ :key="i"
98
+ type="button"
99
+ class="size-4.5 rounded-full border transition hover:scale-110"
100
+ :class="(data.group.color ?? 4) === i ? 'ring-2 ring-black/25 ring-offset-1' : ''"
101
+ :style="{ backgroundColor: c.chip, borderColor: c.border, width: '16px', height: '16px' }"
102
+ @click="data.actions?.setColor(i)"
103
+ />
104
+ <div class="mx-0.5 h-4 w-px bg-black/10" />
105
+ <button
106
+ type="button"
107
+ class="flex size-6 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
108
+ :title="collapsed ? 'Expand group' : 'Collapse group'"
109
+ @click="data.actions?.toggleCollapse()"
110
+ >
111
+ <component :is="collapsed ? ChevronsUpDown : ChevronsDownUp" :size="13" />
112
+ </button>
113
+ <button
114
+ type="button"
115
+ class="flex size-6 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
116
+ title="Ungroup (keep the nodes)"
117
+ @click="data.actions?.ungroup()"
118
+ >
119
+ <Ungroup :size="13" />
120
+ </button>
121
+ <button
122
+ type="button"
123
+ class="flex size-6 items-center justify-center rounded-md text-muted-foreground transition hover:bg-rose-50 hover:text-rose-600"
124
+ title="Remove group frame"
125
+ @click="data.actions?.ungroup()"
126
+ >
127
+ <Trash2 :size="13" />
128
+ </button>
129
+ </div>
130
+ </NodeToolbar>
131
+
132
+ <!-- Collapsed: compact stand-in card with generic edge handles. -->
133
+ <template v-if="collapsed">
134
+ <Handle
135
+ id="__in__top"
136
+ type="target"
137
+ :position="Position.Top"
138
+ class="!size-3 !rounded-full !border-2 !border-white !bg-slate-500 !shadow-sm"
139
+ />
140
+ <div
141
+ class="w-[220px] rounded-lg border-2 bg-white transition-all duration-150"
142
+ :style="{ borderColor: color.border }"
143
+ :class="selected ? 'shadow-[0_0_0_2px_rgba(59,130,246,0.2)]' : 'shadow-sm hover:shadow-md'"
144
+ @mouseenter="onHoverEnter"
145
+ @mouseleave="onHoverLeave"
146
+ @dblclick.stop="data.actions?.toggleCollapse()"
147
+ >
148
+ <div class="flex items-center gap-2.5 px-3 py-2.5">
149
+ <span class="flex size-8 shrink-0 items-center justify-center rounded-md text-white" :style="{ backgroundColor: color.chip }">
150
+ <ChevronsUpDown :size="15" />
151
+ </span>
152
+ <div class="min-w-0 flex-1">
153
+ <div class="truncate text-sm font-semibold leading-tight text-foreground">{{ label }}</div>
154
+ <div class="mt-0.5 text-sm text-muted-foreground">{{ memberCountText }}</div>
155
+ </div>
156
+ </div>
157
+ </div>
158
+ <Handle
159
+ id="default__bottom"
160
+ type="source"
161
+ :position="Position.Bottom"
162
+ class="!size-3 !rounded-full !border-2 !border-white !bg-slate-500 !shadow-sm"
163
+ />
164
+ </template>
165
+
166
+ <!-- Expanded: the frame behind the members. -->
167
+ <div
168
+ v-else
169
+ class="pointer-events-none relative rounded-xl border-2 border-dashed transition-colors"
170
+ :class="selected ? 'shadow-[0_0_0_2px_rgba(59,130,246,0.15)]' : ''"
171
+ :style="{
172
+ width: `${frameW}px`,
173
+ height: `${frameH}px`,
174
+ backgroundColor: color.bg,
175
+ borderColor: color.border
176
+ }"
177
+ >
178
+ <div
179
+ class="pointer-events-auto flex cursor-grab items-center gap-1.5 px-3 pt-1.5 active:cursor-grabbing"
180
+ @mouseenter="onHoverEnter"
181
+ @mouseleave="onHoverLeave"
182
+ >
183
+ <input
184
+ v-if="editing"
185
+ ref="inputEl"
186
+ v-model="draft"
187
+ class="nodrag w-40 rounded border border-black/[0.1] bg-white px-1 py-0.5 text-sm font-semibold focus:outline-none focus:ring-2 focus:ring-primary/40"
188
+ @blur="commitRename"
189
+ @keydown.enter.prevent="commitRename"
190
+ @keydown.stop
191
+ @mousedown.stop
192
+ >
193
+ <span
194
+ v-else
195
+ class="select-none text-sm font-semibold"
196
+ :style="{ color: color.chip }"
197
+ @dblclick.stop="startRename"
198
+ >
199
+ {{ label }}
200
+ </span>
201
+ <span class="select-none text-xs text-muted-foreground">· {{ memberCountText }}</span>
202
+ </div>
203
+
204
+ <!-- Bottom-right resize handle (editable frames only). -->
205
+ <div
206
+ v-if="editable"
207
+ class="nodrag pointer-events-auto absolute bottom-0 right-0 flex size-5 cursor-se-resize items-end justify-end p-1"
208
+ :style="{ color: color.chip }"
209
+ title="Resize group"
210
+ @pointerdown="onResizeStart"
211
+ @pointermove="onResizeMove"
212
+ @pointerup="onResizeEnd"
213
+ @pointercancel="onResizeEnd"
214
+ >
215
+ <svg viewBox="0 0 8 8" class="size-2.5" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round">
216
+ <path d="M7 1 1 7" />
217
+ <path d="M7 4.5 4.5 7" />
218
+ </svg>
219
+ </div>
220
+ </div>
221
+ </div>
222
+ </template>