fds-vue-core 8.1.0 → 8.2.0

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,6 +1,6 @@
1
1
  {
2
2
  "name": "fds-vue-core",
3
- "version": "8.1.0",
3
+ "version": "8.2.0",
4
4
  "description": "FDS Vue Core Component Library",
5
5
  "type": "module",
6
6
  "main": "./dist/fds-vue-core.cjs.js",
@@ -57,19 +57,19 @@
57
57
  "devDependencies": {
58
58
  "@chromatic-com/storybook": "5.2.1",
59
59
  "@intlify/unplugin-vue-i18n": "11.2.3",
60
- "@storybook/addon-a11y": "10.4.5",
61
- "@storybook/addon-docs": "10.4.5",
62
- "@storybook/addon-vitest": "10.4.5",
63
- "@storybook/vue3": "10.4.5",
64
- "@storybook/vue3-vite": "10.4.5",
60
+ "@storybook/addon-a11y": "10.4.6",
61
+ "@storybook/addon-docs": "10.4.6",
62
+ "@storybook/addon-vitest": "10.4.6",
63
+ "@storybook/vue3": "10.4.6",
64
+ "@storybook/vue3-vite": "10.4.6",
65
65
  "@tailwindcss/vite": "4.3.1",
66
66
  "@types/node": "22.16.5",
67
67
  "@vitejs/plugin-vue": "6.0.7",
68
- "@vitest/browser": "3.2.4",
69
- "fg-devkit": "1.9.0",
70
- "storybook": "10.4.5",
68
+ "@vitest/browser": "4.1.9",
69
+ "fg-devkit": "1.9.1",
70
+ "storybook": "10.4.6",
71
71
  "tsx": "4.22.4",
72
- "vite": "7.3.2",
72
+ "vite": "7.3.5",
73
73
  "vite-plugin-dts": "4.5.4",
74
74
  "vite-plugin-vue-devtools": "8.1.3",
75
75
  "vitest": "4.1.9",
@@ -29,12 +29,15 @@ const emit = defineEmits<{
29
29
 
30
30
  const { disabled, href, target, rel, attrs } = useAttrsWithDefaults(props)
31
31
 
32
- const rootClasses = computed(() => ['flex transition-opacity duration-200', props.block && 'block w-full'])
32
+ const rootClasses = computed(() => [
33
+ props.block ? 'flex w-full' : 'inline-flex shrink-0 self-center',
34
+ 'transition-opacity duration-200',
35
+ ])
33
36
 
34
37
  const externalClass = computed(() => attrs.class)
35
38
 
36
39
  const elBase =
37
- 'box-border appearance-none inline-flex items-start justify-center w-fit shadow-none p-0.5 text-base select-none m-0 rounded-md text-left align-start no-underline transition-[box-shadow,border-color,background-color] duration-200 font-main font-bold text-base leading-5 tracking-normal focus-visible:outline-dashed focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-blue-500'
40
+ 'box-border appearance-none inline-flex items-center justify-center w-fit min-h-0 shadow-none p-0.5 text-base select-none m-0 rounded-md text-left align-middle no-underline transition-[box-shadow,border-color,background-color] duration-200 font-main font-bold text-base leading-5 tracking-normal focus-visible:outline-dashed focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-blue-500'
38
41
 
39
42
  const variantClasses = computed(() =>
40
43
  props.invert
@@ -105,7 +108,7 @@ const componentAttrs = computed(() => {
105
108
  <FdsIcon :class="iconFillClass" :name="icon" :size="iconSize" />
106
109
  </span>
107
110
  </template>
108
- <span class="pt-0.5">
111
+ <span>
109
112
  {{ text }}
110
113
  </span>
111
114
  </component>
@@ -102,7 +102,7 @@ const footerClasses = computed(() => [
102
102
  {
103
103
  'sm:flex-row sm:justify-end': !props.alignLeftFooter,
104
104
  'sm:flex-row sm:justify-start': props.alignLeftFooter,
105
- 'sticky bg-white bottom-0 shadow-[0px_-8px_16px_0px_rgba(255,255,255,0.59)] z-[2] -mx-6 px-6 pb-6':
105
+ 'sticky bg-white bottom-0 shadow-[0px_-8px_16px_0px_rgba(255,255,255,0.59)] z-[2] -mx-6 p-6 mt-0!':
106
106
  props.stickyFooter,
107
107
  },
108
108
  ])
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, ref, watch } from 'vue'
3
3
  import { useAttrsWithDefaults } from '../../composables/useAttrsWithDefaults'
4
+ import { debounce } from '../../helpers/debounce'
4
5
  import { useFdsI18n } from '../../plugin/useFdsI18n'
5
6
  import FdsButtonIcon from '../Buttons/FdsButtonIcon/FdsButtonIcon.vue'
6
7
  import FdsSpinner from '../FdsSpinner/FdsSpinner.vue'
@@ -49,16 +50,6 @@ watch(
49
50
  },
50
51
  )
51
52
 
52
- const debounce = <T extends (...args: any[]) => void>(fn: T, delay = 300) => {
53
- let timer: ReturnType<typeof setTimeout>
54
- return (...args: Parameters<T>) => {
55
- clearTimeout(timer)
56
- timer = setTimeout(() => {
57
- fn(...args)
58
- }, delay)
59
- }
60
- }
61
-
62
53
  const handlePageChange = debounce((event: Event) => {
63
54
  const target = event.target as HTMLInputElement
64
55
  const incomingPage = Number.parseInt(target.value)
@@ -81,7 +72,7 @@ const handlePageChange = debounce((event: Event) => {
81
72
 
82
73
  internalValue.value = page
83
74
  emit('paginate', { target: { id: inputId.value }, detail: page })
84
- }, 500)
75
+ }, 300)
85
76
 
86
77
  const handlePagination = (action: 'start' | 'prev' | 'next' | 'end') => {
87
78
  let page = 0
@@ -8,6 +8,7 @@ import {
8
8
  PID_MASK_OPTIONS,
9
9
  useIsPid,
10
10
  } from '../../composables/useIsPid'
11
+ import { debounce } from '../../helpers/debounce'
11
12
  import FdsPagination from '../FdsPagination/FdsPagination.vue'
12
13
  import FdsSpinner from '../FdsSpinner/FdsSpinner.vue'
13
14
  import FdsInput from '../Form/FdsInput/FdsInput.vue'
@@ -258,14 +259,6 @@ watch(
258
259
  { immediate: true },
259
260
  )
260
261
 
261
- const debounce = <T extends (...args: any[]) => void>(fn: T, delay: number) => {
262
- let timeout: ReturnType<typeof setTimeout>
263
- return (...args: Parameters<T>) => {
264
- clearTimeout(timeout)
265
- timeout = setTimeout(() => fn(...args), delay)
266
- }
267
- }
268
-
269
262
  const debouncedEmitChange = debounce((value: string) => {
270
263
  const formattedValue = formatPidWithDash(value)
271
264
  emit('change', formattedValue)
@@ -9,6 +9,7 @@ import {
9
9
  PID_MASK_OPTIONS,
10
10
  useIsPid,
11
11
  } from '../../composables/useIsPid'
12
+ import { debounce } from '../../helpers/debounce'
12
13
  import { useFdsI18n } from '../../plugin/useFdsI18n'
13
14
  import FdsButtonIcon from '../Buttons/FdsButtonIcon/FdsButtonIcon.vue'
14
15
  import FdsSpinner from '../FdsSpinner/FdsSpinner.vue'
@@ -286,28 +287,27 @@ watch(
286
287
  { immediate: true },
287
288
  )
288
289
 
289
- let changeTimeout: ReturnType<typeof setTimeout> | null = null
290
- let inputTimeout: ReturnType<typeof setTimeout> | null = null
291
290
  const isDebouncingChange = ref(false)
292
291
  const isSearching = computed(() => isDebouncingChange.value || props.loading)
293
292
 
294
- const debouncedEmitChange = (value: string) => {
295
- if (changeTimeout) clearTimeout(changeTimeout)
296
- const delay = Math.max(0, props.debounceMs ?? 500)
297
- isDebouncingChange.value = true
298
- changeTimeout = setTimeout(() => {
299
- const formattedValue = formatPidWithDash(value)
300
- emit('change', formattedValue)
293
+ const debouncedEmitChange = debounce(
294
+ (value: string) => {
295
+ emit('change', formatPidWithDash(value))
301
296
  isDebouncingChange.value = false
302
- }, delay)
303
- }
297
+ },
298
+ () => Math.max(0, props.debounceMs ?? 500),
299
+ )
304
300
 
305
- const debouncedEmitInput = (ev: Event) => {
306
- if (inputTimeout) clearTimeout(inputTimeout)
307
- const delay = Math.max(0, props.debounceMs ?? 500)
308
- inputTimeout = setTimeout(() => {
301
+ const debouncedEmitInput = debounce(
302
+ (ev: Event) => {
309
303
  emit('input', ev)
310
- }, delay)
304
+ },
305
+ () => Math.max(0, props.debounceMs ?? 500),
306
+ )
307
+
308
+ const scheduleDebouncedEmitChange = (value: string) => {
309
+ isDebouncingChange.value = true
310
+ debouncedEmitChange(value)
311
311
  }
312
312
 
313
313
  watch(
@@ -318,7 +318,7 @@ watch(
318
318
  }
319
319
  filterAndPaginate()
320
320
  if (isMultiple.value || selectedItem.value === null) {
321
- debouncedEmitChange(newValue)
321
+ scheduleDebouncedEmitChange(newValue)
322
322
  }
323
323
  },
324
324
  )
@@ -443,9 +443,7 @@ function getCandidateMatchValues(item: Record<string, unknown>): string[] {
443
443
  if (isDividerItem(item) && !props.dividerSelectable) return []
444
444
 
445
445
  if (searchFields.value.length > 0) {
446
- return searchFields.value
447
- .map((key) => String(item[key] ?? '').trim())
448
- .filter((value) => value.length > 0)
446
+ return searchFields.value.map((key) => String(item[key] ?? '').trim()).filter((value) => value.length > 0)
449
447
  }
450
448
 
451
449
  return [String(item.label ?? item.name ?? '').trim()].filter((value) => value.length > 0)
@@ -757,8 +755,8 @@ onMounted(() => {
757
755
 
758
756
  onBeforeUnmount(() => {
759
757
  window.removeEventListener('mouseup', onClickOutside as EventListener)
760
- if (changeTimeout) clearTimeout(changeTimeout)
761
- if (inputTimeout) clearTimeout(inputTimeout)
758
+ debouncedEmitChange.cancel()
759
+ debouncedEmitInput.cancel()
762
760
  isDebouncingChange.value = false
763
761
  const input = getInputElement()
764
762
  if (input) {
@@ -0,0 +1,35 @@
1
+ export type DebouncedFunction<T extends (...args: never[]) => void> = ((...args: Parameters<T>) => void) & {
2
+ cancel: () => void
3
+ }
4
+
5
+ /**
6
+ * Returns a debounced function that delays invoking `fn` until after `delayMs` has elapsed
7
+ * since the last call. The returned function has a `cancel()` method to clear pending invocations.
8
+ */
9
+ export function debounce<T extends (...args: never[]) => void>(
10
+ fn: T,
11
+ delayMs: number | (() => number),
12
+ ): DebouncedFunction<T> {
13
+ let timeoutId: ReturnType<typeof setTimeout> | undefined
14
+
15
+ const debounced = (...args: Parameters<T>) => {
16
+ if (timeoutId !== undefined) {
17
+ clearTimeout(timeoutId)
18
+ }
19
+
20
+ const delay = typeof delayMs === 'function' ? delayMs() : delayMs
21
+ timeoutId = setTimeout(() => {
22
+ timeoutId = undefined
23
+ fn(...args)
24
+ }, delay)
25
+ }
26
+
27
+ debounced.cancel = () => {
28
+ if (timeoutId !== undefined) {
29
+ clearTimeout(timeoutId)
30
+ timeoutId = undefined
31
+ }
32
+ }
33
+
34
+ return debounced
35
+ }
package/src/index.ts CHANGED
@@ -74,6 +74,7 @@ import {
74
74
  import { useRouteScrollPositions } from './composables/useRouteScrollPositions'
75
75
  import { useViewportBreakpoint } from './composables/useViewportBreakpoint'
76
76
  import { capitalizeFirstLetter } from './helpers/capitalizeFirstLetter'
77
+ import { debounce } from './helpers/debounce'
77
78
  import { useFileSizeValidation, useFileValidation } from './helpers/fileValidation'
78
79
  import { FDS_VUE_CORE_I18N_KEY, type FdsI18nLike } from './plugin/i18n'
79
80
 
@@ -128,6 +129,7 @@ export {
128
129
  export {
129
130
  capitalizeFirstLetter,
130
131
  clearUnsavedChanges,
132
+ debounce,
131
133
  formatPidWithDash,
132
134
  getExitGuardWizardId,
133
135
  isPidString,
@@ -222,6 +224,8 @@ export default FdsVueCorePlugin
222
224
  export type { FdsNodeShape, FdsTreeNode, FdsTreeViewProps, FdsTreeViewStyles } from './components/FdsTreeView/types'
223
225
 
224
226
  // Export helper types
227
+ export type { DebouncedFunction } from './helpers/debounce'
228
+
225
229
  export type { FileSizeValidationResult, FileValidationOptions, FileValidationResult } from './helpers/fileValidation'
226
230
 
227
231
  // Button component types