@entur/form 9.0.2-beta.0 → 9.1.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.
@@ -47,9 +47,13 @@ export type BaseFormControlProps = React.HTMLAttributes<HTMLDivElement> & {
47
47
  style?: React.CSSProperties;
48
48
  /** Plasserer labelen statisk på toppen av inputfeltet */
49
49
  disableLabelAnimation?: boolean;
50
- /** Setter feedbackText sin rolle til "alert" */
50
+ /** Setter feedback-tekstens rolle for skjermlesere.
51
+ * 'alert' = aria-live="assertive" (avbryter umiddelbart)
52
+ * 'status' = aria-live="polite" (venter til bruker er ferdig)
53
+ * undefined/false = ingen automatisk annonsering
54
+ */
51
55
  onClick?: (event: React.MouseEvent<HTMLElement>) => void;
52
- ariaAlertOnFeedback?: boolean;
56
+ ariaAlertOnFeedback?: boolean | 'alert' | 'status';
53
57
  /** Legg til et element etter feltet */
54
58
  after?: React.ReactNode;
55
59
  /** Legg til et element før feltet */
@@ -99,9 +103,13 @@ export declare const BaseFormControl: React.ForwardRefExoticComponent<React.HTML
99
103
  style?: React.CSSProperties;
100
104
  /** Plasserer labelen statisk på toppen av inputfeltet */
101
105
  disableLabelAnimation?: boolean;
102
- /** Setter feedbackText sin rolle til "alert" */
106
+ /** Setter feedback-tekstens rolle for skjermlesere.
107
+ * 'alert' = aria-live="assertive" (avbryter umiddelbart)
108
+ * 'status' = aria-live="polite" (venter til bruker er ferdig)
109
+ * undefined/false = ingen automatisk annonsering
110
+ */
103
111
  onClick?: (event: React.MouseEvent<HTMLElement>) => void;
104
- ariaAlertOnFeedback?: boolean;
112
+ ariaAlertOnFeedback?: boolean | "alert" | "status";
105
113
  /** Legg til et element etter feltet */
106
114
  after?: React.ReactNode;
107
115
  /** Legg til et element før feltet */
@@ -13,5 +13,7 @@ export type RadioGroupProps = {
13
13
  [key: string]: any;
14
14
  /** Sett radiogruppen i readonly-modus */
15
15
  readOnly?: boolean;
16
+ /** Sett radiogruppen i disabled-modus */
17
+ disabled?: boolean;
16
18
  };
17
19
  export declare const RadioGroup: React.FC<RadioGroupProps>;
@@ -4,6 +4,7 @@ type RadioGroupContextProps = {
4
4
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
5
5
  value: string | null;
6
6
  readOnly?: boolean;
7
+ disabled?: boolean;
7
8
  };
8
9
  export declare const RadioGroupContextProvider: React.Provider<RadioGroupContextProps | null>;
9
10
  export declare const useRadioGroupContext: () => RadioGroupContextProps;
@@ -1,6 +1,6 @@
1
1
  import { default as React } from 'react';
2
- import { VariantType } from '../../utils';
3
2
  import { Placement } from '../../tooltip';
3
+ import { VariantType } from '../../utils';
4
4
  /** @deprecated use variant="information" instead */
5
5
  declare const info = "info";
6
6
  /** @deprecated use variant="negative" instead */
@@ -44,7 +44,16 @@ export type TextFieldProps = {
44
44
  clearable?: boolean;
45
45
  /** Callback for clearable */
46
46
  onClear?: () => void;
47
- ariaAlertOnFeedback?: boolean;
47
+ /** Aria-label for clear button
48
+ * @default "Tøm felt"
49
+ */
50
+ clearButtonAriaLabel?: string;
51
+ /** Setter feedback-tekstens rolle for skjermlesere.
52
+ * 'alert' = aria-live="assertive" (avbryter umiddelbart)
53
+ * 'status' = aria-live="polite" (venter til bruker er ferdig)
54
+ * undefined/false = ingen automatisk annonsering
55
+ */
56
+ ariaAlertOnFeedback?: boolean | 'alert' | 'status';
48
57
  } & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'label'>;
49
58
  export declare const TextField: React.ForwardRefExoticComponent<{
50
59
  /** Tekst eller ikon som kommer før inputfeltet */
@@ -85,6 +94,15 @@ export declare const TextField: React.ForwardRefExoticComponent<{
85
94
  clearable?: boolean;
86
95
  /** Callback for clearable */
87
96
  onClear?: () => void;
88
- ariaAlertOnFeedback?: boolean;
97
+ /** Aria-label for clear button
98
+ * @default "Tøm felt"
99
+ */
100
+ clearButtonAriaLabel?: string;
101
+ /** Setter feedback-tekstens rolle for skjermlesere.
102
+ * 'alert' = aria-live="assertive" (avbryter umiddelbart)
103
+ * 'status' = aria-live="polite" (venter til bruker er ferdig)
104
+ * undefined/false = ingen automatisk annonsering
105
+ */
106
+ ariaAlertOnFeedback?: boolean | "alert" | "status";
89
107
  } & Omit<React.InputHTMLAttributes<HTMLInputElement>, "label" | "size"> & React.RefAttributes<HTMLInputElement>>;
90
108
  export {};
package/dist/form.cjs.js CHANGED
@@ -102,13 +102,11 @@ const InputGroupContext = React__namespace.createContext({
102
102
  });
103
103
  const InputGroupContextProvider = ({ children }) => {
104
104
  const [filled, setFilled] = React__namespace.useState(false);
105
- return /* @__PURE__ */ jsxRuntime.jsx(
106
- InputGroupContext.Provider,
107
- {
108
- value: { isFilled: filled, setFilled },
109
- children
110
- }
105
+ const value = React__namespace.useMemo(
106
+ () => ({ isFilled: filled, setFilled }),
107
+ [filled, setFilled]
111
108
  );
109
+ return /* @__PURE__ */ jsxRuntime.jsx(InputGroupContext.Provider, { value, children });
112
110
  };
113
111
  const useInputGroupContext = () => React__namespace.useContext(InputGroupContext);
114
112
  const InputGroupLabel = ({
@@ -269,7 +267,7 @@ const BaseFormControl = React.forwardRef(
269
267
  FeedbackText,
270
268
  {
271
269
  variant: currentVariant,
272
- role: ariaAlertOnFeedback ? "alert" : void 0,
270
+ role: ariaAlertOnFeedback === true || ariaAlertOnFeedback === "alert" ? "alert" : ariaAlertOnFeedback === "status" ? "status" : void 0,
273
271
  children: feedback
274
272
  }
275
273
  ),
@@ -302,7 +300,7 @@ const Checkbox = React.forwardRef(
302
300
  return /* @__PURE__ */ jsxRuntime.jsxs(
303
301
  "label",
304
302
  {
305
- className: classNames("eds-checkbox__container", className, {
303
+ className: classNames("eds-checkbox", "eds-checkbox__container", className, {
306
304
  "eds-checkbox--disabled": disabled,
307
305
  "eds-checkbox--readonly": readOnly,
308
306
  "eds-checkbox__container--reduced-click-area": reduceClickArea
@@ -444,13 +442,18 @@ const InputPanelBase = React.forwardRef(
444
442
  style,
445
443
  id,
446
444
  disabled = false,
445
+ readOnly = false,
447
446
  type = "radio",
448
447
  onChange,
449
448
  checked,
450
449
  name,
451
450
  ...rest
452
451
  }, ref) => {
453
- const classList = classNames(
452
+ const classList = classNames("eds-input-panel", {
453
+ "eds-input-panel--readonly": readOnly,
454
+ "eds-input-panel--disabled": disabled
455
+ });
456
+ const panelClassList = classNames(
454
457
  className,
455
458
  "eds-input-panel__container",
456
459
  `eds-input-panel--${size}`
@@ -460,10 +463,28 @@ const InputPanelBase = React.forwardRef(
460
463
  const inputPanelId = id || defaultId;
461
464
  const forceUpdate = utils.useForceUpdate();
462
465
  const handleOnChange = (e) => {
463
- if (onChange === void 0) forceUpdate();
466
+ if (readOnly) {
467
+ e.preventDefault();
468
+ return;
469
+ }
470
+ if (onChange === void 0) {
471
+ forceUpdate();
472
+ }
464
473
  onChange?.(e);
465
474
  };
466
- return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "eds-input-panel", htmlFor: inputPanelId, children: [
475
+ const handleOnClick = (e) => {
476
+ if (readOnly) {
477
+ e.preventDefault();
478
+ e.stopPropagation();
479
+ }
480
+ };
481
+ const handleOnKeyDown = (e) => {
482
+ if (readOnly && (e.key === " " || e.key === "Enter")) {
483
+ e.preventDefault();
484
+ e.stopPropagation();
485
+ }
486
+ };
487
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: classList, htmlFor: inputPanelId, children: [
467
488
  /* @__PURE__ */ jsxRuntime.jsx(
468
489
  "input",
469
490
  {
@@ -473,17 +494,20 @@ const InputPanelBase = React.forwardRef(
473
494
  value,
474
495
  checked,
475
496
  onChange: handleOnChange,
497
+ onClick: handleOnClick,
498
+ onKeyDown: handleOnKeyDown,
476
499
  id: inputPanelId,
477
500
  disabled,
501
+ readOnly,
478
502
  ...rest
479
503
  }
480
504
  ),
481
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classList, style, children: [
505
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: panelClassList, style, children: [
482
506
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eds-input-panel__title-wrapper", children: [
483
507
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "eds-input-panel__title", children: title }),
484
508
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eds-input-panel__secondary-label-and-icon-wrapper", children: [
485
509
  secondaryLabel !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: secondaryLabel }),
486
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { pointerEvents: "none" }, children: !(disabled || hideSelectionIndicator) && (type === "radio" ? /* @__PURE__ */ jsxRuntime.jsx(
510
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { pointerEvents: "none" }, children: !hideSelectionIndicator && (type === "radio" ? /* @__PURE__ */ jsxRuntime.jsx(
487
511
  Radio,
488
512
  {
489
513
  name: "",
@@ -492,6 +516,8 @@ const InputPanelBase = React.forwardRef(
492
516
  onChange: () => {
493
517
  return;
494
518
  },
519
+ disabled,
520
+ readOnly,
495
521
  "aria-hidden": "true",
496
522
  tabIndex: -1
497
523
  }
@@ -500,6 +526,8 @@ const InputPanelBase = React.forwardRef(
500
526
  {
501
527
  checked: checked ?? inputRef.current?.checked ?? false,
502
528
  onChange: () => null,
529
+ disabled,
530
+ readOnly,
503
531
  "aria-hidden": "true",
504
532
  tabIndex: -1
505
533
  }
@@ -522,10 +550,17 @@ const RadioPanel = React.forwardRef(
522
550
  hideRadioButton = false,
523
551
  style,
524
552
  id,
525
- disabled = false,
553
+ disabled,
554
+ readOnly,
526
555
  ...rest
527
556
  }, ref) => {
528
- const { name, value: selected, onChange } = useRadioGroupContext();
557
+ const {
558
+ name,
559
+ value: selected,
560
+ onChange,
561
+ readOnly: groupReadOnly,
562
+ disabled: groupDisabled
563
+ } = useRadioGroupContext();
529
564
  return /* @__PURE__ */ jsxRuntime.jsx(
530
565
  InputPanelBase,
531
566
  {
@@ -541,7 +576,8 @@ const RadioPanel = React.forwardRef(
541
576
  hideSelectionIndicator: hideRadioButton,
542
577
  style,
543
578
  id,
544
- disabled,
579
+ disabled: disabled ?? groupDisabled,
580
+ readOnly: readOnly ?? groupReadOnly,
545
581
  ...rest,
546
582
  ref,
547
583
  children
@@ -564,6 +600,7 @@ const CheckboxPanel = React.forwardRef(
564
600
  style,
565
601
  id,
566
602
  disabled = false,
603
+ readOnly = false,
567
604
  ...rest
568
605
  }, ref) => {
569
606
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -582,6 +619,7 @@ const CheckboxPanel = React.forwardRef(
582
619
  style,
583
620
  id,
584
621
  disabled,
622
+ readOnly,
585
623
  ...rest,
586
624
  ref,
587
625
  children
@@ -596,11 +634,12 @@ const RadioGroup = ({
596
634
  onChange,
597
635
  label,
598
636
  readOnly = false,
637
+ disabled = false,
599
638
  ...rest
600
639
  }) => {
601
640
  const contextValue = React.useMemo(
602
- () => ({ name, value, onChange, readOnly }),
603
- [name, value, onChange, readOnly]
641
+ () => ({ name, value, onChange, readOnly, disabled }),
642
+ [name, value, onChange, readOnly, disabled]
604
643
  );
605
644
  return /* @__PURE__ */ jsxRuntime.jsx(RadioGroupContextProvider, { value: contextValue, children: label ? /* @__PURE__ */ jsxRuntime.jsx(Fieldset, { label, ...rest, children }) : children });
606
645
  };
@@ -668,7 +707,21 @@ function hasValue(value) {
668
707
  return value != null && !(Array.isArray(value) && value.length === 0);
669
708
  }
670
709
  function isFilled(obj, SSR = false) {
671
- return obj && (hasValue(obj.value) && obj.value !== "" || SSR && hasValue(obj.defaultValue) && obj.defaultValue !== "");
710
+ if (obj == null) {
711
+ return false;
712
+ }
713
+ if (typeof obj === "string") {
714
+ return obj !== "";
715
+ }
716
+ if (obj && typeof obj === "object") {
717
+ if (hasValue(obj.value) && obj.value !== "") {
718
+ return true;
719
+ }
720
+ if (SSR && hasValue(obj.defaultValue) && obj.defaultValue !== "") {
721
+ return true;
722
+ }
723
+ }
724
+ return false;
672
725
  }
673
726
  const TextArea = React.forwardRef(
674
727
  ({
@@ -784,6 +837,7 @@ const TextField = React.forwardRef(
784
837
  labelProps,
785
838
  clearable = false,
786
839
  onClear,
840
+ clearButtonAriaLabel = "Tøm felt",
787
841
  value,
788
842
  ariaAlertOnFeedback = false,
789
843
  ...rest
@@ -791,6 +845,29 @@ const TextField = React.forwardRef(
791
845
  const randomId = utils.useRandomId("eds-textfield");
792
846
  const textFieldId = labelProps && labelProps.id ? labelProps.id : randomId;
793
847
  const textFieldRef = React.useRef(null);
848
+ const { setFilled } = useInputGroupContext();
849
+ const handleClear = () => {
850
+ const inputElement = textFieldRef.current;
851
+ if (inputElement) {
852
+ const setNativeInputValue = Object.getOwnPropertyDescriptor(
853
+ window.HTMLInputElement.prototype,
854
+ "value"
855
+ )?.set;
856
+ setNativeInputValue?.call(inputElement, "");
857
+ const inputEvent = new Event("input", { bubbles: true });
858
+ inputElement.dispatchEvent(inputEvent);
859
+ inputElement.focus();
860
+ setFilled(false);
861
+ }
862
+ onClear?.();
863
+ };
864
+ const _append = React.useMemo(() => {
865
+ if (!clearable) return append ?? null;
866
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eds-textfield__append", children: [
867
+ append,
868
+ /* @__PURE__ */ jsxRuntime.jsx(ClearButton, { onClear: handleClear, ariaLabel: clearButtonAriaLabel })
869
+ ] });
870
+ }, [append, clearable]);
794
871
  return /* @__PURE__ */ jsxRuntime.jsx(
795
872
  BaseFormControl,
796
873
  {
@@ -798,7 +875,7 @@ const TextField = React.forwardRef(
798
875
  readOnly,
799
876
  variant,
800
877
  prepend,
801
- append: clearable && onClear ? /* @__PURE__ */ jsxRuntime.jsx(ClearButton, { onClear }) : append,
878
+ append: _append,
802
879
  className: classNames(className, "eds-textfield__wrapper"),
803
880
  style,
804
881
  size,
@@ -837,21 +914,21 @@ const TextFieldBase = React.forwardRef(
837
914
  const contextVariant = useVariant();
838
915
  const currentVariant = variant || contextVariant;
839
916
  const { isFilled: isInputFilled, setFilled: setFiller } = useInputGroupContext();
840
- utils.useOnMount(() => {
841
- if (value?.toString() || rest.defaultValue) {
842
- setFiller && !isInputFilled && setFiller(true);
843
- }
844
- });
917
+ const inputRef = React.useRef(null);
845
918
  React.useEffect(() => {
846
- if (value?.toString() && setFiller && !isInputFilled) {
847
- setFiller(true);
919
+ if (setFiller) {
920
+ const filled = isFilled({ value }) || isFilled(inputRef.current, true);
921
+ if (filled !== isInputFilled) {
922
+ setFiller(filled);
923
+ }
848
924
  }
849
925
  }, [value, setFiller, isInputFilled]);
850
926
  const handleChange = (event) => {
851
- if (isFilled(event.target)) {
852
- setFiller && !isInputFilled && setFiller(true);
853
- } else {
854
- setFiller && isInputFilled && setFiller(false);
927
+ if (setFiller && value === void 0) {
928
+ const filled = isFilled(event.target);
929
+ if (filled !== isInputFilled) {
930
+ setFiller(filled);
931
+ }
855
932
  }
856
933
  if (onChange) {
857
934
  onChange(event);
@@ -864,7 +941,7 @@ const TextFieldBase = React.forwardRef(
864
941
  className: "eds-form-control",
865
942
  disabled,
866
943
  readOnly,
867
- ref: forwardRef,
944
+ ref: utils.mergeRefs(forwardRef, inputRef),
868
945
  placeholder,
869
946
  onChange: handleChange,
870
947
  value,
@@ -873,25 +950,24 @@ const TextFieldBase = React.forwardRef(
873
950
  );
874
951
  }
875
952
  );
876
- const ClearButton = ({ onClear, ...props }) => {
877
- const { isFilled: hasValue2, setFilled } = useInputGroupContext();
878
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eds-textfield__clear-button-wrapper", children: [
879
- hasValue2 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "eds-textfield__divider" }),
880
- hasValue2 && /* @__PURE__ */ jsxRuntime.jsx(
881
- "button",
882
- {
883
- className: "eds-textfield__clear-button",
884
- type: "button",
885
- tabIndex: -1,
886
- onClick: () => {
887
- setFilled(false);
888
- onClear();
889
- },
890
- ...props,
891
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.CloseSmallIcon, {})
892
- }
893
- )
894
- ] });
953
+ const ClearButton = ({ onClear, ariaLabel }) => {
954
+ const { isFilled: isFilled2 } = useInputGroupContext();
955
+ if (isFilled2) {
956
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
957
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "eds-textfield__divider" }),
958
+ /* @__PURE__ */ jsxRuntime.jsx(
959
+ button.IconButton,
960
+ {
961
+ className: "eds-textfield__clear-button",
962
+ type: "button",
963
+ "aria-label": ariaLabel,
964
+ onClick: onClear,
965
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.CloseSmallIcon, { "aria-hidden": true })
966
+ }
967
+ )
968
+ ] });
969
+ }
970
+ return null;
895
971
  };
896
972
  const SegmentedContext = React.createContext(
897
973
  null