asma-ui-core 3.90.0 → 3.91.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.
@@ -85,7 +85,7 @@ var ce = (V) => {
85
85
  }
86
86
  const p = ee(a, "dd/MM/yyyy", /* @__PURE__ */ new Date());
87
87
  h?.($(p) ? p : void 0);
88
- }, M = t ? 120 : 160;
88
+ }, M = t ? 120 : 156;
89
89
  return /* @__PURE__ */ N("div", {
90
90
  className: "cursor-default",
91
91
  children: [v && /* @__PURE__ */ m("div", {
@@ -3,20 +3,20 @@ import { StyledButton as i } from "../../../shared-components/button/StyledButto
3
3
  import { OutlineCalendarMonth as l } from "../../../shared-components/OutlineCalendarMonth.js";
4
4
  import "react";
5
5
  import { jsx as r } from "react/jsx-runtime";
6
- var h = ({ onClick: e, disabled: t, localeCode: o }) => /* @__PURE__ */ r(i, {
6
+ var f = ({ onClick: o, disabled: t, localeCode: e }) => /* @__PURE__ */ r(i, {
7
7
  type: "button",
8
8
  size: "large",
9
9
  dataTest: "DatePickerButton",
10
- "aria-label": o?.startsWith("nb") ? "Åpne kalender" : "Open calendar",
10
+ "aria-label": e?.startsWith("nb") ? "Åpne kalender" : "Open calendar",
11
11
  startIcon: /* @__PURE__ */ r(l, {
12
12
  width: 24,
13
13
  height: 24
14
14
  }),
15
15
  variant: "outlined",
16
16
  disabled: t,
17
- onClick: (a) => !t && e(a),
18
- className: n(t && "cursor-not-allowed")
17
+ onClick: (a) => !t && o(a),
18
+ className: n("box-border w-[40px] px-0", t && "cursor-not-allowed")
19
19
  });
20
20
  export {
21
- h as DatePickerButton
21
+ f as DatePickerButton
22
22
  };
@@ -0,0 +1,70 @@
1
+ import { useCallback as y, useEffect as u, useRef as F } from "react";
2
+ import { useController as S, useFormContext as x } from "react-hook-form";
3
+ var B = 150, P = 20, U = "onAutoFillStart";
4
+ function h(r) {
5
+ return r == null ? !0 : typeof r == "string" ? r.trim().length === 0 : typeof r == "number" ? Number.isNaN(r) : !1;
6
+ }
7
+ function V(r) {
8
+ return !(!r || r.required === !1 || r.required == null);
9
+ }
10
+ function X({ getLanguageCode: r, browserAutofill: l = !1 }) {
11
+ return function({ name: n, control: p, rules: f, helperText: I, requestFocus: s, render: L }) {
12
+ const d = x(), { field: t, fieldState: i } = S({
13
+ name: n,
14
+ control: p ?? d.control,
15
+ rules: f
16
+ }), { trigger: a, setValue: A } = d, { ref: o } = t, C = V(f), N = r(), v = F(null), g = (e) => {
17
+ e != null && e !== t.value && t.onChange(e), a(n);
18
+ };
19
+ u(() => {
20
+ s && o({ focus: s });
21
+ }, [o, s]), u(() => {
22
+ i.invalid && a(n);
23
+ }, [N]), u(() => {
24
+ l && (h(t.value) || a(n));
25
+ }, []), u(() => {
26
+ if (!l) return;
27
+ let e = 0;
28
+ const c = setInterval(() => {
29
+ e += 1;
30
+ const T = v.current;
31
+ T?.matches(":-webkit-autofill") ? (g(T.value), clearInterval(c)) : e >= P && clearInterval(c);
32
+ }, B);
33
+ return () => clearInterval(c);
34
+ }, []);
35
+ const _ = () => {
36
+ if (!C && h(t.value)) {
37
+ A(n, t.value, {
38
+ shouldTouch: !0,
39
+ shouldValidate: !1
40
+ });
41
+ return;
42
+ }
43
+ t.onBlur(), a(n);
44
+ };
45
+ u(() => {
46
+ i.error && a(n);
47
+ }, [t.value]);
48
+ const R = (...e) => {
49
+ t.onChange(...e);
50
+ }, E = (e) => {
51
+ l && e.animationName === U && g(e.target?.value);
52
+ }, M = y((e) => {
53
+ v.current = e, typeof o == "function" && o(e);
54
+ }, [o]), O = i.error?.message, m = !!i.error && (!l || i.isTouched);
55
+ return L({
56
+ value: t.value,
57
+ onChange: R,
58
+ onBlur: _,
59
+ onAnimationStart: E,
60
+ name: t.name,
61
+ ref: l ? M : o,
62
+ error: m,
63
+ helperText: m ? O : I,
64
+ reserveHelperText: f != null
65
+ });
66
+ };
67
+ }
68
+ export {
69
+ X as createAsmaFieldController
70
+ };
@@ -0,0 +1,12 @@
1
+ import { createAsmaFieldController as m } from "./createAsmaFieldController.js";
2
+ import { submitChanged as n } from "./submitChanged.js";
3
+ function u(t) {
4
+ const e = t.browserAutofill === !0, r = (o, i, l) => n(o, i, l, { touchInvalidFields: e });
5
+ return {
6
+ AsmaFieldController: m(t),
7
+ submitChanged: r
8
+ };
9
+ }
10
+ export {
11
+ u as createAsmaForms
12
+ };
@@ -0,0 +1,14 @@
1
+ var n = 'input, textarea, select, button, [contenteditable="true"], [contenteditable=""], [tabindex]';
2
+ function o(t) {
3
+ return t.matches(n);
4
+ }
5
+ function c(t) {
6
+ const e = document.getElementById(t) ?? Array.from(document.querySelectorAll("[data-testid]")).find((r) => r.dataset.testid === t);
7
+ return e ? ((o(e) ? e : e.querySelector(n) ?? e).focus({ preventScroll: !0 }), e.scrollIntoView({
8
+ behavior: "instant",
9
+ block: "center"
10
+ }), !0) : !1;
11
+ }
12
+ export {
13
+ c as focusFormField
14
+ };
@@ -0,0 +1,24 @@
1
+ function u(e, f) {
2
+ if (e === !0) return { ...f };
3
+ if (!e || typeof e != "object") return {};
4
+ const c = {};
5
+ for (const n of Object.keys(e)) {
6
+ const o = e[n], t = f[n];
7
+ if (o === !0) {
8
+ c[n] = t;
9
+ continue;
10
+ }
11
+ if (Array.isArray(t)) {
12
+ o && (c[n] = t);
13
+ continue;
14
+ }
15
+ if (o && typeof o == "object" && t != null && typeof t == "object") {
16
+ const r = u(o, t);
17
+ Object.keys(r).length > 0 && (c[n] = r);
18
+ }
19
+ }
20
+ return c;
21
+ }
22
+ export {
23
+ u as getDirtyValues
24
+ };
@@ -0,0 +1,2 @@
1
+ export * from '../src/forms/index'
2
+ export {}
@@ -0,0 +1,12 @@
1
+ import { getDirtyValues as r } from "./getDirtyValues.js";
2
+ import { touchInvalidFields as t } from "./touchInvalidFields.js";
3
+ import { createAsmaForms as e } from "./createAsmaForms.js";
4
+ import { focusFormField as f } from "./focusFormField.js";
5
+ import { useAsmaForm as a } from "./useAsmaForm.js";
6
+ export {
7
+ e as createAsmaForms,
8
+ f as focusFormField,
9
+ r as getDirtyValues,
10
+ t as touchInvalidFields,
11
+ a as useAsmaForm
12
+ };
@@ -0,0 +1,11 @@
1
+ import { getDirtyValues as o } from "./getDirtyValues.js";
2
+ import { touchInvalidFields as n } from "./touchInvalidFields.js";
3
+ async function f(t, a, i, e = {}) {
4
+ if (!(i ? await t.trigger() : await t.trigger(void 0, { shouldFocus: !0 })))
5
+ return e.touchInvalidFields && n(t), i?.(), !1;
6
+ const r = t.getValues(), { dirtyFields: u } = t.control._formState, l = await a(o(u, r), r);
7
+ return t.reset(l ?? r), !0;
8
+ }
9
+ export {
10
+ f as submitChanged
11
+ };
@@ -0,0 +1,12 @@
1
+ function l(e) {
2
+ for (const t of Object.keys(e.control._formState.errors)) {
3
+ const o = t;
4
+ e.setValue(o, e.getValues(o), {
5
+ shouldTouch: !0,
6
+ shouldValidate: !1
7
+ });
8
+ }
9
+ }
10
+ export {
11
+ l as touchInvalidFields
12
+ };
@@ -0,0 +1,16 @@
1
+ import { useForm as o } from "react-hook-form";
2
+ var e = {
3
+ mode: "onBlur",
4
+ reValidateMode: "onSubmit",
5
+ criteriaMode: "all",
6
+ shouldFocusError: !0
7
+ };
8
+ function t(r) {
9
+ return o({
10
+ ...r,
11
+ ...e
12
+ });
13
+ }
14
+ export {
15
+ t as useAsmaForm
16
+ };
@@ -0,0 +1,28 @@
1
+ import { ReactElement, ReactNode } from 'react';
2
+ import { Control, ControllerRenderProps, FieldPath, FieldPathValue, FieldValues, RegisterOptions } from 'react-hook-form';
3
+ import { AsmaFormsOptions } from './types';
4
+ export interface AutofillAnimationEvent {
5
+ animationName: string;
6
+ target: EventTarget | null;
7
+ }
8
+ export interface AsmaFieldRenderProps<TValue = unknown> {
9
+ value: TValue;
10
+ onChange: (...event: unknown[]) => void;
11
+ onBlur: () => void;
12
+ onAnimationStart: (event: AutofillAnimationEvent) => void;
13
+ name: string;
14
+ ref: ControllerRenderProps['ref'];
15
+ error: boolean;
16
+ helperText: ReactNode;
17
+ reserveHelperText: boolean;
18
+ }
19
+ export interface AsmaFieldControllerProps<TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>> {
20
+ name: TName;
21
+ control?: Control<TFieldValues>;
22
+ rules?: Omit<RegisterOptions<TFieldValues, TName>, 'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'>;
23
+ helperText?: ReactNode;
24
+ /** For controls that own their input ref (comboboxes, rich editors): RHF focuses the field through this instead of a DOM node. */
25
+ requestFocus?: () => void;
26
+ render: (props: AsmaFieldRenderProps<FieldPathValue<TFieldValues, TName>>) => ReactElement;
27
+ }
28
+ export declare function createAsmaFieldController({ getLanguageCode, browserAutofill, }: AsmaFormsOptions): <TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>({ name, control, rules, helperText, requestFocus, render, }: AsmaFieldControllerProps<TFieldValues, TName>) => ReactElement;
@@ -0,0 +1,9 @@
1
+ import { FieldValues, UseFormReturn } from 'react-hook-form';
2
+ import { createAsmaFieldController } from './createAsmaFieldController';
3
+ import { SubmitChangedPatch } from './submitChanged';
4
+ import { AsmaFormsOptions } from './types';
5
+ export interface AsmaFormsApi {
6
+ AsmaFieldController: ReturnType<typeof createAsmaFieldController>;
7
+ submitChanged: <TFieldValues extends FieldValues>(methods: UseFormReturn<TFieldValues>, onPatch: SubmitChangedPatch<TFieldValues>, onInvalid?: () => void) => Promise<boolean>;
8
+ }
9
+ export declare function createAsmaForms(options: AsmaFormsOptions): AsmaFormsApi;
@@ -0,0 +1 @@
1
+ export declare function focusFormField(fieldDomId: string): boolean;
@@ -0,0 +1 @@
1
+ export declare function getDirtyValues<T extends Record<string, unknown>>(dirtyFields: Partial<Record<keyof T, unknown>> | boolean | undefined, values: T): Partial<T>;
@@ -0,0 +1,8 @@
1
+ export { createAsmaForms } from './createAsmaForms';
2
+ export { focusFormField } from './focusFormField';
3
+ export { getDirtyValues } from './getDirtyValues';
4
+ export { touchInvalidFields } from './touchInvalidFields';
5
+ export { useAsmaForm } from './useAsmaForm';
6
+ export type { AsmaFieldControllerProps, AsmaFieldRenderProps, AutofillAnimationEvent } from './createAsmaFieldController';
7
+ export type { SubmitChangedPatch } from './submitChanged';
8
+ export type { AsmaFormsOptions } from './types';
@@ -0,0 +1,6 @@
1
+ import { FieldValues, UseFormReturn } from 'react-hook-form';
2
+ export type SubmitChangedPatch<TFieldValues extends FieldValues> = (dirty: Partial<TFieldValues>, all: TFieldValues) => Promise<TFieldValues | void>;
3
+ export interface SubmitChangedOptions {
4
+ touchInvalidFields?: boolean;
5
+ }
6
+ export declare function submitChanged<TFieldValues extends FieldValues>(methods: UseFormReturn<TFieldValues>, onPatch: SubmitChangedPatch<TFieldValues>, onInvalid?: () => void, options?: SubmitChangedOptions): Promise<boolean>;
@@ -0,0 +1,2 @@
1
+ import { FieldValues, UseFormReturn } from 'react-hook-form';
2
+ export declare function touchInvalidFields<TFieldValues extends FieldValues>(methods: UseFormReturn<TFieldValues>): void;
@@ -0,0 +1,6 @@
1
+ export interface AsmaFormsOptions {
2
+ /** Read during every render; a change re-validates already-invalid fields so their message re-renders in the new language. */
3
+ getLanguageCode: () => string;
4
+ /** Login/auth forms: mount validation, autofill sync, merged ref, touched-gated errors, touch-on-submit. */
5
+ browserAutofill?: boolean;
6
+ }
@@ -0,0 +1,2 @@
1
+ import { FieldValues, UseFormProps, UseFormReturn } from 'react-hook-form';
2
+ export declare function useAsmaForm<TFieldValues extends FieldValues = FieldValues, TContext = unknown>(props?: UseFormProps<TFieldValues, TContext>): UseFormReturn<TFieldValues, TContext>;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "3.90.0",
6
+ "version": "3.91.0",
7
7
  "type": "module",
8
8
  "sideEffects": [
9
9
  "**/*.css",
@@ -26,6 +26,9 @@
26
26
  ],
27
27
  "icons/*": [
28
28
  "dist/src/components/icons/*/index.d.ts"
29
+ ],
30
+ "forms": [
31
+ "dist/src/forms/index.d.ts"
29
32
  ]
30
33
  }
31
34
  },
@@ -45,6 +48,11 @@
45
48
  "types": "./dist/src/components/icons/*/index.d.ts",
46
49
  "default": "./dist/components/icons/*/index.js"
47
50
  },
51
+ "./forms": {
52
+ "types": "./dist/src/forms/index.d.ts",
53
+ "import": "./dist/forms/index.js",
54
+ "default": "./dist/forms/index.js"
55
+ },
48
56
  "./dist/style.css": "./dist/style.css",
49
57
  "./tw-configs/twConfigs.json": "./tw-configs/twConfigs.json",
50
58
  "./src/styles/breakpoints": "./src/styles/_breakpoints.scss"
@@ -102,6 +110,8 @@
102
110
  "@storybook/addon-themes": "^10.3.3",
103
111
  "@storybook/addon-vitest": "10.3.3",
104
112
  "@storybook/react-vite": "10.3.3",
113
+ "@testing-library/dom": "^10.4.1",
114
+ "@testing-library/react": "^16.3.2",
105
115
  "@tippyjs/react": "^4.2.6",
106
116
  "@types/node": "^24.12.4",
107
117
  "@types/react": "^18.3.23",
@@ -120,10 +130,12 @@
120
130
  "eslint-plugin-react-you-might-not-need-an-effect": "^0.7.0",
121
131
  "eslint-plugin-regexp": "^2.10.0",
122
132
  "eslint-plugin-storybook": "10.3.3",
133
+ "jsdom": "^30.0.1",
123
134
  "playwright": "^1.57.0",
124
135
  "react": "^18.3.1",
125
136
  "react-copy-to-clipboard": "^5.1.0",
126
137
  "react-dom": "^18.3.1",
138
+ "react-hook-form": "^7.89.0",
127
139
  "react-hot-toast": "^2.4.1",
128
140
  "sass": "^1.98.0",
129
141
  "storybook": "10.3.3",
@@ -142,7 +154,13 @@
142
154
  },
143
155
  "peerDependencies": {
144
156
  "react": "^18.0.0 || ^19.0.0",
145
- "react-dom": "^18.0.0 || ^19.0.0"
157
+ "react-dom": "^18.0.0 || ^19.0.0",
158
+ "react-hook-form": "^7.85.0"
159
+ },
160
+ "peerDependenciesMeta": {
161
+ "react-hook-form": {
162
+ "optional": true
163
+ }
146
164
  },
147
165
  "repository": {
148
166
  "type": "git",