fds-vue-core 7.2.6 → 8.0.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": "7.2.6",
3
+ "version": "8.0.0",
4
4
  "description": "FDS Vue Core Component Library",
5
5
  "type": "module",
6
6
  "main": "./dist/fds-vue-core.cjs.js",
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "axios": "1.16.1",
52
- "date-fns": "4.2.1",
52
+ "date-fns": "4.4.0",
53
53
  "imask": "7.6.1",
54
54
  "libphonenumber-js": "1.12.36",
55
55
  "tailwindcss": "4.3.0",
@@ -58,23 +58,23 @@
58
58
  "devDependencies": {
59
59
  "@chromatic-com/storybook": "5.2.1",
60
60
  "@intlify/unplugin-vue-i18n": "11.2.3",
61
- "@storybook/addon-a11y": "10.4.0",
62
- "@storybook/addon-docs": "10.4.0",
63
- "@storybook/addon-vitest": "10.4.0",
64
- "@storybook/vue3": "10.4.0",
65
- "@storybook/vue3-vite": "10.4.0",
61
+ "@storybook/addon-a11y": "10.4.1",
62
+ "@storybook/addon-docs": "10.4.1",
63
+ "@storybook/addon-vitest": "10.4.1",
64
+ "@storybook/vue3": "10.4.1",
65
+ "@storybook/vue3-vite": "10.4.1",
66
66
  "@tailwindcss/vite": "4.3.0",
67
67
  "@types/node": "22.16.5",
68
68
  "@vitejs/plugin-vue": "6.0.7",
69
69
  "@vitest/browser": "3.2.4",
70
- "fg-devkit": "1.8.5",
71
- "storybook": "10.4.0",
72
- "tsx": "4.22.3",
70
+ "fg-devkit": "1.8.6",
71
+ "storybook": "10.4.1",
72
+ "tsx": "4.22.4",
73
73
  "vite": "7.3.2",
74
74
  "vite-plugin-dts": "4.5.4",
75
75
  "vite-plugin-vue-devtools": "8.1.2",
76
- "vitest": "3.2.4",
77
- "vue": "3.5.34"
76
+ "vitest": "4.1.8",
77
+ "vue": "3.5.35"
78
78
  },
