Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@
"lint": "pnpm prettier; pnpm eslint; pnpm stylelint",
"lint:ci": "pnpm prettier && pnpm eslint && pnpm stylelint",
"format": "pnpm prettier:fix; pnpm eslint:fix; pnpm stylelint:fix",
"check": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json",
"check:watch": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json --watch",
"check": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json --tsgo --incremental",
"check:watch": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json --tsgo --incremental --watch",
"prettier": "prettier --check --plugin prettier-plugin-svelte --plugin prettier-plugin-tailwindcss .",
"prettier:fix": "prettier --write --plugin prettier-plugin-svelte --plugin prettier-plugin-tailwindcss .",
"preview:local": "VITE_TEMPORAL_UI_BUILD_TARGET=local vite preview",
Expand Down Expand Up @@ -152,6 +152,7 @@
"@types/node": "^18.15.3",
"@types/tar-fs": "^2.0.4",
"@types/yargs": "^17.0.24",
"@typescript/native-preview": "7.0.0-dev.20260506.1",
"@vitest/coverage-v8": "^4.1.5",
"@vitest/ui": "^3.1.1",
"autoprefixer": "^10.4.13",
Expand Down Expand Up @@ -203,7 +204,7 @@
"svelte2tsx": "^0.7.35",
"tailwindcss": "^3.4.1",
"tar-fs": "^3.1.2",
"typescript": "^5.2.2",
"typescript": "6.0.3",
"typescript-eslint": "^8.54.0",
"unist-util-visit": "^5.0.0",
"vite": "^6.4.2",
Expand Down
309 changes: 202 additions & 107 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
}
};

const validateDuration = (event: Event & { target: HTMLInputElement }) => {
if (isValidDurationQuery(event.target.value.trim())) {
const validateDuration = (event: InputEvent) => {
if (isValidDurationQuery((event.target as HTMLInputElement).value.trim())) {
isValid = true;
} else {
isValid = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@
);
};

const handleJobIdChange = (event: Event & { target: HTMLInputElement }) => {
jobIdValid = /^[\w.~-]*$/.test(event.target.value);
const handleJobIdChange = (event: InputEvent) => {
jobIdValid = /^[\w.~-]*$/.test((event.target as HTMLInputElement).value);
};
</script>

Expand Down
3 changes: 2 additions & 1 deletion src/lib/holocene/input/chip-input.stories.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<script lang="ts" module>
import type { Meta } from '@storybook/svelte';
import { userEvent, within } from '@storybook/test';
import type { ComponentProps } from 'svelte';

import ChipInput from '$lib/holocene/input/chip-input.svelte';
import { isEmail } from '$lib/utilities/is-email';
Expand Down Expand Up @@ -42,7 +43,7 @@
},
scrollTo: { name: 'Scroll To', control: 'boolean' },
},
} satisfies Meta<ChipInput>;
} satisfies Meta<ComponentProps<typeof ChipInput>>;
</script>

<script lang="ts">
Expand Down
113 changes: 60 additions & 53 deletions src/lib/holocene/input/chip-input.svelte
Original file line number Diff line number Diff line change
@@ -1,41 +1,55 @@
<script lang="ts">
import { writable } from 'svelte/store';

import { twMerge as merge } from 'tailwind-merge';

import Chip from '$lib/holocene/chip.svelte';
import Label from '$lib/holocene/label.svelte';

export let id: string;
export let chips: string[];
export let label: string;
export let labelHidden = false;
export let placeholder = '';
export let name = id;
export let disabled = false;
export let required = false;
export let hintText = '';
export let validator: (value: string) => boolean = () => true;
export let removeChipButtonLabel: string | ((chipValue: string) => string);
export let external = false;
export let maxLength = 0;

const values = writable<string[]>(Array.isArray(chips) ? [...chips] : []);
let displayValue = '';

$: (chips, ($values = chips ?? []));
$: invalid = $values.some((chip) => !validator(chip));

let className = '';
export { className as class };
export let scrollTo = false;
type Props = {
id: string;
chips: string[];
label: string;
labelHidden?: boolean;
placeholder?: string;
name?: string;
disabled?: boolean;
required?: boolean;
hintText?: string;
validator?: (value: string) => boolean;
removeChipButtonLabel: string | ((chipValue: string) => string);
external?: boolean;
maxLength?: number;
class?: string;
scrollTo?: boolean;
};

