@nascentdigital/funnel-ui 4.5.2 → 4.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/client/hooks/breakpoint/BreakpointContainer.d.ts +1 -1
  2. package/dist/client/hooks/breakpoint/BreakpointContainer.js +3 -1
  3. package/dist/client/interaction/index.d.ts +0 -6
  4. package/dist/client/interaction/index.js +0 -6
  5. package/dist/server/blocks/Block.js +2 -2
  6. package/dist/server/blocks/Message.js +2 -1
  7. package/dist/server/blocks/Testimonial.js +13 -10
  8. package/dist/server/blocks/Testimonials.d.ts +35 -35
  9. package/dist/server/blocks/Testimonials.js +21 -66
  10. package/dist/server/blocks/index.d.ts +1 -0
  11. package/dist/server/blocks/index.js +1 -0
  12. package/dist/server/fields/CallToActions.d.ts +12 -0
  13. package/dist/server/fields/CallToActions.js +27 -0
  14. package/dist/server/fields/index.d.ts +1 -0
  15. package/dist/server/fields/index.js +1 -0
  16. package/dist/server/panels/BlockPanel.js +1 -2
  17. package/dist/server/panels/MessagePanel.d.ts +2 -6
  18. package/dist/server/panels/MessagePanel.js +2 -9
  19. package/dist/server/panels/TestimonialPanel.d.ts +44 -18
  20. package/dist/server/panels/TestimonialPanel.js +44 -19
  21. package/dist/server/panels/VisualPanel.d.ts +1 -1
  22. package/package.json +2 -2
  23. package/dist/client/interaction/Popup.d.ts +0 -36
  24. package/dist/client/interaction/Popup.js +0 -244
  25. package/dist/client/interaction/Tooltip.d.ts +0 -16
  26. package/dist/client/interaction/Tooltip.js +0 -122
  27. package/dist/client/interaction/UILevel.d.ts +0 -1
  28. package/dist/client/interaction/UILevel.js +0 -2
  29. package/dist/client/interaction/backdrop/BackdropContainer.d.ts +0 -5
  30. package/dist/client/interaction/backdrop/BackdropContainer.js +0 -114
  31. package/dist/client/interaction/backdrop/BackdropContext.d.ts +0 -8
  32. package/dist/client/interaction/backdrop/BackdropContext.js +0 -6
  33. package/dist/client/interaction/backdrop/BackdropEvent.d.ts +0 -64
  34. package/dist/client/interaction/backdrop/BackdropEvent.js +0 -92
  35. package/dist/client/interaction/backdrop/index.d.ts +0 -4
  36. package/dist/client/interaction/backdrop/index.js +0 -20
  37. package/dist/client/interaction/backdrop/useBackdrop.d.ts +0 -13
  38. package/dist/client/interaction/backdrop/useBackdrop.js +0 -58
  39. package/dist/client/interaction/backdrop/useBackdropClick.d.ts +0 -2
  40. package/dist/client/interaction/backdrop/useBackdropClick.js +0 -21
  41. package/dist/client/interaction/backdrop/useBackdropVisible.d.ts +0 -1
  42. package/dist/client/interaction/backdrop/useBackdropVisible.js +0 -30
  43. package/dist/client/interaction/focus/FocusArea.d.ts +0 -10
  44. package/dist/client/interaction/focus/FocusArea.js +0 -38
  45. package/dist/client/interaction/focus/FocusContext.d.ts +0 -7
  46. package/dist/client/interaction/focus/FocusContext.js +0 -7
  47. package/dist/client/interaction/focus/FocusController.d.ts +0 -9
  48. package/dist/client/interaction/focus/FocusController.js +0 -40
  49. package/dist/client/interaction/focus/FocusService.d.ts +0 -20
  50. package/dist/client/interaction/focus/FocusService.js +0 -204
  51. package/dist/client/interaction/focus/LinkedList.d.ts +0 -18
  52. package/dist/client/interaction/focus/LinkedList.js +0 -122
  53. package/dist/client/interaction/focus/index.d.ts +0 -2
  54. package/dist/client/interaction/focus/index.js +0 -18
  55. package/dist/client/interaction/focus/useFocusService.d.ts +0 -2
  56. package/dist/client/interaction/focus/useFocusService.js +0 -14
  57. package/dist/client/interaction/modal/Modal.d.ts +0 -29
  58. package/dist/client/interaction/modal/Modal.js +0 -136
  59. package/dist/client/interaction/modal/ModalContainer.d.ts +0 -5
  60. package/dist/client/interaction/modal/ModalContainer.js +0 -104
  61. package/dist/client/interaction/modal/ModalContext.d.ts +0 -8
  62. package/dist/client/interaction/modal/ModalContext.js +0 -6
  63. package/dist/client/interaction/modal/ModalEvent.d.ts +0 -39
  64. package/dist/client/interaction/modal/ModalEvent.js +0 -53
  65. package/dist/client/interaction/modal/index.d.ts +0 -2
  66. package/dist/client/interaction/modal/index.js +0 -18
