diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a950d99..a55a09a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ default_install_hook_types: [pre-commit, commit-msg] repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-case-conflict - id: check-symlinks @@ -19,7 +19,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.0.3 + rev: v3.1.0 hooks: - id: prettier args: [--write] # edit files in-place @@ -29,15 +29,15 @@ repos: - svelte - repo: https://github.com/codespell-project/codespell - rev: v2.2.5 + rev: v2.2.6 hooks: - id: codespell stages: [commit, commit-msg] - args: [--ignore-words-list, falsy] + args: [--ignore-words-list, falsy, --check-filenames] exclude: changelog\.md - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.49.0 + rev: v8.53.0 hooks: - id: eslint types: [file] diff --git a/package.json b/package.json index 5f3cfbf..fa4448d 100644 --- a/package.json +++ b/package.json @@ -23,37 +23,37 @@ "update-coverage": "vitest tests/unit --run --coverage && npx istanbul-badges-readme" }, "dependencies": { - "svelte": "^4.2.0" + "svelte": "4.2.3" }, "devDependencies": { "@iconify/svelte": "^3.1.4", - "@playwright/test": "^1.37.1", + "@playwright/test": "^1.39.0", "@sveltejs/adapter-static": "^2.0.3", - "@sveltejs/kit": "^1.24.1", + "@sveltejs/kit": "^1.27.6", "@sveltejs/package": "2.2.2", - "@sveltejs/vite-plugin-svelte": "2.4.5", - "@typescript-eslint/eslint-plugin": "^6.7.0", - "@typescript-eslint/parser": "^6.7.0", - "@vitest/coverage-v8": "^0.34.4", - "eslint": "^8.49.0", - "eslint-plugin-svelte": "^2.33.1", + "@sveltejs/vite-plugin-svelte": "2.5.2", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "@vitest/coverage-v8": "^0.34.6", + "eslint": "^8.53.0", + "eslint-plugin-svelte": "^2.35.0", "hastscript": "^8.0.0", - "highlight.js": "^11.8.0", + "highlight.js": "^11.9.0", "jsdom": "^22.1.0", "mdsvex": "^0.11.0", "mdsvexamples": "^0.4.1", - "prettier": "^3.0.3", - "prettier-plugin-svelte": "^3.0.3", - "rehype-autolink-headings": "^7.0.0", + "prettier": "^3.1.0", + "prettier-plugin-svelte": "^3.1.0", + "rehype-autolink-headings": "^7.1.0", "rehype-slug": "^6.0.0", - "svelte-check": "^3.5.1", - "svelte-preprocess": "^5.0.4", + "svelte-check": "^3.6.0", + "svelte-preprocess": "^5.1.0", "svelte-toc": "^0.5.6", "svelte-zoo": "^0.4.9", - "svelte2tsx": "^0.6.21", + "svelte2tsx": "^0.6.25", "typescript": "5.2.2", - "vite": "^4.4.9", - "vitest": "^0.34.4" + "vite": "^4.5.0", + "vitest": "^0.34.6" }, "keywords": [ "svelte", diff --git a/readme.md b/readme.md index ef6d88c..e3c9d98 100644 --- a/readme.md +++ b/readme.md @@ -605,6 +605,8 @@ The second method allows you to pass in custom classes to the important DOM elem - `ulOptionsClass`: available options listed in the dropdown when component is in `open` state - `liOptionClass`: list items selectable from dropdown list - `liActiveOptionClass`: the currently active dropdown list item (i.e. hovered or navigated to with arrow keys) +- `liUserMsgClass`: user message (last child of dropdown list when no options match user input) +- `liActiveUserMsgClass`: user message when active (i.e. hovered or navigated to with arrow keys) - `maxSelectMsgClass`: small span towards the right end of the input field displaying to the user how many of the allowed number of options they've already selected This simplified version of the DOM structure of the component shows where these classes are inserted: @@ -622,6 +624,10 @@ This simplified version of the DOM structure of the component shows where these
  • Option 2 (currently active)
  • + ... +
  • + Create this option... +
  • ``` diff --git a/src/lib/MultiSelect.svelte b/src/lib/MultiSelect.svelte index f008e9f..7e71175 100644 --- a/src/lib/MultiSelect.svelte +++ b/src/lib/MultiSelect.svelte @@ -38,8 +38,10 @@ export let inputmode: string | null = null export let invalid: boolean = false export let liActiveOptionClass: string = `` + export let liActiveUserMsgClass: string = `` export let liOptionClass: string = `` export let liSelectedClass: string = `` + export let liUserMsgClass: string = `` export let loading: boolean = false export let matchingOptions: Option[] = [] export let maxOptions: number | undefined = undefined @@ -695,7 +697,9 @@ on:blur={() => (option_msg_is_active = false)} role="option" aria-selected="false" - class="user-msg" + class="user-msg {liUserMsgClass} {option_msg_is_active + ? liActiveUserMsgClass + : ``}" style:cursor={{ dupe: `not-allowed`, create: `pointer`, diff --git a/src/routes/(demos)/css-classes/+page.svx b/src/routes/(demos)/css-classes/+page.svx index bc69b9d..91c76b4 100644 --- a/src/routes/(demos)/css-classes/+page.svx +++ b/src/routes/(demos)/css-classes/+page.svx @@ -16,9 +16,12 @@ inputClass="search-text-input" liSelectedClass="selected-li" liActiveOptionClass="hovered-or-arrow-keyed-li" + liUserMsgClass="selectable-msg-li" + liActiveUserMsgClass="hovered-or-arrow-keyed-msg-li" maxSelectMsgClass="user-hint-max-selected-reached" placeholder="Which foods do you like?" selected={options.slice(0, 2)} + allowUserOptions maxSelect={2} /> @@ -57,6 +60,10 @@ This simplified DOM structure of the component shows where these classes are ins
  • Option 2 (currently active)
  • + ... +
  • + Create this option... +
  • diff --git a/tests/MultiSelect.test.ts b/tests/MultiSelect.test.ts index 8dc6f4a..d8f220b 100644 --- a/tests/MultiSelect.test.ts +++ b/tests/MultiSelect.test.ts @@ -119,17 +119,29 @@ test.describe(`external CSS classes`, async () => { [`ulSelected`, `ul.selected`, `user-choices`], [`ulOptions`, `ul.options`, `dropdown`], [`liOption`, `ul.options > li`, `selectable-li`], + [`liUserMsgClass`, `ul.options > li.user-msg`, `selectable-msg-li`], [`input`, `input[autocomplete]`, `search-text-input`], // below classes requires component interaction before appearing in DOM [`liSelected`, `ul.selected > li`, `selected-li`], [`liActiveOption`, `ul.options > li.active`, `hovered-or-arrow-keyed-li`], + [ + `liActiveUserMsgClass`, + `ul.options > li.active.user-msg`, + `hovered-or-arrow-keyed-msg-li`, + ], [`maxSelectMsg`, `span.max-select-msg`, `user-hint-max-selected-reached`], ]) { test(`${prop}Class`, async ({ page }) => { await page.goto(`/css-classes`, { waitUntil: `networkidle` }) await page.click(`#foods input[autocomplete]`) - await page.hover(`ul.options > li`) // hover any option to give it active state + await page.keyboard.type(`O`) // type a word so that the user message shows up + + if (prop !== `liActiveUserMsgClass`) { + await page.hover(`ul.options > li`) // hover any option to give it active state + } else { + await page.hover(`ul.options > li:last-child`) // hover last option to give it active state + } const node = await page.$(`${selector}.${cls}`) expect(node).toBeTruthy() diff --git a/tests/unit/MultiSelect.test.ts b/tests/unit/MultiSelect.test.ts index cd4b8df..296e48a 100644 --- a/tests/unit/MultiSelect.test.ts +++ b/tests/unit/MultiSelect.test.ts @@ -621,7 +621,7 @@ test(`can't select disabled options`, async () => { }) test.each([2, 5, 10])( - `cant select more than maxSelect options`, + `can't select more than maxSelect options`, async (maxSelect: number) => { new MultiSelect({ target: document.body,