@reykjavik/hanna-react 0.10.60 → 0.10.61

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,14 @@
4
4
 
5
5
  - ... <!-- Add new lines here. -->
6
6
 
7
+ ## 0.10.61
8
+
9
+ _2022-08-11_
10
+
11
+ - feat: Add mouse-cursor scroll controls for `Carousel`-related components —
12
+ Remove mousewheel hijacking behavior.
13
+ - fix: `startSeen` hiding components with `html.before-sprinkling` present
14
+
7
15
  ## 0.10.60
8
16
 
9
17
  _2022-06-24_
@@ -22,6 +30,7 @@ _2022-06-20_
22
30
  - feat: Add prop `small` to `ArticleMeta`
23
31
  - fix: Suppress redundant className `.Heading--normal`
24
32
  - feat: Add prop `focalPoint` to all `ImageProps`
33
+ - feat: Add `EffectProp.effectType` prop value `none` for opting out
25
34
 
26
35
  ## 0.10.56 – 0.10.58
27
36
 
@@ -213,6 +222,7 @@ _2022-03-09_
213
222
 
214
223
  - feat: Add props `autoClose` and `onClosed` to `Alert` — use `onClosed` when
215
224
  removing the Alert from the DOM
225
+ - feat: Deprecate the `event` argument for `Alert`'s `onClose` callback
216
226
  - feat: Add prop `align?: 'right'` to `ButtonBar`
217
227
  - fix: Make `FileInput` image previews (thumbnails) more resilient overall
218
228
  - fix: Plug memory-leaks caused by `FileInput` image thumbnails.
@@ -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
+ 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
+ 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.61",
4
4
  "author": "Reykjavík (http://www.reykjavik.is)",
5
5
  "contributors": [
6
6
  "Hugsmiðjan ehf (http://www.hugsmidjan.is)",
@@ -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;