diff --git a/packages/builder/src/components/backend/DataTable/modals/OptionsEditor.svelte b/packages/builder/src/components/backend/DataTable/modals/OptionsEditor.svelte index 27b1d5d4e6..5eab0bf182 100644 --- a/packages/builder/src/components/backend/DataTable/modals/OptionsEditor.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/OptionsEditor.svelte @@ -2,101 +2,104 @@ import { flip } from "svelte/animate" import { dndzone } from "svelte-dnd-action" import { Icon, Popover } from "@budibase/bbui" - import { onMount, tick } from "svelte" + import { tick } from "svelte" import { Constants } from "@budibase/frontend-core" import { getSequentialName } from "helpers/duplicate" + import { writable } from "svelte/store" export let constraints export let optionColors = {} - const flipDurationMs = 150 + const flipDurationMs = 130 const { OptionColours } = Constants - let options = [] - let colorPopovers = [] - let anchors = [] + let openOption = null + let anchor = null + let options = writable( + constraints.inclusion.map((value, idx) => ({ + id: Math.random(), + name: value, + color: optionColors?.[value] || getDefaultColor(idx), + invalid: false, + })) + ) - $: enrichedOptions = options.map((option, idx) => ({ - ...option, - color: optionColors?.[option.name] || defaultColor(idx), - })) + $: options.subscribe(updateConstraints) - const defaultColor = idx => OptionColours[idx % OptionColours.length] + const updateConstraints = options => { + constraints.inclusion = options.map(option => option.name) + let newColors = {} + options.forEach(option => { + newColors[option.name] = option.color + }) + optionColors = newColors + } - const removeInput = name => { - delete optionColors[name] - constraints.inclusion = constraints.inclusion.filter(opt => opt !== name) - options = options.filter(opt => opt.name !== name) - colorPopovers.pop(undefined) - anchors.pop(undefined) + const getDefaultColor = idx => { + return OptionColours[idx % OptionColours.length] } const addNewInput = async () => { - const newName = getSequentialName(constraints.inclusion, "Option ", { - numberFirstItem: true, - }) const newId = Math.random() - options = [...options, { name: newName, id: newId }] - constraints.inclusion = [...constraints.inclusion, newName] - optionColors[newName] = defaultColor(options.length - 1) - colorPopovers.push(undefined) - anchors.push(undefined) + const newName = getSequentialName($options, "Option ", { + numberFirstItem: true, + getName: option => option.name, + }) + options.update(state => { + return [ + ...state, + { + name: newName, + id: newId, + color: getDefaultColor(state.length), + }, + ] + }) + + // Focus new option await tick() document.getElementById(`option-${newId}`)?.focus() } - const handleDndConsider = e => { - options = e.detail.items - } - const handleDndFinalize = e => { - options = e.detail.items - constraints.inclusion = options.map(option => option.name) + const removeInput = id => { + options.update(state => state.filter(option => option.id !== id)) } - const handleColorChange = (name, color, idx) => { - optionColors[name] = color - colorPopovers[idx].hide() + const openColorPicker = id => { + anchor = document.getElementById(`color-${id}`) + openOption = id } - const handleNameChange = (name, idx, newName) => { + const handleColorChange = (id, color) => { + options.update(state => { + state.find(option => option.id === id).color = color + return state.slice() + }) + openOption = null + } + + const handleNameChange = (id, newName) => { // Check we don't already have this option - const existing = options.some((option, optionIdx) => { - return newName === option.name && idx !== optionIdx + const existing = $options.some(option => { + return (option.name === newName) & (option.id !== id) }) const invalid = !newName || existing - options.find(option => option.name === name).invalid = invalid - options = options.slice() + options.update(state => { + state.find(option => option.id === id).invalid = invalid + return state.slice() + }) - // Stop if invalid or no change - if (invalid || name === newName) { + // Stop if invalid + if (invalid) { return } - constraints.inclusion[idx] = newName - options[idx].name = newName - optionColors[newName] = optionColors[name] - delete optionColors[name] + // Update name + options.update(state => { + state.find(option => option.id === id).name = newName + return state.slice() + }) } - - const openColorPickerPopover = optionIdx => { - for (let i = 0; i < colorPopovers.length; i++) { - if (i === optionIdx) { - colorPopovers[i].show() - } else { - colorPopovers[i]?.hide() - } - } - } - - onMount(() => { - // Initialize anchor arrays on mount, assuming 'options' is already populated - colorPopovers = constraints.inclusion.map(() => undefined) - anchors = constraints.inclusion.map(() => undefined) - options = constraints.inclusion.map(value => ({ - id: Math.random(), - name: value, - })) - }) @@ -105,14 +108,14 @@
options.set(e.detail.items)} + on:finalize={e => options.set(e.detail.items)} > - {#each enrichedOptions as option, idx (option.id)} + {#each $options as option (option.id)}
openColorPickerPopover(idx, e.target)} + on:click={() => openColorPicker(option.id)} >
{#each OptionColours as colorOption}
- handleColorChange(option.name, colorOption, idx)} + on:click={() => handleColorChange(option.id, colorOption)} style="--color:{colorOption};" class="circle" class:selected={colorOption === option.color} @@ -155,13 +157,13 @@ value={option.name} placeholder="Option name" id="option-{option.id}" - on:input={e => handleNameChange(option.name, idx, e.target.value)} + on:input={e => handleNameChange(option.id, e.target.value)} /> removeInput(option.id)} />
{/each}