@reykjavik/hanna-react 0.10.91 → 0.10.93

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.
Files changed (66) hide show
  1. package/ArticleCarousel/_ArticleCarouselCard.d.ts +2 -1
  2. package/CHANGELOG.md +23 -0
  3. package/ContactBubble.d.ts +2 -1
  4. package/ContactBubble.js +4 -6
  5. package/Datepicker.d.ts +31 -3
  6. package/Datepicker.js +25 -6
  7. package/FormField.d.ts +11 -2
  8. package/FormField.js +5 -5
  9. package/MainMenu/_PrimaryPanel.d.ts +2 -2
  10. package/MainMenu.d.ts +2 -1
  11. package/MainMenu.js +5 -6
  12. package/Multiselect/_Multiselect.search.d.ts +19 -0
  13. package/Multiselect/_Multiselect.search.js +80 -0
  14. package/Multiselect.d.ts +64 -0
  15. package/Multiselect.js +236 -0
  16. package/ReadSpeakerPlayer.d.ts +64 -0
  17. package/ReadSpeakerPlayer.js +78 -0
  18. package/RelatedLinks.d.ts +2 -1
  19. package/Selectbox.d.ts +3 -3
  20. package/TextInput.d.ts +0 -1
  21. package/_abstract/_CardList.d.ts +2 -1
  22. package/_abstract/_CardList.js +2 -2
  23. package/_abstract/_FocusTrap.d.ts +14 -0
  24. package/_abstract/_FocusTrap.js +24 -0
  25. package/_abstract/_TogglerGroup.d.ts +11 -7
  26. package/_abstract/_TogglerGroup.js +11 -3
  27. package/_abstract/_TogglerGroupField.d.ts +4 -4
  28. package/_abstract/_TogglerGroupField.js +2 -2
  29. package/_abstract/_TogglerInput.d.ts +3 -1
  30. package/_abstract/_TogglerInput.js +7 -4
  31. package/esm/ArticleCarousel/_ArticleCarouselCard.d.ts +2 -1
  32. package/esm/ContactBubble.d.ts +2 -1
  33. package/esm/ContactBubble.js +4 -6
  34. package/esm/Datepicker.d.ts +31 -3
  35. package/esm/Datepicker.js +25 -6
  36. package/esm/FormField.d.ts +11 -2
  37. package/esm/FormField.js +5 -5
  38. package/esm/MainMenu/_PrimaryPanel.d.ts +2 -2
  39. package/esm/MainMenu.d.ts +2 -1
  40. package/esm/MainMenu.js +5 -6
  41. package/esm/Multiselect/_Multiselect.search.d.ts +19 -0
  42. package/esm/Multiselect/_Multiselect.search.js +75 -0
  43. package/esm/Multiselect.d.ts +64 -0
  44. package/esm/Multiselect.js +231 -0
  45. package/esm/ReadSpeakerPlayer.d.ts +64 -0
  46. package/esm/ReadSpeakerPlayer.js +72 -0
  47. package/esm/RelatedLinks.d.ts +2 -1
  48. package/esm/Selectbox.d.ts +3 -3
  49. package/esm/TextInput.d.ts +0 -1
  50. package/esm/_abstract/_CardList.d.ts +2 -1
  51. package/esm/_abstract/_CardList.js +2 -2
  52. package/esm/_abstract/_FocusTrap.d.ts +14 -0
  53. package/esm/_abstract/_FocusTrap.js +19 -0
  54. package/esm/_abstract/_TogglerGroup.d.ts +11 -7
  55. package/esm/_abstract/_TogglerGroup.js +11 -3
  56. package/esm/_abstract/_TogglerGroupField.d.ts +4 -4
  57. package/esm/_abstract/_TogglerGroupField.js +2 -2
  58. package/esm/_abstract/_TogglerInput.d.ts +3 -1
  59. package/esm/_abstract/_TogglerInput.js +7 -4
  60. package/esm/index.d.ts +2 -0
  61. package/esm/utils/useFormatMonitor.d.ts +4 -2
  62. package/esm/utils/useFormatMonitor.js +4 -2
  63. package/index.d.ts +2 -0
  64. package/package.json +13 -5
  65. package/utils/useFormatMonitor.d.ts +4 -2
  66. package/utils/useFormatMonitor.js +4 -2