let {
id,
chips = $bindable([]),
label,
labelHidden = false,
placeholder = '',
name = id,
disabled = false,
required = false,
hintText = '',
validator = () => true,
removeChipButtonLabel,
external = false,
maxLength = 0,
class: className = '',
scrollTo = false,
}: Props = $props();

let displayValue = $state('');

const invalid = $derived(chips.some((chip) => !validator(chip)));

const handleKeydown = (e: KeyboardEvent) => {
e.stopPropagation();
const value = displayValue.trim();
if ((e.key === ',' || e.key === 'Enter') && value !== '') {
e.preventDefault();
values.update((previous) => [...previous, value]);
chips = $values;
chips = [...chips, value];
displayValue = '';
}

Expand All @@ -44,44 +58,37 @@
e.key === 'Backspace' &&
eventTarget &&
eventTarget.value === '' &&
$values.length > 0
chips.length > 0
) {
values.update((previous) => previous.slice(0, -1));
chips = $values;
chips = chips.slice(0, -1);
}
};

const handlePaste = (e: ClipboardEvent) => {
e.preventDefault();
if (maxLength && $values.length >= maxLength) return;
if (maxLength && chips.length >= maxLength) return;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ 'e.clipboardData' is possibly 'null'.

const clipboardContents = e.clipboardData.getData('text/plain');
let newValues = clipboardContents
.split(',')
.map((content) => content.trim());

if (maxLength) {
newValues = newValues.slice(0, maxLength - $values.length);
newValues = newValues.slice(0, maxLength - chips.length);
}

values.update((previous) => [...previous, ...newValues]);
chips = $values;
chips = [...chips, ...newValues];
};

const handleBlur = () => {
const value = displayValue.trim();
if (value !== '') {
values.update((previous) => [...previous, value]);
chips = $values;
chips = [...chips, value];
displayValue = '';
}
};

const removeChip = (index: number) => {
values.update((previous) => {
previous.splice(index, 1);
return previous;
});
chips = $values;
chips = chips.toSpliced(index, 1);
};

const scrollIntoView = (element: HTMLInputElement, _: string[]) => {
Expand Down Expand Up @@ -110,8 +117,8 @@
invalid && 'invalid',
)}
>
{#if $values.length > 0 && !external}
{#each $values as chip, i (`${chip}-${i}`)}
{#if chips.length > 0 && !external}
{#each chips as chip, i (`${chip}-${i}`)}
{@const valid = validator(chip)}
<Chip
removeButtonLabel={typeof removeChipButtonLabel === 'string'
Expand All @@ -136,11 +143,11 @@
multiple
data-testid={id}
bind:value={displayValue}
on:blur={handleBlur}
on:keydown|stopPropagation={handleKeydown}
on:paste={handlePaste}
use:scrollIntoView={$values}
maxlength={maxLength && $values.length >= maxLength ? 0 : undefined}
onblur={handleBlur}
onkeydown={handleKeydown}
onpaste={handlePaste}
use:scrollIntoView={chips}
maxlength={maxLength && chips.length >= maxLength ? 0 : undefined}
size={placeholder.length || undefined}
/>
</div>
Expand All @@ -160,19 +167,19 @@
<span class="count">
<span
class="text-information"
class:warn={maxLength - $values?.length <= 5}
class:error={maxLength === $values?.length}
class:warn={maxLength - chips.length <= 5}
class:error={maxLength === chips?.length}
>
{$values?.length ?? 0}
{chips.length}
</span>&nbsp;/&nbsp;{maxLength}
</span>
{/if}
</div>
{/if}

{#if $values.length > 0 && external}
{#if chips.length > 0 && external}
<div class="flex flex-row flex-wrap gap-1">
{#each $values as chip, i (`${chip}-${i}`)}
{#each chips as chip, i (`${chip}-${i}`)}
{@const valid = validator(chip)}
<Chip
removeButtonLabel={typeof removeChipButtonLabel === 'string'
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"moduleResolution": "bundler",
// "strict": true,
"strict": false,
"module": "es2020",
"lib": ["es2020", "es2023", "dom", "DOM.Iterable"],
"target": "es2019",
Expand Down
Loading