fds-vue-core 7.1.6 → 7.1.7

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/components.d.ts CHANGED
@@ -31,7 +31,7 @@ import type { FdsCheckboxProps } from './src/components/Form/FdsCheckbox/types'
31
31
  import type { FdsInputProps } from './src/components/Form/FdsInput/types'
32
32
  import type { FdsRadioProps } from './src/components/Form/FdsRadio/types'
33
33
  import type { FdsSelectProps } from './src/components/Form/FdsSelect/types'
34
- import type { FdsTextareaProps } from './src/components/Form/FdsTextarea/types'
34
+ import type { FdsTextareaEmits, FdsTextareaProps } from './src/components/Form/FdsTextarea/types'
35
35
  import type { FdsTableProps } from './src/components/Table/FdsTable/types'
36
36
  import type { FdsTableHeadProps } from './src/components/Table/FdsTableHead/types'
37
37
  import type { FdsTabsProps } from './src/components/Tabs/FdsTabs/types'
@@ -61,7 +61,7 @@ declare module 'vue' {
61
61
  FdsRadio: DefineComponent<FdsRadioProps>
62
62
  FdsInput: DefineComponent<FdsInputProps>
63
63
  FdsCheckbox: DefineComponent<FdsCheckboxProps>
64
- FdsTextarea: DefineComponent<FdsTextareaProps>
64
+ FdsTextarea: DefineComponent<FdsTextareaProps, {}, {}, {}, {}, {}, {}, FdsTextareaEmits>
65
65
  FdsSelect: DefineComponent<FdsSelectProps>
66
66
  FdsTable: DefineComponent<FdsTableProps>
67
67
  FdsTableHead: DefineComponent<FdsTableHeadProps>
@@ -5,3 +5,4 @@ export default meta;
5
5
  type Story = StoryObj<typeof meta>;
6
6
  export declare const Default: Story;
7
7
  export declare const Disabled: Story;
8
+ export declare const WithMaxLength: Story;
@@ -4,19 +4,21 @@ type __VLS_PublicProps = {
4
4
  modelValue?: string;
5
5
  } & __VLS_Props;
6
6
  declare const _default: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
7
- "update:modelValue": (value: string) => any;
8
- } & {
9
7
  input: (ev: Event) => any;
10
8
  blur: (ev: FocusEvent) => any;
11
9
  change: (ev: Event) => any;
10
+ valid: (value: boolean | null) => any;
12
11
  "update:value": (value: string) => any;
12
+ "update:modelValue": (value: string) => any;
13
13
  }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
14
14
  onInput?: ((ev: Event) => any) | undefined;
15
15
  onBlur?: ((ev: FocusEvent) => any) | undefined;
16
16
  onChange?: ((ev: Event) => any) | undefined;
17
+ onValid?: ((value: boolean | null) => any) | undefined;
17
18
  "onUpdate:value"?: ((value: string) => any) | undefined;
18
19
  "onUpdate:modelValue"?: ((value: string) => any) | undefined;
19
20
  }>, {
21
+ maxlength: number;
20
22
  value: string;
21
23
  dataTestid: string;
22
24
  label: string;
@@ -11,5 +11,15 @@ export interface FdsTextareaProps extends Omit</* @vue-ignore */ TextareaHTMLAtt
11
11
  };
12
12
  dataTestid?: string;
13
13
  inputClass?: string | Record<string, boolean> | Array<string | Record<string, boolean>>;
14
+ /** Character limit for counter/validation. Does not set native maxlength on the textarea. */
15
+ maxlength?: number;
14
16
  value?: string;
15
17
  }
