@dbcdk/react-components 0.0.42 → 0.0.43

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,5 +47,9 @@ export function CopyButton(props) {
47
47
  handleCopy();
48
48
  }, className: `${styles.link} ${copied ? styles.copied : ''}`, children: [children, copied ? _jsx(Check, {}) : _jsx(Copy, {})] }) }));
49
49
  }
50
- return (_jsxs(Button, { ...rest, "aria-label": children ? '' : ((_b = rest['aria-label']) !== null && _b !== void 0 ? _b : 'Kopier til udklipsholder'), onClick: handleCopy, variant: variant, size: size, children: [_jsx("span", { className: `${styles.container} ${copied ? styles.copied : ''}`, children: copied ? _jsx(Check, {}) : _jsx(Copy, {}) }), children] }));
50
+ return (_jsxs(Button, { ...rest, "aria-label": children ? '' : ((_b = rest['aria-label']) !== null && _b !== void 0 ? _b : 'Kopier til udklipsholder'), onClick: e => {
51
+ e.preventDefault();
52
+ e.stopPropagation();
53
+ handleCopy();
54
+ }, variant: variant, size: size, children: [_jsx("span", { className: `${styles.container} ${copied ? styles.copied : ''}`, children: copied ? _jsx(Check, {}) : _jsx(Copy, {}) }), children] }));
51
55
  }
@@ -25,9 +25,10 @@ export interface FilterFieldProps extends Omit<React.InputHTMLAttributes<HTMLInp
25
25
  placeholder?: string;
26
26
  disabled?: boolean;
27
27
  width?: string;
28
+ debounceTime?: number;
28
29
  }
29
30
  export declare const NUMBER_OPERATORS: Operator[];