@@ -8,7 +8,7 @@ export declare namespace VisualPanel {
8
8
  type Props = BlockPanel.Props & {
9
9
  className?: string;
10
10
  panel: BlockLayout.Visual;
11
- visual: Visual.Model;
11
+ visual?: Visual.Model;
12
12
  visualDecorator?: ReactNode;
13
13
  visualLoading?: Visual.Loading;
14
14
  aspect?: AspectRatio;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nascentdigital/funnel-ui",
3
3
  "description": "UI components for funnel",
4
- "version": "4.5.2",
4
+ "version": "4.5.3",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/",
7
7
  "access": "restricted"
@@ -70,5 +70,5 @@
70
70
  "react-dom": "^19",
71
71
  "ts-jest": "^29.3.2"
72
72
  },
73
- "gitHead": "5b63f9cd8843cc111b4458c71aa031a974455241"
73
+ "gitHead": "ef42bb5440cc23187aeefc29319875ed5402676f"
74
74
  }
@@ -1,36 +0,0 @@
1
- import { Callback, Frame, Rect } from '@nascentdigital/funnel-core';
2
- import { Variants } from 'framer-motion';
3
- import { HTMLAttributes } from 'react';
4
- import { ClickObserver } from './ClickObserver';
5
- import { UILevel } from './UILevel';
6
- export declare function Popup({ className, style, anchor, level, placement, anchorOffset, viewportPadding, animation, dismissTrigger, disableBackground, disabled, bubbleCloseEvent, open, onOpened: onOpenedRaw, onClose, onClosed: onClosedRaw, children, ...props }: Popup.Props): import("react/jsx-runtime").JSX.Element | null;
7
- export declare namespace Popup {
8
- type Props = HTMLAttributes<HTMLDivElement> & {
9
- anchor?: Rect | Element | string | null | undefined;
10
- level?: UILevel;
11
- placement?: Placement;
12
- anchorOffset?: number;
13
- viewportPadding?: number;
14
- animation?: Animation;
15
- dismissTrigger?: ClickObserver.Trigger | 'none';
16
- disableBackground?: boolean;
17
- disabled?: boolean;
18
- bubbleCloseEvent?: boolean;
19
- open?: boolean;
20
- onOpened?: Callback.Void;
21
- onClose?: Callback.Void;
22
- onClosed?: Callback.Void;
23
- };
24
- type Placement = 'top-start' | 'top' | 'top-end' | 'top-match' | 'bottom-start' | 'bottom' | 'bottom-end' | 'bottom-match' | 'left-start' | 'left' | 'left-end' | 'right-start' | 'right' | 'right-end';
25
- type Animation = 'in' | 'out' | 'in-out';
26
- type GetFrameArgs = {
27
- anchorRect: Rect;
28
- contentElement: HTMLDivElement;
29
- placement: Popup.Placement;
30
- anchorOffset: number;
31
- viewportPadding: number;
32
- };
33
- function getFrame({ anchorRect, contentElement, placement, anchorOffset, viewportPadding }: GetFrameArgs): Partial<Frame>;
34
- }
35
- export declare const animations: Variants;
36
- export declare function getAnchorRect(anchor: Rect | Element | string | null | undefined): Rect | null;
@@ -1,244 +0,0 @@
1
- "use strict";
2
- var __rest = (this && this.__rest) || function (s, e) {
3
- var t = {};
4
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
- t[p] = s[p];
6
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
- t[p[i]] = s[p[i]];
10
- }
11
- return t;
12
- };
13
- var __importDefault = (this && this.__importDefault) || function (mod) {
14
- return (mod && mod.__esModule) ? mod : { "default": mod };
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.animations = void 0;
18
- exports.Popup = Popup;
19
- exports.getAnchorRect = getAnchorRect;
20
- const jsx_runtime_1 = require("react/jsx-runtime");
21
- const funnel_core_1 = require("@nascentdigital/funnel-core");
22
- const classnames_1 = __importDefault(require("classnames"));
23
- const framer_motion_1 = require("framer-motion");
24
- const react_1 = require("react");
25
- const react_dom_1 = require("react-dom");
26
- const react_hotkeys_hook_1 = require("react-hotkeys-hook");
27
- const hooks_1 = require("../hooks");
28
- const ClickObserver_1 = require("./ClickObserver");
29
- // component
30
- function Popup(_a) {
31
- var { className, style = {}, anchor, level = 'l1', placement = 'bottom', anchorOffset = 0, viewportPadding = 0, animation = 'in-out', dismissTrigger, disableBackground = false, disabled = false, bubbleCloseEvent = false, open = false, onOpened: onOpenedRaw = funnel_core_1.Fn.NoOp, onClose = funnel_core_1.Fn.NoOp, onClosed: onClosedRaw = funnel_core_1.Fn.NoOp, children } = _a, props = __rest(_a, ["className", "style", "anchor", "level", "placement", "anchorOffset", "viewportPadding", "animation", "dismissTrigger", "disableBackground", "disabled", "bubbleCloseEvent", "open", "onOpened", "onClose", "onClosed", "children"]);
32
- // bind context
33
- const [active, setActive] = (0, react_1.useState)(false);
34
- const openedRef = (0, react_1.useRef)(false);
35
- const contentRef = (0, react_1.useRef)(null);
36
- const id = (0, react_1.useId)();
37
- const anchorRect = getAnchorRect(anchor);
38
- const onOpenedRef = (0, react_1.useRef)(onOpenedRaw);
39
- onOpenedRef.current = onOpenedRaw;
40
- const onClosedRef = (0, react_1.useRef)(onClosedRaw);
41
- onClosedRef.current = onClosedRaw;
42
- const closeCallback = (0, react_1.useCallback)(() => {
43
- if (openedRef.current) {
44
- onClose();
45
- }
46
- }, [onClose]);
47
- // re-render on window size change
48
- (0, hooks_1.useViewportSize)();
49
- // close on escape
50
- (0, react_hotkeys_hook_1.useHotkeys)('esc', closeCallback);
51
- // show/hide based on open change
52
- (0, react_1.useEffect)(() => {
53
- // raise event on open/close
54
- let unlock = undefined;
55
- if (open) {
56
- // update ref
57
- openedRef.current = true;
58
- // activate
59
- setActive(true);
60
- // lock keyboard
61
- unlock = funnel_core_1.Browser.lockFocus(`#${CSS.escape(id)}`);
62
- }
63
- // raise event on close (if it was actually opened)
64
- else if (openedRef.current) {
65
- // deactivate
66
- setActive(false);
67
- // update ref
68
- openedRef.current = false;
69
- }
70
- // on unmount
71
- return () => {
72
- // unlock (if locked)
73
- if (unlock) {
74
- unlock();
75
- }
76
- };
77
- }, [open, id]);
78
- // skip for SSR
79
- if (!funnel_core_1.Browser.available) {
80
- return null;
81
- }
82
- // compute popup rect (if visible + ready)
83
- const contentElement = contentRef.current;
84
- const popupFrame = active && !!anchorRect && !!contentElement
85
- ? Popup.getFrame({
86
- anchorRect,
87
- contentElement,
88
- placement,
89
- anchorOffset,
90
- viewportPadding
91
- })
92
- : {};
93
- // helpers
94
- const onClickOutside = (e) => {
95
- // consume click
96
- if (!bubbleCloseEvent) {
97
- e.preventDefault();
98
- e.stopPropagation();
99
- }
100
- // close
101
- onClose();
102
- };
103
- const onAnimationComplete = (definition) => {
104
- if (definition === 'visible') {
105
- onOpenedRef.current();
106
- }
107
- else if (definition === 'exit') {
108
- onClosedRef.current();
109
- }
110
- };
111
- // render
112
- return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)(framer_motion_1.AnimatePresence, { children: open && !!anchorRect &&
113
- (0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, { id: id, className: (0, classnames_1.default)('Popup-wrap', {
114
- 'Popup-disableBg': disableBackground,
115
- 'Popup-disabled': disabled,
116
- 'Popup-l1': level === 'l1',
117
- 'Popup-l2': level === 'l2',
118
- 'Popup-l3': level === 'l3'
119
- }), variants: exports.animations, initial: animation !== 'out' ? 'hidden' : 'visible', animate: "visible", exit: animation !== 'in' ? 'exit' : 'visible', onAnimationComplete: definition => onAnimationComplete(definition), children: (0, jsx_runtime_1.jsx)(ClickObserver_1.ClickObserver, { trigger: dismissTrigger === 'none' ? 'click' : dismissTrigger, disabled: !open || dismissTrigger === 'none', onClickOutside: onClickOutside, children: (0, jsx_runtime_1.jsx)("div", Object.assign({}, props, { ref: contentRef, style: Object.assign(Object.assign({}, style), popupFrame), className: (0, classnames_1.default)(className, 'Popup-root'), children: children })) }) }, id) }), document.body) }));
120
- }
121
- // extension
122
- (function (Popup) {
123
- // helpers
124
- function getFrame({ anchorRect, contentElement, placement, anchorOffset, viewportPadding }) {
125
- // get geometries
126
- const viewportRect = new funnel_core_1.Rect(viewportPadding, viewportPadding, window.innerWidth - viewportPadding * 2, window.innerHeight - viewportPadding * 2);
127
- const contentWidth = contentElement.offsetWidth;
128
- const contentHeight = contentElement.offsetHeight;
129
- // horizontal layout if left/right
130
- const popupFrame = {};
131
- const [primary, secondary] = placement.split('-');
132
- if (primary === 'left' || primary === 'right') {
133
- // determine space to left/right
134
- const spaceLeft = Math.max(0, anchorRect.left - viewportRect.left - anchorOffset);
135
- const spaceRight = Math.max(0, viewportRect.right - anchorRect.right - anchorOffset);
136
- // place left if it fits or it fits better than on the right
137
- if ((primary === 'left' && (spaceLeft >= contentWidth || spaceLeft > spaceRight))
138
- || (primary === 'right' && spaceRight < contentWidth && spaceRight < spaceLeft)) {
139
- popupFrame.right = viewportRect.right - anchorRect.left + anchorOffset;
140
- }
141
- // or place right
142
- else {
143
- popupFrame.left = anchorRect.right + anchorOffset;
144
- }
145
- // top-align on start
146
- if (secondary === 'start') {
147
- popupFrame.top = anchorRect.top;
148
- }
149
- // bottom-align on end
150
- else if (secondary === 'end') {
151
- popupFrame.bottom = viewportRect.bottom - anchorRect.bottom;
152
- }
153
- // or center against anchor
154
- else {
155
- // set top bounds so it's centered
156
- popupFrame.top = Math.max(anchorRect.top + (anchorRect.height - contentHeight) / 2, viewportRect.top);
157
- // offset if the bottom is bleeding over
158
- if (popupFrame.top + contentHeight > viewportRect.bottom) {
159
- popupFrame.top -= popupFrame.top + contentHeight - viewportRect.bottom;
160
- }
161
- }
162
- }
163
- // or standard vertical layout
164
- else {
165
- // determine space to top/bottom
166
- const spaceTop = Math.max(0, anchorRect.top - viewportRect.top - anchorOffset);
167
- const spaceBottom = Math.max(0, viewportRect.bottom - anchorRect.bottom - anchorOffset);
168
- // place above if it fits or it fits better than on the right
169
- if ((primary === 'top' && (spaceTop >= contentHeight || spaceBottom < spaceTop))
170
- || (primary === 'bottom' && spaceBottom < contentHeight && spaceBottom < spaceTop)) {
171
- popupFrame.bottom = viewportRect.bottom - anchorRect.top + anchorOffset;
172
- }
173
- // or place below
174
- else {
175
- popupFrame.top = anchorRect.bottom + anchorOffset;
176
- }
177
- // left-align on start
178
- if (secondary === 'start') {
179
- popupFrame.left = anchorRect.left;
180
- }
181
- // right-align on end
182
- else if (secondary === 'end') {
183
- popupFrame.right = viewportRect.right - anchorRect.right;
184
- }
185
- // match width
186
- else if (secondary === 'match') {
187
- popupFrame.left = anchorRect.left;
188
- popupFrame.width = anchorRect.width;
189
- }
190
- // or center against anchor
191
- else {
192
- // set left bounds so it's centered
193
- popupFrame.left = Math.max(anchorRect.left + (anchorRect.width - contentWidth) / 2, viewportRect.left);
194
- // offset if the right is bleeding over
195
- if (popupFrame.left + contentWidth > viewportRect.right) {
196
- popupFrame.left -= popupFrame.left + contentWidth - viewportRect.right;
197
- }
198
- }
199
- }
200
- // return position insets
201
- return popupFrame;
202
- }
203
- Popup.getFrame = getFrame;
204
- })(Popup || (exports.Popup = Popup = {}));
205
- // helpers
206
- exports.animations = {
207
- hidden: {
208
- opacity: 0
209
- },
210
- visible: {
211
- opacity: 1,
212
- transition: {
213
- ease: 'easeOut',
214
- duration: 0.5,
215
- when: 'beforeChildren'
216
- }
217
- },
218
- exit: {
219
- opacity: 0,
220
- transition: {
221
- ease: 'easeIn',
222
- duration: 0.15,
223
- bounce: 0,
224
- when: 'afterChildren'
225
- }
226
- }
227
- };
228
- function getAnchorRect(anchor) {
229
- // skip if there's no anchor or if we're SSR
230
- if (!anchor || !funnel_core_1.Browser.available) {
231
- return null;
232
- }
233
- // or skip if we already have a rect
234
- else if (anchor instanceof funnel_core_1.Rect) {
235
- return anchor;
236
- }
237
- // try to coerce anchor into an element (or fail)
238
- const anchorElement = typeof anchor === 'string'
239
- ? document.querySelector(anchor)
240
- : anchor;
241
- return anchorElement
242
- ? funnel_core_1.Rect.fromInsets(anchorElement.getBoundingClientRect())
243
- : null;
244
- }
@@ -1,16 +0,0 @@
1
- import { ReactNode, RefObject } from 'react';
2
- import { Popup } from './Popup';
3
- export declare function Tooltip({ delay, scroller, content, children, anchorClassName, popupClassName, onClose, placement, anchorOffset, viewportPadding, disabled }: DelayedPopup.Props): import("react/jsx-runtime").JSX.Element;
4
- export declare namespace DelayedPopup {
5
- type Props = Pick<Popup.Props, 'anchorOffset' | 'viewportPadding' | 'placement' | 'onClose'> & {
6
- delay?: number;
7
- scroller?: string | RefObject<HTMLElement | null>;
8
- content: ReactNode;
9
- children: ReactNode;
10
- anchorClassName?: string;
11
- popupClassName?: string;
12
- disabled?: boolean;
13
- };
14
- type HoverState = 'inactive' | 'waiting' | 'active';
15
- type TapState = 'inactive' | 'active';
16
- }
@@ -1,122 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.Tooltip = Tooltip;
7
- const jsx_runtime_1 = require("react/jsx-runtime");
8
- const funnel_core_1 = require("@nascentdigital/funnel-core");
9
- const classnames_1 = __importDefault(require("classnames"));
10
- const framer_motion_1 = require("framer-motion");
11
- const react_1 = require("react");
12
- const react_dom_1 = require("react-dom");
13
- const rxjs_1 = require("rxjs");
14
- const hooks_1 = require("../hooks");
15
- const ClickObserver_1 = require("./ClickObserver");
16
- const Popup_1 = require("./Popup");
17
- // component
18
- function Tooltip({ delay = 200, scroller, content, children, anchorClassName, popupClassName, onClose = funnel_core_1.Fn.NoOp, placement = 'bottom', anchorOffset = 0, viewportPadding = 0, disabled = false }) {
19
- // bind state + hooks
20
- const id = (0, react_1.useId)();
21
- const [hoverState, setHoverState] = (0, react_1.useState)('inactive');
22
- const [tapState, setTapState] = (0, react_1.useState)('inactive');
23
- const [anchorRef, anchorElement] = (0, hooks_1.useRefElement)();
24
- const [contentRef, contentElement] = (0, hooks_1.useRefElement)();
25
- const scrollerRef = (0, hooks_1.useElementSelector)(scroller);
26
- const scrollOffsetRef = (0, react_1.useRef)(undefined);
27
- // only pass scroller ref if a scroller element was provided in props
28
- const { scrollY } = (0, hooks_1.useElementScrolled)(scroller ? scrollerRef : undefined);
29
- // set open if either hover state or tap state are active and not disabled
30
- const open = (0, react_1.useMemo)(() => {
31
- return (hoverState === 'active' || tapState === 'active') && !disabled;
32
- }, [hoverState, tapState, disabled]);
33
- // state for: hover [inactive, waiting, active] (mouse focus), tap [inactive, active] (pointer down)
34
- // hooks for: window resize, scroll, on-window-click -> dismiss / deactivate anything
35
- // helpers
36
- const onOpen = (0, react_1.useCallback)(() => {
37
- var _a;
38
- // when tooltip is opened, set initial scroll offset
39
- scrollOffsetRef.current = (_a = scrollY === null || scrollY === void 0 ? void 0 : scrollY.offset) !== null && _a !== void 0 ? _a : 0;
40
- }, [scrollOffsetRef, scrollY]);
41
- // wire on mouse enter
42
- const onPointerEnter = (0, react_1.useCallback)((e) => {
43
- // ignore event if it's not a mouse event
44
- if (e.pointerType !== 'mouse') {
45
- return;
46
- }
47
- // set hover state to waiting
48
- setHoverState('waiting');
49
- // delay opening popup
50
- (0, rxjs_1.timer)(delay).subscribe(() => setHoverState(prevState => {
51
- // if current state is still waiting, set to active and call onOpen
52
- if (prevState === 'waiting') {
53
- onOpen();
54
- return 'active';
55
- }
56
- // if prevState is not waiting, set to inactive
57
- else {
58
- return 'inactive';
59
- }
60
- }));
61
- }, [onOpen, delay]);
62
- // wire on mouse leave
63
- const onPointerLeave = (0, react_1.useCallback)((e) => {
64
- // ignore event if it's not a mouse event
65
- if (e.pointerType !== 'mouse') {
66
- return;
67
- }
68
- // update hover state
69
- setHoverState('inactive');
70
- onClose();
71
- }, [onClose]);
72
- // wire tap on mobile
73
- const onClick = (0, react_1.useCallback)((e) => {
74
- // ignore mouse click - only hover should be triggered when mouse is present
75
- if ('pointerType' in e && e.pointerType === 'mouse') {
76
- return;
77
- }
78
- // update tap state and call onOpen
79
- setTapState('active');
80
- onOpen();
81
- }, [onOpen]);
82
- // dismiss popup
83
- const dismiss = (0, react_1.useCallback)(() => {
84
- // update hover and tap state to inactive
85
- setHoverState('inactive');
86
- setTapState('inactive');
87
- onClose();
88
- }, [onClose]);
89
- // listen for scroll changes
90
- (0, react_1.useEffect)(() => {
91
- // if tooltip is open and scroll offset has changed, dismiss tooltip
92
- const hasScrolled = scrollOffsetRef.current !== undefined && (scrollY === null || scrollY === void 0 ? void 0 : scrollY.offset) !== undefined
93
- && scrollOffsetRef.current !== (scrollY === null || scrollY === void 0 ? void 0 : scrollY.offset);
94
- if (open && hasScrolled) {
95
- dismiss();
96
- }
97
- }, [open, scrollY, dismiss]);
98
- // bind hover and click listeners
99
- (0, react_1.useEffect)(() => {
100
- anchorElement === null || anchorElement === void 0 ? void 0 : anchorElement.addEventListener('pointerenter', onPointerEnter);
101
- anchorElement === null || anchorElement === void 0 ? void 0 : anchorElement.addEventListener('pointerleave', onPointerLeave);
102
- anchorElement === null || anchorElement === void 0 ? void 0 : anchorElement.addEventListener('click', onClick);
103
- // unbind listeners
104
- return () => {
105
- anchorElement === null || anchorElement === void 0 ? void 0 : anchorElement.removeEventListener('pointerenter', onPointerEnter);
106
- anchorElement === null || anchorElement === void 0 ? void 0 : anchorElement.removeEventListener('pointerleave', onPointerLeave);
107
- anchorElement === null || anchorElement === void 0 ? void 0 : anchorElement.removeEventListener('click', onClick);
108
- };
109
- }, [anchorElement, onPointerEnter, onPointerLeave, onClick]);
110
- const anchorRect = (0, Popup_1.getAnchorRect)(anchorElement);
111
- const popupFrame = !!anchorRect && !!contentElement
112
- ? Popup_1.Popup.getFrame({
113
- anchorRect,
114
- contentElement,
115
- placement,
116
- anchorOffset,
117
- viewportPadding
118
- }) : {};
119
- // render
120
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: anchorClassName, ref: anchorRef, children: children }), funnel_core_1.Browser.available && (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)(framer_motion_1.AnimatePresence, { children: open && !!anchorElement &&
121
- (0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, { id: id, className: "fixed inset-0 z-popup pointer-events-none", initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { ease: 'circIn', duration: 0.2 }, children: (0, jsx_runtime_1.jsx)(ClickObserver_1.ClickObserver, { disabled: !open, onClickOutside: dismiss, children: (0, jsx_runtime_1.jsx)("div", { ref: contentRef, style: popupFrame, className: (0, classnames_1.default)(popupClassName, 'absolute pointer-events-auto'), children: content }) }) }, id) }), document.body)] }));
122
- }
@@ -1 +0,0 @@
1
- export type UILevel = 'l1' | 'l2' | 'l3';
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,5 +0,0 @@
1
- import { PropsWithChildren } from 'react';
2
- export declare function BackdropContainer({ children }: BackdropContainer.Props): import("react/jsx-runtime").JSX.Element;
3
- export declare namespace BackdropContainer {
4
- type Props = PropsWithChildren;
5
- }
@@ -1,114 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.BackdropContainer = BackdropContainer;
7
- const jsx_runtime_1 = require("react/jsx-runtime");
8
- const funnel_core_1 = require("@nascentdigital/funnel-core");
9
- const classnames_1 = __importDefault(require("classnames"));
10
- const framer_motion_1 = require("framer-motion");
11
- const react_1 = require("react");
12
- const react_dom_1 = require("react-dom");
13
- const rxjs_1 = require("rxjs");
14
- const BackdropContext_1 = require("./BackdropContext");
15
- const BackdropEvent_1 = require("./BackdropEvent");
16
- // component
17
- function BackdropContainer({ children }) {
18
- var _a, _b;
19
- // bind context
20
- const [dependantId, setDependantId] = (0, react_1.useState)();
21
- const dependantsRef = (0, react_1.useRef)([]);
22
- const dependants = dependantsRef.current;
23
- const context = (0, react_1.useMemo)(() => ({
24
- eventBus: new rxjs_1.Subject()
25
- }), []);
26
- const { eventBus } = context;
27
- const backdropOptions = (_b = (_a = dependants.find(ctx => ctx.id === dependantId)) === null || _a === void 0 ? void 0 : _a.options) !== null && _b !== void 0 ? _b : {};
28
- // listen to events from dependants
29
- (0, react_1.useEffect)(() => {
30
- // subscribe to events, proxying changes to backdrop
31
- const subscription = eventBus
32
- .pipe((0, rxjs_1.filter)(e => BackdropEvent_1.BackdropEvent.Capture.guard(e) || BackdropEvent_1.BackdropEvent.Release.guard(e)))
33
- .subscribe(event => {
34
- // handle capture
35
- const dependants = dependantsRef.current;
36
- const { id } = event;
37
- if (BackdropEvent_1.BackdropEvent.Capture.guard(event)) {
38
- // exit if dependant already exists in list
39
- if (dependants.some(ctx => ctx.id === id)) {
40
- return;
41
- }
42
- // add new dependant
43
- dependants.push({
44
- id,
45
- options: event.options
46
- });
47
- // set dependant to first in active list
48
- setDependantId(id);
49
- // raise event
50
- eventBus.next(BackdropEvent_1.BackdropEvent.Show.create(dependants.map(ctx => ctx.id), id, event.options));
51
- }
52
- // handle release
53
- else if (BackdropEvent_1.BackdropEvent.Release.guard(event)) {
54
- // determine index (or exit if not found)
55
- const dependantIndex = dependants.findIndex(ctx => ctx.id === id);
56
- if (dependantIndex < 0) {
57
- return;
58
- }
59
- // remove dependant from list
60
- dependants.splice(dependantIndex, 1);
61
- // reactivate previous dependant (if any)
62
- const dependantCount = dependants.length;
63
- const nextDependant = dependantCount > 0
64
- ? dependants[dependantCount - 1]
65
- : undefined;
66
- if (nextDependant) {
67
- eventBus.next(BackdropEvent_1.BackdropEvent.FocusChange.create(nextDependant.id));
68
- setDependantId(nextDependant.id);
69
- }
70
- // or unlock ui
71
- else {
72
- eventBus.next(BackdropEvent_1.BackdropEvent.Hide.create());
73
- setDependantId(undefined);
74
- }
75
- }
76
- });
77
- // unmount
78
- return () => subscription.unsubscribe();
79
- }, [eventBus]);
80
- // handle lock/unlock of ui
81
- (0, react_1.useEffect)(() => {
82
- // handle ui lock if we have a dependant
83
- let unlock = undefined;
84
- if (dependantId) {
85
- unlock = funnel_core_1.Browser.lockFocus(`#${CSS.escape(dependantId)}`);
86
- }
87
- // unlock previous (if any)
88
- return () => unlock && unlock();
89
- }, [dependantId]);
90
- // handle click
91
- const onClick = () => {
92
- // raise click event if we're open
93
- if (dependantId) {
94
- eventBus.next(BackdropEvent_1.BackdropEvent.Click.create());
95
- }
96
- };
97
- // render
98
- const { hideNav = false, level = 'l1' } = backdropOptions;
99
- return ((0, jsx_runtime_1.jsxs)(BackdropContext_1.BackdropContext.Provider, { value: context, children: [children, funnel_core_1.Browser.available && (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)(framer_motion_1.AnimatePresence, { mode: "wait", children: dependantId &&
100
- (0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, { className: (0, classnames_1.default)('Backdrop-root', {
101
- 'Backdrop-l1': !hideNav && level === 'l1',
102
- 'Backdrop-l2': hideNav || level === 'l2',
103
- 'Backdrop-l3': !hideNav && level === 'l3'
104
- }), variants: animationVariants, initial: "invisible", animate: "visible", exit: "invisible", onClick: onClick }, "backdrop") }), document.body)] }));
105
- }
106
- // animations
107
- const animationVariants = {
108
- invisible: {
109
- opacity: 0
110
- },
111
- visible: {
112
- opacity: 1
113
- }
114
- };
@@ -1,8 +0,0 @@
1
- import { Subject } from 'rxjs';
2
- import { BackdropEvent } from './BackdropEvent';
3
- export declare const BackdropContext: import("react").Context<BackdropContext.Data>;
4
- export declare namespace BackdropContext {
5
- type Data = {
6
- eventBus: Subject<BackdropEvent>;
7
- };
8
- }
@@ -1,6 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BackdropContext = void 0;
4
- const react_1 = require("react");
5
- // context
6
- exports.BackdropContext = (0, react_1.createContext)({});
@@ -1,64 +0,0 @@
1
- import { Tagged } from 'type-fest';
2
- import { UILevel } from '../UILevel';
3
- export type BackdropEvent = BackdropEvent.Show | BackdropEvent.Hide | BackdropEvent.Click | BackdropEvent.FocusChange | BackdropEvent.Capture | BackdropEvent.Release;
4
- export declare namespace BackdropEvent {
5
- export type Action = 'show' | 'hide' | 'click' | 'focusChange' | 'capture' | 'release';
6
- export type Options = {
7
- hideNav?: boolean;
8
- level?: UILevel;
9
- };
10
- export type DependantId = Tagged<string, 'funnel.BackdropEvent.DependantId'>;
11
- interface Base {
12
- action: Action;
13
- }
14
- export interface Show extends Base {
15
- action: 'show';
16
- dependent: DependantId;
17
- dependants: ReadonlyArray<DependantId>;
18
- options: Options;
19
- }
20
- export namespace Show {
21
- function guard(event: BackdropEvent): event is Show;
22
- function create(dependants: ReadonlyArray<DependantId>, dependent: DependantId, options: Options): Show;
23
- }
24
- export interface Hide extends Base {
25
- action: 'hide';
26
- }
27
- export namespace Hide {
28
- function guard(event: BackdropEvent): event is Hide;
29
- function create(): Hide;
30
- }
31
- export interface Click extends Base {
32
- action: 'click';
33
- }
34
- export namespace Click {
35
- function guard(event: BackdropEvent): event is Click;
36
- function create(): Click;
37
- }
38
- export interface FocusChange extends Base {
39
- action: 'focusChange';
40
- dependent: DependantId;
41
- }
42
- export namespace FocusChange {
43
- function guard(event: BackdropEvent): event is FocusChange;
44
- function create(dependent: DependantId): FocusChange;
45
- }
46
- export interface Capture extends Base {
47
- id: DependantId;
48
- action: 'capture';
49
- options: Options;
50
- }
51
- export namespace Capture {
52
- function guard(event: BackdropEvent): event is Capture;
53
- function create(id: DependantId, options: Options): Capture;
54
- }
55
- export interface Release extends Base {
56
- id: DependantId;
57
- action: 'release';
58
- }
59
- export namespace Release {
60
- function guard(event: BackdropEvent): event is Release;
61
- function create(id: DependantId): Release;
62
- }
63
- export {};
64
- }