package/Multiselect.js ADDED
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Multiselect = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const react_1 = tslib_1.__importStar(require("react"));
6
+ const domid_1 = tslib_1.__importDefault(require("@hugsmidjan/qj/domid"));
7
+ const hooks_1 = require("@hugsmidjan/react/hooks");
8
+ const getBemClass_1 = tslib_1.__importDefault(require("@hugsmidjan/react/utils/getBemClass"));
9
+ const hanna_utils_1 = require("@reykjavik/hanna-utils");
10
+ const i18n_1 = require("@reykjavik/hanna-utils/i18n");
11
+ const _FocusTrap_js_1 = require("./_abstract/_FocusTrap.js");
12
+ const _Multiselect_search_js_1 = require("./Multiselect/_Multiselect.search.js");
13
+ const Checkbox_js_1 = tslib_1.__importDefault(require("./Checkbox.js"));
14
+ const FormField_js_1 = tslib_1.__importDefault(require("./FormField.js"));
15
+ const TagPill_js_1 = tslib_1.__importDefault(require("./TagPill.js"));
16
+ const utils_js_1 = require("./utils.js");
17
+ const metaData = {
18
+ /**
19
+ * The item-count where the list becomes searchable.
20
+ *
21
+ * (The search UI, including the on-screen keyboard, takes up a lot of space
22
+ * on mobile devices, so there's a balance that we want to strike.)
23
+ */
24
+ searchableLimit: 20,
25
+ /**
26
+ * The item-count above which we display a summary of "current" values
27
+ * at the top of the drop-down list.
28
+ *
29
+ * (This summary just gets in the way with ultra short option lists.)
30
+ */
31
+ summaryLimit: 10,
32
+ };
33
+ const { searchableLimit, summaryLimit } = metaData;
34
+ const defaultTexts = {
35
+ pl: {
36
+ search: 'Wyszukaj opcje',
37
+ buttonShow: 'Pokaż opcje',
38
+ // buttonHide: 'Ukryj opcje',
39
+ currentValues: 'Wybrane wartości',
40
+ noneFoundMsg: 'Brak dopasowań',
41
+ },
42
+ en: {
43
+ search: 'Search options',
44
+ buttonShow: 'Show options',
45
+ // buttonHide: 'Hide options',
46
+ currentValues: 'Currently selected',
47
+ noneFoundMsg: 'No matches',
48
+ },
49
+ is: {
50
+ search: 'Leita í valkostum',
51
+ buttonShow: 'Birta valkosti',
52
+ // buttonHide: 'Fela valkosti',
53
+ currentValues: 'Valin gildi',
54
+ noneFoundMsg: 'Ekkert passar',
55
+ },
56
+ };
57
+ const Multiselect = (props) => {
58
+ const { onSelected, options: _options, disabled: _disabled, readOnly } = props;
59
+ const disabled = _disabled === true;
60
+ const disableds = !disabled && _disabled;
61
+ const name = (0, hooks_1.useDomid)(props.name);
62
+ const [values, setValues] = (0, utils_js_1.useMixedControlState)(props, 'value', []);
63
+ const filled = values.length > 0;
64
+ const empty = !filled && !props.placeholder;
65
+ const placeholderText = !values.length ? props.placeholder : undefined;
66
+ const texts = (0, i18n_1.getTexts)(props, defaultTexts);
67
+ const inputRef = (0, react_1.useRef)(null);
68
+ const wrapperRef = (0, react_1.useRef)(null);
69
+ const [activeItemIndex, setActiveItemIndex] = (0, react_1.useState)(-1);
70
+ const [searchQuery, setSearchQuery] = (0, react_1.useState)('');
71
+ const [isOpen, setIsOpen] = (0, react_1.useState)(false);
72
+ const toggleOpen = (newIsOpen) => {
73
+ setIsOpen((isOpen) => {
74
+ newIsOpen = typeof newIsOpen === 'boolean' ? newIsOpen : !isOpen;
75
+ if (!newIsOpen) {
76
+ wrapperRef.current.querySelector('.Multiselect__choices').scrollTo(0, 0);
77
+ setSearchQuery('');
78
+ setActiveItemIndex(-1);
79
+ }
80
+ return newIsOpen;
81
+ });
82
+ };
83
+ (0, hooks_1.useOnClickOutside)(wrapperRef, () => toggleOpen(false));
84
+ const options = (0, react_1.useMemo)(() => _options.map((item) => (typeof item === 'string' ? { value: item } : item)), [_options]);
85
+ const isSearchable = props.forceSearchable || options.length >= searchableLimit;
86
+ /*
87
+ NOTE: he `.MultiSelect__currentvalues` should only be visible when
88
+ there are some items selected, and multiselect is either collapsed,
89
+ or the dropdown has reached `summaryLimit` number of items.
90
+ (For fewer items, the "summary" is just in the way.)
91
+ The `forceSummary` prop overrides this default.
92
+ */
93
+ const showCurrentValues = values.length > 0 &&
94
+ (props.forceSummary || !isOpen || options.length >= summaryLimit);
95
+ const filteredOptions = (0, react_1.useMemo)(() => (0, _Multiselect_search_js_1.filterItems)(options, searchQuery, props.searchScoring), [searchQuery, options, props.searchScoring]);
96
+ const handleCheckboxSelection = (0, react_1.useCallback)((selectedItem) => {
97
+ const selValue = selectedItem.value;
98
+ const isAdding = values.indexOf(selValue) === -1;
99
+ const _newValues = isAdding
100
+ ? [...values, selValue]
101
+ : values.filter((value) => value !== selValue);
102
+ const selectedValues = options
103
+ .filter((item) => _newValues.includes(item.value))
104
+ .map((item) => item.value);
105
+ setValues(selectedValues);
106
+ if (onSelected) {
107
+ onSelected({
108
+ value: selectedItem.value,
109
+ checked: isAdding,
110
+ option: selectedItem,
111
+ selectedValues,
112
+ });
113
+ }
114
+ }, [values, options, onSelected, setValues]);
115
+ const handleInputChange = (event) => {
116
+ const val = event.target.value;
117
+ const fixVal = val === ' ' ? '' : val;
118
+ setSearchQuery(fixVal);
119
+ toggleOpen(true);
120
+ };
121
+ const handleInputKeyDown = (event) => {
122
+ if (searchQuery.length === 0 && [' ', 'Delete', 'Backspace'].includes(event.key)) {
123
+ // setSearchQuery('');
124
+ toggleOpen(activeItemIndex > -1 ? true : !isOpen);
125
+ }
126
+ };
127
+ // When the dropdown is open, add keydown handlers
128
+ (0, react_1.useEffect)(() => {
129
+ if (!isOpen) {
130
+ return;
131
+ }
132
+ const handleKeyDown = (e) => {
133
+ const inputElm = inputRef.current;
134
+ if (e.key === 'ArrowUp') {
135
+ e.preventDefault();
136
+ inputElm.focus();
137
+ setActiveItemIndex((prevIndex) => prevIndex === 0 ? filteredOptions.length - 1 : prevIndex - 1);
138
+ }
139
+ else if (e.key === 'ArrowDown') {
140
+ e.preventDefault();
141
+ inputElm.focus();
142
+ setActiveItemIndex((prevIndex) => prevIndex === filteredOptions.length - 1 ? 0 : prevIndex + 1);
143
+ }
144
+ else if (e.key === 'Escape') {
145
+ e.preventDefault();
146
+ inputElm.blur();
147
+ inputElm.focus();
148
+ toggleOpen(false);
149
+ }
150
+ else if (e.key === 'Enter' || e.key === ' ') {
151
+ if (e.target.closest('.MultiSelect__currentvalues')) {
152
+ return;
153
+ }
154
+ const focusInRange = activeItemIndex >= 0 && activeItemIndex < filteredOptions.length;
155
+ if (focusInRange) {
156
+ e.preventDefault();
157
+ const selItem = filteredOptions[activeItemIndex];
158
+ if (selItem) {
159
+ handleCheckboxSelection(selItem);
160
+ }
161
+ }
162
+ }
163
+ };
164
+ document.addEventListener('keydown', handleKeyDown);
165
+ return () => {
166
+ document.removeEventListener('keydown', handleKeyDown);
167
+ };
168
+ }, [activeItemIndex, filteredOptions, isOpen, handleCheckboxSelection, inputRef]);
169
+ // Auto-close the dropdown when focus has left the building
170
+ (0, react_1.useEffect)(() => {
171
+ const wrapperDiv = wrapperRef.current;
172
+ if (!wrapperDiv) {
173
+ return;
174
+ }
175
+ let closing;
176
+ const cancelClose = () => clearTimeout(closing);
177
+ const closeDropdown = () => {
178
+ closing = setTimeout(() => toggleOpen(false), 200);
179
+ };
180
+ wrapperDiv.addEventListener('focusin', cancelClose);
181
+ wrapperDiv.addEventListener('focusout', closeDropdown);
182
+ return () => {
183
+ wrapperDiv.removeEventListener('focusin', cancelClose);
184
+ wrapperDiv.removeEventListener('focusout', closeDropdown);
185
+ };
186
+ }, []);
187
+ (0, react_1.useEffect)(() => {
188
+ var _a, _b;
189
+ (_b = (_a = wrapperRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.Multiselect__option')[activeItemIndex]) === null || _b === void 0 ? void 0 : _b.scrollIntoView({
190
+ behavior: 'smooth',
191
+ block: 'nearest',
192
+ });
193
+ }, [activeItemIndex]);
194
+ return (react_1.default.createElement(FormField_js_1.default, { className: (0, getBemClass_1.default)('Multiselect', props.nowrap && 'nowrap', props.className), ssr: props.ssr, group: "inputlike", label: props.label, LabelTag: props.LabelTag, hideLabel: props.hideLabel, small: props.small, filled: filled, empty: empty, disabled: disabled, invalid: props.invalid, errorMessage: props.errorMessage, assistText: props.assistText, readOnly: readOnly, required: props.required, reqText: props.reqText, id: props.id, renderInput: (className, inputProps, addFocusProps, isBrowser) => {
195
+ const { id } = inputProps;
196
+ return (react_1.default.createElement("div", Object.assign({ className: (0, getBemClass_1.default)('Multiselect__input', [isOpen && 'open'], className.input) }, addFocusProps(), { "data-sprinkled": isBrowser, ref: wrapperRef }),
197
+ !isBrowser ? null : isSearchable ? (react_1.default.createElement("input", { className: "Multiselect__search", id: `toggler:${id}`, "aria-label": texts.search, "aria-controls": (0, domid_1.default)(), "data-expanded": isOpen || undefined, onChange: handleInputChange, onKeyDown: handleInputKeyDown, onClick: () => toggleOpen(), value: searchQuery,
198
+ // onFocus={handleInputFocus}
199
+ placeholder: placeholderText, disabled: disabled, ref: inputRef })) : (react_1.default.createElement("button", { className: "Multiselect__toggler", id: `toggler:${id}`, type: "button", "aria-label": texts.buttonShow, "aria-controls": (0, domid_1.default)(), "aria-expanded": isOpen, onClick: () => toggleOpen(), disabled: disabled,
200
+ // Seems like an innocent hack for visible "placeholder" value.
201
+ // For scren-readers aria-label should take precedence.
202
+ ref: inputRef }, placeholderText || ' ')),
203
+ react_1.default.createElement("div", { className: "Multiselect__choices", tabIndex: -1 },
204
+ isBrowser && showCurrentValues && (react_1.default.createElement("div", { className: "Multiselect__currentvalues", onClick: isOpen || disabled
205
+ ? undefined
206
+ : () => {
207
+ var _a;
208
+ toggleOpen();
209
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
210
+ }, "aria-label": `${texts.currentValues}:` }, values
211
+ .map((value) => options.find((opt) => opt.value === value))
212
+ .filter(hanna_utils_1.notNully)
213
+ .map((item, idx) => (react_1.default.createElement(TagPill_js_1.default, Object.assign({ key: idx, large: true, label: item.label || item.value }, (isOpen && !readOnly
214
+ ? {
215
+ removable: true,
216
+ onRemove: () => {
217
+ handleCheckboxSelection(item);
218
+ },
219
+ }
220
+ : { removable: false }))))))),
221
+ react_1.default.createElement("ul", { id: id, className: "Multiselect__options", "aria-expanded": isBrowser ? isOpen : undefined, hidden: isBrowser && !isOpen, role: "group", "aria-labelledby": inputProps['aria-labelledby'], "aria-describedby": inputProps['aria-describedby'], "aria-required": props.required },
222
+ filteredOptions.length ? (filteredOptions.map((item, idx) => {
223
+ const isDisabled = item.disabled != null
224
+ ? item.disabled
225
+ : disableds && disableds.includes(idx);
226
+ const isChecked = values.includes(item.value);
227
+ return (react_1.default.createElement(Checkbox_js_1.default, Object.assign({ key: idx, className: (0, getBemClass_1.default)('Multiselect__option', activeItemIndex === idx && 'focused'), disabled: isDisabled, readOnly: readOnly, required: props.required, Wrapper: "li", name: name }, item, { checked: isChecked, "aria-invalid": props.invalid, label: item.label || item.value, onChange: () => handleCheckboxSelection(item), onFocus: () => setActiveItemIndex(idx), wrapperProps: {
228
+ onMouseEnter: () => setActiveItemIndex(idx),
229
+ } })));
230
+ })) : searchQuery ? (react_1.default.createElement("li", { className: "Multiselect__noresults" }, texts.noneFoundMsg)) : undefined,
231
+ react_1.default.createElement(_FocusTrap_js_1.FocusTrap, { Tag: "li" })))));
232
+ } }));
233
+ };
234
+ exports.Multiselect = Multiselect;
235
+ /** Configuration constants for the Multiselect components */
236
+ exports.Multiselect.meta = metaData;
@@ -0,0 +1,64 @@
1
+ import { DefaultTexts } from '@reykjavik/hanna-utils/i18n';
2
+ import { HTMLProps } from './utils.js';
3
+ export type ReadSpeakerPlayerI18n = {
4
+ linkText: string;
5
+ linkLabel?: string;
6
+ };
7
+ export declare const defaultReadSpeakerPlayerTexts: DefaultTexts<ReadSpeakerPlayerI18n>;
8
+ export type ReadSpeakerPlayerProps = {
9
+ /**
10
+ * Your ReadSpeaker account/customer ID
11
+ *
12
+ * @see https://docs.typo3.org/p/readspeaker/readspeaker-services/main/en-us/Configuration/Index.html#customer-id
13
+ */
14
+ customerId?: string;
15
+ /**
16
+ * Reading language/locale for the ReadSpeaker player.
17
+ *
18
+ * If you don't specify a `lang`, the player will try to auto-detect
19
+ * the language of the page, and pick a default `voice` for that language.
20
+ *
21
+ * @see https://docs.typo3.org/p/readspeaker/readspeaker-services/main/en-us/Configuration/Index.html#reading-language
22
+ */
23
+ lang?: Lowercase<string>;
24
+ /**
25
+ * Reading voice for the ReadSpeaker player.
26
+ *
27
+ * This prop only makes sense if you specfy a reading `lang`, as
28
+ * the voices are language-specific.
29
+ *
30
+ * @see https://docs.typo3.org/p/readspeaker/readspeaker-services/main/en-us/Configuration/Index.html#voice
31
+ */
32
+ voice?: string;
33
+ /**
34
+ * The DOM `id=""` of the element to read.
35
+ *
36
+ * @see https://docs.typo3.org/p/readspeaker/readspeaker-services/main/en-us/Configuration/Index.html#reading-area-id
37
+ */
38
+ readId?: string;
39
+ /**
40
+ * The DOM class-name of the element(s) to read
41
+ *
42
+ * Comma-separated list of class-names may also work...
43
+ *
44
+ * @see https://docs.typo3.org/p/readspeaker/readspeaker-services/main/en-us/Configuration/Index.html#reading-area-class
45
+ */
46
+ readClass?: string;
47
+ texts?: ReadSpeakerPlayerI18n;
48
+ align?: 'left' | 'right';
49
+ /** Tooggles CSS float layout */
50
+ float?: boolean;
51
+ /** Custom HTML attributes for the wrapper element. */
52
+ wrapperProps?: HTMLProps<'div'>;
53
+ };
54
+ /**
55
+ * Embeds a ReadSpeaker player in the page
56
+ *
57
+ * @see https://docs.typo3.org/p/readspeaker/readspeaker-services/main/en-us/Configuration/Index.html
58
+ */
59
+ export declare const ReadSpeakerPlayer: (props: ReadSpeakerPlayerProps) => JSX.Element;
60
+ /**
61
+ * Run this function if you find that the player keeps reading when a user
62
+ * swaps pages.
63
+ */
64
+ export declare const stopReading: () => void | undefined;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stopReading = exports.ReadSpeakerPlayer = exports.defaultReadSpeakerPlayerTexts = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const react_1 = tslib_1.__importStar(require("react"));
6
+ const getBemClass_1 = tslib_1.__importDefault(require("@hugsmidjan/react/utils/getBemClass"));
7
+ const i18n_1 = require("@reykjavik/hanna-utils/i18n");
8
+ const scriptTagId = 'rs_req_Init';
9
+ const scriptTagSelector = `script#${scriptTagId}`;
10
+ let buttons = 0;
11
+ exports.defaultReadSpeakerPlayerTexts = {
12
+ en: { linkText: 'Listen', linkLabel: 'Listen to this page read outloud' },
13
+ is: { linkText: 'Hlusta', linkLabel: 'Hlusta á þessa síðu lesna upphátt' },
14
+ pl: { linkText: 'Posłuchaj', linkLabel: 'Posłuchaj tej strony odczytanej na głos' },
15
+ };
16
+ /**
17
+ * Embeds a ReadSpeaker player in the page
18
+ *
19
+ * @see https://docs.typo3.org/p/readspeaker/readspeaker-services/main/en-us/Configuration/Index.html
20
+ */
21
+ const ReadSpeakerPlayer = (props) => {
22
+ const { align, float, customerId = '11315', lang = '', voice = /^is(?:_is)?$/i.test(lang) ? 'is_dora' : '', readId = '', readClass = readId ? '' : 'Layout__main', wrapperProps = {}, texts, } = props;
23
+ const { linkText, linkLabel } = (0, i18n_1.getTexts)({ lang: lang.slice(0, 2), texts }, exports.defaultReadSpeakerPlayerTexts);
24
+ (0, react_1.useEffect)(() => {
25
+ var _a, _b;
26
+ if (buttons < 0) {
27
+ return;
28
+ }
29
+ if (buttons === 0) {
30
+ if (document.querySelector(scriptTagSelector)) {
31
+ buttons = -1;
32
+ return;
33
+ }
34
+ const script = document.createElement('script');
35
+ script.id = scriptTagId;
36
+ script.src = `https://cdn-eu.readspeaker.com/script/${customerId}/webReader/webReader.js?pids=wr`;
37
+ script.onload = () => { var _a, _b; return (_b = (_a = window.rspkr) === null || _a === void 0 ? void 0 : _a.ui) === null || _b === void 0 ? void 0 : _b.addClickEvents(); };
38
+ script.async = true;
39
+ document.head.appendChild(script);
40
+ }
41
+ buttons++;
42
+ (_b = (_a = window.rspkr) === null || _a === void 0 ? void 0 : _a.ui) === null || _b === void 0 ? void 0 : _b.addClickEvents();
43
+ return () => {
44
+ var _a;
45
+ buttons--;
46
+ if (buttons === 0) {
47
+ (_a = document.querySelector(scriptTagSelector)) === null || _a === void 0 ? void 0 : _a.remove();
48
+ }
49
+ };
50
+ },
51
+ // eslint-disable-next-line react-hooks/exhaustive-deps
52
+ [
53
+ // We're not trying to support dynamic changes to `customerId`
54
+ // or multiple different `customerId`s on the same page.
55
+ // If you try that, things will be weird and wonky.
56
+ ]);
57
+ return (react_1.default.createElement("div", Object.assign({}, wrapperProps, { className: (0, getBemClass_1.default)('ReadSpeakerPlayer', [align === 'right' && `align-${align}`, float && 'float'], wrapperProps.className) }),
58
+ react_1.default.createElement("div", { id: "readspeaker_button1", className: "rs_skip rsbtn rs_preserve" },
59
+ react_1.default.createElement("a", { rel: "nofollow", className: "rsbtn_play", accessKey: "L", title: linkLabel || linkText, href: `https://app-eu.readspeaker.com/cgi-bin/rsent?${new URLSearchParams({
60
+ customerid: customerId,
61
+ lang,
62
+ voice: lang && voice,
63
+ autoLang: !lang ? 'true' : 'false',
64
+ readclass: readClass,
65
+ readid: readId,
66
+ })}` },
67
+ react_1.default.createElement("span", { className: "rsbtn_left rsimg rspart" },
68
+ react_1.default.createElement("span", { className: "rsbtn_text" },
69
+ react_1.default.createElement("span", null, linkText))),
70
+ react_1.default.createElement("span", { className: "rsbtn_right rsimg rsplay rspart" })))));
71
+ };
72
+ exports.ReadSpeakerPlayer = ReadSpeakerPlayer;
73
+ /**
74
+ * Run this function if you find that the player keeps reading when a user
75
+ * swaps pages.
76
+ */
77
+ const stopReading = () => { var _a, _b; return (_b = (_a = window.rspkr) === null || _a === void 0 ? void 0 : _a.ui) === null || _b === void 0 ? void 0 : _b.destroyActivePlayer(); };
78
+ exports.stopReading = stopReading;
package/RelatedLinks.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  declare const types: {
2
3
  readonly external: 1;
3
4
  readonly document: 1;
@@ -8,7 +9,7 @@ export type RelatedLinkType = keyof typeof types;
8
9
  export type RelatedLinkItem = {
9
10
  href: string;
10
11
  label: string;
11
- target?: string;
12
+ target?: React.HTMLAttributeAnchorTarget;
12
13
  type?: RelatedLinkType;
13
14
  };
14
15
  export type RelatedLinksProps = {
package/Selectbox.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import type { OptionOrValue, SelectboxProps as _SelectboxProps } from '@hugsmidjan/react/Selectbox';
1
+ import type { OptionOrValue, SelectboxOptions as _SelectboxOptions, SelectboxProps as _SelectboxProps } from '@hugsmidjan/react/Selectbox';
2
2
  import { FormFieldWrappingProps } from './FormField.js';
3
- export { type SelectboxOption, type SelectboxOptions as SelectboxOptionList,
3
+ export { type SelectboxOption, type SelectboxOptions as SelectboxOptionList, } from '@hugsmidjan/react/Selectbox';
4
4
  /** @deprecated Use `SelectboxOptionList` instead (Will be removed in v0.11) */
5
- type SelectboxOptions, } from '@hugsmidjan/react/Selectbox';
5
+ export type SelectboxOptions = _SelectboxOptions;
6
6
  export type SelectboxProps<O extends OptionOrValue = OptionOrValue> = FormFieldWrappingProps & Omit<_SelectboxProps<O>, 'bem'> & {
7
7
  small?: boolean;
8
8
  };
package/TextInput.d.ts CHANGED
@@ -4,7 +4,6 @@ type InputElmProps = JSX.IntrinsicElements['input'];
4
4
  type TextareaElmProps = JSX.IntrinsicElements['textarea'];
5
5
  export type TextInputProps = {
6
6
  small?: boolean;
7
- children?: never;
8
7
  } & FormFieldWrappingProps & (({
9
8
  type?: 'text' | 'email' | 'tel' | 'number' | 'date' | 'url' | 'password' | 'search';
10
9
  inputRef?: RefObject<HTMLInputElement>;
@@ -1,4 +1,4 @@
1
- import { ReactElement, ReactNode } from 'react';
1
+ import React, { ReactElement, ReactNode } from 'react';
2
2
  import { EitherObj } from '@reykjavik/hanna-utils';
3
3
  import { ImageProps } from './_Image.js';
4
4
  type BaseCardProps = {
@@ -12,6 +12,7 @@ export type ImageCardProps = BaseCardProps & {
12
12
  };
13
13
  export type TextCardProps = BaseCardProps & {
14
14
  summary?: string;
15
+ target?: React.HTMLAttributeAnchorTarget;
15
16
  };
16
17
  export type CardListProps<T> = {
17
18
  cards: Array<T>;
@@ -6,10 +6,10 @@ const react_1 = tslib_1.__importDefault(require("react"));
6
6
  const _Button_js_1 = require("./_Button.js");
7
7
  const _Image_js_1 = require("./_Image.js");
8
8
  const Card = (props) => {
9
- const { bem, href, title, imgPlaceholder, image, meta, summary } = props;
9
+ const { bem, href, title, imgPlaceholder, image, meta, summary, target } = props;
10
10
  const cardClass = `${bem}__card`;
11
11
  return (react_1.default.createElement(react_1.default.Fragment, null,
12
- react_1.default.createElement(_Button_js_1.Button, { bem: cardClass, href: href },
12
+ react_1.default.createElement(_Button_js_1.Button, { bem: cardClass, href: href, target: target },
13
13
  ' ',
14
14
  react_1.default.createElement(_Image_js_1.Image, Object.assign({ className: `${bem}__image` }, image, { placeholder: imgPlaceholder })),
15
15
  react_1.default.createElement("span", { className: `${cardClass}__title` }, title),
@@ -0,0 +1,14 @@
1
+ export type FocusTrapProps = {
2
+ /** The HTML tag to use for the trap element. (Default `<span />`) */
3
+ Tag?: `span` | `li`;
4
+ /** Set to `true` for focus traps positioned at the top of a container. */
5
+ atTop?: boolean;
6
+ /**
7
+ * How deep the trap is placed in the DOM tree beneath its container element.
8
+ *
9
+ * Default: `1`
10
+ */
11
+ depth?: number;
12
+ };
13
+ /** A focus trap element that can be used to keep keyboard focus within a container block. */
14
+ export declare const FocusTrap: (props: FocusTrapProps) => JSX.Element;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FocusTrap = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const react_1 = tslib_1.__importDefault(require("react"));
6
+ /** A focus trap element that can be used to keep keyboard focus within a container block. */
7
+ const FocusTrap = (props) => {
8
+ const Tag = props.Tag || 'span';
9
+ return (react_1.default.createElement(Tag, { tabIndex: 0, onFocus: (e) => {
10
+ var _a;
11
+ let container = e.currentTarget;
12
+ let depth = Math.max(props.depth || 0, 1);
13
+ while (depth-- && container) {
14
+ container = container.parentElement;
15
+ }
16
+ if (!container) {
17
+ return;
18
+ }
19
+ const focusables = container.querySelectorAll('a,input, select, textarea,button, [tabindex]');
20
+ const targetIdx = props.atTop ? focusables.length - 1 : 0;
21
+ (_a = focusables[targetIdx]) === null || _a === void 0 ? void 0 : _a.focus();
22
+ } }));
23
+ };
24
+ exports.FocusTrap = FocusTrap;
@@ -1,24 +1,28 @@
1
1
  import { FormFieldInputProps } from '../FormField.js';
2
2
  import { HTMLProps } from '../utils.js';
3
3
  import { TogglerInputProps } from './_TogglerInput.js';
4
- export type TogglerGroupOption = {
4
+ export type TogglerGroupOption<T = 'default'> = {
5
5
  value: string;
6
- label?: string | JSX.Element;
6
+ label?: T extends 'default' ? string | JSX.Element : T;
7
7
  disabled?: boolean;
8
8
  id?: string;
9
9
  };
10
- export type TogglerGroupOptions = Array<TogglerGroupOption>;
10
+ export type TogglerGroupOptions<T = 'default'> = Array<TogglerGroupOption<T>>;
11
11
  type RestrictedInputProps = Omit<HTMLProps<'input'>, 'type' | 'value' | 'defaultValue' | 'checked' | 'defaultChecked' | 'className' | 'id' | 'name' | 'children'>;
12
- export type TogglerGroupProps = {
13
- options: TogglerGroupOptions;
12
+ export type TogglerGroupProps<T = 'default'> = {
13
+ options: Array<string> | TogglerGroupOptions<T>;
14
14
  className?: string;
15
- name: string;
15
+ name?: string;
16
16
  disabled?: boolean | ReadonlyArray<number>;
17
17
  inputProps?: RestrictedInputProps;
18
18
  onSelected?: (payload: {
19
+ /** The value of being selected/updated */
19
20
  value: string;
21
+ /** The new checked state of the selected value */
20
22
  checked: boolean;
21
- option: TogglerGroupOption;
23
+ /** The option object being selected */
24
+ option: TogglerGroupOption<T>;
25
+ /** The updated value array */
22
26
  selectedValues: Array<string>;
23
27
  }) => void;
24
28
  } & Omit<FormFieldInputProps, 'disabled'>;
@@ -2,14 +2,22 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TogglerGroup = void 0;
4
4
  const tslib_1 = require("tslib");
5
- const react_1 = tslib_1.__importDefault(require("react"));
5
+ const react_1 = tslib_1.__importStar(require("react"));
6
+ const hooks_1 = require("@hugsmidjan/react/hooks");
6
7
  const getBemClass_1 = tslib_1.__importDefault(require("@hugsmidjan/react/utils/getBemClass"));
7
8
  const utils_js_1 = require("../utils.js");
8
9
  const TogglerGroup = (props) => {
9
10
  const {
10
11
  // id,
11
- className, bem, name, disabled, Toggler, onSelected, options, isRadio, inputProps = {}, } = props;
12
+ className, bem, disabled, readOnly, Toggler, onSelected, isRadio, inputProps = {}, } = props;
12
13
  const [values, setValues] = (0, utils_js_1.useMixedControlState)(props, 'value', []);
14
+ const name = (0, hooks_1.useDomid)(props.name);
15
+ const options = (0, react_1.useMemo)(() => {
16
+ const _options = props.options;
17
+ return typeof _options[0] === 'string'
18
+ ? _options.map((option) => ({ value: option }))
19
+ : _options;
20
+ }, [props.options]);
13
21
  return (react_1.default.createElement("ul", { className: (0, getBemClass_1.default)(bem, null, className), role: "group", "aria-labelledby": props['aria-labelledby'], "aria-describedby": props['aria-describedby'], "aria-required": props.required }, options.map((option, i) => {
14
22
  const isDisabled = option.disabled != null
15
23
  ? option.disabled
@@ -27,7 +35,7 @@ const TogglerGroup = (props) => {
27
35
  }
28
36
  setValues(selectedValues);
29
37
  onSelected && onSelected({ value, checked, option, selectedValues });
30
- }, disabled: isDisabled, "aria-invalid": props['aria-invalid'], checked: isChecked })));
38
+ }, disabled: isDisabled, readOnly: readOnly, "aria-invalid": props['aria-invalid'], checked: isChecked })));
31
39
  })));
32
40
  };
33
41
  exports.TogglerGroup = TogglerGroup;
@@ -3,9 +3,9 @@ import { BemPropsModifier } from '@hugsmidjan/react/types';
3
3
  import { FormFieldGroupWrappingProps } from '../FormField.js';
4
4
  import { TogglerGroupOption, TogglerGroupOptions, TogglerGroupProps } from './_TogglerGroup.js';
5
5
  import { TogglerInputProps } from './_TogglerInput.js';
6
- export type TogglerGroupFieldProps = {
6
+ export type TogglerGroupFieldProps<T = 'default'> = {
7
7
  className?: string;
8
- } & FormFieldGroupWrappingProps & TogglerGroupProps;
8
+ } & Omit<FormFieldGroupWrappingProps, 'disabled'> & TogglerGroupProps<T>;
9
9
  type _TogglerGroupFieldProps = {
10
10
  Toggler: (props: TogglerInputProps) => ReactElement;
11
11
  isRadio?: true;
@@ -13,7 +13,7 @@ type _TogglerGroupFieldProps = {
13
13
  defaultValue?: string | ReadonlyArray<string>;
14
14
  bem: string;
15
15
  } & BemPropsModifier;
16
- export type TogglerGroupFieldOption = TogglerGroupOption;
17
- export type TogglerGroupFieldOptions = TogglerGroupOptions;
16
+ export type TogglerGroupFieldOption<T = 'default'> = TogglerGroupOption<T>;
17
+ export type TogglerGroupFieldOptions<T = 'default'> = TogglerGroupOptions<T>;
18
18
  export declare const TogglerGroupField: (props: TogglerGroupFieldProps & _TogglerGroupFieldProps) => JSX.Element;
19
19
  export {};
@@ -14,8 +14,8 @@ const TogglerGroupField = (props) => {
14
14
  : typeof defaultValue === 'string'
15
15
  ? [defaultValue]
16
16
  : defaultValue, [defaultValue]);
17
- return (react_1.default.createElement(FormField_js_1.default, { className: (0, getBemClass_1.default)(bem, modifier, className), group: true, label: label, LabelTag: LabelTag, assistText: assistText, hideLabel: hideLabel, disabled: disabled, readOnly: readOnly, invalid: invalid, errorMessage: errorMessage, required: required, reqText: reqText, id: id, renderInput: (className, inputProps) => {
18
- return (react_1.default.createElement(_TogglerGroup_js_1.TogglerGroup, Object.assign({ bem: className.options }, inputProps, togglerGroupProps, { value: _value, defaultValue: _defaultValue, Toggler: Toggler })));
17
+ return (react_1.default.createElement(FormField_js_1.default, { className: (0, getBemClass_1.default)(bem, modifier, className), group: true, label: label, LabelTag: LabelTag, assistText: assistText, hideLabel: hideLabel, disabled: !!disabled, readOnly: readOnly, invalid: invalid, errorMessage: errorMessage, required: required, reqText: reqText, id: id, renderInput: (className, inputProps) => {
18
+ return (react_1.default.createElement(_TogglerGroup_js_1.TogglerGroup, Object.assign({ bem: className.options }, inputProps, togglerGroupProps, { disabled: disabled, value: _value, defaultValue: _defaultValue, Toggler: Toggler })));
19
19
  } }));
20
20
  };
21
21
  exports.TogglerGroupField = TogglerGroupField;
@@ -2,7 +2,6 @@ import { BemPropsModifier } from '@hugsmidjan/react/types';
2
2
  export type TogglerInputProps = {
3
3
  label: string | JSX.Element;
4
4
  children?: never;
5
- Wrapper?: 'div' | 'li';
6
5
  invalid?: boolean;
7
6
  /** Hidden label prefix text to indicate that the field is required.
8
7
  *
@@ -13,6 +12,9 @@ export type TogglerInputProps = {
13
12
  * */
14
13
  reqText?: string | false;
15
14
  errorMessage?: string | JSX.Element;
15
+ Wrapper?: 'div' | 'li';
16
+ wrapperProps?: JSX.IntrinsicElements['div'];
17
+ inputProps?: JSX.IntrinsicElements['input'];
16
18
  } & BemPropsModifier & Omit<JSX.IntrinsicElements['input'], 'type'>;
17
19
  type _TogglerInputProps = {
18
20
  bem: string;
@@ -6,22 +6,25 @@ const react_1 = tslib_1.__importDefault(require("react"));
6
6
  const hooks_1 = require("@hugsmidjan/react/hooks");
7
7
  const getBemClass_1 = tslib_1.__importDefault(require("@hugsmidjan/react/utils/getBemClass"));
8
8
  const TogglerInput = (props) => {
9
- const { bem, modifier, className, label, invalid, errorMessage, Wrapper = 'div', required, reqText, type, id, innerWrap } = props, inputProps = tslib_1.__rest(props, ["bem", "modifier", "className", "label", "invalid", "errorMessage", "Wrapper", "required", "reqText", "type", "id", "innerWrap"]);
9
+ const { bem, modifier, className, label, invalid, errorMessage, Wrapper = 'div', required, reqText, type, id, innerWrap, wrapperProps, inputProps } = props, restInputProps = tslib_1.__rest(props, ["bem", "modifier", "className", "label", "invalid", "errorMessage", "Wrapper", "required", "reqText", "type", "id", "innerWrap", "wrapperProps", "inputProps"]);
10
10
  const domid = (0, hooks_1.useDomid)(id);
11
11
  const errorId = errorMessage && 'error' + domid;
12
12
  const reqStar = required && reqText !== false && (react_1.default.createElement("abbr", { className: bem + '__label__reqstar',
13
13
  // TODO: add mo-better i18n thinking
14
14
  title: (reqText || 'Þarf að haka í') + ': ' }, "*"));
15
+ const readOnly = restInputProps.readOnly || (inputProps || {}).readOnly;
15
16
  const labelContent = (react_1.default.createElement(react_1.default.Fragment, null,
16
17
  ' ',
17
18
  reqStar,
18
19
  " ",
19
20
  label,
20
21
  ' '));
21
- return (react_1.default.createElement(Wrapper, { className: (0, getBemClass_1.default)(bem, modifier, className) },
22
- react_1.default.createElement("input", Object.assign({ className: bem + '__input', type: type, id: domid, "aria-invalid": invalid || !!errorMessage || undefined, "aria-describedby": errorId }, inputProps)),
22
+ return (react_1.default.createElement(Wrapper, Object.assign({}, wrapperProps, { className: (0, getBemClass_1.default)(bem, modifier, className) }),
23
+ react_1.default.createElement("input", Object.assign({ className: bem + '__input', type: type, id: domid, "aria-invalid": invalid || !!errorMessage || undefined, "aria-describedby": errorId }, restInputProps, inputProps, (readOnly && { disabled: true }))),
23
24
  ' ',
24
- react_1.default.createElement("label", { className: bem + '__label', htmlFor: domid }, innerWrap ? (react_1.default.createElement("span", { className: bem + '__label__wrap' }, labelContent)) : (labelContent)),
25
+ react_1.default.createElement("label", { className: bem + '__label', htmlFor: domid },
26
+ innerWrap ? (react_1.default.createElement("span", { className: bem + '__label__wrap' }, labelContent)) : (labelContent),
27
+ readOnly && (react_1.default.createElement("input", { type: "hidden", name: restInputProps.name, value: restInputProps.value }))),
25
28
  errorMessage && (react_1.default.createElement("div", { className: bem + '__error', id: errorId }, errorMessage))));
26
29
  };
27
30
  exports.TogglerInput = TogglerInput;