@reykjavik/hanna-react 0.10.103 → 0.10.104
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/AccordionList.js +2 -2
- package/Alert.d.ts +2 -2
- package/Alert.js +1 -0
- package/AutosuggestSearch.d.ts +40 -0
- package/AutosuggestSearch.js +70 -0
- package/BasicTable.d.ts +24 -4
- package/BasicTable.js +20 -19
- package/BreadCrumbs.d.ts +2 -2
- package/CHANGELOG.md +34 -0
- package/ContactBubble.d.ts +4 -3
- package/ContactBubble.js +7 -4
- package/Datepicker.d.ts +8 -3
- package/Datepicker.js +36 -14
- package/FileInput.d.ts +2 -1
- package/FileInput.js +2 -2
- package/FormField.js +2 -2
- package/Gallery.d.ts +2 -1
- package/Gallery.js +5 -0
- package/Layout.d.ts +4 -3
- package/Layout.js +0 -3
- package/MainMenu/_PrimaryPanel.d.ts +3 -4
- package/MainMenu/_PrimaryPanel.js +1 -1
- package/MainMenu.d.ts +6 -4
- package/MainMenu.js +8 -16
- package/Multiselect.d.ts +2 -1
- package/Multiselect.js +4 -3
- package/NameCard.d.ts +2 -1
- package/NameCard.js +7 -0
- package/Pagination.d.ts +2 -2
- package/ReadSpeakerPlayer.js +13 -5
- package/SearchInput.d.ts +24 -2
- package/SearchInput.js +13 -3
- package/SearchResults.d.ts +2 -1
- package/SearchResults.js +10 -2
- package/ShareButtons.d.ts +3 -2
- package/SiteSearchAutocomplete.d.ts +11 -11
- package/SiteSearchAutocomplete.js +21 -7
- package/SiteSearchCurtain.js +2 -2
- package/SiteSearchInput.d.ts +19 -10
- package/SiteSearchInput.js +9 -6
- package/Tooltip.js +4 -3
- package/VerticalTabsTOC.js +2 -2
- package/_abstract/_ScrollWrapper.d.ts +10 -0
- package/_abstract/_ScrollWrapper.js +21 -0
- package/_abstract/_Table.d.ts +71 -0
- package/_abstract/_Table.js +55 -0
- package/_abstract/_TogglerGroup.js +2 -2
- package/_abstract/_TogglerInput.js +2 -2
- package/_mixed_export_resolution_/ReactDatepicker.d.ts +3 -0
- package/esm/AccordionList.js +1 -1
- package/esm/Alert.d.ts +2 -2
- package/esm/Alert.js +1 -0
- package/esm/AutosuggestSearch.d.ts +40 -0
- package/esm/AutosuggestSearch.js +66 -0
- package/esm/BasicTable.d.ts +24 -4
- package/esm/BasicTable.js +19 -18
- package/esm/BreadCrumbs.d.ts +2 -2
- package/esm/ContactBubble.d.ts +4 -3
- package/esm/ContactBubble.js +6 -3
- package/esm/Datepicker.d.ts +8 -3
- package/esm/Datepicker.js +35 -13
- package/esm/FileInput.d.ts +2 -1
- package/esm/FileInput.js +1 -1
- package/esm/FormField.js +1 -1
- package/esm/Gallery.d.ts +2 -1
- package/esm/Gallery.js +5 -0
- package/esm/Layout.d.ts +4 -3
- package/esm/Layout.js +0 -3
- package/esm/MainMenu/_PrimaryPanel.d.ts +3 -4
- package/esm/MainMenu/_PrimaryPanel.js +1 -1
- package/esm/MainMenu.d.ts +6 -4
- package/esm/MainMenu.js +8 -16
- package/esm/Multiselect.d.ts +2 -1
- package/esm/Multiselect.js +2 -1
- package/esm/NameCard.d.ts +2 -1
- package/esm/NameCard.js +7 -0
- package/esm/Pagination.d.ts +2 -2
- package/esm/ReadSpeakerPlayer.js +13 -5
- package/esm/SearchInput.d.ts +24 -2
- package/esm/SearchInput.js +13 -3
- package/esm/SearchResults.d.ts +2 -1
- package/esm/SearchResults.js +9 -1
- package/esm/ShareButtons.d.ts +3 -2
- package/esm/SiteSearchAutocomplete.d.ts +11 -11
- package/esm/SiteSearchAutocomplete.js +21 -7
- package/esm/SiteSearchCurtain.js +1 -1
- package/esm/SiteSearchInput.d.ts +19 -10
- package/esm/SiteSearchInput.js +8 -6
- package/esm/Tooltip.js +2 -1
- package/esm/VerticalTabsTOC.js +1 -1
- package/esm/_abstract/_ScrollWrapper.d.ts +10 -0
- package/esm/_abstract/_ScrollWrapper.js +16 -0
- package/esm/_abstract/_Table.d.ts +71 -0
- package/esm/_abstract/_Table.js +51 -0
- package/esm/_abstract/_TogglerGroup.js +1 -1
- package/esm/_abstract/_TogglerInput.js +1 -1
- package/esm/_mixed_export_resolution_/ReactDatepicker.d.ts +3 -0
- package/esm/index.d.ts +1 -0
- package/esm/utils/browserSide.d.ts +119 -1
- package/esm/utils/browserSide.js +152 -1
- package/esm/utils/config.d.ts +1 -14
- package/esm/utils/config.js +0 -2
- package/esm/utils/useCallbackOnEsc.d.ts +6 -0
- package/esm/utils/useCallbackOnEsc.js +25 -0
- package/esm/utils/useDomid.d.ts +8 -0
- package/esm/utils/useDomid.js +17 -0
- package/esm/utils/useLaggedState.d.ts +18 -0
- package/esm/utils/useLaggedState.js +84 -0
- package/esm/utils/useOnClickOutside.d.ts +9 -0
- package/esm/utils/useOnClickOutside.js +32 -0
- package/esm/utils/useScrollEdgeDetect.d.ts +15 -0
- package/esm/utils/useScrollEdgeDetect.js +45 -0
- package/esm/utils/useShortState.d.ts +2 -0
- package/esm/utils/useShortState.js +34 -0
- package/esm/utils.d.ts +1 -0
- package/esm/utils.js +1 -0
- package/index.d.ts +1 -0
- package/package.json +6 -2
- package/utils/browserSide.d.ts +119 -1
- package/utils/browserSide.js +156 -4
- package/utils/config.d.ts +1 -14
- package/utils/config.js +1 -4
- package/utils/useCallbackOnEsc.d.ts +6 -0
- package/utils/useCallbackOnEsc.js +29 -0
- package/utils/useDomid.d.ts +8 -0
- package/utils/useDomid.js +21 -0
- package/utils/useLaggedState.d.ts +18 -0
- package/utils/useLaggedState.js +88 -0
- package/utils/useOnClickOutside.d.ts +9 -0
- package/utils/useOnClickOutside.js +35 -0
- package/utils/useScrollEdgeDetect.d.ts +15 -0
- package/utils/useScrollEdgeDetect.js +50 -0
- package/utils/useShortState.d.ts +2 -0
- package/utils/useShortState.js +38 -0
- package/utils.d.ts +1 -0
- package/utils.js +1 -0
package/esm/utils/browserSide.js
CHANGED
|
@@ -1 +1,152 @@
|
|
|
1
|
-
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
let alreadyBrowserSide = false;
|
|
3
|
+
const defaultSSRSupport = true;
|
|
4
|
+
/**
|
|
5
|
+
* The default value use for the optional `ssrSupport` parameter
|
|
6
|
+
* on the `useIsBRowserSide` and `useIsServerSide` hooks.`
|
|
7
|
+
*/
|
|
8
|
+
let DEFAULT_SSR_SUPPORT = defaultSSRSupport;
|
|
9
|
+
/**
|
|
10
|
+
* Low-level useState wrapper that initializes the state to one value
|
|
11
|
+
* during initial render and then updates it to another value
|
|
12
|
+
* once the component has been mounted.
|
|
13
|
+
*
|
|
14
|
+
* After that it's just a normal [value, setValue] pair.
|
|
15
|
+
*
|
|
16
|
+
* NOTE: The optional `ssrSupport` parameter is ignored after the initial render
|
|
17
|
+
*/
|
|
18
|
+
const useClientState = (serverState, clientState,
|
|
19
|
+
/**
|
|
20
|
+
* Indicates whether server-side rendering is supported or not.
|
|
21
|
+
*
|
|
22
|
+
* The `ssr-only` value is useful for cases where you need
|
|
23
|
+
* to demo the server-rendered version in a browser.
|
|
24
|
+
*/
|
|
25
|
+
ssrSupport = DEFAULT_SSR_SUPPORT) => {
|
|
26
|
+
const stateTuple = useState(() => (ssrSupport === 'ssr-only'
|
|
27
|
+
? serverState
|
|
28
|
+
: ssrSupport && !alreadyBrowserSide
|
|
29
|
+
? serverState
|
|
30
|
+
: clientState) // TODO: Remove this type assertion once @types/react and typescript have been updated
|
|
31
|
+
);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
alreadyBrowserSide = true;
|
|
34
|
+
if (ssrSupport !== 'ssr-only') {
|
|
35
|
+
stateTuple[1](clientState);
|
|
36
|
+
}
|
|
37
|
+
}, []);
|
|
38
|
+
return stateTuple;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Returns `true` if `useEffect` has not executed yet.
|
|
42
|
+
*
|
|
43
|
+
* This signals that we're in "server-side rendering" mode
|
|
44
|
+
* and it's not yet appropriate to do JS-driven UI enhancements.
|
|
45
|
+
*
|
|
46
|
+
* ```js
|
|
47
|
+
* const Knob = (props) => {
|
|
48
|
+
* const [visible, setVisible] = useState(false);
|
|
49
|
+
* const isServer = useIsServerSide();
|
|
50
|
+
* const handleClick = () => {
|
|
51
|
+
* setVisible(!visible);
|
|
52
|
+
* props.onClick && props.onClick(!visible);
|
|
53
|
+
* };
|
|
54
|
+
*
|
|
55
|
+
* if (isServer) {
|
|
56
|
+
* return <span className="Knob">{props.label}</span>
|
|
57
|
+
* }
|
|
58
|
+
* return (
|
|
59
|
+
* <button className="Knob" aria-pressed={visible} onClick={handleClick}>
|
|
60
|
+
* {props.label}
|
|
61
|
+
* </button>
|
|
62
|
+
* );
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* SSR support mode can optionally be set to:
|
|
67
|
+
*
|
|
68
|
+
* - `true` (the default) enables the serve-side phase (returns `true` then `undefined`).
|
|
69
|
+
* - `false` disables (skips) the serve-side phase (always returns `undefined`).
|
|
70
|
+
* - `"ssr-only"` disables (skips) the browser-side phase (always returns `true`).
|
|
71
|
+
*
|
|
72
|
+
* NOTE: The `ssrSupport` parameter is ignored after the initial render.
|
|
73
|
+
*/
|
|
74
|
+
export const useIsServerSide = (ssrSupport) =>
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
76
|
+
useClientState(true, false, ssrSupport)[0] || undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Returns `true` when `useEffect` has executed.
|
|
79
|
+
*
|
|
80
|
+
* This signals the time to apply Progressive Enhancement.
|
|
81
|
+
*
|
|
82
|
+
* ```js
|
|
83
|
+
* const Knob = (props) => {
|
|
84
|
+
* const [visible, setVisible] = useState(false);
|
|
85
|
+
* const isBrowser = useIsBrowserSide();
|
|
86
|
+
* const handleClick = () => {
|
|
87
|
+
* setVisible(!visible);
|
|
88
|
+
* props.onClick && props.onClick(!visible);
|
|
89
|
+
* };
|
|
90
|
+
*
|
|
91
|
+
* if (isBrowser) {
|
|
92
|
+
* return (
|
|
93
|
+
* <button className="Knob" aria-pressed={visible} onClick={handleClick}>
|
|
94
|
+
* {props.label}
|
|
95
|
+
* </button>
|
|
96
|
+
* );
|
|
97
|
+
* }
|
|
98
|
+
* return <span className="Knob">{props.label}</span>
|
|
99
|
+
* }
|
|
100
|
+
* ```
|
|
101
|
+
*
|
|
102
|
+
* SSR support mode can optionally be set to:
|
|
103
|
+
*
|
|
104
|
+
* - `true` (the default) enables the serve-side phase (returns `true` then `undefined`).
|
|
105
|
+
* - `false` disables (skips) the serve-side phase (always returns `true`).
|
|
106
|
+
* - `"ssr-only"` disables (skips) the browser-side phase (always returns `undefined`).
|
|
107
|
+
*
|
|
108
|
+
* NOTE: The `ssrSupport` parameter is ignored after the initial render.
|
|
109
|
+
*/
|
|
110
|
+
export const useIsBrowserSide = (ssrSupport) =>
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
112
|
+
useClientState(false, true, ssrSupport)[0] || undefined;
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
const _history = [];
|
|
115
|
+
/**
|
|
116
|
+
* Allows you to set a the default SSRSupport value for the `useIsBRowserSide`
|
|
117
|
+
* and `useIsServerSide` hooks.
|
|
118
|
+
*
|
|
119
|
+
* Example use:
|
|
120
|
+
*
|
|
121
|
+
* ```js
|
|
122
|
+
* setDefaultSSR(false);
|
|
123
|
+
* ```
|
|
124
|
+
*
|
|
125
|
+
* The values are pushed to a simple stack, and if you want to revert
|
|
126
|
+
* a temporarily set value, use the `setDefaultSSR.pop()` method
|
|
127
|
+
* to go back to the previous value. Example:
|
|
128
|
+
*
|
|
129
|
+
* ```js
|
|
130
|
+
* setDefaultSSR('ssr-only');
|
|
131
|
+
* // ...render some components...
|
|
132
|
+
* setDefaultSSR.pop(); // go back to the previous state
|
|
133
|
+
* ```
|
|
134
|
+
*
|
|
135
|
+
* You explicitly switch to using the library's default by passing `undefined`
|
|
136
|
+
* as an argument — like so:
|
|
137
|
+
*
|
|
138
|
+
* ```js
|
|
139
|
+
* setDefaultSSR(undefined);
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export const setDefaultSSR = (ssrSupport) => {
|
|
143
|
+
DEFAULT_SSR_SUPPORT = ssrSupport != null ? ssrSupport : defaultSSRSupport;
|
|
144
|
+
_history.unshift(DEFAULT_SSR_SUPPORT);
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* Unsets the last pushed defaultSSR value
|
|
148
|
+
*/
|
|
149
|
+
setDefaultSSR.pop = () => {
|
|
150
|
+
_history.shift();
|
|
151
|
+
DEFAULT_SSR_SUPPORT = _history[0] != null ? _history[0] : defaultSSRSupport;
|
|
152
|
+
};
|
package/esm/utils/config.d.ts
CHANGED
|
@@ -1,14 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export type { LinkRenderer } from '../_abstract/_Link.js';
|
|
3
|
-
export { setLinkRenderer } from '../_abstract/_Link.js';
|
|
4
|
-
export { setDefaultSSR } from '@hugsmidjan/react/hooks';
|
|
5
|
-
export type SSRSupportProps = {
|
|
6
|
-
/**
|
|
7
|
-
* Indicates whether server-side rendering is supported or not.
|
|
8
|
-
*
|
|
9
|
-
* The `ssr-only` value is useful for cases where you need
|
|
10
|
-
* to demo the server-rendered version in a browser.
|
|
11
|
-
*/
|
|
12
|
-
ssr?: SSRSupport;
|
|
13
|
-
};
|
|
14
|
-
export type { SSRSupport };
|
|
1
|
+
export { type LinkRenderer, setLinkRenderer } from '../_abstract/_Link.js';
|
package/esm/utils/config.js
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Performs a callback whenever the user hits the ESC key.
|
|
4
|
+
*
|
|
5
|
+
* Pass `undefined` to remove the event listener
|
|
6
|
+
*/
|
|
7
|
+
export const useCallbackOnEsc = (callback) => {
|
|
8
|
+
const cb = useRef(callback);
|
|
9
|
+
const active = !!callback;
|
|
10
|
+
cb.current = callback;
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!active) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const callbackOnEsc = (e) => {
|
|
16
|
+
if (e.key === 'Escape') {
|
|
17
|
+
cb.current && cb.current();
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
document.addEventListener('keydown', callbackOnEsc);
|
|
21
|
+
return () => {
|
|
22
|
+
document.removeEventListener('keydown', callbackOnEsc);
|
|
23
|
+
};
|
|
24
|
+
}, [active]);
|
|
25
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a stable, unique ID string.
|
|
3
|
+
*
|
|
4
|
+
* Uses useId from React@18 when available, but falls back on a custom id
|
|
5
|
+
* generator. (The custom generator causes angry hydration warnings in dev
|
|
6
|
+
* mode).
|
|
7
|
+
*/
|
|
8
|
+
export declare const useDomid: (staticId?: string) => string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import domid from '@hugsmidjan/qj/domid';
|
|
3
|
+
// @ts-expect-error (transparently feature-detect useId hook, which is introduced in React@18)
|
|
4
|
+
const useId = React.useId;
|
|
5
|
+
/**
|
|
6
|
+
* Returns a stable, unique ID string.
|
|
7
|
+
*
|
|
8
|
+
* Uses useId from React@18 when available, but falls back on a custom id
|
|
9
|
+
* generator. (The custom generator causes angry hydration warnings in dev
|
|
10
|
+
* mode).
|
|
11
|
+
*/
|
|
12
|
+
export const useDomid = useId
|
|
13
|
+
? (staticId) => {
|
|
14
|
+
const id = useId();
|
|
15
|
+
return staticId || id;
|
|
16
|
+
}
|
|
17
|
+
: (staticId) => React.useRef(staticId || domid()).current;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `useState` alternative with in-built support for delayed (debounced) effect.
|
|
3
|
+
*
|
|
4
|
+
* This is especially useful when emulating "focusin"/"focusout" events,
|
|
5
|
+
* and a less jittery 'onMouseEnter'/'onMouseLeave' behavior.
|
|
6
|
+
*
|
|
7
|
+
* The returned `setState` function accepts an optional `customDelay` parameter.
|
|
8
|
+
*
|
|
9
|
+
* An explicitly falsy delay (`0` | `false` | `null`) results in a
|
|
10
|
+
* immediate (normal) `setState` invocation, whereas any other value uses
|
|
11
|
+
* `setTimeout` to delay the update.
|
|
12
|
+
*
|
|
13
|
+
* Each `setState` call automatically cancels any pending scheduled update.
|
|
14
|
+
*
|
|
15
|
+
* You can also use the `setState.cancel()` helper to to cancel the last
|
|
16
|
+
* scheduled update, without explicitly setting a new value.
|
|
17
|
+
*/
|
|
18
|
+
export declare const useLaggedState: <S>(initialState: S | (() => S), defaultDelay?: number, thenState?: S | (() => S) | undefined) => [currentState: S, setState: (newState: S | ((prevState: S, upcomingState: S) => S), customDelay?: number | false) => void, nextState: S, isTransitioning: true | undefined];
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { useState, useRef, useEffect, useMemo } from 'react';
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
/**
|
|
4
|
+
* A `useState` alternative with in-built support for delayed (debounced) effect.
|
|
5
|
+
*
|
|
6
|
+
* This is especially useful when emulating "focusin"/"focusout" events,
|
|
7
|
+
* and a less jittery 'onMouseEnter'/'onMouseLeave' behavior.
|
|
8
|
+
*
|
|
9
|
+
* The returned `setState` function accepts an optional `customDelay` parameter.
|
|
10
|
+
*
|
|
11
|
+
* An explicitly falsy delay (`0` | `false` | `null`) results in a
|
|
12
|
+
* immediate (normal) `setState` invocation, whereas any other value uses
|
|
13
|
+
* `setTimeout` to delay the update.
|
|
14
|
+
*
|
|
15
|
+
* Each `setState` call automatically cancels any pending scheduled update.
|
|
16
|
+
*
|
|
17
|
+
* You can also use the `setState.cancel()` helper to to cancel the last
|
|
18
|
+
* scheduled update, without explicitly setting a new value.
|
|
19
|
+
*/
|
|
20
|
+
export const useLaggedState = (initialState,
|
|
21
|
+
/**
|
|
22
|
+
* Default delay in milliseconds. A value of `0` results in a
|
|
23
|
+
* immediate (normal) setState invocation.
|
|
24
|
+
*
|
|
25
|
+
* NOTE: The `defaultDelay` parameter is ignored after the initial render.
|
|
26
|
+
*/
|
|
27
|
+
defaultDelay = 0,
|
|
28
|
+
/**
|
|
29
|
+
* A state that should be automatically transitioned to after the initial mounting.
|
|
30
|
+
*
|
|
31
|
+
* Syntatic sugar equivalent to the following:
|
|
32
|
+
*
|
|
33
|
+
* ```js
|
|
34
|
+
* const [value, _, setValue] = useLaggedState(initialState, defaultDelay);
|
|
35
|
+
* useEffect(() => {
|
|
36
|
+
* setValue(thenState, defaultDelay);
|
|
37
|
+
* }, [])
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
thenState) => {
|
|
41
|
+
const timeout = useRef();
|
|
42
|
+
const [[currentState, nextState], setLocalState] = useState(() => {
|
|
43
|
+
const initial = typeof initialState === 'function' ? initialState() : initialState;
|
|
44
|
+
return [initial, initial];
|
|
45
|
+
});
|
|
46
|
+
const setState = useMemo(() => {
|
|
47
|
+
const _setter = (newState, customDelay) => {
|
|
48
|
+
timeout.current && clearTimeout(timeout.current);
|
|
49
|
+
const delay = customDelay !== undefined ? customDelay : defaultDelay;
|
|
50
|
+
setLocalState((prevState) => {
|
|
51
|
+
const [current, upcoming] = prevState;
|
|
52
|
+
const newValue = typeof newState === 'function'
|
|
53
|
+
? newState(current, upcoming)
|
|
54
|
+
: newState;
|
|
55
|
+
if (!delay || newValue === current) {
|
|
56
|
+
// Instant update!
|
|
57
|
+
return newValue === current && newValue === upcoming
|
|
58
|
+
? prevState
|
|
59
|
+
: [newValue, newValue];
|
|
60
|
+
}
|
|
61
|
+
// Debounced update!
|
|
62
|
+
timeout.current = setTimeout(() => setLocalState([newValue, newValue]), delay);
|
|
63
|
+
return [current, newValue];
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
_setter.cancel = () => {
|
|
67
|
+
_setter((prevState) => prevState, null);
|
|
68
|
+
};
|
|
69
|
+
return _setter;
|
|
70
|
+
},
|
|
71
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
72
|
+
[
|
|
73
|
+
// defaultDelay,
|
|
74
|
+
]);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
thenState !== undefined && setState(thenState);
|
|
77
|
+
return () => {
|
|
78
|
+
timeout.current && clearTimeout(timeout.current);
|
|
79
|
+
};
|
|
80
|
+
}, [] // eslint-disable-line react-hooks/exhaustive-deps
|
|
81
|
+
);
|
|
82
|
+
const isTransitioning = currentState !== nextState || undefined;
|
|
83
|
+
return [currentState, setState, nextState, isTransitioning];
|
|
84
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { MutableRefObject, RefObject } from 'react';
|
|
2
|
+
type Ref<E extends HTMLElement> = MutableRefObject<E> | RefObject<E>;
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param ref single or array of refs to check for click outside
|
|
6
|
+
* @param handler callback to run when clicked outside of the ref
|
|
7
|
+
*/
|
|
8
|
+
declare const useOnClickOutside: <E extends HTMLElement>(ref: Ref<E> | Ref<E>[], handler: (event: globalThis.MouseEvent | globalThis.TouchEvent) => void) => void;
|
|
9
|
+
export { useOnClickOutside };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useEffect, useMemo } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* @param ref single or array of refs to check for click outside
|
|
5
|
+
* @param handler callback to run when clicked outside of the ref
|
|
6
|
+
*/
|
|
7
|
+
const useOnClickOutside = (ref, handler) => {
|
|
8
|
+
const refs = Array.isArray(ref) ? ref : [ref];
|
|
9
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
10
|
+
const stableRefs = useMemo(() => refs, refs);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const listener = (event) => {
|
|
13
|
+
const shouldTrigger = !stableRefs.some((r) => {
|
|
14
|
+
const node = r.current;
|
|
15
|
+
if (!node) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return node.contains(event.target);
|
|
19
|
+
});
|
|
20
|
+
if (shouldTrigger) {
|
|
21
|
+
handler(event);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
document.addEventListener('mousedown', listener);
|
|
25
|
+
document.addEventListener('touchstart', listener);
|
|
26
|
+
return () => {
|
|
27
|
+
document.removeEventListener('mousedown', listener);
|
|
28
|
+
document.removeEventListener('touchstart', listener);
|
|
29
|
+
};
|
|
30
|
+
}, [handler, stableRefs]);
|
|
31
|
+
};
|
|
32
|
+
export { useOnClickOutside };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
type ScrollAxis = 'horizontal' | 'vertical';
|
|
3
|
+
type AtState = {
|
|
4
|
+
start: boolean;
|
|
5
|
+
end: boolean;
|
|
6
|
+
};
|
|
7
|
+
export type ScrollEdgeDetectOptions<RefElm extends HTMLElement = HTMLElement> = {
|
|
8
|
+
axis: ScrollAxis;
|
|
9
|
+
/** **NOTE:** Make sure this function is stable to avoid unnecessary re-runs */
|
|
10
|
+
getElm?: (elm: RefElm | null | undefined) => HTMLElement | undefined | null | false;
|
|
11
|
+
/** Initial `at` status */
|
|
12
|
+
startAt?: AtState;
|
|
13
|
+
};
|
|
14
|
+
export declare const useScrollEdgeDetect: <RefElm extends HTMLElement = HTMLElement>(options: ScrollAxis | ScrollEdgeDetectOptions<RefElm>, scrollerRef?: RefObject<RefElm> | undefined) => [scrollElmRef: RefObject<RefElm>, at: AtState];
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import throttle from '@hugsmidjan/qj/throttle';
|
|
3
|
+
const tolerance = 8; // px
|
|
4
|
+
const throttleMs = 100;
|
|
5
|
+
export const useScrollEdgeDetect = (options, scrollerRef) => {
|
|
6
|
+
const opts = typeof options === 'string' ? { axis: options } : options;
|
|
7
|
+
const _scrollerRef = useRef(null);
|
|
8
|
+
scrollerRef = scrollerRef || _scrollerRef;
|
|
9
|
+
const [at, setAt] = useState(opts.startAt || { start: true, end: true });
|
|
10
|
+
const scrollerRefElm = scrollerRef.current;
|
|
11
|
+
const { getElm, axis } = opts;
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const scrollerElm = scrollerRefElm && getElm ? getElm(scrollerRefElm) : scrollerRefElm;
|
|
14
|
+
if (!(scrollerElm instanceof HTMLElement)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const checkScroll = throttle(() => setAt((at) => {
|
|
18
|
+
let scroll, offsetSize, totalSize;
|
|
19
|
+
if (axis === 'horizontal') {
|
|
20
|
+
scroll = scrollerElm.scrollLeft;
|
|
21
|
+
offsetSize = scrollerElm.offsetWidth;
|
|
22
|
+
totalSize = scrollerElm.scrollWidth;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
scroll = scrollerElm.scrollTop;
|
|
26
|
+
offsetSize = scrollerElm.offsetHeight;
|
|
27
|
+
totalSize = scrollerElm.scrollHeight;
|
|
28
|
+
}
|
|
29
|
+
const start = scroll < tolerance;
|
|
30
|
+
const end = totalSize - (offsetSize + scroll) < tolerance;
|
|
31
|
+
if (at.start === start && at.end === end) {
|
|
32
|
+
return at;
|
|
33
|
+
}
|
|
34
|
+
return { start, end };
|
|
35
|
+
}), throttleMs);
|
|
36
|
+
scrollerElm.addEventListener('scroll', checkScroll);
|
|
37
|
+
window.addEventListener('resize', checkScroll);
|
|
38
|
+
checkScroll();
|
|
39
|
+
return () => {
|
|
40
|
+
scrollerElm.removeEventListener('scroll', checkScroll);
|
|
41
|
+
window.removeEventListener('resize', checkScroll);
|
|
42
|
+
};
|
|
43
|
+
}, [scrollerRefElm, getElm, axis]);
|
|
44
|
+
return [scrollerRef, at];
|
|
45
|
+
};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** State variable that always snaps back to `undefined` after `duration` milliseconds. */
|
|
2
|
+
export declare const useShortState: <S>(initialState?: S | (() => S) | undefined, defaultDuration?: number) => readonly [S | undefined, (newState: S | (() => S), duration?: number) => void];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
const DEFAULT_DURATION = 0;
|
|
3
|
+
// TODO: Add function signtures allowing either zero args, or 2.
|
|
4
|
+
/** State variable that always snaps back to `undefined` after `duration` milliseconds. */
|
|
5
|
+
export const useShortState = (
|
|
6
|
+
/** Initial temporary state that then gets reverted back
|
|
7
|
+
* to `undefined` after `duration` milliseconds
|
|
8
|
+
* */
|
|
9
|
+
initialState,
|
|
10
|
+
/** Default duration, can be overridden on a case-by-case basis
|
|
11
|
+
* by passing a custom duration to the `setState` function
|
|
12
|
+
* */
|
|
13
|
+
defaultDuration = DEFAULT_DURATION) => {
|
|
14
|
+
const [state, _setState] = useState(initialState);
|
|
15
|
+
const timeout = useRef();
|
|
16
|
+
const cancelTimeout = () => {
|
|
17
|
+
timeout.current && clearTimeout(timeout.current);
|
|
18
|
+
};
|
|
19
|
+
const setState = useRef((newState, duration = defaultDuration) => {
|
|
20
|
+
_setState(newState);
|
|
21
|
+
cancelTimeout();
|
|
22
|
+
timeout.current = setTimeout(() => {
|
|
23
|
+
timeout.current = null;
|
|
24
|
+
_setState(undefined);
|
|
25
|
+
}, duration);
|
|
26
|
+
}).current;
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (initialState !== undefined) {
|
|
29
|
+
setState(initialState, defaultDuration);
|
|
30
|
+
}
|
|
31
|
+
return cancelTimeout;
|
|
32
|
+
}, []);
|
|
33
|
+
return [state, setState];
|
|
34
|
+
};
|
package/esm/utils.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './utils/browserSide.js';
|
|
|
3
3
|
export * from './utils/config.js';
|
|
4
4
|
export { HannaUIState, useHannaUIState } from './utils/HannaUIState.js';
|
|
5
5
|
export * from './utils/useDidChange.js';
|
|
6
|
+
export * from './utils/useDomid.js';
|
|
6
7
|
export * from './utils/useFormatMonitor.js';
|
|
7
8
|
export * from './utils/useGetSVGtext.js';
|
|
8
9
|
export * from './utils/useMenuToggling.js';
|
package/esm/utils.js
CHANGED
|
@@ -2,6 +2,7 @@ export * from './utils/browserSide.js';
|
|
|
2
2
|
export * from './utils/config.js';
|
|
3
3
|
export { HannaUIState, useHannaUIState } from './utils/HannaUIState.js';
|
|
4
4
|
export * from './utils/useDidChange.js';
|
|
5
|
+
export * from './utils/useDomid.js';
|
|
5
6
|
export * from './utils/useFormatMonitor.js';
|
|
6
7
|
export * from './utils/useGetSVGtext.js';
|
|
7
8
|
export * from './utils/useMenuToggling.js';
|
package/index.d.ts
CHANGED
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
/// <reference path="./Bling.d.tsx" />
|
|
90
90
|
/// <reference path="./BgBox.d.tsx" />
|
|
91
91
|
/// <reference path="./BasicTable.d.tsx" />
|
|
92
|
+
/// <reference path="./AutosuggestSearch.d.tsx" />
|
|
92
93
|
/// <reference path="./Attention.d.tsx" />
|
|
93
94
|
/// <reference path="./ArticleMeta.d.tsx" />
|
|
94
95
|
/// <reference path="./ArticleCarousel.d.tsx" />
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reykjavik/hanna-react",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.104",
|
|
4
4
|
"author": "Reykjavík (http://www.reykjavik.is)",
|
|
5
5
|
"contributors": [
|
|
6
6
|
"Hugsmiðjan ehf (http://www.hugsmidjan.is)",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"@hugsmidjan/qj": "^4.22.1",
|
|
19
19
|
"@hugsmidjan/react": "^0.4.32",
|
|
20
20
|
"@reykjavik/hanna-css": "^0.4.7",
|
|
21
|
-
"@reykjavik/hanna-utils": "^0.2.
|
|
21
|
+
"@reykjavik/hanna-utils": "^0.2.10",
|
|
22
22
|
"@types/react-autosuggest": "^10.1.0",
|
|
23
23
|
"@types/react-datepicker": "^4.8.0",
|
|
24
24
|
"@types/react-transition-group": "^4.4.0",
|
|
@@ -409,6 +409,10 @@
|
|
|
409
409
|
"import": "./esm/BasicTable.js",
|
|
410
410
|
"require": "./BasicTable.js"
|
|
411
411
|
},
|
|
412
|
+
"./AutosuggestSearch": {
|
|
413
|
+
"import": "./esm/AutosuggestSearch.js",
|
|
414
|
+
"require": "./AutosuggestSearch.js"
|
|
415
|
+
},
|
|
412
416
|
"./Attention": {
|
|
413
417
|
"import": "./esm/Attention.js",
|
|
414
418
|
"require": "./Attention.js"
|
package/utils/browserSide.d.ts
CHANGED
|
@@ -1 +1,119 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Indicates whether server-side rendering is supported or not.
|
|
3
|
+
*
|
|
4
|
+
* The `ssr-only` value is useful for cases where you need
|
|
5
|
+
* to demo the server-rendered version in a browser.
|
|
6
|
+
*/
|
|
7
|
+
export type SSRSupport = boolean | 'ssr-only';
|
|
8
|
+
export type SSRSupportProps = {
|
|
9
|
+
/**
|
|
10
|
+
* Indicates whether server-side rendering is supported or not.
|
|
11
|
+
*
|
|
12
|
+
* The `ssr-only` value is useful for cases where you need
|
|
13
|
+
* to demo the server-rendered version in a browser.
|
|
14
|
+
*/
|
|
15
|
+
ssr?: SSRSupport;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Returns `true` if `useEffect` has not executed yet.
|
|
19
|
+
*
|
|
20
|
+
* This signals that we're in "server-side rendering" mode
|
|
21
|
+
* and it's not yet appropriate to do JS-driven UI enhancements.
|
|
22
|
+
*
|
|
23
|
+
* ```js
|
|
24
|
+
* const Knob = (props) => {
|
|
25
|
+
* const [visible, setVisible] = useState(false);
|
|
26
|
+
* const isServer = useIsServerSide();
|
|
27
|
+
* const handleClick = () => {
|
|
28
|
+
* setVisible(!visible);
|
|
29
|
+
* props.onClick && props.onClick(!visible);
|
|
30
|
+
* };
|
|
31
|
+
*
|
|
32
|
+
* if (isServer) {
|
|
33
|
+
* return <span className="Knob">{props.label}</span>
|
|
34
|
+
* }
|
|
35
|
+
* return (
|
|
36
|
+
* <button className="Knob" aria-pressed={visible} onClick={handleClick}>
|
|
37
|
+
* {props.label}
|
|
38
|
+
* </button>
|
|
39
|
+
* );
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* SSR support mode can optionally be set to:
|
|
44
|
+
*
|
|
45
|
+
* - `true` (the default) enables the serve-side phase (returns `true` then `undefined`).
|
|
46
|
+
* - `false` disables (skips) the serve-side phase (always returns `undefined`).
|
|
47
|
+
* - `"ssr-only"` disables (skips) the browser-side phase (always returns `true`).
|
|
48
|
+
*
|
|
49
|
+
* NOTE: The `ssrSupport` parameter is ignored after the initial render.
|
|
50
|
+
*/
|
|
51
|
+
export declare const useIsServerSide: (ssrSupport?: SSRSupport) => true | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Returns `true` when `useEffect` has executed.
|
|
54
|
+
*
|
|
55
|
+
* This signals the time to apply Progressive Enhancement.
|
|
56
|
+
*
|
|
57
|
+
* ```js
|
|
58
|
+
* const Knob = (props) => {
|
|
59
|
+
* const [visible, setVisible] = useState(false);
|
|
60
|
+
* const isBrowser = useIsBrowserSide();
|
|
61
|
+
* const handleClick = () => {
|
|
62
|
+
* setVisible(!visible);
|
|
63
|
+
* props.onClick && props.onClick(!visible);
|
|
64
|
+
* };
|
|
65
|
+
*
|
|
66
|
+
* if (isBrowser) {
|
|
67
|
+
* return (
|
|
68
|
+
* <button className="Knob" aria-pressed={visible} onClick={handleClick}>
|
|
69
|
+
* {props.label}
|
|
70
|
+
* </button>
|
|
71
|
+
* );
|
|
72
|
+
* }
|
|
73
|
+
* return <span className="Knob">{props.label}</span>
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* SSR support mode can optionally be set to:
|
|
78
|
+
*
|
|
79
|
+
* - `true` (the default) enables the serve-side phase (returns `true` then `undefined`).
|
|
80
|
+
* - `false` disables (skips) the serve-side phase (always returns `true`).
|
|
81
|
+
* - `"ssr-only"` disables (skips) the browser-side phase (always returns `undefined`).
|
|
82
|
+
*
|
|
83
|
+
* NOTE: The `ssrSupport` parameter is ignored after the initial render.
|
|
84
|
+
*/
|
|
85
|
+
export declare const useIsBrowserSide: (ssrSupport?: SSRSupport) => true | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* Allows you to set a the default SSRSupport value for the `useIsBRowserSide`
|
|
88
|
+
* and `useIsServerSide` hooks.
|
|
89
|
+
*
|
|
90
|
+
* Example use:
|
|
91
|
+
*
|
|
92
|
+
* ```js
|
|
93
|
+
* setDefaultSSR(false);
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* The values are pushed to a simple stack, and if you want to revert
|
|
97
|
+
* a temporarily set value, use the `setDefaultSSR.pop()` method
|
|
98
|
+
* to go back to the previous value. Example:
|
|
99
|
+
*
|
|
100
|
+
* ```js
|
|
101
|
+
* setDefaultSSR('ssr-only');
|
|
102
|
+
* // ...render some components...
|
|
103
|
+
* setDefaultSSR.pop(); // go back to the previous state
|
|
104
|
+
* ```
|
|
105
|
+
*
|
|
106
|
+
* You explicitly switch to using the library's default by passing `undefined`
|
|
107
|
+
* as an argument — like so:
|
|
108
|
+
*
|
|
109
|
+
* ```js
|
|
110
|
+
* setDefaultSSR(undefined);
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export declare const setDefaultSSR: {
|
|
114
|
+
(ssrSupport: SSRSupport | undefined): void;
|
|
115
|
+
/**
|
|
116
|
+
* Unsets the last pushed defaultSSR value
|
|
117
|
+
*/
|
|
118
|
+
pop(): void;
|
|
119
|
+
};
|