79
79
  "knip": {
80
80
  "ignoreBinaries": [
@@ -32,6 +32,7 @@ const autoId = `fds-block-link-${Math.random().toString(36).slice(2, 9)}`
32
32
  const blockLinkId = computed(() => id.value ?? autoId)
33
33
 
34
34
  const isInteractive = computed(() => !props.disabled && props.interactive)
35
+ const keyboardTabIndex = computed(() => (isInteractive.value ? 0 : -1))
35
36
 
36
37
  const innerClasses = computed(() => [
37
38
  props.disabled ? 'cursor-not-allowed shadow-none hover:border-transparent active:bg-transparent' : 'cursor-pointer',
@@ -65,7 +66,21 @@ function handleClick(event: Event) {
65
66
  }
66
67
 
67
68
  function handleKeydown(event: KeyboardEvent) {
68
- if (event.key === 'Enter' || event.key === ' ') {
69
+ if (!isInteractive.value) return
70
+
71
+ const isEnter = event.key === 'Enter' || event.code === 'Enter' || event.code === 'NumpadEnter'
72
+ const isSpace = event.key === ' ' || event.key === 'Spacebar'
73
+
74
+ // Links (incl. router-link): Enter activates natively; only Space needs a synthetic click.
75
+ if (componentType.value === 'a' || componentType.value === 'router-link') {
76
+ if (isSpace) {
77
+ event.preventDefault()
78
+ handleClick(event)
79
+ }
80
+ return
81
+ }
82
+
83
+ if (isEnter || isSpace) {
69
84
  event.preventDefault()
70
85
  handleClick(event)
71
86
  }
@@ -117,6 +132,7 @@ defineSlots<{
117
132
  :download="componentType === 'a' ? download : undefined"
118
133
  :disabled="componentType === 'button' ? props.disabled : undefined"
119
134
  :aria-disabled="props.disabled"
135
+ :tabindex="keyboardTabIndex"
120
136
  :data-testid="dataTestid"
121
137
  @click="handleClick"
122
138
  @keydown="handleKeydown"
@@ -15,7 +15,6 @@ const meta: Meta<typeof FdsPhonenumber> = {
15
15
  invalidMessage: { control: 'text' },
16
16
  defaultCountry: { control: 'text' },
17
17
  locale: { control: 'text' },
18
- numberType: { control: 'select', options: ['mobile', 'any'] },
19
18
  },
20
19
  args: {
21
20
  label: 'Telefonnummer',
@@ -80,37 +79,6 @@ export const InvalidNumber: Story = {
80
79
  }),
81
80
  }
82
81
 
83
- export const AllowAnyNumberType: Story = {
84
- args: {
85
- numberType: 'any',
86
- meta: 'Mobil och fast telefon accepteras (t.ex. Stockholm 08 + 6–7 siffror)',
87
- },
88
- render: (args) => ({
89
- components: { FdsPhonenumber },
90
- setup() {
91
- // Stockholm: 08 + 7 siffror
92
- const number = ref('081234567')
93
- const country = ref('SE')
94
- const phoneValid = ref<boolean | null>(null)
95
- const e164 = ref('')
96
- return { args, number, country, phoneValid, e164 }
97
- },
98
- template: `
99
- <div>
100
- <FdsPhonenumber
101
- v-bind="args"
102
- v-model="number"
103
- v-model:country="country"
104
- @valid="phoneValid = $event"
105
- @update:e164="e164 = $event"
106
- />
107
- <p class="mt-4 text-sm">valid: {{ phoneValid }}</p>
108
- <p class="text-sm">E.164: {{ e164 || '—' }}</p>
109
- </div>
110
- `,
111
- }),
112
- }
113
-
114
82
  export const EnglishLocale: Story = {
115
83
  args: {
116
84
  locale: 'en',
@@ -34,7 +34,6 @@ const props = withDefaults(defineProps<FdsPhonenumberProps>(), {
34
34
  defaultCountry: 'SE',
35
35
  countries: undefined,
36
36
  locale: undefined,
37
- numberType: 'mobile',
38
37
  disabled: false,
39
38
  dataTestid: undefined,
40
39
  selectClass: undefined,
@@ -78,8 +77,6 @@ const countryItems = computed(() => {
78
77
  )
79
78
  })
80
79
 
81
- const phoneValidationOptions = computed(() => ({ numberType: props.numberType }))
82
-
83
80
  /** Set after the phone number field has blurred; validation does not run on input. */
84
81
  const committedValid = ref<boolean | null>(null)
85
82
 
@@ -104,19 +101,15 @@ const showInvalidMessage = computed(
104
101
  const noCountryResults = ref(false)
105
102
 
106
103
  function runValidation() {
107
- const validationState = getPhoneValidationState(
108
- nationalNumber.value ?? '',
109
- country.value ?? 'SE',
110
- phoneValidationOptions.value,
111
- )
104
+ const validationState = getPhoneValidationState(nationalNumber.value ?? '', country.value ?? 'SE')
112
105
  committedValid.value = validationState
113
106
  emit('valid', validationState)
114
107
 
115
- const result = validatePhoneNumber(nationalNumber.value ?? '', country.value ?? 'SE', phoneValidationOptions.value)
108
+ const result = validatePhoneNumber(nationalNumber.value ?? '', country.value ?? 'SE')
116
109
  emit('update:e164', result.isValid && result.phoneNumber ? result.phoneNumber : '')
117
110
  }
118
111
 
119
- watch([country, () => props.numberType], () => {
112
+ watch(country, () => {
120
113
  if (committedValid.value === null) {
121
114
  return
122
115
  }
@@ -1,4 +1,4 @@
1
- import { getCountries, getCountryCallingCode } from 'libphonenumber-js'
1
+ import { getCountries, getCountryCallingCode } from 'libphonenumber-js/mobile'
2
2
 
3
3
  export interface CountryPhoneOption {
4
4
  value: string
@@ -1,12 +1,6 @@
1
1
  import type { FdsInputProps } from '../FdsInput/types'
2
2
  import type { CountryPhoneOption } from './countries'
3
3
 
4
- /**
5
- * `mobile` – mobile numbers only (Google libphonenumber metadata).
6
- * `any` – mobile and fixed-line numbers.
7
- */
8
- export type FdsPhonenumberNumberType = 'mobile' | 'any'
9
-
10
4
  type FdsPhonenumberInputPassthroughProps = Pick<
11
5
  FdsInputProps,
12
6
  | 'id'
@@ -37,8 +31,6 @@ export interface FdsPhonenumberProps extends FdsPhonenumberInputPassthroughProps
37
31
  countries?: CountryPhoneOption[]
38
32
  /** BCP 47 locale for country names in the list, e.g. `sv-SE` or `en`. Falls back to FDS i18n locale. */
39
33
  locale?: string
40
- /** Which number types are accepted when validating with libphonenumber-js. */
41
- numberType?: FdsPhonenumberNumberType
42
34
  disabled?: boolean
43
35
  dataTestid?: string
44
36
  selectClass?: string
@@ -1,10 +1,4 @@
1
- import type { CountryCode, NumberType } from 'libphonenumber-js'
2
- import parsePhoneNumberFromString from 'libphonenumber-js/max'
3
- import type { FdsPhonenumberNumberType } from './types'
4
-
5
- interface ValidatePhoneOptions {
6
- numberType?: FdsPhonenumberNumberType
7
- }
1
+ import parsePhoneNumberFromString, { type CountryCode, type NumberType } from 'libphonenumber-js/mobile'
8
2
 
9
3
  /** Result shape kept stable for consumers (same fields as previous `phone` package integration). */
10
4
  export interface PhoneValidationResult {
@@ -24,16 +18,9 @@ const INVALID_RESULT: PhoneValidationResult = {
24
18
  }
25
19
 
26
20
  const MOBILE_NUMBER_TYPES = new Set<NumberType>(['MOBILE', 'FIXED_LINE_OR_MOBILE'])
27
- const ANY_NUMBER_TYPES = new Set<NumberType>(['MOBILE', 'FIXED_LINE', 'FIXED_LINE_OR_MOBILE'])
28
21
 
29
- function matchesNumberType(type: NumberType | undefined, numberType: FdsPhonenumberNumberType): boolean {
30
- if (!type) {
31
- return numberType === 'any'
32
- }
33
- if (numberType === 'mobile') {
34
- return MOBILE_NUMBER_TYPES.has(type)
35
- }
36
- return ANY_NUMBER_TYPES.has(type)
22
+ function isMobileNumberType(type: NumberType | undefined): boolean {
23
+ return type !== undefined && MOBILE_NUMBER_TYPES.has(type)
37
24
  }
38
25
 
39
26
  function buildCandidates(value: string): string[] {
@@ -61,11 +48,7 @@ function toValidationResult(parsed: NonNullable<ReturnType<typeof parsePhoneNumb
61
48
  }
62
49
  }
63
50
 
64
- function runPhoneValidation(
65
- value: string,
66
- countryIso2: string,
67
- numberType: FdsPhonenumberNumberType,
68
- ): PhoneValidationResult {
51
+ function runPhoneValidation(value: string, countryIso2: string): PhoneValidationResult {
69
52
  const country = countryIso2 as CountryCode
70
53
 
71
54
  for (const candidate of buildCandidates(value)) {
@@ -73,7 +56,7 @@ function runPhoneValidation(
73
56
  if (!parsed?.isValid()) {
74
57
  continue
75
58
  }
76
- if (matchesNumberType(parsed.getType(), numberType)) {
59
+ if (isMobileNumberType(parsed.getType())) {
77
60
  return toValidationResult(parsed)
78
61
  }
79
62
  }
@@ -81,27 +64,19 @@ function runPhoneValidation(
81
64
  return INVALID_RESULT
82
65
  }
83
66
 
84
- export function validatePhoneNumber(
85
- nationalNumber: string,
86
- countryIso2: string,
87
- options: ValidatePhoneOptions = {},
88
- ): PhoneValidationResult {
67
+ export function validatePhoneNumber(nationalNumber: string, countryIso2: string): PhoneValidationResult {
89
68
  const trimmed = nationalNumber.trim()
90
69
  if (!trimmed) {
91
70
  return INVALID_RESULT
92
71
  }
93
72
 
94
- return runPhoneValidation(trimmed, countryIso2, options.numberType ?? 'mobile')
73
+ return runPhoneValidation(trimmed, countryIso2)
95
74
  }
96
75
 
97
76
  /** `null` when empty, otherwise whether the number is valid for the selected country. */
98
- export function getPhoneValidationState(
99
- nationalNumber: string,
100
- countryIso2: string,
101
- options: ValidatePhoneOptions = {},
102
- ): boolean | null {
77
+ export function getPhoneValidationState(nationalNumber: string, countryIso2: string): boolean | null {
103
78
  if (!nationalNumber.trim()) {
104
79
  return null
105
80
  }
106
- return validatePhoneNumber(nationalNumber, countryIso2, options).isValid
81
+ return validatePhoneNumber(nationalNumber, countryIso2).isValid
107
82
  }
package/src/index.ts CHANGED
@@ -263,11 +263,7 @@ export {
263
263
  type PhoneValidationResult,
264
264
  } from './components/Form/FdsPhonenumber/validatePhone'
265
265
 
266
- export type {
267
- FdsPhonenumberEmits,
268
- FdsPhonenumberNumberType,
269
- FdsPhonenumberProps,
270
- } from './components/Form/FdsPhonenumber/types'
266
+ export type { FdsPhonenumberEmits, FdsPhonenumberProps } from './components/Form/FdsPhonenumber/types'
271
267
 
272
268
  // Table component types
273
269
  export type { FdsTableProps } from './components/Table/FdsTable/types'
package/src/lang/en.json CHANGED
@@ -29,7 +29,7 @@
29
29
  "FdsDevModeStorage.tabs.localStorage": "localStorage",
30
30
  "FdsDevModeStorage.tabs.sessionStorage": "sessionStorage",
31
31
  "FdsDevModeStorage.valueLabel": "Value",
32
- "FdsInput.clearInput": "Clear input",
32
+ "FdsInput.clearInput": "Delete text",
33
33
  "FdsInput.hidePassword": "Hide",
34
34
  "FdsInput.openDatePicker": "Open date picker",
35
35
  "FdsInput.showPassword": "Show",
package/src/lang/sv.json CHANGED
@@ -29,7 +29,7 @@
29
29
  "FdsDevModeStorage.tabs.localStorage": "localStorage",
30
30
  "FdsDevModeStorage.tabs.sessionStorage": "sessionStorage",
31
31
  "FdsDevModeStorage.valueLabel": "Värde",
32
- "FdsInput.clearInput": "Rensa input",
32
+ "FdsInput.clearInput": "Rensa text",
33
33
  "FdsInput.hidePassword": "Dölj",
34
34
  "FdsInput.openDatePicker": "Öppna datumväljare",
35
35
  "FdsInput.showPassword": "Visa",