@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 +3 -3
- package/CHANGELOG.md +24 -0
- package/FileInput/FileInput.utils.d.ts +36 -0
- package/FileInput/FileInput.utils.js +69 -0
- package/FileInput.d.ts +18 -3
- package/FileInput.js +14 -54
- package/TagPill.js +3 -2
- package/_abstract/_AbstractCarousel.js +38 -63
- package/assets.d.ts +1 -1
- package/package.json +3 -3
- package/utils/seenEffect.d.ts +3 -4
- package/utils/seenEffect.js +20 -19
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-
|
|
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 =
|
|
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.
|
|
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
|
|
2
|
+
declare type DropzonePropsProps = {
|
|
3
3
|
accept?: string;
|
|
4
4
|
multiple?: boolean;
|
|
5
5
|
};
|
|
6
6
|
export declare type FileInputProps = {
|
|
7
|
-
dropzoneProps:
|
|
7
|
+
dropzoneProps: DropzonePropsProps;
|
|
8
8
|
dropzoneText: string | JSX.Element;
|
|
9
9
|
showFileSize?: boolean;
|
|
10
10
|
showImagePreviews?: boolean;
|
|
11
11
|
removeFileText: string;
|
|
12
|
-
onFilesUpdated?: (
|
|
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
|
-
|
|
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 = (
|
|
72
|
+
const addFiles = (added) => {
|
|
111
73
|
if (fileInput.current) {
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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',
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
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.
|
|
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.
|
|
19
|
-
"@reykjavik/hanna-utils": "^0.1.
|
|
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",
|
package/utils/seenEffect.d.ts
CHANGED
|
@@ -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
|
|
20
|
-
export declare const getEffectAttr: (maybeType?: string
|
|
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
|
|
26
|
+
export declare const useSeenEffect: <E extends Element = HTMLDivElement>(startSeen?: boolean, customRef?: RefObject<E> | undefined) => [RefObject<E> | undefined, true | undefined];
|
|
28
27
|
export {};
|
package/utils/seenEffect.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useSeenEffect = exports.getEffectAttr = exports.
|
|
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
|
|
56
|
-
exports.
|
|
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.
|
|
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
|
|
68
|
-
const ref = !startSeen && (customRef || localRef);
|
|
66
|
+
const ref = customRef || localRef;
|
|
69
67
|
(0, react_1.useEffect)(() => {
|
|
70
|
-
setSeen(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
}, [
|
|
79
|
-
return [ref
|
|
79
|
+
}, [_startSeen, ref]);
|
|
80
|
+
return [ref, isSeen];
|
|
80
81
|
};
|
|
81
82
|
exports.useSeenEffect = useSeenEffect;
|