@raclettejs/workbench 0.1.33-canary.3 → 0.1.33

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 (27) hide show
  1. package/.raclette/backend/raclette.config.js +2 -2
  2. package/.raclette/frontend/raclette.config.js +2 -2
  3. package/.raclette/frontend/yarn.lock +143 -126
  4. package/.raclette/virtual/backend/raclette.config.js +2 -2
  5. package/.raclette/virtual/frontend/raclette.config.js +2 -2
  6. package/.raclette/virtual/frontend/yarn.lock +143 -126
  7. package/CHANGELOG.md +19 -3
  8. package/i18n/de-DE.json +15 -0
  9. package/i18n/en-EU.json +15 -0
  10. package/i18n/sk-SK.json +15 -0
  11. package/package.json +5 -4
  12. package/plugins/pacifico__compositions/frontend/widgets/CompositionListWidget.vue +39 -21
  13. package/plugins/pacifico__interactionLinks/frontend/widgets/InteractionLinkListWidget.vue +39 -21
  14. package/plugins/pacifico__plugins/frontend/index.ts +3 -3
  15. package/plugins/pacifico__plugins/frontend/widgets/PluginListWidget.vue +11 -12
  16. package/plugins/pacifico__tags/frontend/widgets/TagListWidget.vue +39 -21
  17. package/plugins/pacifico__users/frontend/widgets/UserListWidget.vue +79 -57
  18. package/services/frontend/src/app/components/dynamicForm/DynamicForm.vue +139 -51
  19. package/services/frontend/src/app/components/dynamicForm/DynamicInput.vue +95 -5
  20. package/services/frontend/src/app/components/dynamicForm/ObjectListInput.vue +78 -41
  21. package/services/frontend/src/app/components/dynamicForm/TextDivider.vue +46 -6
  22. package/services/frontend/src/app/components/dynamicForm/typedInputs/ListInput.vue +118 -27
  23. package/services/frontend/src/app/components/dynamicForm/typedInputs/RecordInput.vue +131 -21
  24. package/services/frontend/src/app/components/dynamicForm/typedInputs/TagsInput.vue +1 -0
  25. package/services/frontend/src/app/components/dynamicForm/typedInputs/TriggerInput.vue +4 -4
  26. package/services/frontend/src/app/composables/useWorkbenchTableActions.ts +29 -0
  27. package/services/frontend/src/orchestrator/assets/styles/themes/light.ts +4 -3
@@ -1,50 +1,141 @@
1
1
  <template>
2
- <div class="tw:grid tw:gap-2">
3
- <!-- existing tags -->
4
- <v-text-field
2
+ <div class="tw:grid" :class="listGapClasses" role="list">
3
+ <div
5
4
  v-for="(_, index) in modelValue"
6
- v-bind="$attrs"
7
- :key="index"
8
- :id="index === 0 ? inputId : undefined"
9
- v-model="modelValue[index]"
10
- variant="outlined"
11
- append-icon="mdi-minus"
12
- @click:append="() => modelValue.length > 1 && modelValue.splice(index, 1)"
13
- />
14
-
15
- <!-- add new entry -->
16
- <v-text-field
17
- :id="modelValue?.length === 0 ? inputId : undefined"
18
- v-bind="$attrs"
19
- v-model="virtualInput"
20
- variant="outlined"
21
- hide-details
22
- append-icon="mdi-plus"
23
- @click:append="addVirtualInput"
24
- @blur="addVirtualInput"
25
- />
5
+ :key="`row-${index}`"
6
+ class="tw:flex tw:min-w-0 tw:items-start tw:gap-2"
7
+ role="listitem"
8
+ >
9
+ <v-text-field
10
+ :model-value="modelValue[index]"
11
+ v-bind="passthroughAttrs(index)"
12
+ class="tw:min-w-0 tw:flex-1"
13
+ :id="fieldId(index)"
14
+ variant="outlined"
15
+ color="primary"
16
+ :density="effectiveDensity"
17
+ append-inner-icon="mdi-minus"
18
+ :aria-label="rowLabel(index)"
19
+ @click:append-inner="removeAt(index)"
20
+ @update:model-value="updateAt(index, String($event ?? ''))"
21
+ @blur="onFieldBlur"
22
+ />
23
+ </div>
24
+
25
+ <div role="listitem">
26
+ <v-text-field
27
+ v-model="virtualInput"
28
+ v-bind="passthroughAttrs(-1)"
29
+ class="tw:w-full"
30
+ :id="addFieldDomId"
31
+ variant="outlined"
32
+ color="primary"
33
+ :density="effectiveDensity"
34
+ hide-details
35
+ append-inner-icon="mdi-plus"
36
+ :aria-label="t('workbench.dynamicInputs.list.addNewLabel')"
37
+ @click:append-inner="addVirtualInput"
38
+ @blur="addAndBlur"
39
+ />
40
+ </div>
26
41
  </div>
