@zuzjs/ui 0.2.8 → 0.3.1
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/dist/index.js +266 -78
- package/dist/styles.css +71 -0
- package/package.json +1 -1
- package/src/comps/box.tsx +5 -1
- package/src/comps/button.tsx +2 -1
- package/src/comps/contextmenu.tsx +1 -3
- package/src/comps/image.tsx +15 -7
- package/src/comps/input.tsx +7 -2
- package/src/comps/toaster.tsx +9 -7
- package/src/context/AppProvider.tsx +8 -3
- package/src/context/store/lang.tsx +26 -0
- package/src/context/store/theme.tsx +15 -14
- package/src/core/defaultTheme.ts +14 -13
- package/src/core/index.ts +74 -2
- package/src/hooks/index.tsx +2 -1
- package/src/hooks/useContextMenu.tsx +88 -0
- package/src/hooks/useImage.tsx +75 -49
- package/src/hooks/useLang.tsx +9 -0
- package/src/index.tsx +2 -0
- package/src/scss/props.scss +10 -0
- package/src/scss/style.scss +27 -0
package/src/comps/image.tsx
CHANGED
|
@@ -9,17 +9,25 @@ import Box from './box'
|
|
|
9
9
|
|
|
10
10
|
const Image = forwardRef((props : { [ key: string ] : any }, ref : LegacyRef<HTMLImageElement>) => {
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const { src, isLoading, error } = useImage({ srcList: props.src, useSuspense: false });
|
|
13
13
|
|
|
14
14
|
return (
|
|
15
15
|
<ClassNames>
|
|
16
|
-
{({ css, cx }) =>
|
|
17
|
-
{
|
|
18
|
-
{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
{({ css, cx }) => <>
|
|
17
|
+
{isLoading && <Box className={`${props.as ? `${props.as} ` : ``}${cx(css`background: #eee;${buildCSS(props)}`)}`} />}
|
|
18
|
+
{!isLoading && <img src={src}
|
|
19
|
+
className={`${props.as ? `${props.as} ` : ``}${cx(css`${buildCSS(props)}`)}`}
|
|
20
|
+
ref={ref} />}
|
|
21
|
+
</>}
|
|
22
22
|
</ClassNames>
|
|
23
|
+
// <ClassNames>
|
|
24
|
+
// {({ css, cx }) => <picture className={cx(css`${buildCSS({ flex: true })}`)}>
|
|
25
|
+
// {status == 'loading' && <Box className={`${props.as ? `${props.as} ` : ``}${cx(css`background: #eee;${buildCSS(props)}`)}`} />}
|
|
26
|
+
// {status == 'loaded' && <img src={props.src}
|
|
27
|
+
// className={`${props.as ? `${props.as} ` : ``}${cx(css`${buildCSS(props)}`)}`}
|
|
28
|
+
// ref={ref} />}
|
|
29
|
+
// </picture>}
|
|
30
|
+
// </ClassNames>
|
|
23
31
|
)
|
|
24
32
|
})
|
|
25
33
|
|
package/src/comps/input.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from 'react';
|
|
8
8
|
import { ClassNames } from '@emotion/react'
|
|
9
9
|
import { nanoid } from 'nanoid';
|
|
10
|
-
import { buildCSS } from '../core'
|
|
10
|
+
import { cleanProps, buildCSS } from '../core'
|
|
11
11
|
import { STORE_FORM_KEY } from '../context/AppProvider'
|
|
12
12
|
import useDispatch from '../hooks/useDispatch'
|
|
13
13
|
import useStore from '../hooks/useStore'
|
|
@@ -32,8 +32,10 @@ const Input = forwardRef((props : { [ key: string ] : any }, ref : LegacyRef<HTM
|
|
|
32
32
|
form,
|
|
33
33
|
touched,
|
|
34
34
|
onSubmit,
|
|
35
|
+
value,
|
|
35
36
|
defaultValue,
|
|
36
|
-
fref
|
|
37
|
+
fref,
|
|
38
|
+
autoComplete
|
|
37
39
|
} = props;
|
|
38
40
|
|
|
39
41
|
const dispatch = useDispatch(STORE_FORM_KEY)
|
|
@@ -48,13 +50,16 @@ const Input = forwardRef((props : { [ key: string ] : any }, ref : LegacyRef<HTM
|
|
|
48
50
|
return (
|
|
49
51
|
<ClassNames>
|
|
50
52
|
{({ css, cx }) => <El
|
|
53
|
+
{...cleanProps(props)}
|
|
51
54
|
type={type || `text`}
|
|
52
55
|
placeholder={placeholder || undefined}
|
|
56
|
+
autoComplete={autoComplete || undefined}
|
|
53
57
|
name={name || nanoid()}
|
|
54
58
|
multiple={type == 'file' ? multiple : undefined}
|
|
55
59
|
accept={accept || `*`}
|
|
56
60
|
className={`${as ? `${as} ` : ``}f ${cx(css`${_defaultCSS}${buildCSS(props)}&:hover {${buildCSS(props.hover || {})}} &:focus {${buildCSS(props.focus || {})}}`)}`}
|
|
57
61
|
ref={_ref}
|
|
62
|
+
value={value || undefined}
|
|
58
63
|
defaultValue={defaultValue || ``}
|
|
59
64
|
onKeyUp={(e : SyntheticEvent) => {
|
|
60
65
|
let k = e['keyCode'] || ['which'];
|
package/src/comps/toaster.tsx
CHANGED
|
@@ -6,6 +6,7 @@ class Toaster {
|
|
|
6
6
|
#startTop : number
|
|
7
7
|
#tout : null
|
|
8
8
|
#defaultTimeLeft : number
|
|
9
|
+
#root : string
|
|
9
10
|
|
|
10
11
|
constructor(config?: {
|
|
11
12
|
root : string,
|
|
@@ -14,12 +15,13 @@ class Toaster {
|
|
|
14
15
|
this.#startTop = 20
|
|
15
16
|
this.#defaultTimeLeft = 4
|
|
16
17
|
const rootID = config?.root || `toast-container`;
|
|
17
|
-
|
|
18
|
+
this.#root = rootID;
|
|
19
|
+
if(window.document.querySelector(`#${rootID}`)) return;
|
|
18
20
|
var self = this;
|
|
19
|
-
var root = document.createElement('div');
|
|
21
|
+
var root = window.document.createElement('div');
|
|
20
22
|
root.id = rootID;
|
|
21
|
-
document.body.appendChild(root);
|
|
22
|
-
this.#container = document.querySelector(`#${rootID}`)
|
|
23
|
+
window.document.body.appendChild(root);
|
|
24
|
+
this.#container = window.document.querySelector(`#${rootID}`)
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
show = (
|
|
@@ -57,8 +59,8 @@ class Toaster {
|
|
|
57
59
|
var self = this;
|
|
58
60
|
var tout = null,
|
|
59
61
|
ID = 'toast-' + nanoid(),
|
|
60
|
-
toast = document.createElement('div'),
|
|
61
|
-
toastBG = document.createElement('div');
|
|
62
|
+
toast = window.document.createElement('div'),
|
|
63
|
+
toastBG = window.document.createElement('div');
|
|
62
64
|
|
|
63
65
|
toast.id = ID;
|
|
64
66
|
toast.style.backgroundColor = `#111`
|
|
@@ -76,7 +78,7 @@ class Toaster {
|
|
|
76
78
|
|
|
77
79
|
toast.innerHTML = config.message || `You haven't passed "message" in this toast`;
|
|
78
80
|
|
|
79
|
-
self.#container.appendChild(toast);
|
|
81
|
+
(self.#container || window.document.querySelector(`#${self.#root}`)).appendChild(toast);
|
|
80
82
|
|
|
81
83
|
self.arrangeToasts()
|
|
82
84
|
.then(() => {
|
|
@@ -7,6 +7,7 @@ import React, {
|
|
|
7
7
|
import PropTypes from 'prop-types'
|
|
8
8
|
import AppContext from './AppContext'
|
|
9
9
|
import AppTheme from './store/theme'
|
|
10
|
+
import AppLang from './store/lang'
|
|
10
11
|
|
|
11
12
|
let isMounted = true;
|
|
12
13
|
export const STORE_KEY = `__zuzjs`
|
|
@@ -32,7 +33,8 @@ const rootReducer = (state, action ) => ({
|
|
|
32
33
|
const AppProvider = ({
|
|
33
34
|
children,
|
|
34
35
|
initialState = {},
|
|
35
|
-
theme = {}
|
|
36
|
+
theme = {},
|
|
37
|
+
lang = {}
|
|
36
38
|
}) => {
|
|
37
39
|
|
|
38
40
|
|
|
@@ -46,7 +48,8 @@ const AppProvider = ({
|
|
|
46
48
|
const rootState = useMemo(() => ({
|
|
47
49
|
...defaultState,
|
|
48
50
|
...initialState,
|
|
49
|
-
theme: new AppTheme({ theme }).get()
|
|
51
|
+
theme: new AppTheme({ theme }).get(),
|
|
52
|
+
lang: new AppLang({ lang }).get(),
|
|
50
53
|
}), [initialState])
|
|
51
54
|
|
|
52
55
|
const [state, _dispatch] = useReducer(rootReducer, rootState);
|
|
@@ -89,13 +92,15 @@ const AppProvider = ({
|
|
|
89
92
|
|
|
90
93
|
AppProvider.defaultProps = {
|
|
91
94
|
theme: {},
|
|
95
|
+
lang: {},
|
|
92
96
|
initialState : {},
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
AppProvider.propTypes = {
|
|
96
100
|
children: PropTypes.node.isRequired,
|
|
97
101
|
initialState: PropTypes.instanceOf(Object),
|
|
98
|
-
theme: PropTypes.instanceOf(Object)
|
|
102
|
+
theme: PropTypes.instanceOf(Object),
|
|
103
|
+
lang: PropTypes.instanceOf(Object)
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
export default AppProvider
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class AppLang {
|
|
2
|
+
|
|
3
|
+
#mode;
|
|
4
|
+
#listen;
|
|
5
|
+
#lang;
|
|
6
|
+
|
|
7
|
+
constructor(_conf){
|
|
8
|
+
const conf = _conf || {}
|
|
9
|
+
this.#listen = "listen" in conf ? conf.listen : (mod) => { console.log(`Lang switched to ${mod}`); };
|
|
10
|
+
this.#mode = conf.mode || "en";
|
|
11
|
+
this.#lang = {
|
|
12
|
+
//CORE
|
|
13
|
+
tag: "en",
|
|
14
|
+
...( "lang" in conf ? {...conf.lang} : {})
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get = () => {
|
|
19
|
+
let self = this;
|
|
20
|
+
return self.#lang
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default AppLang
|
|
@@ -8,7 +8,7 @@ class AppTheme {
|
|
|
8
8
|
constructor(_conf){
|
|
9
9
|
const conf = _conf || {}
|
|
10
10
|
this.#listen = "listen" in conf ? conf.listen : (mod) => { console.log(`Theme switched to ${mod}`); };
|
|
11
|
-
this.#mode = conf.mode || "auto";
|
|
11
|
+
this.#mode = conf.mode || conf.theme.mode || "auto";
|
|
12
12
|
this.#lightTheme = {
|
|
13
13
|
//CORE
|
|
14
14
|
tag: "light",
|
|
@@ -31,19 +31,20 @@ class AppTheme {
|
|
|
31
31
|
|
|
32
32
|
get = () => {
|
|
33
33
|
let self = this;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
if(self.#mode === "auto"){
|
|
35
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
|
36
|
+
self.#mode = event.matches ? "dark" : "light";
|
|
37
|
+
self.#listen(self.#mode);
|
|
38
|
+
});
|
|
39
|
+
return window.matchMedia &&
|
|
40
|
+
window.matchMedia('(prefers-color-scheme: dark)').matches ?
|
|
41
|
+
self.#darkTheme : self.#lightTheme;
|
|
42
|
+
}else{
|
|
43
|
+
if(self.#mode === "light"){
|
|
44
|
+
return self.#lightTheme;
|
|
45
|
+
}else if(self.#mode === "dark"){
|
|
46
|
+
return self.#darkTheme;
|
|
47
|
+
}
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
package/src/core/defaultTheme.ts
CHANGED
|
@@ -67,19 +67,20 @@ class AppTheme {
|
|
|
67
67
|
|
|
68
68
|
get = () => {
|
|
69
69
|
let self = this;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
70
|
+
if(self.#mode === "auto"){
|
|
71
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
|
72
|
+
self.#mode = event.matches ? "dark" : "light";
|
|
73
|
+
self.#listen(self.#mode);
|
|
74
|
+
});
|
|
75
|
+
return window.matchMedia &&
|
|
76
|
+
window.matchMedia('(prefers-color-scheme: dark)').matches ?
|
|
77
|
+
self.#darkTheme : self.#lightTheme;
|
|
78
|
+
}else{
|
|
79
|
+
if(self.#mode === "light"){
|
|
80
|
+
return self.#lightTheme;
|
|
81
|
+
}else if(self.#mode === "dark"){
|
|
82
|
+
return self.#darkTheme;
|
|
83
|
+
}
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
|
package/src/core/index.ts
CHANGED
|
@@ -38,6 +38,18 @@ const makeCSSValue = (k : any, v : any, o : any) => {
|
|
|
38
38
|
return `${k}:${v}${unit};`;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
const cleanProps = (props : any) => {
|
|
42
|
+
let _props = { ...props }
|
|
43
|
+
Object.keys(_props).map(k => {
|
|
44
|
+
if(k in cssProps){
|
|
45
|
+
delete _props[k]
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
let _extras = [`as`, `hover`, `bref`]
|
|
49
|
+
_extras.map(x => x in _props && delete _props[x])
|
|
50
|
+
return _props
|
|
51
|
+
}
|
|
52
|
+
|
|
41
53
|
const buildCSS = (props : any) => {
|
|
42
54
|
let css = ``;
|
|
43
55
|
Object.keys(props).map(k => {
|
|
@@ -78,7 +90,7 @@ const randstr = function(len? : number){
|
|
|
78
90
|
return text;
|
|
79
91
|
}
|
|
80
92
|
|
|
81
|
-
const setCookie = (key : string, value : any, expiry? : number) => Cookies.set(key, value, { expires: expiry || 7 })
|
|
93
|
+
const setCookie = (key : string, value : any, expiry? : number, host? : string) => Cookies.set(key, value, { expires: expiry || 7, domain: host || window.location.host })
|
|
82
94
|
|
|
83
95
|
const getCookie = (key : string) => Cookies.get(key) || null;
|
|
84
96
|
|
|
@@ -254,10 +266,65 @@ const getUriParams = () => {
|
|
|
254
266
|
|
|
255
267
|
const rgb2hex = rgb => `#${rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/).slice(1).map(n => parseInt(n, 10).toString(16).padStart(2, '0')).join('')}`
|
|
256
268
|
|
|
269
|
+
const getHostname = url => {
|
|
270
|
+
if(window.URL){
|
|
271
|
+
let u = new window.URL(url);
|
|
272
|
+
return u.hostname
|
|
273
|
+
}else{
|
|
274
|
+
var a = document.createElement(`a`)
|
|
275
|
+
a.href = url
|
|
276
|
+
return a.hostname.replace("www.", "")
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const imgPromiseFactory = ({decode = true, crossOrigin = ''}) =>
|
|
281
|
+
(src): Promise<void> => {
|
|
282
|
+
return new Promise((resolve, reject) => {
|
|
283
|
+
const i = new Image()
|
|
284
|
+
if (crossOrigin) i.crossOrigin = crossOrigin
|
|
285
|
+
i.onload = () => {
|
|
286
|
+
decode && i.decode ? i.decode().then(resolve).catch(reject) : resolve()
|
|
287
|
+
}
|
|
288
|
+
i.onerror = reject
|
|
289
|
+
i.src = src
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const parseFilename = nm => {
|
|
294
|
+
var re = /(?:\.([^.]+))?$/;
|
|
295
|
+
return {
|
|
296
|
+
name: nm.split('.').slice(0, -1).join('.'),
|
|
297
|
+
ext: re.exec(nm)[1]
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const camelCase = str => str.replace(":", "-").replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
|
|
302
|
+
|
|
303
|
+
const getMousePosition = e => {
|
|
304
|
+
const pos = {
|
|
305
|
+
x: (e as MouseEvent).clientX,
|
|
306
|
+
y: (e as MouseEvent).clientY,
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const touch = (e as TouchEvent).changedTouches;
|
|
310
|
+
|
|
311
|
+
if (touch) {
|
|
312
|
+
pos.x = touch[0].clientX;
|
|
313
|
+
pos.y = touch[0].clientY;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (!pos.x || pos.x < 0) pos.x = 0;
|
|
317
|
+
|
|
318
|
+
if (!pos.y || pos.y < 0) pos.y = 0;
|
|
319
|
+
|
|
320
|
+
return pos;
|
|
321
|
+
}
|
|
322
|
+
|
|
257
323
|
export {
|
|
258
324
|
addProps,
|
|
259
325
|
addScript,
|
|
260
326
|
buildCSS,
|
|
327
|
+
cleanProps,
|
|
261
328
|
buildFormData,
|
|
262
329
|
byName,
|
|
263
330
|
byId,
|
|
@@ -266,6 +333,7 @@ export {
|
|
|
266
333
|
isEmail,
|
|
267
334
|
isIPv4,
|
|
268
335
|
isUrl,
|
|
336
|
+
imgPromiseFactory,
|
|
269
337
|
randstr,
|
|
270
338
|
setCSSVar,
|
|
271
339
|
getCookie,
|
|
@@ -280,6 +348,10 @@ export {
|
|
|
280
348
|
rgb2hex,
|
|
281
349
|
generateModalRoutes,
|
|
282
350
|
generatePreservedRoutes,
|
|
283
|
-
generateRegularRoutes
|
|
351
|
+
generateRegularRoutes,
|
|
352
|
+
getHostname,
|
|
353
|
+
parseFilename,
|
|
354
|
+
camelCase,
|
|
355
|
+
getMousePosition
|
|
284
356
|
}
|
|
285
357
|
|
package/src/hooks/index.tsx
CHANGED
|
@@ -5,4 +5,5 @@ export { default as useDispatch } from './useDispatch'
|
|
|
5
5
|
export { default as useResizeObserver } from './useResizeObserver'
|
|
6
6
|
export { default as useDevice } from './useDevice'
|
|
7
7
|
export { default as useToast } from './useToast'
|
|
8
|
-
|
|
8
|
+
export { default as useLang } from './useLang'
|
|
9
|
+
export { default as useContextMenu } from './useContextMenu'
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { SyntheticEvent, useEffect, useState, useRef } from "react"
|
|
2
|
+
import ReactDOM from 'react-dom/client'
|
|
3
|
+
import Box from '../comps/box'
|
|
4
|
+
import { getMousePosition } from "../core"
|
|
5
|
+
|
|
6
|
+
const useContextMenu = (contextID : string) => {
|
|
7
|
+
|
|
8
|
+
const ID = `context-${contextID}`
|
|
9
|
+
const [visible, setVisible] = useState(false)
|
|
10
|
+
const [root, setRoot] = useState(null)
|
|
11
|
+
const nodeRef = useRef<HTMLDivElement>(null);
|
|
12
|
+
|
|
13
|
+
const el = (e : string) => window.document.createElement(e)
|
|
14
|
+
|
|
15
|
+
function checkBoundaries(x: number, y: number) {
|
|
16
|
+
if (nodeRef.current) {
|
|
17
|
+
const { innerWidth, innerHeight } = window;
|
|
18
|
+
const { offsetWidth, offsetHeight } = nodeRef.current;
|
|
19
|
+
|
|
20
|
+
if (x + offsetWidth > innerWidth) x -= x + offsetWidth - innerWidth;
|
|
21
|
+
|
|
22
|
+
if (y + offsetHeight > innerHeight) y -= y + offsetHeight - innerHeight;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return { x, y };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const hide = () => {
|
|
29
|
+
try{
|
|
30
|
+
root?.unmount()
|
|
31
|
+
setRoot(null)
|
|
32
|
+
}catch(e){}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const Menu = (e: MouseEvent, items : Array<any>) => {
|
|
36
|
+
|
|
37
|
+
const p = getMousePosition(e);
|
|
38
|
+
const { x, y } = checkBoundaries(p.x, p.y);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Box
|
|
42
|
+
bref={nodeRef}
|
|
43
|
+
flex dir={`cols`}
|
|
44
|
+
fixed
|
|
45
|
+
top={y}
|
|
46
|
+
left={x}
|
|
47
|
+
as={`zuz-contextmenu ${ID}`}>
|
|
48
|
+
{items && items.map((m, i) => m.id == `line` ? <Box as={`line`} key={`line-${i}-${m.id}`} /> : <button
|
|
49
|
+
key={`cm-${i}-${m.id}`}
|
|
50
|
+
onClick={ev => {
|
|
51
|
+
if(m.onClick){
|
|
52
|
+
m.onClick(ev, m)
|
|
53
|
+
}else{
|
|
54
|
+
console.log(`No onClick eventFound`)
|
|
55
|
+
}
|
|
56
|
+
hide()
|
|
57
|
+
}}>{m.label}</button>)}
|
|
58
|
+
</Box>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const show = (e : MouseEvent, items : Array<any>) => {
|
|
63
|
+
e.preventDefault(); e.stopPropagation();
|
|
64
|
+
if(!window.document.querySelector(`#context-${contextID}`)){
|
|
65
|
+
let div = el(`div`)
|
|
66
|
+
div.id = ID
|
|
67
|
+
window.document.body.appendChild(div)
|
|
68
|
+
}
|
|
69
|
+
root.render(Menu(e, items))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if(!window.document.querySelector(`#context-${contextID}`)){
|
|
74
|
+
let div = el(`div`)
|
|
75
|
+
div.id = ID
|
|
76
|
+
window.document.body.appendChild(div)
|
|
77
|
+
}
|
|
78
|
+
if(!root) setRoot(ReactDOM.createRoot(document.getElementById(ID)))
|
|
79
|
+
}, [root])
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
show,
|
|
83
|
+
hide
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default useContextMenu
|
package/src/hooks/useImage.tsx
CHANGED
|
@@ -1,58 +1,84 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {useState} from 'react'
|
|
2
|
+
import { imgPromiseFactory } from '../core'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
export type useImageProps = {
|
|
5
|
+
srcList: string | string[]
|
|
6
|
+
imgPromise?: (...args: any[]) => Promise<void>
|
|
7
|
+
useSuspense?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const removeBlankArrayElements = (a) => a.filter((x) => x)
|
|
11
|
+
const stringToArray = (x) => (Array.isArray(x) ? x : [x])
|
|
12
|
+
const cache = {}
|
|
7
13
|
|
|
8
|
-
|
|
14
|
+
// sequential map.find for promises
|
|
15
|
+
const promiseFind = (arr, promiseFactory) => {
|
|
16
|
+
let done = false
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const queueNext = (src) => {
|
|
19
|
+
return promiseFactory(src).then(() => {
|
|
20
|
+
done = true
|
|
21
|
+
resolve(src)
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
arr
|
|
26
|
+
.reduce((p, src) => {
|
|
27
|
+
// ensure we aren't done before enqueuing the next source
|
|
28
|
+
return p.catch(() => {
|
|
29
|
+
if (!done) return queueNext(src)
|
|
30
|
+
})
|
|
31
|
+
}, queueNext(arr.shift()))
|
|
32
|
+
.catch(reject)
|
|
33
|
+
})
|
|
34
|
+
}
|
|
9
35
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
36
|
+
const useImage = ({
|
|
37
|
+
srcList,
|
|
38
|
+
imgPromise = imgPromiseFactory({decode: true}),
|
|
39
|
+
useSuspense = true,
|
|
40
|
+
} : useImageProps) : {src: string | undefined; isLoading: boolean; error: any} => {
|
|
13
41
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
42
|
+
const [, setIsSettled] = useState(false)
|
|
43
|
+
const sourceList = removeBlankArrayElements(stringToArray(srcList))
|
|
44
|
+
const sourceKey = sourceList.join('')
|
|
45
|
+
|
|
46
|
+
if (!cache[sourceKey]) {
|
|
47
|
+
// create promise to loop through sources and try to load one
|
|
48
|
+
cache[sourceKey] = {
|
|
49
|
+
promise: promiseFind(sourceList, imgPromise),
|
|
50
|
+
cache: 'pending',
|
|
51
|
+
error: null,
|
|
52
|
+
}
|
|
20
53
|
}
|
|
21
54
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
},
|
|
52
|
-
[url, crossOrigin, referrerpolicy]
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
return [imageRef.current, statusRef.current];
|
|
55
|
+
// when promise resolves/reject, update cache & state
|
|
56
|
+
if (cache[sourceKey].cache === 'resolved') {
|
|
57
|
+
return {src: cache[sourceKey].src, isLoading: false, error: null}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (cache[sourceKey].cache === 'rejected') {
|
|
61
|
+
if (useSuspense) throw cache[sourceKey].error
|
|
62
|
+
return {isLoading: false, error: cache[sourceKey].error, src: undefined}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
cache[sourceKey].promise
|
|
66
|
+
// if a source was found, update cache
|
|
67
|
+
// when not using suspense, update state to force a rerender
|
|
68
|
+
.then((src) => {
|
|
69
|
+
cache[sourceKey] = {...cache[sourceKey], cache: 'resolved', src}
|
|
70
|
+
if (!useSuspense) setIsSettled(sourceKey)
|
|
71
|
+
})
|
|
72
|
+
// if no source was found, or if another error occurred, update cache
|
|
73
|
+
// when not using suspense, update state to force a rerender
|
|
74
|
+
.catch((error) => {
|
|
75
|
+
cache[sourceKey] = {...cache[sourceKey], cache: 'rejected', error}
|
|
76
|
+
if (!useSuspense) setIsSettled(sourceKey)
|
|
77
|
+
})
|
|
78
|
+
// cache[sourceKey].cache === 'pending')
|
|
79
|
+
if (useSuspense) throw cache[sourceKey].promise
|
|
80
|
+
return {isLoading: true, src: undefined, error: null}
|
|
81
|
+
|
|
56
82
|
}
|
|
57
83
|
|
|
58
84
|
export default useImage;
|
package/src/index.tsx
CHANGED
|
@@ -4,6 +4,8 @@ import { Provider, createSlice } from './context'
|
|
|
4
4
|
export * from './core/index';
|
|
5
5
|
export * from './hooks/index';
|
|
6
6
|
|
|
7
|
+
export { Link } from "react-router-dom"
|
|
8
|
+
|
|
7
9
|
export { default as App } from './comps/app'
|
|
8
10
|
export { default as Box } from './comps/box'
|
|
9
11
|
export { default as Button } from './comps/button'
|
package/src/scss/props.scss
CHANGED
|
@@ -57,4 +57,14 @@ $colors: 'fff','111','222','333','444','555','666','777','888','999';
|
|
|
57
57
|
@if $i % 2 == 0 {
|
|
58
58
|
.s#{$i} { font-size: #{$i}px; }
|
|
59
59
|
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/*
|
|
63
|
+
BoldSize
|
|
64
|
+
It will generate .b400 { font-weight: 400; } from '400'
|
|
65
|
+
*/
|
|
66
|
+
$bsizes: 100,200,300,400,500,600,700,800,900;
|
|
67
|
+
.bold{ font-weight: bold; }
|
|
68
|
+
@each $n in $bsizes{
|
|
69
|
+
.b#{$n} { font-weight: #{$n}; }
|
|
60
70
|
}
|