fds-vue-core 6.0.10 → 6.0.13

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.
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "fds-vue-core",
3
- "version": "6.0.10",
3
+ "version": "6.0.13",
4
4
  "description": "FDS Vue Core Component Library",
5
5
  "type": "module",
6
+ "bin": {
7
+ "fds-vuln-commit": "./scripts/vuln-commit.mjs"
8
+ },
6
9
  "main": "./dist/fds-vue-core.cjs.js",
7
10
  "module": "./dist/fds-vue-core.es.js",
8
11
  "types": "./components.d.ts",
@@ -24,7 +27,8 @@
24
27
  "./tsconfig.base.json": "./configs/tsconfig.base.json",
25
28
  "./eslint.config.base.js": "./configs/eslint.config.base.js",
26
29
  "./prettier.config.js": "./configs/prettier.config.js",
27
- "./vscode-settings.json": "./configs/vscode-settings.json"
30
+ "./vscode-settings.json": "./configs/vscode-settings.json",
31
+ "./vuln-commit": "./scripts/vuln-commit.mjs"
28
32
  },
29
33
  "files": [
30
34
  "dist",
@@ -45,11 +49,31 @@
45
49
  "engines": {
46
50
  "node": "^20.19.0 || >=22.12.0"
47
51
  },
52
+ "scripts": {
53
+ "serve": "vite",
54
+ "build": "vite build && npm run build:types && cp ./src/tokens.css ./dist/tokens.css && cp ./src/slot-styles.css ./dist/slot-styles.css && cp ./src/apply.css ./dist/apply.css && cp ./src/fonts.css ./dist/fonts.css && cp -r ./public/assets ./dist/assets && mkdir -p ./dist/assets/img/logos && cp -r ./src/assets/img/logos/* ./dist/assets/img/logos/",
55
+ "build:types": "vue-tsc --project tsconfig.build.json",
56
+ "preview": "vite preview",
57
+ "lint": "eslint . --fix",
58
+ "lint:ts": "vue-tsc --noEmit --project tsconfig.app.json && vue-tsc --noEmit --project tsconfig.node.json",
59
+ "lint:all": "pnpm lint && pnpm lint:ts",
60
+ "format": "prettier --write src/",
61
+ "prepublishOnly": "npm run build",
62
+ "storybook": "STORYBOOK=true storybook dev -p 6006",
63
+ "build-storybook": "STORYBOOK=true storybook build",
64
+ "sync:peers": "node scripts/sync-peers-from-dev.mjs",
65
+ "postinstall": "test -f scripts/sync-peers-from-dev.mjs && node scripts/sync-peers-from-dev.mjs || true",
66
+ "yalc:publish": "npm run build && yalc publish",
67
+ "yalc:push": "npm run build && yalc push",
68
+ "serve:playground": "pnpm --dir playground-consumer dev",
69
+ "create:release": "bash ./scripts/release.sh",
70
+ "vuln-commit": "node scripts/vuln-commit.mjs"
71
+ },
48
72
  "peerDependencies": {
49
73
  "vue": "^3.5.25"
50
74
  },
51
75
  "dependencies": {
52
- "axios": "^1.13.6",
76
+ "axios": "1.15.0",
53
77
  "imask": "^7.6.1",
54
78
  "tailwindcss": "^4.1.18"
55
79
  },
@@ -95,22 +119,18 @@
95
119
  "src/apply.css"
96
120
  ]
97
121
  },
