|
1 | 1 | <script lang="ts"> |
| 2 | + import { untrack } from 'svelte'; |
| 3 | +
|
2 | 4 | import Button from '$lib/holocene/button.svelte'; |
3 | 5 | import ChipInput from '$lib/holocene/input/chip-input.svelte'; |
4 | 6 | import Input from '$lib/holocene/input/input.svelte'; |
|
12 | 14 | type SearchAttributeSchema, |
13 | 15 | type SearchAttributesSchema, |
14 | 16 | } from '$lib/stores/search-attributes'; |
15 | | - import type { SelectOptionValue } from '$lib/types/global'; |
16 | 17 | import { SEARCH_ATTRIBUTE_TYPE } from '$lib/types/workflows'; |
17 | 18 |
|
18 | 19 | import DatetimeInput from './datetime-input.svelte'; |
|
26 | 27 |
|
27 | 28 | let { attributes, attribute = $bindable(), onRemove, id }: Props = $props(); |
28 | 29 |
|
29 | | - let label = $state<SelectOptionValue | undefined>(undefined); |
30 | | - let _label = $derived(attribute.label || (label && label.toString())); |
| 30 | + let type = $state(attribute.type); |
| 31 | + let value = $state(attribute.value); |
| 32 | + let label = $state(attribute.label); |
| 33 | +
|
| 34 | + $effect(() => { |
| 35 | + const v = value; |
| 36 | + // effect should only re-run when the local value state changes |
| 37 | + // changes to attribute should not re-trigger the effect |
| 38 | + untrack(() => { |
| 39 | + attribute.value = v; |
| 40 | + }); |
| 41 | + }); |
31 | 42 |
|
32 | | - const isDisabled = (value: string) => { |
33 | | - return !!attributes.find((a) => a.label === value); |
| 43 | + const isDisabled = (v: string) => { |
| 44 | + return label !== v && !!attributes.find((a) => a.label === v); |
34 | 45 | }; |
35 | 46 |
|
36 | 47 | const getType = (attr: string) => $customSearchAttributes[attr]; |
37 | 48 |
|
38 | 49 | const handleAttributeChange = (attr: string) => { |
39 | | - attribute.label = attr; |
40 | | - const type = getType(attr); |
| 50 | + const previousType = type; |
| 51 | + const newType = getType(attr); |
| 52 | +
|
| 53 | + label = attr; |
| 54 | + type = newType; |
41 | 55 |
|
42 | | - if (type === SEARCH_ATTRIBUTE_TYPE.KEYWORDLIST) { |
43 | | - attribute.value = []; |
44 | | - } else if (attribute.type !== type) { |
45 | | - attribute.value = null; |
| 56 | + if (newType === SEARCH_ATTRIBUTE_TYPE.KEYWORDLIST) { |
| 57 | + value = []; |
| 58 | + } else if (previousType !== newType) { |
| 59 | + value = null; |
46 | 60 | } |
47 | 61 |
|
48 | | - attribute.type = type; |
| 62 | + attribute = { label, value, type }; |
49 | 63 | }; |
50 | 64 | </script> |
51 | 65 |
|
|
59 | 73 | data-testid="search-attribute-select" |
60 | 74 | label={translate('workflows.custom-search-attribute')} |
61 | 75 | placeholder={translate('workflows.select-attribute')} |
62 | | - bind:value={_label} |
| 76 | + bind:value={label} |
63 | 77 | onChange={handleAttributeChange} |
64 | 78 | > |
65 | 79 | {#each $customSearchAttributeOptions as { value, label, type } (value)} |
|
72 | 86 | <Button |
73 | 87 | variant="ghost" |
74 | 88 | leadingIcon="close" |
| 89 | + data-testid="search-attribute-close-button" |
75 | 90 | class="mt-6 w-10 rounded-full sm:hidden" |
76 | | - on:click={() => onRemove(attribute.label)} |
| 91 | + on:click={() => onRemove(label)} |
77 | 92 | /> |
78 | 93 | </div> |
79 | | - {#if attribute.type === SEARCH_ATTRIBUTE_TYPE.BOOL} |
| 94 | + {#if type === SEARCH_ATTRIBUTE_TYPE.BOOL} |
80 | 95 | <Select |
81 | 96 | label={translate('common.value')} |
82 | 97 | id="attribute-value-{id}" |
83 | | - bind:value={attribute.value} |
| 98 | + bind:value |
84 | 99 | > |
85 | 100 | <Option value={true}>{translate('common.true')}</Option> |
86 | 101 | <Option value={false}>{translate('common.false')}</Option> |
87 | 102 | </Select> |
88 | | - {:else if attribute.type === SEARCH_ATTRIBUTE_TYPE.DATETIME} |
89 | | - <DatetimeInput bind:value={attribute.value} /> |
90 | | - {:else if attribute.type === SEARCH_ATTRIBUTE_TYPE.INT || attribute.type === SEARCH_ATTRIBUTE_TYPE.DOUBLE} |
| 103 | + {:else if type === SEARCH_ATTRIBUTE_TYPE.DATETIME} |
| 104 | + <DatetimeInput bind:value /> |
| 105 | + {:else if type === SEARCH_ATTRIBUTE_TYPE.INT || type === SEARCH_ATTRIBUTE_TYPE.DOUBLE} |
91 | 106 | <div> |
92 | 107 | <NumberInput |
93 | 108 | label={translate('common.value')} |
94 | 109 | id="attribute-value-{id}" |
95 | | - valid={attribute.value < Number.MAX_SAFE_INTEGER} |
| 110 | + valid={value < Number.MAX_SAFE_INTEGER} |
96 | 111 | hintText="Number is too large" |
97 | | - bind:value={attribute.value} |
| 112 | + bind:value |
98 | 113 | max={Number.MAX_SAFE_INTEGER} |
99 | 114 | /> |
100 | 115 | </div> |
101 | | - {:else if attribute.type === SEARCH_ATTRIBUTE_TYPE.KEYWORDLIST} |
| 116 | + {:else if type === SEARCH_ATTRIBUTE_TYPE.KEYWORDLIST} |
102 | 117 | <ChipInput |
103 | 118 | label={translate('common.value')} |
104 | 119 | id="attribute-value-{id}" |
105 | | - bind:chips={attribute.value} |
| 120 | + bind:chips={value} |
106 | 121 | class="w-full" |
107 | 122 | removeChipButtonLabel={(chip) => |
108 | 123 | translate('workflows.remove-keyword-label', { keyword: chip })} |
109 | 124 | /> |
110 | | - {:else if attribute.type === SEARCH_ATTRIBUTE_TYPE.TEXT || attribute.type === SEARCH_ATTRIBUTE_TYPE.KEYWORD || attribute.type === SEARCH_ATTRIBUTE_TYPE.UNSPECIFIED} |
| 125 | + {:else if type === SEARCH_ATTRIBUTE_TYPE.TEXT || type === SEARCH_ATTRIBUTE_TYPE.KEYWORD || type === SEARCH_ATTRIBUTE_TYPE.UNSPECIFIED} |
111 | 126 | <Input |
112 | 127 | label={translate('common.value')} |
113 | 128 | data-testid="custom-search-attribute-value" |
114 | 129 | id="attribute-value-{id}" |
115 | 130 | class="grow" |
116 | | - bind:value={attribute.value} |
| 131 | + bind:value |
117 | 132 | /> |
118 | 133 | {:else} |
119 | 134 | <Input |
|
131 | 146 | leadingIcon="close" |
132 | 147 | data-testid="search-attribute-close-button" |
133 | 148 | class="mt-6 w-10 rounded-full max-sm:hidden" |
134 | | - on:click={() => onRemove(attribute.label)} |
| 149 | + on:click={() => onRemove(label)} |
135 | 150 | /> |
136 | 151 | </div> |
0 commit comments