@equinor/eds-core-react 0.45.1 → 0.47.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.
@@ -2279,7 +2279,7 @@ const Text$3 = styled__default.default.span.withConfig({
2279
2279
  displayName: "Label__Text",
2280
2280
  componentId: "sc-1gi2bcn-1"
2281
2281
  })(["margin:0;"]);
2282
- const Label$3 = /*#__PURE__*/react.forwardRef(function Label(props, ref) {
2282
+ const Label$2 = /*#__PURE__*/react.forwardRef(function Label(props, ref) {
2283
2283
  const {
2284
2284
  label = '',
2285
2285
  meta,
@@ -2545,7 +2545,7 @@ const HelperText$1 = styled__default.default(TextfieldHelperText).withConfig({
2545
2545
  displayName: "InputWrapper__HelperText",
2546
2546
  componentId: "sc-v6o9z1-1"
2547
2547
  })(["margin-top:8px;margin-left:8px;"]);
2548
- const Label$2 = styled__default.default(Label$3).withConfig({
2548
+ const Label$1 = styled__default.default(Label$2).withConfig({
2549
2549
  displayName: "InputWrapper__Label",
2550
2550
  componentId: "sc-v6o9z1-2"
2551
2551
  })(["margin-left:8px;margin-right:8px;"]);
@@ -2580,7 +2580,7 @@ const InputWrapper$2 = /*#__PURE__*/react.forwardRef(function InputWrapper({
2580
2580
  children: /*#__PURE__*/jsxRuntime.jsxs(Container$5, {
2581
2581
  ...other,
2582
2582
  ref: ref,
2583
- children: [hasLabel && /*#__PURE__*/jsxRuntime.jsx(Label$2, {
2583
+ children: [hasLabel && /*#__PURE__*/jsxRuntime.jsx(Label$1, {
2584
2584
  label,
2585
2585
  ...labelProps
2586
2586
  }), children, hasHelperText && /*#__PURE__*/jsxRuntime.jsx(HelperText$1, {
@@ -5791,10 +5791,9 @@ const {
5791
5791
  output
5792
5792
  }
5793
5793
  } = slider;
5794
- const fakeTrackBg = styled.css(["background-image:url(\"data:image/svg+xml,<svg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'><rect x='0' y='11' fill='", "' width='100%' height='4' rx='2' /></svg>\");background-size:cover;background-repeat:no-repeat;"], track.background);
5795
- const fakeTrackBgHover = styled.css({
5796
- backgroundImage: `url("data:image/svg+xml,<svg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'><rect x='0' y='11' fill='${track.states.hover.background}' width='100%' height='4' rx='2' /></svg>")`
5797
- });
5794
+ const encodedTrackColor = encodeURIComponent(track.background);
5795
+ const encodedHoverColor = encodeURIComponent(track.states.hover.background);
5796
+ const fakeTrackBg = styled.css(["background-image:url(\"data:image/svg+xml,<svg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'><rect x='0' y='11' fill='", "' width='100%' height='4' rx='2' /></svg>\");background-size:cover;background-repeat:no-repeat;"], encodedTrackColor);
5798
5797
  const trackFill = styled.css(["grid-column:1 / span 2;grid-row:2;height:", ";margin-bottom:", ";align-self:end;content:'';"], track.height, track.spacings.bottom);
5799
5798
  const wrapperGrid = styled.css(["display:grid;grid-template-rows:max-content 24px;grid-template-columns:1fr 1fr;width:100%;position:relative;"]);
5800
5799
  const RangeWrapper = styled__default.default.div.attrs(({
@@ -5815,13 +5814,14 @@ const RangeWrapper = styled__default.default.div.attrs(({
5815
5814
  '--max': $max,
5816
5815
  '--showTooltip': $labelAlwaysOn ? 1 : 0,
5817
5816
  '--background': $disabled ? track.entities.indicator.states.disabled.background : $hideActiveTrack ? 'transparent' : track.entities.indicator.background,
5818
- '--background-hover': $hideActiveTrack ? 'transparent' : track.entities.indicator.states.hover.background,
5819
5817
  ...style
5820
5818
  }
5821
5819
  })).withConfig({
5822
5820
  displayName: "Slider__RangeWrapper",
5823
5821
  componentId: "sc-n1grrg-0"
5824
- })(["--dif:calc(var(--max) - var(--min));--realWidth:calc(100% - 12px);", " ", " &::before,&::after{", ";background:var(--background);}&::before{margin-left:calc( calc(", " / 2) + (var(--a) - var(--min)) / var(--dif) * var(--realWidth) );width:calc((var(--b) - var(--a)) / var(--dif) * var(--realWidth));}&::after{margin-left:calc( calc(", " / 2) + (var(--b) - var(--min)) / var(--dif) * var(--realWidth) );width:calc((var(--a) - var(--b)) / var(--dif) * var(--realWidth));}&:has(:focus-visible),&:hover{& > output{--showTooltip:1;--tooltip-background:", ";}}@media (hover:hover) and (pointer:fine){&:hover:not([data-disabled]){", " &::before,&::after{background:var(--background-hover);}}}", ";", ";"], wrapperGrid, fakeTrackBg, trackFill, handle.width, handle.width, output.states.hover.background, fakeTrackBgHover, ({
5822
+ })(["--dif:calc(var(--max) - var(--min));--realWidth:calc(100% - 12px);", " ", " &::before,&::after{", ";background:var(--background);}&::before{margin-left:calc( calc(", " / 2) + (var(--a) - var(--min)) / var(--dif) * var(--realWidth) );width:calc((var(--b) - var(--a)) / var(--dif) * var(--realWidth));}&::after{margin-left:calc( calc(", " / 2) + (var(--b) - var(--min)) / var(--dif) * var(--realWidth) );width:calc((var(--a) - var(--b)) / var(--dif) * var(--realWidth));}&:has(:focus-visible),&:hover{& > output{--showTooltip:1;--tooltip-background:", ";}}@media (hover:hover) and (pointer:fine){&:hover:not([data-disabled]){background-image:url(\"data:image/svg+xml,<svg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'><rect x='0' y='11' fill='", "' width='100%' height='4' rx='2' /></svg>\");&::before,&::after{background:", ";}}}", ";", ";"], wrapperGrid, fakeTrackBg, trackFill, handle.width, handle.width, output.states.hover.background, encodedHoverColor, ({
5823
+ $hideActiveTrack
5824
+ }) => $hideActiveTrack ? 'transparent' : track.entities.indicator.states.hover.background, ({
5825
5825
  $touchNavigation
5826
5826
  }) => $touchNavigation && styled.css(["& > input[type='range']{pointer-events:none;}& > input[type='range']::-webkit-slider-thumb{pointer-events:auto;}& > input[type='range']::-moz-range-thumb{pointer-events:auto;}"]), ({
5827
5827
  $labelBelow
@@ -5842,13 +5842,14 @@ const Wrapper = styled__default.default.div.attrs(({
5842
5842
  '--value': value,
5843
5843
  '--showTooltip': $labelAlwaysOn ? 1 : 0,
5844
5844
  '--background': $disabled ? track.entities.indicator.states.disabled.background : $hideActiveTrack ? 'transparent' : track.entities.indicator.background,
5845
- '--background-hover': $hideActiveTrack ? 'transparent' : track.entities.indicator.states.hover.background,
5846
5845
  ...style
5847
5846
  }
5848
5847
  })).withConfig({
5849
5848
  displayName: "Slider__Wrapper",
5850
5849
  componentId: "sc-n1grrg-1"
5851
- })(["--dif:calc(var(--max) - var(--min));--realWidth:calc(100% - 12px);", " ", " &::after{", " background:var(--background)}&::after{margin-right:calc( (var(--max) - var(--value)) / var(--dif) * var(--realWidth) );margin-left:3px;}&:has(:focus-visible),&:hover{& > output{--showTooltip:1;--tooltip-background:", ";}}@media (hover:hover) and (pointer:fine){&:hover:not([data-disabled]){", " &::after{background:var(--background-hover);}}", ";}"], wrapperGrid, fakeTrackBg, trackFill, output.states.hover.background, fakeTrackBgHover, ({
5850
+ })(["--dif:calc(var(--max) - var(--min));--realWidth:calc(100% - 12px);", " ", " &::after{", " background:var(--background)}&::after{margin-right:calc( (var(--max) - var(--value)) / var(--dif) * var(--realWidth) );margin-left:3px;}&:has(:focus-visible),&:hover{& > output{--showTooltip:1;--tooltip-background:", ";}}@media (hover:hover) and (pointer:fine){&:hover:not([data-disabled]){background-image:url(\"data:image/svg+xml,<svg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'><rect x='0' y='11' fill='", "' width='100%' height='4' rx='2' /></svg>\");&::after{background:", ";}}", ";}"], wrapperGrid, fakeTrackBg, trackFill, output.states.hover.background, encodedHoverColor, ({
5851
+ $hideActiveTrack
5852
+ }) => $hideActiveTrack ? 'transparent' : track.entities.indicator.states.hover.background, ({
5852
5853
  $labelBelow
5853
5854
  }) => $labelBelow && styled.css(["& > output{top:calc(100% + 1px);bottom:unset;}"]));
5854
5855
  const WrapperGroupLabel = styled__default.default.div.withConfig({
@@ -7759,9 +7760,9 @@ const MenuItem$1 = /*#__PURE__*/react.forwardRef(function MenuItem({
7759
7760
  onClick: e => {
7760
7761
  if (onClick) {
7761
7762
  onClick(e);
7762
- if (onClose !== null && closeMenuOnClick) {
7763
- onClose(e);
7764
- }
7763
+ }
7764
+ if (onClose !== null && closeMenuOnClick) {
7765
+ onClose(e);
7765
7766
  }
7766
7767
  },
7767
7768
  children: /*#__PURE__*/jsxRuntime.jsx(Content, {
@@ -8440,7 +8441,7 @@ const NativeSelect = /*#__PURE__*/react.forwardRef(function NativeSelect({
8440
8441
  theme: token,
8441
8442
  children: /*#__PURE__*/jsxRuntime.jsxs(Container$3, {
8442
8443
  ...containerProps,
8443
- children: [showLabel && /*#__PURE__*/jsxRuntime.jsx(Label$3, {
8444
+ children: [showLabel && /*#__PURE__*/jsxRuntime.jsx(Label$2, {
8444
8445
  ...labelProps
8445
8446
  }), /*#__PURE__*/jsxRuntime.jsx(StyledSelect, {
8446
8447
  ...selectProps,
@@ -9237,7 +9238,7 @@ const StyledLabel = styled__default.default.label.withConfig({
9237
9238
  }) => $isDisabled ? 'not-allowed' : 'pointer', ({
9238
9239
  $size
9239
9240
  }) => $size === 'small' ? '12px' : '8px');
9240
- const Label$1 = styled__default.default.span.withConfig({
9241
+ const Label = styled__default.default.span.withConfig({
9241
9242
  displayName: "Switch__Label",
9242
9243
  componentId: "sc-sdaahx-1"
9243
9244
  })(({
@@ -9274,7 +9275,7 @@ const Switch = /*#__PURE__*/react.forwardRef(function Switch({
9274
9275
  disabled: disabled,
9275
9276
  ...rest,
9276
9277
  ref: ref
9277
- }), label && /*#__PURE__*/jsxRuntime.jsx(Label$1, {
9278
+ }), label && /*#__PURE__*/jsxRuntime.jsx(Label, {
9278
9279
  children: label
9279
9280
  })]
9280
9281
  }) : size === 'small' ? /*#__PURE__*/jsxRuntime.jsx(SwitchSmall, {
@@ -9447,8 +9448,8 @@ const StyledListItem = styled__default.default.li.withConfig({
9447
9448
  const backgroundColor = $highlighted === 'true' ? theme.states.hover.background : $active === 'true' ? theme.states.active.background : theme.background;
9448
9449
  return styled.css(["display:flex;grid-area:1 / -1;align-items:center;align-self:start;margin:0;list-style:none;background-color:", ";user-select:none;overflow:hidden;touch-action:manipulation;z-index:3;cursor:", ";", " ", " ", ""], backgroundColor, $highlighted === 'true' ? 'pointer' : 'default', edsUtils.typographyTemplate(theme.typography), edsUtils.spacingsTemplate(theme.spacings), $isdisabled === 'true' ? styled.css(["color:", ";"], theme.states.disabled.typography.color) : '');
9449
9450
  });
9450
- const Label = styled__default.default.span.withConfig({
9451
- displayName: "Option__Label",
9451
+ const AutocompleteOptionLabel = styled__default.default.span.withConfig({
9452
+ displayName: "Option__AutocompleteOptionLabel",
9452
9453
  componentId: "sc-1ly11zu-1"
9453
9454
  })(({
9454
9455
  theme,
@@ -9491,7 +9492,7 @@ function AutocompleteOptionInner(props, ref) {
9491
9492
  }
9492
9493
  }), optionComponent ? /*#__PURE__*/jsxRuntime.jsx(jsxRuntime.Fragment, {
9493
9494
  children: optionComponent
9494
- }) : /*#__PURE__*/jsxRuntime.jsx(Label, {
9495
+ }) : /*#__PURE__*/jsxRuntime.jsx(AutocompleteOptionLabel, {
9495
9496
  $multiline: multiline,
9496
9497
  children: value
9497
9498
  })]
@@ -9499,11 +9500,49 @@ function AutocompleteOptionInner(props, ref) {
9499
9500
  }
9500
9501
  const AutocompleteOption = /*#__PURE__*/react.forwardRef(AutocompleteOptionInner);
9501
9502
 
9503
+ const StyledAddItemIcon = styled__default.default(Icon$1).withConfig({
9504
+ displayName: "AddNewOption__StyledAddItemIcon",
9505
+ componentId: "sc-8xtrxx-0"
9506
+ })(({
9507
+ multiple
9508
+ }) => {
9509
+ return styled.css(["padding:", ";color:", ";"], multiple ? '0.75rem' : '0 0.75rem 0 0', edsTokens.tokens.colors.interactive.primary__resting.hex);
9510
+ });
9511
+ function AddNewOptionInner(props, ref) {
9512
+ const {
9513
+ value,
9514
+ multiline,
9515
+ multiple,
9516
+ highlighted,
9517
+ onClick,
9518
+ ...other
9519
+ } = props;
9520
+ return /*#__PURE__*/jsxRuntime.jsxs(StyledListItem, {
9521
+ ref: ref,
9522
+ $highlighted: highlighted,
9523
+ "aria-label": `Add new option: ${value}`,
9524
+ onClick: e => {
9525
+ onClick(e);
9526
+ },
9527
+ ...other,
9528
+ children: [/*#__PURE__*/jsxRuntime.jsx(StyledAddItemIcon, {
9529
+ multiple: multiple,
9530
+ data: edsIcons.add_box
9531
+ }), /*#__PURE__*/jsxRuntime.jsx(AutocompleteOptionLabel, {
9532
+ $multiline: multiline,
9533
+ children: value
9534
+ })]
9535
+ });
9536
+ }
9537
+ const AddNewOption = /*#__PURE__*/react.forwardRef(AddNewOptionInner);
9538
+
9502
9539
  const Container$2 = styled__default.default.div.withConfig({
9503
9540
  displayName: "Autocomplete__Container",
9504
9541
  componentId: "sc-yvif0e-0"
9505
9542
  })(["position:relative;"]);
9506
9543
  const AllSymbol = Symbol('Select all');
9544
+ const AddSymbol = Symbol('Add new');
9545
+
9507
9546
  // MARK: styled components
9508
9547
  const StyledList = styled__default.default(List$1).withConfig({
9509
9548
  displayName: "Autocomplete__StyledList",
@@ -9637,6 +9676,7 @@ const defaultOptionDisabled = () => false;
9637
9676
  function AutocompleteInner(props, ref) {
9638
9677
  const {
9639
9678
  options = [],
9679
+ totalOptions,
9640
9680
  label,
9641
9681
  meta,
9642
9682
  className,
@@ -9646,6 +9686,7 @@ function AutocompleteInner(props, ref) {
9646
9686
  loading = false,
9647
9687
  hideClearButton = false,
9648
9688
  onOptionsChange,
9689
+ onAddNewOption,
9649
9690
  onInputChange,
9650
9691
  selectedOptions: _selectedOptions,
9651
9692
  multiple,
@@ -9700,8 +9741,9 @@ function AutocompleteInner(props, ref) {
9700
9741
  }, [allowSelectAll, multiple, typedInputValue]);
9701
9742
  const availableItems = react.useMemo(() => {
9702
9743
  if (showSelectAll) return [AllSymbol, ..._availableItems];
9744
+ if (_availableItems.length === 0 && onAddNewOption) return [AddSymbol];
9703
9745
  return _availableItems;
9704
- }, [_availableItems, showSelectAll]);
9746
+ }, [_availableItems, showSelectAll, onAddNewOption]);
9705
9747
 
9706
9748
  //issue 2304, update dataset when options are added dynamically
9707
9749
  react.useEffect(() => {
@@ -9731,7 +9773,7 @@ function AutocompleteInner(props, ref) {
9731
9773
  ...multipleSelectionProps,
9732
9774
  onSelectedItemsChange: changes => {
9733
9775
  if (onOptionsChange) {
9734
- let selectedItems = changes.selectedItems.filter(item => item !== AllSymbol);
9776
+ let selectedItems = changes.selectedItems.filter(item => item !== AllSymbol || item !== AddSymbol);
9735
9777
  if (itemCompare) {
9736
9778
  selectedItems = inputOptions.filter(item => selectedItems.some(compare => itemCompare(item, compare)));
9737
9779
  }
@@ -9877,6 +9919,8 @@ function AutocompleteInner(props, ref) {
9877
9919
  if (selectedItem != null && !optionDisabled(selectedItem)) {
9878
9920
  if (selectedItem === AllSymbol) {
9879
9921
  toggleAllSelected();
9922
+ } else if (selectedItem === AddSymbol) {
9923
+ onAddNewOption?.(typedInputValue);
9880
9924
  } else if (multiple) {
9881
9925
  const shouldRemove = itemCompare ? selectedItems.some(i => itemCompare(selectedItem, i)) : selectedItems.includes(selectedItem);
9882
9926
  if (shouldRemove) {
@@ -9920,11 +9964,29 @@ function AutocompleteInner(props, ref) {
9920
9964
  ...changes,
9921
9965
  isOpen: !(disabled || readOnly)
9922
9966
  };
9967
+ case downshift.useCombobox.stateChangeTypes.InputKeyDownEnter:
9968
+ case downshift.useCombobox.stateChangeTypes.ItemClick:
9969
+ if (changes.selectedItem === AddSymbol) {
9970
+ onAddNewOption(typedInputValue);
9971
+ return {
9972
+ ...changes,
9973
+ inputValue: '',
9974
+ selectedItem: null
9975
+ };
9976
+ }
9977
+ return {
9978
+ ...changes
9979
+ };
9923
9980
  case downshift.useCombobox.stateChangeTypes.InputBlur:
9924
9981
  return {
9925
9982
  ...changes,
9926
9983
  inputValue: changes.selectedItem ? getLabel(changes.selectedItem) : ''
9927
9984
  };
9985
+ case downshift.useCombobox.stateChangeTypes.InputChange:
9986
+ setTypedInputValue(changes.inputValue);
9987
+ return {
9988
+ ...changes
9989
+ };
9928
9990
  case downshift.useCombobox.stateChangeTypes.InputKeyDownArrowDown:
9929
9991
  case downshift.useCombobox.stateChangeTypes.InputKeyDownHome:
9930
9992
  if (readOnly) {
@@ -9978,7 +10040,9 @@ function AutocompleteInner(props, ref) {
9978
10040
  }
9979
10041
  // MARK: multiselect specific
9980
10042
  if (multiple) {
9981
- placeholderText = typeof placeholderText !== 'undefined' ? placeholderText : `${selectedItems.length}/${inputOptions.length} selected`;
10043
+ const showPlaceholder = placeholderText && selectedItems.length === 0;
10044
+ const optionCount = totalOptions || inputOptions.length;
10045
+ placeholderText = showPlaceholder ? placeholderText : `${selectedItems.length}/${optionCount} selected`;
9982
10046
  comboBoxProps = {
9983
10047
  ...comboBoxProps,
9984
10048
  selectedItem: null,
@@ -10195,6 +10259,29 @@ function AutocompleteInner(props, ref) {
10195
10259
  })
10196
10260
  }, 'select-all');
10197
10261
  }
10262
+ if (item === AddSymbol && onAddNewOption) {
10263
+ return /*#__PURE__*/jsxRuntime.jsx(AddNewOption, {
10264
+ "data-index": 0,
10265
+ "data-testid": 'add-item',
10266
+ "aria-setsize": availableItems.length,
10267
+ multiple: multiple,
10268
+ highlighted: highlightedIndex === index && !isDisabled ? 'true' : 'false',
10269
+ multiline: multiline,
10270
+ style: {
10271
+ position: 'sticky',
10272
+ top: 0,
10273
+ zIndex: 99
10274
+ },
10275
+ ...getItemProps({
10276
+ ...(multiline && {
10277
+ ref: rowVirtualizer.measureElement
10278
+ }),
10279
+ item,
10280
+ index: index
10281
+ }),
10282
+ value: `${typedInputValue}`
10283
+ }, 'add-item');
10284
+ }
10198
10285
  return /*#__PURE__*/jsxRuntime.jsx(AutocompleteOption, {
10199
10286
  "data-index": index,
10200
10287
  "aria-setsize": availableItems.length,
@@ -10236,7 +10323,7 @@ function AutocompleteInner(props, ref) {
10236
10323
  children: /*#__PURE__*/jsxRuntime.jsxs(Container$2, {
10237
10324
  className: className,
10238
10325
  style: style,
10239
- children: [/*#__PURE__*/jsxRuntime.jsx(Label$3, {
10326
+ children: [/*#__PURE__*/jsxRuntime.jsx(Label$2, {
10240
10327
  ...getLabelProps(),
10241
10328
  label: label,
10242
10329
  meta: meta,
@@ -12478,7 +12565,7 @@ exports.EdsProvider = EdsProvider;
12478
12565
  exports.Icon = Icon$1;
12479
12566
  exports.Input = Input$4;
12480
12567
  exports.InputWrapper = InputWrapper$2;
12481
- exports.Label = Label$3;
12568
+ exports.Label = Label$2;
12482
12569
  exports.LinearProgress = LinearProgress;
12483
12570
  exports.List = List$1;
12484
12571
  exports.ListItem = ListItem$2;
@@ -0,0 +1,45 @@
1
+ import { add_box } from '@equinor/eds-icons';
2
+ import { tokens } from '@equinor/eds-tokens';
3
+ import { forwardRef } from 'react';
4
+ import styled, { css } from 'styled-components';
5
+ import { Icon } from '../Icon/index.js';
6
+ import { StyledListItem, AutocompleteOptionLabel } from './Option.js';
7
+ import { jsxs, jsx } from 'react/jsx-runtime';
8
+
9
+ const StyledAddItemIcon = styled(Icon).withConfig({
10
+ displayName: "AddNewOption__StyledAddItemIcon",
11
+ componentId: "sc-8xtrxx-0"
12
+ })(({
13
+ multiple
14
+ }) => {
15
+ return css(["padding:", ";color:", ";"], multiple ? '0.75rem' : '0 0.75rem 0 0', tokens.colors.interactive.primary__resting.hex);
16
+ });
17
+ function AddNewOptionInner(props, ref) {
18
+ const {
19
+ value,
20
+ multiline,
21
+ multiple,
22
+ highlighted,
23
+ onClick,
24
+ ...other
25
+ } = props;
26
+ return /*#__PURE__*/jsxs(StyledListItem, {
27
+ ref: ref,
28
+ $highlighted: highlighted,
29
+ "aria-label": `Add new option: ${value}`,
30
+ onClick: e => {
31
+ onClick(e);
32
+ },
33
+ ...other,
34
+ children: [/*#__PURE__*/jsx(StyledAddItemIcon, {
35
+ multiple: multiple,
36
+ data: add_box
37
+ }), /*#__PURE__*/jsx(AutocompleteOptionLabel, {
38
+ $multiline: multiline,
39
+ children: value
40
+ })]
41
+ });
42
+ }
43
+ const AddNewOption = /*#__PURE__*/forwardRef(AddNewOptionInner);
44
+
45
+ export { AddNewOption };
@@ -11,6 +11,7 @@ import { multiSelect, selectTokens } from './Autocomplete.tokens.js';
11
11
  import { useToken, useIsomorphicLayoutEffect, bordersTemplate } from '@equinor/eds-utils';
12
12
  import { AutocompleteOption } from './Option.js';
13
13
  import { useFloating, offset, flip, size, useInteractions, autoUpdate } from '@floating-ui/react';
14
+ import { AddNewOption } from './AddNewOption.js';
14
15
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
15
16
  import pickBy from '../../node_modules/.pnpm/ramda@0.30.1/node_modules/ramda/es/pickBy.js';
16
17
  import mergeWith from '../../node_modules/.pnpm/ramda@0.30.1/node_modules/ramda/es/mergeWith.js';
@@ -24,6 +25,8 @@ const Container = styled.div.withConfig({
24
25
  componentId: "sc-yvif0e-0"
25
26
  })(["position:relative;"]);
26
27
  const AllSymbol = Symbol('Select all');
28
+ const AddSymbol = Symbol('Add new');
29
+
27
30
  // MARK: styled components
28
31
  const StyledList = styled(List).withConfig({
29
32
  displayName: "Autocomplete__StyledList",
@@ -157,6 +160,7 @@ const defaultOptionDisabled = () => false;
157
160
  function AutocompleteInner(props, ref) {
158
161
  const {
159
162
  options = [],
163
+ totalOptions,
160
164
  label,
161
165
  meta,
162
166
  className,
@@ -166,6 +170,7 @@ function AutocompleteInner(props, ref) {
166
170
  loading = false,
167
171
  hideClearButton = false,
168
172
  onOptionsChange,
173
+ onAddNewOption,
169
174
  onInputChange,
170
175
  selectedOptions: _selectedOptions,
171
176
  multiple,
@@ -220,8 +225,9 @@ function AutocompleteInner(props, ref) {
220
225
  }, [allowSelectAll, multiple, typedInputValue]);
221
226
  const availableItems = useMemo(() => {
222
227
  if (showSelectAll) return [AllSymbol, ..._availableItems];
228
+ if (_availableItems.length === 0 && onAddNewOption) return [AddSymbol];
223
229
  return _availableItems;
224
- }, [_availableItems, showSelectAll]);
230
+ }, [_availableItems, showSelectAll, onAddNewOption]);
225
231
 
226
232
  //issue 2304, update dataset when options are added dynamically
227
233
  useEffect(() => {
@@ -251,7 +257,7 @@ function AutocompleteInner(props, ref) {
251
257
  ...multipleSelectionProps,
252
258
  onSelectedItemsChange: changes => {
253
259
  if (onOptionsChange) {
254
- let selectedItems = changes.selectedItems.filter(item => item !== AllSymbol);
260
+ let selectedItems = changes.selectedItems.filter(item => item !== AllSymbol || item !== AddSymbol);
255
261
  if (itemCompare) {
256
262
  selectedItems = inputOptions.filter(item => selectedItems.some(compare => itemCompare(item, compare)));
257
263
  }
@@ -397,6 +403,8 @@ function AutocompleteInner(props, ref) {
397
403
  if (selectedItem != null && !optionDisabled(selectedItem)) {
398
404
  if (selectedItem === AllSymbol) {
399
405
  toggleAllSelected();
406
+ } else if (selectedItem === AddSymbol) {
407
+ onAddNewOption?.(typedInputValue);
400
408
  } else if (multiple) {
401
409
  const shouldRemove = itemCompare ? selectedItems.some(i => itemCompare(selectedItem, i)) : selectedItems.includes(selectedItem);
402
410
  if (shouldRemove) {
@@ -440,11 +448,29 @@ function AutocompleteInner(props, ref) {
440
448
  ...changes,
441
449
  isOpen: !(disabled || readOnly)
442
450
  };
451
+ case useCombobox.stateChangeTypes.InputKeyDownEnter:
452
+ case useCombobox.stateChangeTypes.ItemClick:
453
+ if (changes.selectedItem === AddSymbol) {
454
+ onAddNewOption(typedInputValue);
455
+ return {
456
+ ...changes,
457
+ inputValue: '',
458
+ selectedItem: null
459
+ };
460
+ }
461
+ return {
462
+ ...changes
463
+ };
443
464
  case useCombobox.stateChangeTypes.InputBlur:
444
465
  return {
445
466
  ...changes,
446
467
  inputValue: changes.selectedItem ? getLabel(changes.selectedItem) : ''
447
468
  };
469
+ case useCombobox.stateChangeTypes.InputChange:
470
+ setTypedInputValue(changes.inputValue);
471
+ return {
472
+ ...changes
473
+ };
448
474
  case useCombobox.stateChangeTypes.InputKeyDownArrowDown:
449
475
  case useCombobox.stateChangeTypes.InputKeyDownHome:
450
476
  if (readOnly) {
@@ -498,7 +524,9 @@ function AutocompleteInner(props, ref) {
498
524
  }
499
525
  // MARK: multiselect specific
500
526
  if (multiple) {
501
- placeholderText = typeof placeholderText !== 'undefined' ? placeholderText : `${selectedItems.length}/${inputOptions.length} selected`;
527
+ const showPlaceholder = placeholderText && selectedItems.length === 0;
528
+ const optionCount = totalOptions || inputOptions.length;
529
+ placeholderText = showPlaceholder ? placeholderText : `${selectedItems.length}/${optionCount} selected`;
502
530
  comboBoxProps = {
503
531
  ...comboBoxProps,
504
532
  selectedItem: null,
@@ -715,6 +743,29 @@ function AutocompleteInner(props, ref) {
715
743
  })
716
744
  }, 'select-all');
717
745
  }
746
+ if (item === AddSymbol && onAddNewOption) {
747
+ return /*#__PURE__*/jsx(AddNewOption, {
748
+ "data-index": 0,
749
+ "data-testid": 'add-item',
750
+ "aria-setsize": availableItems.length,
751
+ multiple: multiple,
752
+ highlighted: highlightedIndex === index && !isDisabled ? 'true' : 'false',
753
+ multiline: multiline,
754
+ style: {
755
+ position: 'sticky',
756
+ top: 0,
757
+ zIndex: 99
758
+ },
759
+ ...getItemProps({
760
+ ...(multiline && {
761
+ ref: rowVirtualizer.measureElement
762
+ }),
763
+ item,
764
+ index: index
765
+ }),
766
+ value: `${typedInputValue}`
767
+ }, 'add-item');
768
+ }
718
769
  return /*#__PURE__*/jsx(AutocompleteOption, {
719
770
  "data-index": index,
720
771
  "aria-setsize": availableItems.length,
@@ -16,8 +16,8 @@ const StyledListItem = styled.li.withConfig({
16
16
  const backgroundColor = $highlighted === 'true' ? theme.states.hover.background : $active === 'true' ? theme.states.active.background : theme.background;
17
17
  return css(["display:flex;grid-area:1 / -1;align-items:center;align-self:start;margin:0;list-style:none;background-color:", ";user-select:none;overflow:hidden;touch-action:manipulation;z-index:3;cursor:", ";", " ", " ", ""], backgroundColor, $highlighted === 'true' ? 'pointer' : 'default', typographyTemplate(theme.typography), spacingsTemplate(theme.spacings), $isdisabled === 'true' ? css(["color:", ";"], theme.states.disabled.typography.color) : '');
18
18
  });
19
- const Label = styled.span.withConfig({
20
- displayName: "Option__Label",
19
+ const AutocompleteOptionLabel = styled.span.withConfig({
20
+ displayName: "Option__AutocompleteOptionLabel",
21
21
  componentId: "sc-1ly11zu-1"
22
22
  })(({
23
23
  theme,
@@ -60,7 +60,7 @@ function AutocompleteOptionInner(props, ref) {
60
60
  }
61
61
  }), optionComponent ? /*#__PURE__*/jsx(Fragment, {
62
62
  children: optionComponent
63
- }) : /*#__PURE__*/jsx(Label, {
63
+ }) : /*#__PURE__*/jsx(AutocompleteOptionLabel, {
64
64
  $multiline: multiline,
65
65
  children: value
66
66
  })]
@@ -68,4 +68,4 @@ function AutocompleteOptionInner(props, ref) {
68
68
  }
69
69
  const AutocompleteOption = /*#__PURE__*/forwardRef(AutocompleteOptionInner);
70
70
 
71
- export { AutocompleteOption };
71
+ export { AutocompleteOption, AutocompleteOptionLabel, StyledListItem };
@@ -80,9 +80,9 @@ const MenuItem = /*#__PURE__*/forwardRef(function MenuItem({
80
80
  onClick: e => {
81
81
  if (onClick) {
82
82
  onClick(e);
83
- if (onClose !== null && closeMenuOnClick) {
84
- onClose(e);
85
- }
83
+ }
84
+ if (onClose !== null && closeMenuOnClick) {
85
+ onClose(e);
86
86
  }
87
87
  },
88
88
  children: /*#__PURE__*/jsx(Content, {
@@ -15,10 +15,9 @@ const {
15
15
  output
16
16
  }
17
17
  } = slider;
18
- const fakeTrackBg = css(["background-image:url(\"data:image/svg+xml,<svg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'><rect x='0' y='11' fill='", "' width='100%' height='4' rx='2' /></svg>\");background-size:cover;background-repeat:no-repeat;"], track.background);
19
- const fakeTrackBgHover = css({
20
- backgroundImage: `url("data:image/svg+xml,<svg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'><rect x='0' y='11' fill='${track.states.hover.background}' width='100%' height='4' rx='2' /></svg>")`
21
- });
18
+ const encodedTrackColor = encodeURIComponent(track.background);
19
+ const encodedHoverColor = encodeURIComponent(track.states.hover.background);
20
+ const fakeTrackBg = css(["background-image:url(\"data:image/svg+xml,<svg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'><rect x='0' y='11' fill='", "' width='100%' height='4' rx='2' /></svg>\");background-size:cover;background-repeat:no-repeat;"], encodedTrackColor);
22
21
  const trackFill = css(["grid-column:1 / span 2;grid-row:2;height:", ";margin-bottom:", ";align-self:end;content:'';"], track.height, track.spacings.bottom);
23
22
  const wrapperGrid = css(["display:grid;grid-template-rows:max-content 24px;grid-template-columns:1fr 1fr;width:100%;position:relative;"]);
24
23
  const RangeWrapper = styled.div.attrs(({
@@ -39,13 +38,14 @@ const RangeWrapper = styled.div.attrs(({
39
38
  '--max': $max,
40
39
  '--showTooltip': $labelAlwaysOn ? 1 : 0,
41
40
  '--background': $disabled ? track.entities.indicator.states.disabled.background : $hideActiveTrack ? 'transparent' : track.entities.indicator.background,
42
- '--background-hover': $hideActiveTrack ? 'transparent' : track.entities.indicator.states.hover.background,
43
41
  ...style
44
42
  }
45
43
  })).withConfig({
46
44
  displayName: "Slider__RangeWrapper",
47
45
  componentId: "sc-n1grrg-0"
48
- })(["--dif:calc(var(--max) - var(--min));--realWidth:calc(100% - 12px);", " ", " &::before,&::after{", ";background:var(--background);}&::before{margin-left:calc( calc(", " / 2) + (var(--a) - var(--min)) / var(--dif) * var(--realWidth) );width:calc((var(--b) - var(--a)) / var(--dif) * var(--realWidth));}&::after{margin-left:calc( calc(", " / 2) + (var(--b) - var(--min)) / var(--dif) * var(--realWidth) );width:calc((var(--a) - var(--b)) / var(--dif) * var(--realWidth));}&:has(:focus-visible),&:hover{& > output{--showTooltip:1;--tooltip-background:", ";}}@media (hover:hover) and (pointer:fine){&:hover:not([data-disabled]){", " &::before,&::after{background:var(--background-hover);}}}", ";", ";"], wrapperGrid, fakeTrackBg, trackFill, handle.width, handle.width, output.states.hover.background, fakeTrackBgHover, ({
46
+ })(["--dif:calc(var(--max) - var(--min));--realWidth:calc(100% - 12px);", " ", " &::before,&::after{", ";background:var(--background);}&::before{margin-left:calc( calc(", " / 2) + (var(--a) - var(--min)) / var(--dif) * var(--realWidth) );width:calc((var(--b) - var(--a)) / var(--dif) * var(--realWidth));}&::after{margin-left:calc( calc(", " / 2) + (var(--b) - var(--min)) / var(--dif) * var(--realWidth) );width:calc((var(--a) - var(--b)) / var(--dif) * var(--realWidth));}&:has(:focus-visible),&:hover{& > output{--showTooltip:1;--tooltip-background:", ";}}@media (hover:hover) and (pointer:fine){&:hover:not([data-disabled]){background-image:url(\"data:image/svg+xml,<svg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'><rect x='0' y='11' fill='", "' width='100%' height='4' rx='2' /></svg>\");&::before,&::after{background:", ";}}}", ";", ";"], wrapperGrid, fakeTrackBg, trackFill, handle.width, handle.width, output.states.hover.background, encodedHoverColor, ({
47
+ $hideActiveTrack
48
+ }) => $hideActiveTrack ? 'transparent' : track.entities.indicator.states.hover.background, ({
49
49
  $touchNavigation
50
50
  }) => $touchNavigation && css(["& > input[type='range']{pointer-events:none;}& > input[type='range']::-webkit-slider-thumb{pointer-events:auto;}& > input[type='range']::-moz-range-thumb{pointer-events:auto;}"]), ({
51
51
  $labelBelow
@@ -66,13 +66,14 @@ const Wrapper = styled.div.attrs(({
66
66
  '--value': value,
67
67
  '--showTooltip': $labelAlwaysOn ? 1 : 0,
68
68
  '--background': $disabled ? track.entities.indicator.states.disabled.background : $hideActiveTrack ? 'transparent' : track.entities.indicator.background,
69
- '--background-hover': $hideActiveTrack ? 'transparent' : track.entities.indicator.states.hover.background,
70
69
  ...style
71
70
  }
72
71
  })).withConfig({
73
72
  displayName: "Slider__Wrapper",
74
73
  componentId: "sc-n1grrg-1"
75
- })(["--dif:calc(var(--max) - var(--min));--realWidth:calc(100% - 12px);", " ", " &::after{", " background:var(--background)}&::after{margin-right:calc( (var(--max) - var(--value)) / var(--dif) * var(--realWidth) );margin-left:3px;}&:has(:focus-visible),&:hover{& > output{--showTooltip:1;--tooltip-background:", ";}}@media (hover:hover) and (pointer:fine){&:hover:not([data-disabled]){", " &::after{background:var(--background-hover);}}", ";}"], wrapperGrid, fakeTrackBg, trackFill, output.states.hover.background, fakeTrackBgHover, ({
74
+ })(["--dif:calc(var(--max) - var(--min));--realWidth:calc(100% - 12px);", " ", " &::after{", " background:var(--background)}&::after{margin-right:calc( (var(--max) - var(--value)) / var(--dif) * var(--realWidth) );margin-left:3px;}&:has(:focus-visible),&:hover{& > output{--showTooltip:1;--tooltip-background:", ";}}@media (hover:hover) and (pointer:fine){&:hover:not([data-disabled]){background-image:url(\"data:image/svg+xml,<svg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'><rect x='0' y='11' fill='", "' width='100%' height='4' rx='2' /></svg>\");&::after{background:", ";}}", ";}"], wrapperGrid, fakeTrackBg, trackFill, output.states.hover.background, encodedHoverColor, ({
75
+ $hideActiveTrack
76
+ }) => $hideActiveTrack ? 'transparent' : track.entities.indicator.states.hover.background, ({
76
77
  $labelBelow
77
78
  }) => $labelBelow && css(["& > output{top:calc(100% + 1px);bottom:unset;}"]));
78
79
  const WrapperGroupLabel = styled.div.withConfig({
@@ -0,0 +1,13 @@
1
+ import { LiHTMLAttributes } from 'react';
2
+ export type AutocompleteOptionProps = {
3
+ value: string;
4
+ multiple: boolean;
5
+ highlighted: string;
6
+ multiline: boolean;
7
+ } & LiHTMLAttributes<HTMLLIElement>;
8
+ declare function AddNewOptionInner(props: AutocompleteOptionProps, ref: React.ForwardedRef<HTMLLIElement>): import("react/jsx-runtime").JSX.Element;
9
+ export declare const AddNewOption: (props: AutocompleteOptionProps & {
10
+ ref?: React.ForwardedRef<HTMLLIElement>;
11
+ displayName?: string | undefined;
12
+ }) => ReturnType<typeof AddNewOptionInner>;
13
+ export {};
@@ -6,6 +6,8 @@ export type AutocompleteChanges<T> = {
6
6
  export type AutocompleteProps<T> = {
7
7
  /** List of options in dropdown */
8
8
  options: readonly T[];
9
+ /** Total number of options */
10
+ totalOptions?: number;
9
11
  /** Label for the select element */
10
12
  label: ReactNode;
11
13
  /** Array of initial selected items
@@ -53,6 +55,8 @@ export type AutocompleteProps<T> = {
53
55
  * Returns input value
54
56
  */
55
57
  onInputChange?: (text: string) => void;
58
+ /** Callback for clicking the add new option button */
59
+ onAddNewOption?: (text: string) => void;
56
60
  /** Enable multiselect */
57
61
  multiple?: boolean;
58
62
  /** Add select-all option. Throws an error if true while multiple = false */
@@ -1,4 +1,13 @@
1
1
  import { LiHTMLAttributes, ReactNode } from 'react';
2
+ type StyledListItemType = {
3
+ $highlighted?: string;
4
+ $active?: string;
5
+ $isdisabled?: string;
6
+ };
7
+ export declare const StyledListItem: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, StyledListItemType>> & string;
8
+ export declare const AutocompleteOptionLabel: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, {
9
+ $multiline: boolean;
10
+ }>> & string;
2
11
  export type AutocompleteOptionProps = {
3
12
  value: string;
4
13
  multiple: boolean;
@@ -1,9 +1,10 @@
1
+ import { ReactNode } from 'react';
1
2
  import { TypographyProps } from '../Typography/Typography';
2
3
  export type BannerMessageProps = {
3
- /** Text content */
4
- children: string;
4
+ /** Content */
5
+ children: ReactNode;
5
6
  } & Omit<TypographyProps, 'children'>;
6
7
  export declare const BannerMessage: import("react").ForwardRefExoticComponent<{
7
- /** Text content */
8
- children: string;
8
+ /** Content */
9
+ children: ReactNode;
9
10
  } & Omit<TypographyProps, "children"> & import("react").RefAttributes<HTMLElement>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equinor/eds-core-react",
3
- "version": "0.45.1",
3
+ "version": "0.47.0",
4
4
  "description": "The React implementation of the Equinor Design System",
5
5
  "sideEffects": [
6
6
  "**/*.css"
@@ -67,8 +67,8 @@
67
67
  "rollup-plugin-delete": "^2.2.0",
68
68
  "rollup-plugin-postcss": "^4.0.2",
69
69
  "rollup-preserve-directives": "^1.1.3",
70
- "storybook": "^8.6.12",
71
- "styled-components": "6.1.17",
70
+ "storybook": "^8.6.14",
71
+ "styled-components": "6.1.18",
72
72
  "tsc-watch": "^6.2.1",
73
73
  "typescript": "^5.8.3"
74
74
  },
@@ -78,19 +78,19 @@
78
78
  "styled-components": ">=5.1"
79
79
  },
80
80
  "dependencies": {
81
- "@babel/runtime": "^7.27.0",
81
+ "@babel/runtime": "^7.27.4",
82
82
  "@floating-ui/react": "^0.27.8",
83
83
  "@internationalized/date": "^3.8.0",
84
84
  "@react-aria/utils": "^3.28.2",
85
85
  "@react-stately/calendar": "^3.8.0",
86
86
  "@react-stately/datepicker": "^3.14.0",
87
87
  "@react-types/shared": "^3.29.0",
88
- "@tanstack/react-virtual": "3.13.2",
88
+ "@tanstack/react-virtual": "3.13.8",
89
89
  "downshift": "9.0.8",
90
90
  "react-aria": "^3.39.0",
91
91
  "@equinor/eds-tokens": "0.9.2",
92
- "@equinor/eds-icons": "^0.22.0",
93
- "@equinor/eds-utils": "0.8.7"
92
+ "@equinor/eds-utils": "0.8.7",
93
+ "@equinor/eds-icons": "^0.22.0"
94
94
  },
95
95
  "scripts": {
96
96
  "build": "rollup -c --bundleConfigAsCjs && tsc -p tsconfig.build.json",