98
- "scripts": {
99
- "serve": "vite",
100
- "build": "vite build && npm run build:types && cp ./src/tokens.css ./dist/tokens.css && cp ./src/slot-styles.css ./dist/slot-styles.css && cp ./src/apply.css ./dist/apply.css && cp ./src/fonts.css ./dist/fonts.css && cp -r ./public/assets ./dist/assets && mkdir -p ./dist/assets/img/logos && cp -r ./src/assets/img/logos/* ./dist/assets/img/logos/",
101
- "build:types": "vue-tsc --project tsconfig.build.json",
102
- "preview": "vite preview",
103
- "lint": "eslint . --fix",
104
- "lint:ts": "vue-tsc --noEmit --project tsconfig.app.json && vue-tsc --noEmit --project tsconfig.node.json",
105
- "lint:all": "pnpm lint && pnpm lint:ts",
106
- "format": "prettier --write src/",
107
- "storybook": "STORYBOOK=true storybook dev -p 6006",
108
- "build-storybook": "STORYBOOK=true storybook build",
109
- "sync:peers": "node scripts/sync-peers-from-dev.mjs",
110
- "postinstall": "test -f scripts/sync-peers-from-dev.mjs && node scripts/sync-peers-from-dev.mjs || true",
111
- "yalc:publish": "npm run build && yalc publish",
112
- "yalc:push": "npm run build && yalc push",
113
- "serve:playground": "pnpm --dir playground-consumer dev",
114
- "create:release": "bash ./scripts/release.sh"
115
- }
116
- }
122
+ "pnpm": {
123
+ "overrides": {
124
+ "minimatch": "^10.2.3",
125
+ "flatted": "^3.4.2",
126
+ "lodash": ">=4.18.0"
127
+ },
128
+ "onlyBuiltDependencies": [
129
+ "@parcel/watcher",
130
+ "core-js-pure",
131
+ "esbuild",
132
+ "vue-demi"
133
+ ]
134
+ },
135
+ "packageManager": "pnpm@10.17.1"
136
+ }
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execSync } from "node:child_process";
4
+
5
+ function run(command) {
6
+ try {
7
+ return execSync(command, { encoding: "utf8" }).trim();
8
+ } catch {
9
+ return "";
10
+ }
11
+ }
12
+
13
+ function runOrThrow(command) {
14
+ return execSync(command, { encoding: "utf8", stdio: "inherit" });
15
+ }
16
+
17
+ function out(text = "") {
18
+ process.stdout.write(`${text}\n`);
19
+ }
20
+
21
+ function parseJsonAtRef(refPath) {
22
+ const raw = run(`git show ${refPath}`);
23
+ if (!raw) return null;
24
+ try {
25
+ return JSON.parse(raw);
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ function readStagedFile(path) {
32
+ return parseJsonAtRef(`:${path}`);
33
+ }
34
+
35
+ function readHeadFile(path) {
36
+ return parseJsonAtRef(`HEAD:${path}`);
37
+ }
38
+
39
+ function collectDeps(pkgJson) {
40
+ if (!pkgJson) return {};
41
+ return {
42
+ ...(pkgJson.dependencies || {}),
43
+ ...(pkgJson.devDependencies || {}),
44
+ ...(pkgJson.peerDependencies || {}),
45
+ ...(pkgJson.optionalDependencies || {}),
46
+ ...(pkgJson.overrides || {}),
47
+ ...((pkgJson.pnpm && pkgJson.pnpm.overrides) || {}),
48
+ };
49
+ }
50
+
51
+ function normalize(version) {
52
+ if (typeof version !== "string") return version;
53
+ return version.trim();
54
+ }
55
+
56
+ function semverFrom(value) {
57
+ if (typeof value !== "string") return null;
58
+ const match = value.match(/(\d+\.\d+\.\d+)/);
59
+ return match ? match[1] : null;
60
+ }
61
+
62
+ function main() {
63
+ const stagedFiles = run("git diff --cached --name-only")
64
+ .split("\n")
65
+ .map((s) => s.trim())
66
+ .filter(Boolean);
67
+
68
+ const packageJsonPaths = stagedFiles.filter((p) => p.endsWith("package.json"));
69
+ if (packageJsonPaths.length === 0) {
70
+ out("No staged package.json changes found.");
71
+ process.exit(0);
72
+ }
73
+
74
+ const updates = [];
75
+
76
+ for (const path of packageJsonPaths) {
77
+ const stagedPkg = readStagedFile(path);
78
+ const headPkg = readHeadFile(path) || {};
79
+ if (!stagedPkg) continue;
80
+
81
+ const before = collectDeps(headPkg);
82
+ const after = collectDeps(stagedPkg);
83
+ const keys = new Set([...Object.keys(before), ...Object.keys(after)]);
84
+
85
+ for (const name of keys) {
86
+ const oldV = normalize(before[name]);
87
+ const newV = normalize(after[name]);
88
+ if (oldV && newV && oldV !== newV) {
89
+ updates.push({ name, from: oldV, to: newV, path });
90
+ }
91
+ }
92
+ }
93
+
94
+ if (updates.length === 0) {
95
+ out("No staged dependency version changes found in package.json.");
96
+ process.exit(0);
97
+ }
98
+
99
+ out("Detected staged dependency updates:\n");
100
+ for (const u of updates) {
101
+ out(`- ${u.name}: ${u.from} -> ${u.to} (${u.path})`);
102
+ }
103
+
104
+ const commitMessage =
105
+ updates.length === 1
106
+ ? `vuln(${updates[0].name}@${semverFrom(updates[0].from) || updates[0].from}): bump to ${semverFrom(updates[0].to) || updates[0].to}`
107
+ : "vuln(): updated dependencies";
108
+ const commitBody =
109
+ updates.length > 1
110
+ ? `updated dependencies\n${updates.map((u) => `- ${u.name}: ${u.from} -> ${u.to}`).join("\n")}`
111
+ : "";
112
+
113
+ const hasStagedChanges = run("git diff --cached --name-only");
114
+ if (!hasStagedChanges) {
115
+ out("\nNo staged changes to commit.");
116
+ process.exit(1);
117
+ }
118
+
119
+ const escapedMessage = commitMessage.replace(/"/g, '\\"');
120
+ const escapedBody = commitBody.replace(/"/g, '\\"');
121
+ const commitCommand = commitBody
122
+ ? `git commit -m "${escapedMessage}" -m "${escapedBody}"`
123
+ : `git commit -m "${escapedMessage}"`;
124
+
125
+ out(`\nCreating commit:\n${commitMessage}\n`);
126
+ runOrThrow(commitCommand);
127
+ }
128
+
129
+ main();
package/src/apply.css CHANGED
@@ -39,7 +39,7 @@
39
39
  }
40
40
  .link,
41
41
  .html a {
42
- @apply text-blue-600 hover:text-blue-700 active:text-blue-800 underline focus-visible:outline-dashed focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-blue-500;
42
+ @apply text-blue-600 hover:text-blue-700 active:text-blue-800 underline focus-visible:outline-dashed focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-blue-500 cursor-pointer;
43
43
  }
44
44
 
45
45
  .link--with-parens::before {
@@ -87,6 +87,10 @@ const meta: Meta<typeof FdsSearchSelect> = {
87
87
  control: { type: 'boolean' },
88
88
  description: 'If true, preserves the order of searchFields when displaying results',
89
89
  },
90
+ debounceMs: {
91
+ control: { type: 'number' },
92
+ description: 'Debounce delay in milliseconds for input/change events',
93
+ },
90
94
  initialValue: {
91
95
  control: { type: 'text' },
92
96
  description: 'Initial value to display in the input field',
@@ -173,6 +177,7 @@ const meta: Meta<typeof FdsSearchSelect> = {
173
177
  maxItems: undefined,
174
178
  searchFields: ['companyName', 'orgNumber'],
175
179
  preserveOrder: false,
180
+ debounceMs: 500,
176
181
  initialValue: '',
177
182
  displayValue: '',
178
183
  disabled: false,
@@ -450,38 +455,63 @@ export const EnglishLocale: Story = {
450
455
  },
451
456
  }
452
457
 
453
- export const WithPagination: Story = {
458
+ export const ServerPaginationWithLoadMore: Story = {
459
+ render: (args: Story['args']) => ({
460
+ components: { FdsSearchSelect },
461
+ setup: () => {
462
+ const pageSize = 5
463
+ const allItems = Array.from({ length: 18 }, (_, i) => {
464
+ const index = i + 1
465
+ return {
466
+ companyName: `Company ${index}`,
467
+ orgNumber: String(1000000000 + index),
468
+ companyFqn: `Company ${index} | ${1000000000 + index}`,
469
+ }
470
+ })
471
+
472
+ const currentPage = ref(1)
473
+ const totalPages = Math.ceil(allItems.length / pageSize)
474
+ const loadingMore = ref(false)
475
+ const items = ref(allItems.slice(0, pageSize))
476
+
477
+ const handleLoadMore = async () => {
478
+ if (loadingMore.value || currentPage.value >= totalPages) return
479
+ loadingMore.value = true
480
+ await new Promise((resolve) => setTimeout(resolve, 1000))
481
+ currentPage.value += 1
482
+ items.value = allItems.slice(0, currentPage.value * pageSize)
483
+ loadingMore.value = false
484
+ }
485
+
486
+ return {
487
+ args,
488
+ items,
489
+ loadingMore,
490
+ currentPage,
491
+ totalPages,
492
+ totalCount: allItems.length,
493
+ handleLoadMore,
494
+ }
495
+ },
496
+ template: `
497
+ <FdsSearchSelect
498
+ v-bind="args"
499
+ :items="items"
500
+ :page="currentPage"
501
+ :totalPages="totalPages"
502
+ :totalCount="totalCount"
503
+ :loadingMore="loadingMore"
504
+ :autoLoadOnScroll="true"
505
+ :maxListHeight="320"
506
+ @loadMore="handleLoadMore"
507
+ />
508
+ `,
509
+ }),
454
510
  args: {
455
- items: [
456
- ...mockItems,
457
- {
458
- companyName: 'Company 6',
459
- orgNumber: '6666666666',
460
- companyFqn: 'Company 6 | 6666666666',
461
- },
462
- {
463
- companyName: 'Company 7',
464
- orgNumber: '7777777777',
465
- companyFqn: 'Company 7 | 7777777777',
466
- },
467
- {
468
- companyName: 'Company 8',
469
- orgNumber: '8888888888',
470
- companyFqn: 'Company 8 | 8888888888',
471
- },
472
- {
473
- companyName: 'Company 9',
474
- orgNumber: '9999999999',
475
- companyFqn: 'Company 9 | 9999999999',
476
- },
477
- {
478
- companyName: 'Company 10',
479
- orgNumber: '1010101010',
480
- companyFqn: 'Company 10 | 1010101010',
481
- },
482
- ],
483
511
  label: 'Sök organisation',
484
- maxItems: 5,
512
+ searchFields: ['companyName', 'orgNumber'],
513
+ searchContext: { context: 'organisationer', linkWord: 'av' },
514
+ noResultPrompt: 'Inga resultat hittades',
485
515
  },
486
516
  }
487
517
 
@@ -3,7 +3,6 @@ import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
3
3
  import { useBoldQuery } from '../../composables/useBoldQuery'
4
4
  import { isPidString, useIsPid } from '../../composables/useIsPid'
5
5
  import FdsButtonIcon from '../Buttons/FdsButtonIcon/FdsButtonIcon.vue'
6
- import FdsPagination from '../FdsPagination/FdsPagination.vue'
7
6
  import FdsSpinner from '../FdsSpinner/FdsSpinner.vue'
8
7
  import FdsInput from '../Form/FdsInput/FdsInput.vue'
9
8
  import type { FdsSearchSelectProProps } from './types'
@@ -35,6 +34,7 @@ const props = withDefaults(defineProps<FdsSearchSelectProProps>(), {
35
34
  loading: false,
36
35
  searchFields: () => [],
37
36
  preserveOrder: false,
37
+ debounceMs: 500,
38
38
  initialValue: '',
39
39
  displayValue: '',
40
40
  disabled: false,
@@ -282,22 +282,25 @@ const formatPidWithDash = (value: string): string => {
282
282
  return digits
283
283
  }
284
284
 
285
- const debounce = <T extends (...args: any[]) => void>(fn: T, delay: number) => {
286
- let timeout: ReturnType<typeof setTimeout>
287
- return (...args: Parameters<T>) => {
288
- clearTimeout(timeout)
289
- timeout = setTimeout(() => fn(...args), delay)
290
- }
291
- }
285
+ let changeTimeout: ReturnType<typeof setTimeout> | null = null
286
+ let inputTimeout: ReturnType<typeof setTimeout> | null = null
292
287
 
293
- const debouncedEmitChange = debounce((value: string) => {
288
+ const debouncedEmitChange = (value: string) => {
289
+ if (changeTimeout) clearTimeout(changeTimeout)
290
+ const delay = Math.max(0, props.debounceMs ?? 500)
291
+ changeTimeout = setTimeout(() => {
294
292
  const formattedValue = formatPidWithDash(value)
295
293
  emit('change', formattedValue)
296
- }, 500)
294
+ }, delay)
295
+ }
297
296
 
298
- const debouncedEmitInput = debounce((ev: Event) => {
299
- emit('input', ev)
300
- }, 500)
297
+ const debouncedEmitInput = (ev: Event) => {
298
+ if (inputTimeout) clearTimeout(inputTimeout)
299
+ const delay = Math.max(0, props.debounceMs ?? 500)
300
+ inputTimeout = setTimeout(() => {
301
+ emit('input', ev)
302
+ }, delay)
303
+ }
301
304
 
302
305
  watch(
303
306
  () => searchTerm.value,
@@ -335,14 +338,6 @@ const onClickOutside = (e: PointerEvent) => {
335
338
  }
336
339
  }
337
340
 
338
- const totalPages = computed(() => {
339
- // Only return totalPages if both page and totalPages props are provided
340
- if (props.page !== undefined && props.totalPages !== undefined) {
341
- return props.totalPages
342
- }
343
- return null
344
- })
345
-
346
341
  const handleInput = (e: Event) => {
347
342
  const target = e.target as HTMLInputElement
348
343
  const { value } = target
@@ -432,10 +427,6 @@ const handleMatchingString = (item: Record<string, unknown>): string => {
432
427
  return result
433
428
  }
434
429
 
435
- const handlePagination = (_payload: { target: { id: string }; detail: number }) => {
436
- emit('paginate', _payload.detail)
437
- }
438
-
439
430
  const selectItem = (item: Record<string, unknown>) => {
440
431
  selectedItem.value = item
441
432
  setValidSelectedState()
@@ -660,6 +651,8 @@ onMounted(() => {
660
651
 
661
652
  onBeforeUnmount(() => {
662
653
  window.removeEventListener('mouseup', onClickOutside as EventListener)
654
+ if (changeTimeout) clearTimeout(changeTimeout)
655
+ if (inputTimeout) clearTimeout(inputTimeout)
663
656
  const input = getInputElement()
664
657
  if (input) {
665
658
  input.removeEventListener('keydown', handleInputKeyDown)
@@ -849,14 +842,6 @@ defineExpose<{
849
842
  </li>
850
843
  </ul>
851
844
 
852
- <!-- Pagination -->
853
- <FdsPagination
854
- v-if="page !== undefined && totalPages !== null && totalPages > 1"
855
- :current="page"
856
- :max="totalPages"
857
- @paginate="handlePagination"
858
- class="my-4! px-2"
859
- />
860
845
  </template>
861
846
 
862
847
  <!-- No Results -->
@@ -64,6 +64,8 @@ export interface FdsSearchSelectProProps extends Omit</* @vue-ignore */ HTMLAttr
64
64
  searchFields?: string[]
65
65
  /** Preserves and prioritizes `searchFields` order when normalizing item output. */
66
66
  preserveOrder?: boolean
67
+ /** Debounce delay in ms for `change` and `input` emits. */
68
+ debounceMs?: number
67
69
  /** Initial text value for the input field. */
68
70
  initialValue?: string
69
71
  /**
@@ -374,9 +374,14 @@ export const useSearchSelectProItems = ({
374
374
  }
375
375
 
376
376
  const hasInternalMoreItems = computed(() => matchingItems.value.length > displayedItems.value.length)
377
+ const hasMoreServerPages = computed(() => {
378
+ if (props.page === undefined || props.totalPages === undefined) return false
379
+ return props.page < props.totalPages
380
+ })
377
381
  const shouldShowLoadMore = computed(() => {
378
382
  if (isMultiple.value && showSelectedOnly.value) return false
379
383
  if (hasInternalMoreItems.value) return true
384
+ if (hasMoreServerPages.value) return true
380
385
  if (props.maxItems && props.maxItems > 0) return false
381
386
  return props.showLoadMore
382
387
  })
package/src/style.css CHANGED
@@ -49,7 +49,7 @@
49
49
  }
50
50
  .link,
51
51
  .html a {
52
- @apply text-blue-600 hover:text-blue-700 active:text-blue-800 underline focus-visible:outline-dashed focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-blue-500;
52
+ @apply text-blue-600 hover:text-blue-700 active:text-blue-800 underline focus-visible:outline-dashed focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-blue-500 cursor-pointer;
53
53
  }
54
54
 
55
55
  .capitalize-first::first-letter {