30
- export declare function FilterField({ field, control, operator, value, onChange, operators, options, single, size, variant, label, placeholder, disabled, 'data-cy': dataCy, width, ...inputProps }: FilterFieldProps & {
31
+ export declare function FilterField({ field, control, operator, value, onChange, operators, options, single, size, variant, label, placeholder, disabled, 'data-cy': dataCy, width, debounceTime, ...inputProps }: FilterFieldProps & {
31
32
  'data-cy'?: string;
32
33
  }): React.ReactElement;
33
34
  export {};
@@ -44,6 +44,8 @@ export const NUMBER_OPERATORS = [
44
44
  'isEmpty',
45
45
  'isNotEmpty',
46
46
  ];
47
+ const VALUELESS_OPERATORS = ['isEmpty', 'isNotEmpty'];
48
+ const INPUT_DEBOUNCE_MS = 500;
47
49
  function OperatorDropdown({ value, onChange, operators, size = 'sm', disabled, }) {
48
50
  const popRef = useRef(null);
49
51
  const [activeIndex, setActiveIndex] = useState(() => Math.max(0, operators.indexOf(value)));
@@ -68,8 +70,7 @@ function isFilterActive(value) {
68
70
  return value.trim().length > 0;
69
71
  return value != null;
70
72
  }
71
- const VALUELESS_OPERATORS = ['isEmpty', 'isNotEmpty'];
72
- export function FilterField({ field, control, operator, value, onChange, operators, options = [], single = true, size = 'md', variant = 'surface', label, placeholder = 'Type value…', disabled, 'data-cy': dataCy, width, ...inputProps }) {
73
+ export function FilterField({ field, control, operator, value, onChange, operators, options = [], single = true, size = 'md', variant = 'surface', label, placeholder = 'Type value…', disabled, 'data-cy': dataCy, width, debounceTime = INPUT_DEBOUNCE_MS, ...inputProps }) {
73
74
  var _a, _b;
74
75
  const ops = useMemo(() => operators !== null && operators !== void 0 ? operators : DEFAULT_TEXT_OPERATORS, [operators]);
75
76
  const [selectedOperator, setSelectedOperator] = useState(operator);
@@ -81,66 +82,92 @@ export function FilterField({ field, control, operator, value, onChange, operato
81
82
  }, [operator, ops]);
82
83
  const [localValue, setLocalValue] = useState((_a = value) !== null && _a !== void 0 ? _a : '');
83
84
  const debounceRef = useRef(null);
84
- const isTypingRef = useRef(false);
85
+ /**
86
+ * Holds the exact text value we most recently sent upward.
87
+ * While waiting for that value to round-trip back through URL/provider state,
88
+ * we ignore intermediate external churn so the input does not visually flicker.
89
+ */
90
+ const pendingValueRef = useRef(null);
85
91
  const emit = (next) => {
86
92
  var _a, _b;
87
93
  const nextOperator = (_a = next.operator) !== null && _a !== void 0 ? _a : selectedOperator;
88
94
  const nextValue = (_b = next.value) !== null && _b !== void 0 ? _b : value;
89
- if (next.operator)
95
+ if (next.operator) {
90
96
  setSelectedOperator(nextOperator);
97
+ }
91
98
  onChange({
92
99
  field,
93
100
  operator: nextOperator,
94
101
  value: nextValue,
95
102
  });
96
103
  };
104
+ const clearDebounce = () => {
105
+ if (debounceRef.current) {
106
+ clearTimeout(debounceRef.current);
107
+ debounceRef.current = null;
108
+ }
109
+ };
110
+ const scheduleEmitValue = (nextVal) => {
111
+ clearDebounce();
112
+ pendingValueRef.current = nextVal;
113
+ debounceRef.current = setTimeout(() => {
114
+ emit({ value: nextVal });
115
+ }, debounceTime);
116
+ };
117
+ const flushPendingValue = () => {
118
+ if (control !== 'input')
119
+ return;
120
+ clearDebounce();
121
+ pendingValueRef.current = localValue;
122
+ emit({ value: localValue });
123
+ };
97
124
  const handleOperatorChange = (op) => {
98
125
  setSelectedOperator(op);
99
126
  if (!active && !VALUELESS_OPERATORS.includes(op))
100
127
  return;
101
128
  if (VALUELESS_OPERATORS.includes(op)) {
129
+ clearDebounce();
130
+ pendingValueRef.current = null;
102
131
  emit({ operator: op, value: null });
103
132
  return;
104
133
  }
134
+ clearDebounce();
135
+ pendingValueRef.current =
136
+ control === 'input' ? localValue : typeof value === 'string' ? value : null;
105
137
  emit({ operator: op });
106
138
  };
107
- const scheduleEmitValue = (nextVal) => {
108
- if (debounceRef.current)
109
- clearTimeout(debounceRef.current);
110
- debounceRef.current = setTimeout(() => {
111
- isTypingRef.current = false;
112
- emit({ value: nextVal });
113
- }, 250);
114
- };
115
139
  useEffect(() => {
116
140
  var _a;
117
141
  if (control !== 'input')
118
142
  return;
119
143
  const incoming = (_a = value) !== null && _a !== void 0 ? _a : '';
120
- if (!isTypingRef.current && incoming !== localValue) {
121
- setLocalValue(incoming);
144
+ const pending = pendingValueRef.current;
145
+ if (pending !== null) {
146
+ if (incoming === pending) {
147
+ pendingValueRef.current = null;
148
+ }
149
+ return;
122
150
  }
123
- if (incoming === localValue) {
124
- isTypingRef.current = false;
151
+ if (incoming !== localValue) {
152
+ setLocalValue(incoming);
125
153
  }
126
154
  }, [value, control, localValue]);
127
155
  useEffect(() => {
128
156
  return () => {
129
- if (debounceRef.current)
130
- clearTimeout(debounceRef.current);
157
+ clearDebounce();
131
158
  };
132
159
  }, []);
133
160
  return (_jsxs("div", { ...(dataCy ? { 'data-cy': dataCy } : {}), className: [styles.filterField, styles[size], styles[variant], active ? styles.active : '']
134
161
  .filter(Boolean)
135
162
  .join(' '), children: [label ? _jsx("span", { className: `${styles.label} ${styles[size]}`, children: label }) : null, _jsx(OperatorDropdown, { value: selectedOperator, onChange: handleOperatorChange, operators: ops, size: size, disabled: disabled }), _jsx("div", { className: `${control === 'input' ? 'dbc-flex dbc-flex-grow' : styles.valueWrapper}`, style: { width }, children: control === 'input' ? (_jsx(Input, { variant: "embedded", ...inputProps, value: localValue, onChange: e => {
136
163
  const next = e.currentTarget.value;
137
- isTypingRef.current = true;
138
164
  setLocalValue(next);
139
165
  scheduleEmitValue(next);
166
+ }, onBlur: () => {
167
+ flushPendingValue();
140
168
  }, fullWidth: true, inputSize: size, placeholder: placeholder, disabled: disabled, onClear: () => {
141
- isTypingRef.current = false;
142
- if (debounceRef.current)
143
- clearTimeout(debounceRef.current);
169
+ clearDebounce();
170
+ pendingValueRef.current = '';
144
171
  setLocalValue('');
145
172
  emit({ value: '' });
146
173
  } })) : single ? (_jsx(Select, { options: options, selectedValue: (_b = value) !== null && _b !== void 0 ? _b : null, onChange: v => emit({ value: v }), placeholder: placeholder, size: size, variant: "inline", onClear: () => emit({ value: '' }), disabled: disabled })) : (_jsx(MultiSelect, { options: options, size: size, variant: "inline", selectedValues: (Array.isArray(value) ? value : []), onChange: nextSelectedValues => emit({ value: nextSelectedValues }), onClear: () => emit({ value: [] }), fullWidth: true, disabled: disabled, children: placeholder })) })] }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbcdk/react-components",
3
- "version": "0.0.42",
3
+ "version": "0.0.43",
4
4
  "description": "Reusable React components for DBC projects",
5
5
  "license": "ISC",
6
6
  "author": "",