@reykjavik/hanna-react 0.10.127 → 0.10.129

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@
4
4
 
5
5
  - ... <!-- Add new lines here. -->
6
6
 
7
+ ## 0.10.129
8
+
9
+ _2024-07-16_
10
+
11
+ - feat: Add prop `childrenHTML` to `Carousel` for Real Nasty Dirty Work™ —
12
+ primary use case is progressive enhancement of static HTML.
13
+ - feat: Gracefully catch (and log) errors in Sprinkles' lifecycle methods
14
+
15
+ ## 0.10.128
16
+
17
+ _2024-07-01_
18
+
19
+ - feat: Add prop `renderItemSubContent` to all checkbox and radio group
20
+ compnents — to support more advanced UI patterns.
21
+ - feat: Add prop `allow` to `IframeBlock`
22
+
7
23
  ## 0.10.127
8
24
 
9
25
  _2024-06-27_
package/IframeBlock.d.ts CHANGED
@@ -9,6 +9,7 @@ export type IframeBlockProps = {
9
9
  framed?: boolean;
10
10
  compact?: boolean;
11
11
  align?: 'right';
12
+ allow?: JSX.IntrinsicElements['iframe']['allow'];
12
13
  } & EitherObj<{
13
14
  /** Fixed height, no auto-resizing of the iframe */
14
15
  height: number;
package/IframeBlock.js CHANGED
@@ -14,11 +14,11 @@ const iframe_resizer_react_1 = tslib_1.__importDefault(require("iframe-resizer-r
14
14
  * ```
15
15
  */
16
16
  const IframeBlock = (props) => {
17
- const { title, src, framed, compact, align } = props;
17
+ const { title, src, framed, compact, align, allow } = props;
18
18
  const className = (0, classUtils_1.modifiedClass)('IframeBlock', [framed && 'framed', compact && 'compact', align === 'right' && `align--${align}`], (props.wrapperProps || {}).className);
19
19
  if (typeof props.height === 'number') {
20
20
  const { wrapperProps, scrolling, height } = props;
21
- return (react_1.default.createElement("iframe", Object.assign({}, wrapperProps, { className: className, title: title, src: src,
21
+ return (react_1.default.createElement("iframe", Object.assign({}, wrapperProps, { className: className, title: title, src: src, allow: allow,
22
22
  // hidden tiger: pass negative height to disable iframe-resizer but not set height explicitly
23
23
  // (Silly hack, don't rely on this)
24
24
  height: height < 0 ? undefined : height,
@@ -20,6 +20,12 @@ export type CarouselProps<I extends Record<string, unknown> = Record<string, nev
20
20
  * `<Fragment />` or some such.
21
21
  */
22
22
  itemCount?: number;
23
+ }, {
24
+ /**
25
+ * HTML content that should be dangerously inserted directly into the
26
+ * `__itemlist` element
27
+ */
28
+ childrenHTML: string;
23
29
  }> & WrapperElmProps & DeprecatedSeenProp;
24
30
  type AbstractCarouselProps<I extends Record<string, unknown> = Record<string, unknown>, P extends Record<string, unknown> | undefined = Record<string, unknown>> = CarouselProps<I, P> & BemProps & {
25
31
  title?: string;
@@ -24,17 +24,35 @@ const scrollXBy = (elm, deltaX) => {
24
24
  };
25
25
  // eslint-disable-next-line complexity
26
26
  const AbstractCarousel = (props) => {
27
- const { title, items = [], Component, ComponentProps, bem = 'Carousel', modifier, ssr, className, wrapperProps, } = props;
27
+ const { title, items = [], Component, ComponentProps, childrenHTML, bem = 'Carousel', modifier, ssr, className, wrapperProps, } = props;
28
+ const isBrowser = (0, utils_js_1.useIsBrowserSide)(ssr);
28
29
  const children = !props.children
29
30
  ? undefined
30
31
  : Array.isArray(props.children)
31
32
  ? props.children.filter(hanna_utils_1.notNully)
32
33
  : [props.children];
33
34
  const [leftOffset, setLeftOffset] = (0, react_1.useState)();
34
- const itemCount = props.itemCount != null ? props.itemCount : (children || items).length;
35
+ const htmlChildrenCount = (0, react_1.useMemo)(() => {
36
+ if (!(childrenHTML === null || childrenHTML === void 0 ? void 0 : childrenHTML.trim())) {
37
+ return;
38
+ }
39
+ if (!isBrowser) {
40
+ return 1; // Return arbitrary non-zero count to avoid early return below.
41
+ // During SSR (and initial hydration render) we want to render the
42
+ // Carousel component, even though we don't know the exact number of
43
+ // items yet.
44
+ }
45
+ const div = document.createElement('div');
46
+ div.innerHTML = childrenHTML;
47
+ return div.children.length;
48
+ }, [childrenHTML, isBrowser]);
49
+ const itemCount = props.itemCount != null
50
+ ? props.itemCount
51
+ : childrenHTML
52
+ ? htmlChildrenCount
53
+ : (children || items).length;
35
54
  const listRef = (0, react_1.useRef)(null);
36
55
  const [activeItem, setActiveItem] = (0, react_1.useState)(0);
37
- const isBrowser = (0, utils_js_1.useIsBrowserSide)(ssr);
38
56
  // Since listElm gets unmounted and remounted based on isBrowser, we
39
57
  // wait for isBrowser is true before setting scroll and resize events.
40
58
  const listElm = isBrowser && listRef.current;
@@ -91,10 +109,12 @@ const AbstractCarousel = (props) => {
91
109
  }
92
110
  const itemList = (react_1.default.createElement("div", { className: `${bem}__itemlist`, style: leftOffset
93
111
  ? { '--Carousel--leftOffset': `${leftOffset}px` }
94
- : undefined, "data-scroll-snapping": leftOffset ? 'true' : undefined, ref: listRef }, children ||
95
- items.map((item, i) => (
96
- // @ts-expect-error (Can't be arsed...)
97
- react_1.default.createElement(Component, Object.assign({ key: i }, ComponentProps, item))))));
112
+ : undefined, "data-scroll-snapping": leftOffset ? 'true' : undefined, ref: listRef, dangerouslySetInnerHTML: childrenHTML ? { __html: childrenHTML } : undefined }, childrenHTML
113
+ ? undefined
114
+ : children ||
115
+ items.map((item, i) => (
116
+ // @ts-expect-error (Can't be arsed...)
117
+ react_1.default.createElement(Component, Object.assign({ key: i }, ComponentProps, item))))));
98
118
  return (react_1.default.createElement("div", Object.assign({}, wrapperProps, { className: (0, classUtils_1.modifiedClass)(bem, modifier,
99
119
  // Prefer `className` over `wrapperProps.className`
100
120
  className || (wrapperProps || {}).className), "data-sprinkled": isBrowser }),
@@ -1,3 +1,4 @@
1
+ import { ReactNode } from 'react';
1
2
  import { FormFieldInputProps } from '../FormField.js';
2
3
  import { HTMLProps } from '../utils.js';
3
4
  import { TogglerInputProps } from './_TogglerInput.js';
@@ -25,6 +26,12 @@ export type TogglerGroupProps<T = 'default', Extras = {}> = {
25
26
  /** The updated value array */
26
27
  selectedValues: Array<string>;
27
28
  }) => void;
29
+ /**
30
+ * Render function that allows inserting content below each toggler
31
+ * (checkbox/radio) element, depending on the item's checked status
32
+ * (or other external state)
33
+ */
34
+ renderItemSubContent?: (option: TogglerGroupOption<T, Extras>, checked: boolean) => ReactNode;
28
35
  } & Omit<FormFieldInputProps, 'disabled'>;
29
36
  type _TogglerGroupProps = {
30
37
  bem: string;
@@ -9,7 +9,7 @@ const useDomid_js_1 = require("../utils/useDomid.js");
9
9
  const TogglerGroup = (props) => {
10
10
  const {
11
11
  // id,
12
- className, bem, disabled, readOnly, Toggler, onSelected, isRadio, inputProps = {}, } = props;
12
+ className, bem, disabled, readOnly, Toggler, onSelected, isRadio, renderItemSubContent, inputProps = {}, } = props;
13
13
  const [values, setValues] = (0, utils_js_1.useMixedControlState)(props, 'value', []);
14
14
  const name = (0, useDomid_js_1.useDomid)(props.name);
15
15
  const options = (0, react_1.useMemo)(() => {
@@ -25,7 +25,7 @@ const TogglerGroup = (props) => {
25
25
  ? disabled.includes(i)
26
26
  : disabled;
27
27
  const isChecked = values.includes(option.value);
28
- return (react_1.default.createElement(Toggler, Object.assign({ key: i }, inputProps, { className: `${bem}__item`, name: name, Wrapper: "li" }, option, { label: option.label || option.value, onChange: (e) => {
28
+ const togglerProps = Object.assign(Object.assign(Object.assign(Object.assign({}, inputProps), { name: name }), option), { label: option.label || option.value, onChange: (e) => {
29
29
  inputProps.onChange && inputProps.onChange(e);
30
30
  const { value } = option;
31
31
  const checked = e.currentTarget.checked;
@@ -35,7 +35,13 @@ const TogglerGroup = (props) => {
35
35
  }
36
36
  setValues(selectedValues);
37
37
  onSelected && onSelected({ value, checked, option, selectedValues });
38
- }, disabled: isDisabled, readOnly: readOnly, "aria-invalid": props['aria-invalid'], checked: isChecked })));
38
+ }, disabled: isDisabled, readOnly: readOnly, 'aria-invalid': props['aria-invalid'], checked: isChecked });
39
+ if (renderItemSubContent) {
40
+ return (react_1.default.createElement("li", { key: i, className: `${bem}__item` },
41
+ react_1.default.createElement(Toggler, Object.assign({}, togglerProps)),
42
+ renderItemSubContent(option, isChecked)));
43
+ }
44
+ return (react_1.default.createElement(Toggler, Object.assign({ key: i, className: `${bem}__item`, Wrapper: "li" }, togglerProps)));
39
45
  })));
40
46
  };
41
47
  exports.TogglerGroup = TogglerGroup;
@@ -9,6 +9,7 @@ export type IframeBlockProps = {
9
9
  framed?: boolean;
10
10
  compact?: boolean;
11
11
  align?: 'right';
12
+ allow?: JSX.IntrinsicElements['iframe']['allow'];
12
13
  } & EitherObj<{
13
14
  /** Fixed height, no auto-resizing of the iframe */
14
15
  height: number;
@@ -10,11 +10,11 @@ import IframeResizer from 'iframe-resizer-react';
10
10
  * ```
11
11
  */
12
12
  export const IframeBlock = (props) => {
13
- const { title, src, framed, compact, align } = props;
13
+ const { title, src, framed, compact, align, allow } = props;
14
14
  const className = modifiedClass('IframeBlock', [framed && 'framed', compact && 'compact', align === 'right' && `align--${align}`], (props.wrapperProps || {}).className);
15
15
  if (typeof props.height === 'number') {
16
16
  const { wrapperProps, scrolling, height } = props;
17
- return (React.createElement("iframe", Object.assign({}, wrapperProps, { className: className, title: title, src: src,
17
+ return (React.createElement("iframe", Object.assign({}, wrapperProps, { className: className, title: title, src: src, allow: allow,
18
18
  // hidden tiger: pass negative height to disable iframe-resizer but not set height explicitly
19
19
  // (Silly hack, don't rely on this)
20
20
  height: height < 0 ? undefined : height,
@@ -20,6 +20,12 @@ export type CarouselProps<I extends Record<string, unknown> = Record<string, nev
20
20
  * `<Fragment />` or some such.
21
21
  */
22
22
  itemCount?: number;
23
+ }, {
24
+ /**
25
+ * HTML content that should be dangerously inserted directly into the
26
+ * `__itemlist` element
27
+ */
28
+ childrenHTML: string;
23
29
  }> & WrapperElmProps & DeprecatedSeenProp;
24
30
  type AbstractCarouselProps<I extends Record<string, unknown> = Record<string, unknown>, P extends Record<string, unknown> | undefined = Record<string, unknown>> = CarouselProps<I, P> & BemProps & {
25
31
  title?: string;
@@ -20,17 +20,35 @@ const scrollXBy = (elm, deltaX) => {
20
20
  };
21
21
  // eslint-disable-next-line complexity
22
22
  export const AbstractCarousel = (props) => {
23
- const { title, items = [], Component, ComponentProps, bem = 'Carousel', modifier, ssr, className, wrapperProps, } = props;
23
+ const { title, items = [], Component, ComponentProps, childrenHTML, bem = 'Carousel', modifier, ssr, className, wrapperProps, } = props;
24
+ const isBrowser = useIsBrowserSide(ssr);
24
25
  const children = !props.children
25
26
  ? undefined
26
27
  : Array.isArray(props.children)
27
28
  ? props.children.filter(notNully)
28
29
  : [props.children];
29
30
  const [leftOffset, setLeftOffset] = useState();
30
- const itemCount = props.itemCount != null ? props.itemCount : (children || items).length;
31
+ const htmlChildrenCount = useMemo(() => {
32
+ if (!(childrenHTML === null || childrenHTML === void 0 ? void 0 : childrenHTML.trim())) {
33
+ return;
34
+ }
35
+ if (!isBrowser) {
36
+ return 1; // Return arbitrary non-zero count to avoid early return below.
37
+ // During SSR (and initial hydration render) we want to render the
38
+ // Carousel component, even though we don't know the exact number of
39
+ // items yet.
40
+ }
41
+ const div = document.createElement('div');
42
+ div.innerHTML = childrenHTML;
43
+ return div.children.length;
44
+ }, [childrenHTML, isBrowser]);
45
+ const itemCount = props.itemCount != null
46
+ ? props.itemCount
47
+ : childrenHTML
48
+ ? htmlChildrenCount
49
+ : (children || items).length;
31
50
  const listRef = useRef(null);
32
51
  const [activeItem, setActiveItem] = useState(0);
33
- const isBrowser = useIsBrowserSide(ssr);
34
52
  // Since listElm gets unmounted and remounted based on isBrowser, we
35
53
  // wait for isBrowser is true before setting scroll and resize events.
36
54
  const listElm = isBrowser && listRef.current;
@@ -87,10 +105,12 @@ export const AbstractCarousel = (props) => {
87
105
  }
88
106
  const itemList = (React.createElement("div", { className: `${bem}__itemlist`, style: leftOffset
89
107
  ? { '--Carousel--leftOffset': `${leftOffset}px` }
90
- : undefined, "data-scroll-snapping": leftOffset ? 'true' : undefined, ref: listRef }, children ||
91
- items.map((item, i) => (
92
- // @ts-expect-error (Can't be arsed...)
93
- React.createElement(Component, Object.assign({ key: i }, ComponentProps, item))))));
108
+ : undefined, "data-scroll-snapping": leftOffset ? 'true' : undefined, ref: listRef, dangerouslySetInnerHTML: childrenHTML ? { __html: childrenHTML } : undefined }, childrenHTML
109
+ ? undefined
110
+ : children ||
111
+ items.map((item, i) => (
112
+ // @ts-expect-error (Can't be arsed...)
113
+ React.createElement(Component, Object.assign({ key: i }, ComponentProps, item))))));
94
114
  return (React.createElement("div", Object.assign({}, wrapperProps, { className: modifiedClass(bem, modifier,
95
115
  // Prefer `className` over `wrapperProps.className`
96
116
  className || (wrapperProps || {}).className), "data-sprinkled": isBrowser }),
@@ -1,3 +1,4 @@
1
+ import { ReactNode } from 'react';
1
2
  import { FormFieldInputProps } from '../FormField.js';
2
3
  import { HTMLProps } from '../utils.js';
3
4
  import { TogglerInputProps } from './_TogglerInput.js';
@@ -25,6 +26,12 @@ export type TogglerGroupProps<T = 'default', Extras = {}> = {
25
26
  /** The updated value array */
26
27
  selectedValues: Array<string>;
27
28
  }) => void;
29
+ /**
30
+ * Render function that allows inserting content below each toggler
31
+ * (checkbox/radio) element, depending on the item's checked status
32
+ * (or other external state)
33
+ */
34
+ renderItemSubContent?: (option: TogglerGroupOption<T, Extras>, checked: boolean) => ReactNode;
28
35
  } & Omit<FormFieldInputProps, 'disabled'>;
29
36
  type _TogglerGroupProps = {
30
37
  bem: string;
@@ -5,7 +5,7 @@ import { useDomid } from '../utils/useDomid.js';
5
5
  export const TogglerGroup = (props) => {
6
6
  const {
7
7
  // id,
8
- className, bem, disabled, readOnly, Toggler, onSelected, isRadio, inputProps = {}, } = props;
8
+ className, bem, disabled, readOnly, Toggler, onSelected, isRadio, renderItemSubContent, inputProps = {}, } = props;
9
9
  const [values, setValues] = useMixedControlState(props, 'value', []);
10
10
  const name = useDomid(props.name);
11
11
  const options = useMemo(() => {
@@ -21,7 +21,7 @@ export const TogglerGroup = (props) => {
21
21
  ? disabled.includes(i)
22
22
  : disabled;
23
23
  const isChecked = values.includes(option.value);
24
- return (React.createElement(Toggler, Object.assign({ key: i }, inputProps, { className: `${bem}__item`, name: name, Wrapper: "li" }, option, { label: option.label || option.value, onChange: (e) => {
24
+ const togglerProps = Object.assign(Object.assign(Object.assign(Object.assign({}, inputProps), { name: name }), option), { label: option.label || option.value, onChange: (e) => {
25
25
  inputProps.onChange && inputProps.onChange(e);
26
26
  const { value } = option;
27
27
  const checked = e.currentTarget.checked;
@@ -31,6 +31,12 @@ export const TogglerGroup = (props) => {
31
31
  }
32
32
  setValues(selectedValues);
33
33
  onSelected && onSelected({ value, checked, option, selectedValues });
34
- }, disabled: isDisabled, readOnly: readOnly, "aria-invalid": props['aria-invalid'], checked: isChecked })));
34
+ }, disabled: isDisabled, readOnly: readOnly, 'aria-invalid': props['aria-invalid'], checked: isChecked });
35
+ if (renderItemSubContent) {
36
+ return (React.createElement("li", { key: i, className: `${bem}__item` },
37
+ React.createElement(Toggler, Object.assign({}, togglerProps)),
38
+ renderItemSubContent(option, isChecked)));
39
+ }
40
+ return (React.createElement(Toggler, Object.assign({ key: i, className: `${bem}__item`, Wrapper: "li" }, togglerProps)));
35
41
  })));
36
42
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reykjavik/hanna-react",
3
- "version": "0.10.127",
3
+ "version": "0.10.129",
4
4
  "author": "Reykjavík (http://www.reykjavik.is)",
5
5
  "contributors": [
6
6
  "Hugsmiðjan ehf (http://www.hugsmidjan.is)",