18
+ export interface FdsTextareaEmits {
19
+ input: [ev: Event];
20
+ change: [ev: Event];
21
+ 'update:value': [value: string];
22
+ blur: [ev: FocusEvent];
23
+ /** Emitted when maxlength is set: true/false for within/over limit, null when maxlength is unset. */
24
+ valid: [value: boolean | null];
25
+ }
@@ -83,6 +83,7 @@ const en = {
83
83
  "FdsSearchSelectPro.loadingMore": "Loading more...",
84
84
  "FdsSearchSelectPro.showMore": "Show more",
85
85
  "FdsSearchSelectPro.unspecified": "Unspecified",
86
+ "FdsTextarea.characters": "characters",
86
87
  "FdsTreeView.childrenCountPrefix": "(+",
87
88
  "FdsTreeView.closePopover": "Close",
88
89
  "FdsTreeView.collapseNode": "Collapse {title}",
@@ -144,6 +145,7 @@ const sv = {
144
145
  "FdsSearchSelectPro.loadingMore": "Hämtar fler...",
145
146
  "FdsSearchSelectPro.showMore": "Visa fler",
146
147
  "FdsSearchSelectPro.unspecified": "Ospecificerat",
148
+ "FdsTextarea.characters": "tecken",
147
149
  "FdsTreeView.childrenCountPrefix": "(+",
148
150
  "FdsTreeView.closePopover": "Stäng",
149
151
  "FdsTreeView.collapseNode": "Fäll ihop {title}",
@@ -187,15 +189,26 @@ const useFdsI18n = () => {
187
189
  if (!values) return message2;
188
190
  return Object.entries(values).reduce((acc, [name, value]) => acc.split(`{${name}}`).join(String(value)), message2);
189
191
  };
192
+ const silentTranslateOptions = { missingWarn: false, fallbackWarn: false };
193
+ const translateFromDictionary = (key, values) => {
194
+ const value = dictionary.value[key];
195
+ if (typeof value !== "string") return void 0;
196
+ return interpolate(value, values);
197
+ };
190
198
  const t = (key, values) => {
191
199
  const translateFn = i18n?.global?.t ?? i18n?.t;
192
200
  if (typeof translateFn === "function") {
193
- const translated = translateFn(key, values);
194
- if (typeof translated === "string") return translated;
201
+ let translated;
202
+ if (values != null && Object.keys(values).length > 0) {
203
+ translated = translateFn(key, values, silentTranslateOptions);
204
+ } else {
205
+ translated = translateFn(key, "", silentTranslateOptions);
206
+ }
207
+ if (typeof translated === "string" && translated !== key && translated !== "") {
208
+ return translated;
209
+ }
195
210
  }
196
- const value = dictionary.value[key];
197
- if (typeof value !== "string") return "";
198
- return interpolate(value, values);
211
+ return translateFromDictionary(key, values) ?? key;
199
212
  };
200
213
  return {
201
214
  i18n,
@@ -16211,7 +16224,7 @@ const _hoisted_3$1 = { class: "relative" };
16211
16224
  const _hoisted_4$1 = ["value"];
16212
16225
  const _hoisted_5$1 = {
16213
16226
  key: 2,
16214
- class: "text-red-600 font-bold mt-1"
16227
+ class: "text-red-700 text-right font-bold mt-1"
16215
16228
  };
16216
16229
  const _sfc_main$8 = /* @__PURE__ */ vue.defineComponent({
16217
16230
  ...{
@@ -16228,13 +16241,15 @@ const _sfc_main$8 = /* @__PURE__ */ vue.defineComponent({
16228
16241
  modelModifiers: { default: () => ({}) },
16229
16242
  dataTestid: { default: void 0 },
16230
16243
  inputClass: { default: void 0 },
16244
+ maxlength: { default: void 0 },
16231
16245
  value: { default: void 0 }
16232
16246
  }, {
16233
16247
  "modelValue": { default: void 0, required: false },
16234
16248
  "modelModifiers": {}
16235
16249
  }),
16236
- emits: /* @__PURE__ */ vue.mergeModels(["input", "change", "update:value", "blur"], ["update:modelValue"]),
16250
+ emits: /* @__PURE__ */ vue.mergeModels(["input", "change", "update:value", "blur", "valid"], ["update:modelValue"]),
16237
16251
  setup(__props, { emit: __emit }) {
16252
+ const { t } = useFdsI18n();
16238
16253
  const modelValue = vue.useModel(__props, "modelValue");
16239
16254
  const props = __props;
16240
16255
  const emit = __emit;
@@ -16252,7 +16267,7 @@ const _sfc_main$8 = /* @__PURE__ */ vue.defineComponent({
16252
16267
  return eventHandlers;
16253
16268
  });
16254
16269
  const textareaAttrs = vue.computed(() => {
16255
- const { class: _, id: _id, rows: _rows, ...rest } = attrs;
16270
+ const { class: _, id: _id, rows: _rows, maxlength: _maxlength, ...rest } = attrs;
16256
16271
  const filtered = {};
16257
16272
  for (const key in rest) {
16258
16273
  if (!key.startsWith("on") || typeof rest[key] !== "function") {
@@ -16263,33 +16278,61 @@ const _sfc_main$8 = /* @__PURE__ */ vue.defineComponent({
16263
16278
  ...filtered,
16264
16279
  id: textareaId.value,
16265
16280
  rows: rows.value ?? 4,
16266
- "aria-invalid": props.valid === false ? true : void 0,
16281
+ "aria-invalid": props.valid === false || isOverMaxLength.value ? true : void 0,
16267
16282
  "data-testid": props.dataTestid ?? void 0
16268
16283
  };
16269
16284
  });
16270
16285
  const isLazy = vue.computed(() => props.modelModifiers?.lazy === true);
16286
+ const internalValue = vue.computed({
16287
+ get: () => modelValue.value !== void 0 ? modelValue.value : props.value ?? "",
16288
+ set: (newValue) => {
16289
+ if (modelValue.value !== void 0) {
16290
+ modelValue.value = newValue;
16291
+ }
16292
+ emit("update:value", newValue);
16293
+ }
16294
+ });
16295
+ const liveValue = vue.ref("");
16296
+ vue.watch(
16297
+ () => internalValue.value,
16298
+ (value) => {
16299
+ if (value !== liveValue.value) {
16300
+ liveValue.value = value;
16301
+ }
16302
+ },
16303
+ { immediate: true }
16304
+ );
16271
16305
  const showInvalidMessage = vue.computed(() => props.valid === false && !props.optional && props.invalidMessage);
16272
16306
  const isInvalid = vue.computed(() => props.valid === false && !props.optional && !disabled.value);
16273
16307
  const isValid2 = vue.computed(() => props.valid === true);
16308
+ const characterCount = vue.computed(() => liveValue.value.length);
16309
+ const showCharacterCount = vue.computed(() => props.maxlength != null);
16310
+ const isOverMaxLength = vue.computed(() => props.maxlength != null && characterCount.value > props.maxlength);
16311
+ const maxLengthValid = vue.computed(() => {
16312
+ if (props.maxlength == null) {
16313
+ return null;
16314
+ }
16315
+ return characterCount.value <= props.maxlength;
16316
+ });
16317
+ vue.watch(
16318
+ maxLengthValid,
16319
+ (value) => {
16320
+ emit("valid", value);
16321
+ },
16322
+ { immediate: true }
16323
+ );
16274
16324
  const inputClasses = vue.computed(() => [
16275
16325
  "block w-full rounded-md border border-gray-500 px-3 py-2",
16276
16326
  "focus:outline-2 focus:outline-blue-500 focus:border-transparent",
16277
16327
  disabled.value ? "text-gray-800 outline-dashed outline-2 -outline-offset-2 outline-gray-400 cursor-not-allowed border-transparent bg-gray-50" : "bg-white",
16278
16328
  isInvalid.value && "outline-2 -outline-offset-2 outline-red-600",
16329
+ isOverMaxLength.value && !disabled.value && "outline-2 -outline-offset-2 outline-red-600",
16279
16330
  props.inputClass
16280
16331
  ]);
16281
16332
  const validationIconClasses = vue.computed(() => ["absolute right-3 top-3 flex items-center gap-2 pointer-events-none"]);
16282
- const internalValue = vue.computed({
16283
- get: () => modelValue.value !== void 0 ? modelValue.value : props.value ?? "",
16284
- set: (newValue) => {
16285
- if (modelValue.value !== void 0) {
16286
- modelValue.value = newValue;
16287
- }
16288
- emit("update:value", newValue);
16289
- }
16290
- });
16291
16333
  function handleInputChange(ev) {
16292
16334
  const target = ev.target;
16335
+ liveValue.value = target.value;
16293
16336
  if (isLazy.value && ev.type === "input") {
16294
16337
  return;
16295
16338
  }
@@ -16313,7 +16356,7 @@ const _sfc_main$8 = /* @__PURE__ */ vue.defineComponent({
16313
16356
  __props.meta ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_2$1, vue.toDisplayString(__props.meta), 1)) : vue.createCommentVNode("", true),
16314
16357
  vue.createElementVNode("div", _hoisted_3$1, [
16315
16358
  vue.createElementVNode("textarea", vue.mergeProps({
16316
- value: internalValue.value,
16359
+ value: liveValue.value,
16317
16360
  class: inputClasses.value
16318
16361
  }, textareaAttrs.value, {
16319
16362
  onInput: handleInputChange,
@@ -16334,7 +16377,10 @@ const _sfc_main$8 = /* @__PURE__ */ vue.defineComponent({
16334
16377
  })) : vue.createCommentVNode("", true)
16335
16378
  ], 2)
16336
16379
  ]),
16337
- showInvalidMessage.value ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_5$1, vue.toDisplayString(__props.invalidMessage), 1)) : vue.createCommentVNode("", true)
16380
+ showInvalidMessage.value ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_5$1, vue.toDisplayString(__props.invalidMessage), 1)) : showCharacterCount.value ? (vue.openBlock(), vue.createElementBlock("p", {
16381
+ key: 3,
16382
+ class: vue.normalizeClass(["mt-1 text-right font-bold", isOverMaxLength.value ? "text-red-700" : "text-gray-900"])
16383
+ }, vue.toDisplayString(characterCount.value) + " / " + vue.toDisplayString(__props.maxlength) + " " + vue.toDisplayString(vue.unref(t)("FdsTextarea.characters")), 3)) : vue.createCommentVNode("", true)
16338
16384
  ], 2);
16339
16385
  };
16340
16386
  }