27
42
  </template>
28
43
 
29
44
  <script setup lang="ts">
30
- import { ref } from "vue"
45
+ import { computed, ref, useAttrs } from "vue"
46
+ import { useI18n } from "vue-i18n"
31
47
 
32
48
  defineOptions({
33
49
  inheritAttrs: false,
34
50
  })
35
51
 
36
- defineProps<{
52
+ const props = defineProps<{
37
53
  inputId?: string
38
54
  }>()
39
55
 
40
- const modelValue = defineModel<string[]>({ required: true })
56
+ const emit = defineEmits<{
57
+ blur: []
58
+ }>()
59
+
60
+ const { t } = useI18n()
61
+ const attrs = useAttrs()
41
62
 
63
+ const modelValue = defineModel<string[]>({ required: true })
42
64
  const virtualInput = ref("")
43
65
 
66
+ const baseId = computed(() => props.inputId ?? "dynamic-list")
67
+
68
+ const densityToken = computed(
69
+ () => (attrs["density"] as string | undefined) ?? "comfortable",
70
+ )
71
+
72
+ const effectiveDensity = computed(() =>
73
+ densityToken.value === "spacious" ? "default" : densityToken.value,
74
+ )
75
+
76
+ const listGapClasses = computed(() => {
77
+ if (densityToken.value === "compact") return "tw:gap-2"
78
+ if (
79
+ densityToken.value === "spacious" ||
80
+ densityToken.value === "default"
81
+ ) {
82
+ return "tw:gap-3"
83
+ }
84
+ return "tw:gap-2 tw:md:gap-3"
85
+ })
86
+
87
+ const passthroughAttrs = (index: number) => {
88
+ const r = { ...(attrs as Record<string, unknown>) }
89
+ if (index > 0 || index === -1) {
90
+ delete r.errorMessages
91
+ delete r["error-messages"]
92
+ delete r.errorMessage
93
+ delete r["error-message"]
94
+ }
95
+ if (index === -1) {
96
+ r.hideDetails = true
97
+ }
98
+ return r
99
+ }
100
+
101
+ function fieldId(index: number): string {
102
+ if (index === 0 && props.inputId) return props.inputId
103
+ return `${baseId.value}-${index}`
104
+ }
105
+
106
+ const addFieldDomId = computed(() =>
107
+ modelValue.value.length === 0 && props.inputId
108
+ ? props.inputId
109
+ : `${baseId.value}-new`,
110
+ )
111
+
112
+ const rowLabel = (index: number) =>
113
+ t("workbench.dynamicInputs.list.rowLabel", { n: index + 1 })
114
+
115
+ const onFieldBlur = () => {
116
+ emit("blur")
117
+ }
118
+
119
+ const addAndBlur = () => {
120
+ addVirtualInput()
121
+ emit("blur")
122
+ }
123
+
124
+ const updateAt = (index: number, value: string) => {
125
+ const next = [...modelValue.value]
126
+ next[index] = value
127
+ modelValue.value = next
128
+ }
129
+
130
+ const removeAt = (index: number) => {
131
+ if (modelValue.value.length <= 1) return
132
+ modelValue.value = modelValue.value.filter((_, i) => i !== index)
133
+ }
134
+
44
135
  const addVirtualInput = () => {
45
136
  const val = virtualInput.value.trim()
46
137
  if (val !== "") {
47
- modelValue.value.push(val)
138
+ modelValue.value = [...modelValue.value, val]
48
139
  virtualInput.value = ""
49
140
  }
50
141
  }
@@ -1,49 +1,154 @@
1
1
  <template>
2
- <div class="tw:grid tw:gap-2">
2
+ <div
3
+ role="group"
4
+ class="tw:grid tw:gap-3"
5
+ v-bind="rootA11yAttrs"
6
+ >
7
+ <div
8
+ v-if="errorSummary"
9
+ role="alert"
10
+ class="tw:text-sm tw:leading-snug tw:text-[color:rgb(var(--v-theme-error))]"
11
+ >
12
+ {{ errorSummary }}
13
+ </div>
14
+
3
15
  <div
4
16
  v-for="(entry, index) in entries"
5
17
  :key="index"
6
- class="tw:grid tw:grid-cols-12 tw:gap-2"
18
+ class="tw:grid tw:grid-cols-1 tw:gap-2 sm:tw:grid-cols-12 sm:tw:items-center sm:tw:gap-2"
7
19
  >
8
20
  <v-text-field
9
21
  :model-value="entry.key"
22
+ v-bind="sharedFieldBindings"
10
23
  @update:model-value="entry.key = $event"
11
- @blur="emitEntries"
12
- label="Key"
13
- class="tw:col-span-5"
14
24
  hide-details
15
25
  variant="outlined"
26
+ color="primary"
27
+ :density="effectiveDensity"
28
+ class="tw:sm:col-span-5"
29
+ :aria-label="
30
+ t('workbench.dynamicInputs.record.keyLabel', { n: index + 1 })
31
+ "
32
+ @blur="emitEntriesAndBlur"
16
33
  />
17
34
 
18
35
  <v-text-field
19
36
  :model-value="entry.value"
37
+ v-bind="sharedFieldBindings"
20
38
  @update:model-value="entry.value = $event"
21
- @blur="emitEntries"
22
- label="Value"
23
- class="tw:col-span-6"
24
39
  hide-details
25
40
  variant="outlined"
41
+ color="primary"
42
+ :density="effectiveDensity"
43
+ class="tw:sm:col-span-6"
44
+ :aria-label="
45
+ t('workbench.dynamicInputs.record.valueLabel', { n: index + 1 })
46
+ "
47
+ @blur="emitEntriesAndBlur"
26
48
  />
49
+ <div class="tw:flex tw:justify-end sm:tw:col-span-1 sm:tw:justify-center">
50
+ <v-btn
51
+ icon="mdi-minus"
52
+ variant="text"
53
+ color="error"
54
+ size="small"
55
+ :density="effectiveDensity"
56
+ class="tw:shrink-0"
57
+ :aria-label="t('workbench.dynamicInputs.record.removeRow')"
58
+ type="button"
59
+ @click="remove(index)"
60
+ />
61
+ </div>
62
+ </div>
63
+
64
+ <div class="tw:flex tw:justify-end">
27
65
  <v-btn
28
- icon="mdi-minus"
29
- color="red"
30
- class="tw:col-span-1"
31
- @click="remove(index)"
66
+ icon="mdi-plus"
67
+ variant="tonal"
68
+ color="secondary"
69
+ size="small"
70
+ :density="effectiveDensity"
71
+ :disabled="hasEmptyKeyEntry"
72
+ type="button"
73
+ :aria-label="t('workbench.dynamicInputs.record.addRow')"
74
+ @click="add"
32
75
  />
33
76
  </div>
34
-
35
- <v-btn
36
- icon="mdi-plus"
37
- color="green"
38
- @click="add"
39
- :disabled="hasEmptyKeyEntry"
40
- />
41
77
  </div>
42
78
  </template>
43
79
 
44
80
  <script setup lang="ts">
45
- import { computed, ref, watch } from "vue"
81
+ import { computed, ref, useAttrs, watch } from "vue"
46
82
  import { equals } from "ramda"
83
+ import { useI18n } from "vue-i18n"
84
+
85
+ defineOptions({
86
+ inheritAttrs: false,
87
+ })
88
+
89
+ const { t } = useI18n()
90
+ const attrs = useAttrs()
91
+ const emit = defineEmits<{ blur: [] }>()
92
+
93
+ const densityToken = computed(
94
+ () => (attrs["density"] as string | undefined) ?? "comfortable",
95
+ )
96
+
97
+ const effectiveDensity = computed(() =>
98
+ densityToken.value === "spacious" ? "default" : densityToken.value,
99
+ )
100
+
101
+ const pickErrorMessages = (raw: Record<string, unknown>): string | undefined => {
102
+ const m =
103
+ raw["errorMessages"] ?? raw["error-messages"] ?? raw["errorMessage"]
104
+ if (m == null) return undefined
105
+ if (Array.isArray(m)) return m.filter(Boolean)[0]?.toString()
106
+ return String(m)
107
+ }
108
+
109
+ const errorSummary = computed(() => pickErrorMessages(attrs))
110
+
111
+ const omitKeys = (
112
+ raw: Record<string, unknown>,
113
+ keys: readonly string[],
114
+ ): Record<string, unknown> => {
115
+ const o = { ...raw }
116
+ for (const k of keys) delete o[k]
117
+ return o
118
+ }
119
+
120
+ const SHARED_OMIT: readonly string[] = [
121
+ "errorMessages",
122
+ "error-messages",
123
+ "errorMessage",
124
+ "hideDetails",
125
+ "hide-details",
126
+ "disabled",
127
+ ]
128
+
129
+ const ROOT_A11Y_KEYS: readonly string[] = [
130
+ "aria-describedby",
131
+ "aria-required",
132
+ "aria-invalid",
133
+ ]
134
+
135
+ const rootA11yAttrs = computed(() => {
136
+ const raw = attrs as Record<string, unknown>
137
+ const pick: Record<string, unknown> = {}
138
+ for (const k of ROOT_A11Y_KEYS) {
139
+ if (raw[k] !== undefined) pick[k] = raw[k]
140
+ }
141
+ return pick
142
+ })
143
+
144
+ const sharedFieldBindings = computed(() => ({
145
+ ...omitKeys(attrs as Record<string, unknown>, [
146
+ ...SHARED_OMIT,
147
+ ...ROOT_A11Y_KEYS,
148
+ ]),
149
+ disabled: attrs.disabled ?? false,
150
+ density: effectiveDensity.value,
151
+ }))
47
152
 
48
153
  const modelValue = defineModel<Record<string, string>>({ required: true })
49
154
 
@@ -64,15 +169,20 @@ const emitEntries = () => {
64
169
  modelValue.value = obj
65
170
  }
66
171
 
172
+ const emitEntriesAndBlur = () => {
173
+ emitEntries()
174
+ emit("blur")
175
+ }
176
+
67
177
  const add = () => {
68
178
  entries.value.push({ key: "", value: "" })
69
179
  }
70
180
 
71
181
  const remove = (index: number) => {
72
182
  entries.value.splice(index, 1)
183
+ emitEntriesAndBlur()
73
184
  }
74
185
 
75
- // synchronize local clone of the modelValue
76
186
  watch(
77
187
  () => modelValue.value,
78
188
  (newVal, oldVal) => {
@@ -10,6 +10,7 @@
10
10
  item-title="title"
11
11
  multiple
12
12
  variant="outlined"
13
+ color="primary"
13
14
  chips
14
15
  >
15
16
  <template #chip="{ internalItem }">
@@ -2,9 +2,9 @@
2
2
  <div class="tw:grid tw:gap-2">
3
3
  <!-- existing triggers -->
4
4
  <div
5
- class="tw:py-2 tw:border-solid tw:rounded"
6
5
  v-for="(trigger, index) in modelValue"
7
6
  :key="index"
7
+ class="tw:grid tw:gap-3 tw:rounded-md tw:pb-4"
8
8
  >
9
9
  <v-select
10
10
  v-model="trigger.type"
@@ -17,10 +17,9 @@
17
17
  @update:model-value="updateTriggerType($event, index)"
18
18
  @click:append="modelValue.splice(index, 1)"
19
19
  :label="$t('workbench.dynamicInputs.triggers.triggerTypeLabel')"
20
- :disabled="true"
21
20
  />
22
21
 
23
- <div class="tw:grid tw:gap-2 tw:p-2">
22
+ <div class="tw:grid tw:gap-2 tw:pl-4">
24
23
  <template
25
24
  v-if="
26
25
  ['app-bar', 'page-navigation', 'user-menu'].includes(trigger.type)
@@ -33,7 +32,8 @@
33
32
  v-bind="$attrs"
34
33
  :input-id="`title-${index}`"
35
34
  :title="$t('workbench.dynamicInputs.triggers.titleLabel')"
36
- />
35
+ hide-details
36
+ />
37
37
 
38
38
  <v-number-input
39
39
  v-model="trigger.settings.sortOrder"
@@ -0,0 +1,29 @@
1
+ import { useRouteState } from "@raclettejs/core/orchestrator/composables"
2
+
3
+ export const useWorkbenchTableActions = () => {
4
+ const routeState = useRouteState()
5
+
6
+ const goToCreate = (interactionLinkId: string) => {
7
+ routeState.triggerInteractionLinkById(interactionLinkId)
8
+ }
9
+
10
+ const goToEditById = (
11
+ interactionLinkId: string,
12
+ id: string | number | undefined | null,
13
+ ) => {
14
+ if (id === undefined || id === null) {
15
+ return
16
+ }
17
+ routeState.triggerInteractionLinkById(interactionLinkId, {
18
+ id: String(id),
19
+ })
20
+ }
21
+
22
+ return {
23
+ routeState,
24
+ goToCreate,
25
+ goToEditById,
26
+ }
27
+ }
28
+
29
+ export default useWorkbenchTableActions
@@ -2,8 +2,9 @@
2
2
  * light theme
3
3
  */
4
4
  export default {
5
- background: "#F4F4F4",
6
- surface: "#F4F4F4",
5
+ background: "#ffffff",
6
+ surface: "#ffffff",
7
+ "surface-light": "#ffffff",
7
8
  primary: "#DD8B41",
8
9
  "primary-darken-1": "#B26F34",
9
10
  "primary-darken-2": "#9D612E",
@@ -23,7 +24,7 @@ export default {
23
24
  "secondary-darken-1": "#247DA1",
24
25
  "secondary-darken-2": "#1B5D78",
25
26
  error: "#B00020",
26
- info: "#2196F3",
27
+ info: "#1973BE",
27
28
  success: "#3A823D",
28
29
  warning: "#FB8C00",
29
30
  "surface-2": "#F4F4F4",