@reykjavik/hanna-react 0.10.60 → 0.10.63

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/Alert.js CHANGED
@@ -8,7 +8,7 @@ const getBemClass_1 = tslib_1.__importDefault(require("@hugsmidjan/react/utils/g
8
8
  const i18n_1 = require("@reykjavik/hanna-utils/i18n");
9
9
  const _Button_1 = tslib_1.__importDefault(require("./_abstract/_Button"));
10
10
  const env_1 = require("./utils/env");
11
- // FIXME: Eventually import from @reykjavik/hanna-styles/constants
11
+ // FIXME: Eventually import from @reykjavik/hanna-css
12
12
  const AlertCloseTransitionDuration = 400;
13
13
  const useAutoClosing = (autoClose) => {
14
14
  const [temp, setTemp] = (0, react_1.useState)(0);
@@ -40,7 +40,7 @@ exports.alertTypes = {
40
40
  };
41
41
  const Alert = (props) => {
42
42
  const { type, childrenHTML, children, onClose, closeUrl, closable = !!(onClose || closeUrl != null), ssr, onClosed, } = props;
43
- const autoClose = props.autoClose && props.autoClose > 0 ? props.autoClose : 0;
43
+ const autoClose = Math.max(props.autoClose || 0, 0);
44
44
  const closing = (0, react_1.useRef)();
45
45
  const [open, setOpen] = (0, react_1.useState)(!!ssr);
46
46
  const isBrowser = (0, hooks_1.useIsBrowserSide)(ssr);
@@ -51,7 +51,7 @@ const Alert = (props) => {
51
51
  });
52
52
  const closeAlert = (0, react_1.useCallback)((event) => {
53
53
  const ret = onClose &&
54
- // @ts-expect-error (@deprecated `event` parameter will be removed in v0.9)
54
+ // @ts-expect-error (@deprecated `event` parameter will be removed in v0.11)
55
55
  onClose(event);
56
56
  if (ret !== false) {
57
57
  setOpen(false);
package/CHANGELOG.md CHANGED
@@ -4,6 +4,28 @@
4
4
 
5
5
  - ... <!-- Add new lines here. -->
6
6
 
7
+ ## 0.10.63
8
+
9
+ _2022-08-29_
10
+
11
+ - fix: `FileInput` in single-file mode doesn't report deleted files on add
12
+ - fix: Hide `Carousel` mouse-cursor scroll controls at start/end positions
13
+ - fix: Pass `id` and other HTML props to static (span) `TagPill`s
14
+
15
+ ## 0.10.62
16
+
17
+ _2022-08-23_
18
+
19
+ - feat: Add `diff` object to `FileInput`'s `onFilesUpdated` callback signature
20
+
21
+ ## 0.10.61
22
+
23
+ _2022-08-11_
24
+
25
+ - feat: Add mouse-cursor scroll controls for `Carousel`-related components —
26
+ Remove mousewheel hijacking behavior.
27
+ - fix: `startSeen` hiding components with `html.before-sprinkling` present
28
+
7
29
  ## 0.10.60
8
30
 
9
31
  _2022-06-24_
@@ -22,6 +44,7 @@ _2022-06-20_
22
44
  - feat: Add prop `small` to `ArticleMeta`
23
45
  - fix: Suppress redundant className `.Heading--normal`
24
46
  - feat: Add prop `focalPoint` to all `ImageProps`
47
+ - feat: Add `EffectProp.effectType` prop value `none` for opting out
25
48
 
26
49
  ## 0.10.56 – 0.10.58
27
50
 
@@ -213,6 +236,7 @@ _2022-03-09_
213
236
 
214
237
  - feat: Add props `autoClose` and `onClosed` to `Alert` — use `onClosed` when
215
238
  removing the Alert from the DOM
239
+ - feat: Deprecate the `event` argument for `Alert`'s `onClose` callback
216
240
  - feat: Add prop `align?: 'right'` to `ButtonBar`
217
241
  - fix: Make `FileInput` image previews (thumbnails) more resilient overall
218
242
  - fix: Plug memory-leaks caused by `FileInput` image thumbnails.
@@ -0,0 +1,36 @@
1
+ export declare type CustomFile = {
2
+ preview?: string;
3
+ } & File;
4
+ /**
5
+ * Attaches a `preview` prop to file objects that don't already have a `preview` key defined
6
+ *
7
+ * The preview's value is either a data URI (for image-type files) or `undefined`
8
+ */
9
+ export declare const addPreview: (file: CustomFile) => void;
10
+ /**
11
+ * Revokes `preview` data URIs to avoid memory leaks
12
+ *
13
+ * (See: https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL)
14
+ */
15
+ export declare const releasePreview: (file: CustomFile) => void;
16
+ /**
17
+ * Small+stupid file size pretty-printer.
18
+ */
19
+ export declare const formatBytes: (bytes: number, decimals?: number) => string;
20
+ /**
21
+ * Figures out how to handle adding files to a FileInput
22
+ * Which files to retaine, which too delete, and
23
+ * what the updated fileList should look like.
24
+ *
25
+ *
26
+ */
27
+ export declare const getFileListUpdate: (oldFileList: Array<File>, added: Array<File>, replaceMode: boolean) => {
28
+ fileList: File[];
29
+ diff: {
30
+ added: File[];
31
+ deleted: File[];
32
+ } | {
33
+ added: File[];
34
+ deleted?: undefined;
35
+ };
36
+ };
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getFileListUpdate = exports.formatBytes = exports.releasePreview = exports.addPreview = void 0;
4
+ /**
5
+ * Attaches a `preview` prop to file objects that don't already have a `preview` key defined
6
+ *
7
+ * The preview's value is either a data URI (for image-type files) or `undefined`
8
+ */
9
+ const addPreview = (file) => {
10
+ if (!('preview' in file)) {
11
+ file.preview = file.type.includes('image/') ? URL.createObjectURL(file) : undefined;
12
+ }
13
+ };
14
+ exports.addPreview = addPreview;
15
+ /**
16
+ * Revokes `preview` data URIs to avoid memory leaks
17
+ *
18
+ * (See: https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL)
19
+ */
20
+ const releasePreview = (file) => {
21
+ file.preview && URL.revokeObjectURL(file.preview);
22
+ delete file.preview;
23
+ };
24
+ exports.releasePreview = releasePreview;
25
+ /**
26
+ * Small+stupid file size pretty-printer.
27
+ */
28
+ const formatBytes = (bytes, decimals = 2) => {
29
+ if (bytes === 0) {
30
+ return '0 Bytes';
31
+ }
32
+ const k = 1024;
33
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
34
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
35
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
36
+ };
37
+ exports.formatBytes = formatBytes;
38
+ /**
39
+ * Figures out how to handle adding files to a FileInput
40
+ * Which files to retaine, which too delete, and
41
+ * what the updated fileList should look like.
42
+ *
43
+ *
44
+ */
45
+ const getFileListUpdate = (oldFileList, added,
46
+ /**
47
+ * `replaceMode: true` is the default "single-file" input behavior.
48
+ *
49
+ * Pass `false` to this argument when the "multiple" prop is true.
50
+ */
51
+ replaceMode) => {
52
+ const deleted = replaceMode ? oldFileList : [];
53
+ const retained = [];
54
+ if (!replaceMode) {
55
+ oldFileList.forEach((oldFile) => {
56
+ if (added.find(({ name }) => name === oldFile.name)) {
57
+ deleted.push(oldFile);
58
+ }
59
+ else {
60
+ retained.push(oldFile);
61
+ }
62
+ });
63
+ }
64
+ return {
65
+ fileList: retained.concat(added),
66
+ diff: deleted.length ? { added, deleted } : { added },
67
+ };
68
+ };
69
+ exports.getFileListUpdate = getFileListUpdate;
package/FileInput.d.ts CHANGED
@@ -1,15 +1,30 @@
1
1
  import { FormFieldWrappingProps } from './FormField';
2
- declare type dropzonePropsProps = {
2
+ declare type DropzonePropsProps = {
3
3
  accept?: string;
4
4
  multiple?: boolean;
5
5
  };
6
6
  export declare type FileInputProps = {
7
- dropzoneProps: dropzonePropsProps;
7
+ dropzoneProps: DropzonePropsProps;
8
8
  dropzoneText: string | JSX.Element;
9
9
  showFileSize?: boolean;
10
10
  showImagePreviews?: boolean;
11
11
  removeFileText: string;
12
- onFilesUpdated?: (files: Array<File>) => void;
12
+ onFilesUpdated?: (
13
+ /** Updated, full list of Files. */
14
+ files: Array<File>,
15
+ /** Information about which Files were added or removed during with this update.
16
+ *
17
+ * NOTE: When a diff contains both added and deleted files, this indicates a
18
+ * name-conflict occurred — i.e. one of the added files has a name that
19
+ * existed in the old file list.
20
+ * In such cases the deletion is more implicit than explicit, and depending
21
+ * on the circumstances, you MIGHT wish to either warn the user, rename
22
+ * one of the files, instead of overwriting/deleting the older file, etc.
23
+ */
24
+ diff: {
25
+ deleted?: Array<File>;
26
+ added?: Array<File>;
27
+ }) => void;
13
28
  name?: string;
14
29
  value?: ReadonlyArray<File>;
15
30
  } & FormFieldWrappingProps;
package/FileInput.js CHANGED
@@ -5,26 +5,8 @@ const react_1 = tslib_1.__importStar(require("react"));
5
5
  const react_dropzone_1 = require("react-dropzone"); // https://react-dropzone.js.org/#!/Dropzone
6
6
  const hooks_1 = require("@hugsmidjan/react/hooks");
7
7
  const getBemClass_1 = tslib_1.__importDefault(require("@hugsmidjan/react/utils/getBemClass"));
8
+ const FileInput_utils_1 = require("./FileInput/FileInput.utils");
8
9
  const FormField_1 = tslib_1.__importDefault(require("./FormField"));
9
- /**
10
- * Attaches a `preview` prop to file objects that don't already have a `preview` key defined
11
- *
12
- * The preview's value is either a data URI (for image-type files) or `undefined`
13
- */
14
- const addPreview = (file) => {
15
- if (!('preview' in file)) {
16
- file.preview = file.type.includes('image/') ? URL.createObjectURL(file) : undefined;
17
- }
18
- };
19
- /**
20
- * Revokes `preview` data URIs to avoid memory leaks
21
- *
22
- * (See: https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL)
23
- */
24
- const releasePreview = (file) => {
25
- file.preview && URL.revokeObjectURL(file.preview);
26
- delete file.preview;
27
- };
28
10
  const arrayToFileList = (arr) => {
29
11
  const fileList = new DataTransfer();
30
12
  arr.forEach((item) => {
@@ -32,29 +14,6 @@ const arrayToFileList = (arr) => {
32
14
  });
33
15
  return fileList.files;
34
16
  };
35
- const dedupeFilesArray = (files) => {
36
- const newArray = [];
37
- const found = {};
38
- files.forEach((file) => {
39
- if (!(file.name in found)) {
40
- newArray.push(file);
41
- found[file.name] = true;
42
- }
43
- else {
44
- releasePreview(file);
45
- }
46
- });
47
- return newArray;
48
- };
49
- const formatBytes = (bytes, decimals = 2) => {
50
- if (bytes === 0) {
51
- return '0 Bytes';
52
- }
53
- const k = 1024;
54
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
55
- const i = Math.floor(Math.log(bytes) / Math.log(k));
56
- return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
57
- };
58
17
  const FileInput = (props) => {
59
18
  const { className, id, label, hideLabel, dropzoneProps = { multiple: true }, dropzoneText, removeFileText, assistText, disabled, invalid, errorMessage, required, reqText, onFilesUpdated = () => undefined, showFileSize, showImagePreviews, value = [] } = props, inputElementProps = tslib_1.__rest(props, ["className", "id", "label", "hideLabel", "dropzoneProps", "dropzoneText", "removeFileText", "assistText", "disabled", "invalid", "errorMessage", "required", "reqText", "onFilesUpdated", "showFileSize", "showImagePreviews", "value"]);
60
19
  const domid = (0, hooks_1.useDomid)(id);
@@ -64,7 +23,7 @@ const FileInput = (props) => {
64
23
  const [isHover, setIsHover] = (0, react_1.useState)(false);
65
24
  const { getRootProps, getInputProps, isDragReject, inputRef } = (0, react_dropzone_1.useDropzone)(Object.assign({ onDrop: (acceptedFiles) => {
66
25
  acceptedFiles = acceptedFiles.map((file) => {
67
- addPreview(file);
26
+ (0, FileInput_utils_1.addPreview)(file);
68
27
  return file;
69
28
  });
70
29
  addFiles(acceptedFiles); // eslint-disable-line
@@ -90,30 +49,31 @@ const FileInput = (props) => {
90
49
  } }, dropzoneProps));
91
50
  // Add previews on incoming files
92
51
  // (NOTE: `addPreview` ignores files that already have preview.)
93
- files.forEach(addPreview);
52
+ files.forEach(FileInput_utils_1.addPreview);
94
53
  (0, react_1.useEffect)(() => () => {
95
54
  // Make sure to revoke the data uris on unmount to avoid memory leaks
96
- files.forEach(releasePreview);
55
+ files.forEach(FileInput_utils_1.releasePreview);
97
56
  }, [files]);
98
57
  const removeFile = (name) => {
99
58
  if (fileInput.current) {
59
+ const deleted = [];
100
60
  const newFileList = files.filter((file) => {
101
61
  if (file.name !== name) {
102
62
  return true;
103
63
  }
104
- releasePreview(file);
64
+ deleted.push(file);
65
+ (0, FileInput_utils_1.releasePreview)(file);
66
+ return false;
105
67
  });
106
68
  fileInput.current.files = arrayToFileList(newFileList);
107
- onFilesUpdated(newFileList);
69
+ onFilesUpdated(newFileList, { deleted });
108
70
  }
109
71
  };
110
- const addFiles = (filelist) => {
72
+ const addFiles = (added) => {
111
73
  if (fileInput.current) {
112
- const newFileList = dropzoneProps.multiple
113
- ? dedupeFilesArray(files.concat(filelist))
114
- : filelist;
115
- fileInput.current.files = arrayToFileList(newFileList);
116
- onFilesUpdated(newFileList);
74
+ const { fileList, diff } = (0, FileInput_utils_1.getFileListUpdate)(files, added, !dropzoneProps.multiple);
75
+ fileInput.current.files = arrayToFileList(fileList);
76
+ onFilesUpdated(fileList, diff);
117
77
  }
118
78
  if (inputRef.current) {
119
79
  // Empty on every add
@@ -130,7 +90,7 @@ const FileInput = (props) => {
130
90
  react_1.default.createElement("span", { className: "FileInput__filename" }, file.name),
131
91
  showFileSize && (react_1.default.createElement("small", { className: "FileInput__filesize" },
132
92
  " - (",
133
- formatBytes(file.size),
93
+ (0, FileInput_utils_1.formatBytes)(file.size),
134
94
  ")"))))));
135
95
  return (react_1.default.createElement(FormField_1.default, { className: (0, getBemClass_1.default)('FileInput', [dropzoneProps.multiple && 'multi'], className), label: label, id: domid + '-fake', LabelTag: "h4", assistText: assistText, hideLabel: hideLabel, disabled: disabled, invalid: invalid, errorMessage: errorMessage, required: required, reqText: reqText, renderInput: (className, inputProps /* , addFocusProps */) => {
136
96
  return (react_1.default.createElement("div", { className: className.control, ref: fileInputWrapper },
package/TagPill.js CHANGED
@@ -18,10 +18,11 @@ const TagPill = (props) => {
18
18
  removable &&
19
19
  isStatic &&
20
20
  !onRemove &&
21
- console.warn('static (non-button) `TagPill`s must not be removable');
21
+ console.warn('Removable static (non-button) `TagPill`s ' +
22
+ 'must have an `onRemove` handler defined');
22
23
  const modifiers = [modifier, large && 'large', colors[color]];
23
24
  const removeBtn = removable && (react_1.default.createElement("button", { className: "TagPill__remove", onClick: onRemove && (() => onRemove()), "aria-label": removeLabelLong, type: "button" }, removeLabel));
24
- return isStatic ? (react_1.default.createElement("span", { className: (0, getBemClass_1.default)('TagPill', modifiers) },
25
+ return isStatic ? (react_1.default.createElement("span", Object.assign({ className: (0, getBemClass_1.default)('TagPill', modifiers) }, buttonProps),
25
26
  label,
26
27
  " ",
27
28
  removeBtn)) : onRemove ? (react_1.default.createElement("span", { className: (0, getBemClass_1.default)('TagPill', modifiers) },
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  const react_1 = tslib_1.__importStar(require("react"));
5
5
  const A_1 = tslib_1.__importDefault(require("@hugsmidjan/qj/A"));
6
+ const debounce_1 = tslib_1.__importDefault(require("@hugsmidjan/qj/debounce"));
6
7
  const focusElm_1 = tslib_1.__importDefault(require("@hugsmidjan/qj/focusElm"));
7
8
  const throttle_1 = tslib_1.__importDefault(require("@hugsmidjan/qj/throttle"));
8
9
  const hooks_1 = require("@hugsmidjan/react/hooks");
@@ -10,8 +11,7 @@ const getBemClass_1 = tslib_1.__importDefault(require("@hugsmidjan/react/utils/g
10
11
  const hanna_utils_1 = require("@reykjavik/hanna-utils");
11
12
  const CarouselStepper_1 = tslib_1.__importDefault(require("../CarouselStepper"));
12
13
  const seenEffect_1 = require("../utils/seenEffect");
13
- const WHEEL_AMPLIFIER = 3;
14
- const WHEEL_HIJACK_TIMEOUT_MS = 667;
14
+ // ---------------------------------------------------------------------------
15
15
  const scrollXBy = (elm, deltaX) => {
16
16
  const left = elm.scrollLeft + deltaX;
17
17
  elm.scrollTo(left, elm.scrollTop);
@@ -22,54 +22,6 @@ const scrollXBy = (elm, deltaX) => {
22
22
  // NOTE 2: Both Chrome and Safari tend to snap hard to the nearest list item
23
23
  // while Firefox is more smooth. Haven't found a way around that.
24
24
  };
25
- const exitWheelHijack = (elm) => () => {
26
- elm.style.scrollSnapType = '';
27
- const lastElmOffset = elm.lastElementChild
28
- ? elm.lastElementChild.offsetLeft
29
- : 0;
30
- // trigger one last scroll event, to make sure the element's
31
- // `style.scrollSnapType`'s behavior kicks in.
32
- // Otherwise the list may stay stuck in an over-scrolled state —
33
- // off to the right.
34
- scrollXBy(elm, Math.min(0, lastElmOffset - elm.scrollLeft));
35
- };
36
- const handleMouseWheel = (e) => {
37
- const elm = e.currentTarget;
38
- if (e.deltaX || elm.$hasXDeltaed) {
39
- elm.$hasXDeltaed = true;
40
- exitWheelHijack(elm);
41
- return;
42
- }
43
- if (e.deltaY && !e.deltaX) {
44
- if (e.deltaY < 0) {
45
- // Stop scroll-capture when list is at left-edge
46
- if (elm.scrollLeft < 50) {
47
- return;
48
- }
49
- }
50
- else {
51
- // Stop scroll-capture when list is beyond left edge of last list item
52
- if (elm.scrollLeft >
53
- (elm.lastElementChild ? elm.lastElementChild.offsetLeft : 0)) {
54
- exitWheelHijack(elm);
55
- return;
56
- }
57
- }
58
- e.preventDefault();
59
- e.stopImmediatePropagation();
60
- // Disable `scroll-snap-style` because otherwise
61
- // small-scale elm.scrollTo() calls have no effect
62
- elm.style.scrollSnapType = 'initial';
63
- scrollXBy(elm, e.deltaY * WHEEL_AMPLIFIER);
64
- clearTimeout(elm.$timeout);
65
- elm.$timeout = setTimeout(exitWheelHijack(elm), WHEEL_HIJACK_TIMEOUT_MS);
66
- }
67
- };
68
- const handleMouseLeave = (e) => {
69
- const elm = e.currentTarget;
70
- exitWheelHijack(elm);
71
- elm.$hasXDeltaed = undefined;
72
- };
73
25
  const AbstractCarousel = (props) => {
74
26
  const { title, items = [], Component, ComponentProps, bem = 'Carousel', modifier, ssr, startSeen, } = props;
75
27
  const children = props.children && props.children.filter(hanna_utils_1.notNully);
@@ -78,9 +30,10 @@ const AbstractCarousel = (props) => {
78
30
  const listRef = (0, react_1.useRef)(null);
79
31
  const [activeItem, setActiveItem] = (0, react_1.useState)(0);
80
32
  const isBrowser = (0, hooks_1.useIsBrowserSide)(ssr);
81
- // update on activeItem state change
33
+ // Since listElm gets unmounted and remounted based on isBrowser, we
34
+ // wait for isBrowser is true before setting scroll and resize events.
35
+ const listElm = isBrowser && listRef.current;
82
36
  (0, react_1.useEffect)(() => {
83
- const listElm = listRef.current;
84
37
  if (!listElm) {
85
38
  return;
86
39
  }
@@ -100,34 +53,56 @@ const AbstractCarousel = (props) => {
100
53
  });
101
54
  }, 300, true);
102
55
  calcLeftOffset();
103
- listElm.addEventListener('wheel', handleMouseWheel);
104
56
  listElm.addEventListener('scroll', calcActiveItem, { passive: true });
105
57
  window.addEventListener('resize', calcLeftOffset, { passive: true });
106
58
  return () => {
107
- listElm.removeEventListener('wheel', handleMouseWheel);
108
59
  listElm.removeEventListener('scroll', calcActiveItem);
109
60
  window.removeEventListener('resize', calcLeftOffset);
110
61
  };
111
- }, []);
62
+ }, [listElm]);
112
63
  const scrollToItem = (newActive) => {
113
- setActiveItem(newActive);
114
64
  const listElm = listRef.current;
115
65
  const newItem = listElm.children[newActive];
116
- listElm.scrollTo(newItem ? newItem.offsetLeft : 0, 0);
66
+ if (!newItem) {
67
+ return;
68
+ }
69
+ setActiveItem(newActive);
70
+ listElm.scrollLeft = newItem.offsetLeft || 1;
117
71
  setTimeout(() => (0, focusElm_1.default)(newItem), 500);
118
72
  };
73
+ const { delayedScrollLeft, delayedScrollRight } = (0, react_1.useMemo)(() => {
74
+ const delayedScrollLeft = (0, debounce_1.default)((currentActive) => {
75
+ scrollToItem(currentActive - 1);
76
+ setTimeout(() => delayedScrollLeft(currentActive - 1), 0);
77
+ }, 1000);
78
+ const delayedScrollRight = (0, debounce_1.default)((currentActive) => {
79
+ scrollToItem(currentActive + 1);
80
+ setTimeout(() => delayedScrollRight(currentActive + 1), 0);
81
+ }, 1000);
82
+ return { delayedScrollLeft, delayedScrollRight };
83
+ }, []);
119
84
  const [outerRef] = (0, seenEffect_1.useSeenEffect)(startSeen);
120
85
  if (!itemCount) {
121
86
  return null;
122
87
  }
88
+ const itemList = (react_1.default.createElement("div", { className: bem + '__itemlist', style: leftOffset
89
+ ? { '--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_1.default.createElement(Component, Object.assign({ key: i }, ComponentProps, item))))));
123
94
  return (react_1.default.createElement("div", { className: (0, getBemClass_1.default)(bem, modifier, props.className), ref: outerRef, "data-sprinkled": isBrowser },
124
95
  title && react_1.default.createElement("h2", { className: bem + '__title' }, title),
125
- react_1.default.createElement("div", { className: bem + '__itemlist', style: leftOffset
126
- ? { '--Carousel--leftOffset': `${leftOffset}px` }
127
- : undefined, "data-scroll-snapping": leftOffset ? 'true' : undefined, onMouseLeave: handleMouseLeave, ref: listRef }, children ||
128
- items.map((item, i) => (
129
- // @ts-expect-error (Can't be arsed...)
130
- react_1.default.createElement(Component, Object.assign({ key: i }, ComponentProps, item))))),
96
+ isBrowser ? (react_1.default.createElement("div", { className: bem + '__itemlist-wrapper' },
97
+ itemList,
98
+ activeItem > 0 && (react_1.default.createElement("div", { className: bem + '__itemlist-goLeft', onClick: () => {
99
+ delayedScrollLeft.cancel();
100
+ scrollToItem(activeItem - 1);
101
+ }, onMouseOver: () => delayedScrollLeft(activeItem), onMouseOut: () => delayedScrollLeft.cancel() })),
102
+ activeItem < itemCount - 1 && (react_1.default.createElement("div", { className: bem + '__itemlist-goRight', onClick: () => {
103
+ delayedScrollRight.cancel();
104
+ scrollToItem(activeItem + 1);
105
+ }, onMouseOver: () => delayedScrollRight(activeItem), onMouseOut: () => delayedScrollRight.cancel() })))) : (itemList),
131
106
  isBrowser && (react_1.default.createElement(CarouselStepper_1.default, { itemCount: itemCount, setCurrent: scrollToItem, current: activeItem }))));
132
107
  };
133
108
  exports.default = AbstractCarousel;
package/assets.d.ts CHANGED
@@ -27,7 +27,7 @@ getIllustrationUrl,
27
27
  /** @deprecated Instead `import type { illustrations } from '@reykjavik/hanna-utils/assets';` (Will be removed in v0.11) */
28
28
  illustrations, };
29
29
  /** @deprecated Use `getCssBundleUrl` from '@reykjavik/hanna-css' instead (Will be reomved in v0.11) */
30
- export declare const getCssBundleUrl: (cssTokens: string | Array<string>, version?: string | undefined) => string;
30
+ export declare const getCssBundleUrl: (cssTokens: string | Array<string>, version?: string) => string;
31
31
  /** @deprecated (Will be removed in v0.11) */
32
32
  export declare const efnistakn_menu: readonly ["menu/borgarstjori", "menu/borgarstjorn", "menu/bygg_framkv", "menu/fjarmal", "menu/fundargerdir", "menu/itrottir_aftreying", "menu/log_reglugerdir", "menu/mannaudur", "menu/menning", "menu/rad_nefndir", "menu/skipulag", "menu/skolar_fristund", "menu/svid_deildir", "menu/umhverfi_samgongur", "menu/velferd_fjolskylda"];
33
33
  /** @deprecated (Will be removed in v0.11) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reykjavik/hanna-react",
3
- "version": "0.10.60",
3
+ "version": "0.10.63",
4
4
  "author": "Reykjavík (http://www.reykjavik.is)",
5
5
  "contributors": [
6
6
  "Hugsmiðjan ehf (http://www.hugsmidjan.is)",
@@ -15,8 +15,8 @@
15
15
  "dependencies": {
16
16
  "@hugsmidjan/qj": "^4.10.2",
17
17
  "@hugsmidjan/react": "^0.4.17",
18
- "@reykjavik/hanna-css": "^0.3.3",
19
- "@reykjavik/hanna-utils": "^0.1.7",
18
+ "@reykjavik/hanna-css": "^0.3.7",
19
+ "@reykjavik/hanna-utils": "^0.1.11",
20
20
  "@types/react": "^17.0.24",
21
21
  "@types/react-autosuggest": "^10.1.0",
22
22
  "@types/react-datepicker": "^3.0.2",
@@ -4,7 +4,6 @@ export declare const getObserver: {
4
4
  (target: Element, callback?: ((target: Element) => void) | undefined): (() => void) | undefined;
5
5
  DATA_ATTR_NAME: string;
6
6
  };
7
- export declare const seenEffectOptOut: (target: Element, setFlag?: boolean) => void;
8
7
  declare const effects: {
9
8
  readonly fadein: 1;
10
9
  readonly fadeup: 1;
@@ -16,13 +15,13 @@ export declare type EffectProp = {
16
15
  effectType?: SeenEffectType | 'none';
17
16
  };
18
17
  /** Asserts that a prop value is a SeenEffectType and returns undefined otherwise */
19
- export declare const assertEffectType: (maybeType?: string | undefined) => SeenEffectType | undefined;
20
- export declare const getEffectAttr: (maybeType?: string | undefined) => {
18
+ export declare const ensureEffectType: (maybeType?: string) => SeenEffectType | undefined;
19
+ export declare const getEffectAttr: (maybeType?: string) => {
21
20
  'data-seen-effect': string | undefined;
22
21
  };
23
22
  export declare type SeenProp = {
24
23
  /** Should the component appear instantly, and not transition-in once seen */
25
24
  startSeen?: boolean;
26
25
  };
27
- export declare const useSeenEffect: <E extends Element = HTMLDivElement>(startSeen?: boolean | undefined, customRef?: RefObject<E> | undefined) => [RefObject<E> | undefined, true | undefined];
26
+ export declare const useSeenEffect: <E extends Element = HTMLDivElement>(startSeen?: boolean, customRef?: RefObject<E> | undefined) => [RefObject<E> | undefined, true | undefined];
28
27
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useSeenEffect = exports.getEffectAttr = exports.assertEffectType = exports.seenEffectOptOut = exports.getObserver = exports.DATA_ATTR_NAME = void 0;
3
+ exports.useSeenEffect = exports.getEffectAttr = exports.ensureEffectType = exports.getObserver = exports.DATA_ATTR_NAME = void 0;
4
4
  const react_1 = require("react");
5
5
  exports.DATA_ATTR_NAME = 'is-seen';
6
6
  const STACKING_DELAY = 400; // ms
@@ -36,13 +36,10 @@ const getObserver = (target, callback) => {
36
36
  }
37
37
  target.setAttribute(dataAttr, 'false');
38
38
  observer.observe(target);
39
+ // return teardown/unmount effect
39
40
  return () => observer.unobserve(target);
40
41
  };
41
42
  exports.getObserver = getObserver;
42
- const seenEffectOptOut = (target, setFlag = true) => {
43
- setFlag ? target.setAttribute(dataAttr, '') : target.removeAttribute(dataAttr);
44
- };
45
- exports.seenEffectOptOut = seenEffectOptOut;
46
43
  exports.getObserver.DATA_ATTR_NAME = exports.DATA_ATTR_NAME;
47
44
  // ---------------------------------------------------------------------------
48
45
  // ---------------------------------------------------------------------------
@@ -52,10 +49,10 @@ const effects = {
52
49
  fadeleft: 1,
53
50
  };
54
51
  /** Asserts that a prop value is a SeenEffectType and returns undefined otherwise */
55
- const assertEffectType = (maybeType) => maybeType && maybeType in effects ? maybeType : undefined;
56
- exports.assertEffectType = assertEffectType;
52
+ const ensureEffectType = (maybeType) => maybeType && maybeType in effects ? maybeType : undefined;
53
+ exports.ensureEffectType = ensureEffectType;
57
54
  const getEffectAttr = (maybeType) => ({
58
- 'data-seen-effect': maybeType === 'none' ? undefined : (0, exports.assertEffectType)(maybeType) || '',
55
+ 'data-seen-effect': maybeType === 'none' ? undefined : (0, exports.ensureEffectType)(maybeType) || '',
59
56
  });
60
57
  exports.getEffectAttr = getEffectAttr;
61
58
  const useSeenEffect = (
@@ -63,19 +60,23 @@ const useSeenEffect = (
63
60
  startSeen,
64
61
  /** Bring Your Own RefObject */
65
62
  customRef) => {
63
+ const _startSeen = startSeen || undefined; // normalize
64
+ const [isSeen, setSeen] = (0, react_1.useState)(_startSeen);
66
65
  const localRef = (0, react_1.useRef)(null);
67
- const [isSeen, setSeen] = (0, react_1.useState)(startSeen || undefined);
68
- const ref = !startSeen && (customRef || localRef);
66
+ const ref = customRef || localRef;
69
67
  (0, react_1.useEffect)(() => {
70
- setSeen(startSeen || undefined);
71
- if (ref && ref.current) {
72
- // NOTE: Given that `ref` is defined, then
73
- // `startSeen` is implicily `false | undefined` at
74
- // this point.
75
- (0, exports.seenEffectOptOut)(ref.current, false);
76
- return (0, exports.getObserver)(ref.current, () => setSeen(true));
68
+ setSeen(_startSeen);
69
+ const refElm = ref.current;
70
+ if (refElm) {
71
+ if (_startSeen) {
72
+ refElm.setAttribute(dataAttr, '');
73
+ }
74
+ else {
75
+ refElm.removeAttribute(dataAttr);
76
+ return (0, exports.getObserver)(refElm, () => setSeen(true));
77
+ }
77
78
  }
78
- }, [ref, startSeen]);
79
- return [ref || undefined, isSeen];
79
+ }, [_startSeen, ref]);
80
+ return [ref, isSeen];
80
81
  };
81
82
  exports.useSeenEffect = useSeenEffect;