@zuzjs/ui 0.3.0 → 0.3.2
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 +249 -21
- package/dist/styles.css +27 -0
- package/jest.config.js +7 -0
- package/package.json +1 -1
- package/rollup.config.js +68 -0
- package/src/comps/box.tsx +6 -1
- package/src/comps/button.tsx +3 -1
- package/src/comps/contextmenu.tsx +39 -22
- package/src/comps/input.tsx +145 -7
- package/src/comps/mediaplayer.tsx +12 -0
- package/src/context/store/theme.tsx +1 -1
- package/src/core/index.ts +122 -2
- package/src/core/styles.ts +12 -1
- package/src/hooks/index.tsx +3 -1
- package/src/hooks/useContextMenu.tsx +123 -0
- package/src/hooks/useMediaPlayer.tsx +27 -0
- package/src/hooks/useNavigator.tsx +6 -0
- package/src/hooks/useRender.tsx +29 -0
- package/src/index.tsx +1 -1
- package/src/scss/style.scss +27 -0
- package/tsconfig.json +21 -0
- package/tsconfig.lib.json +9 -0
- package/tsconfig.spec.json +21 -0
package/src/comps/input.tsx
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FC,
|
|
3
|
+
Ref,
|
|
3
4
|
LegacyRef,
|
|
4
5
|
forwardRef,
|
|
5
6
|
useRef,
|
|
6
7
|
SyntheticEvent,
|
|
8
|
+
useEffect,
|
|
9
|
+
memo,
|
|
7
10
|
} from 'react';
|
|
8
11
|
import { ClassNames } from '@emotion/react'
|
|
9
12
|
import { nanoid } from 'nanoid';
|
|
10
|
-
import { buildCSS } from '../core'
|
|
13
|
+
import { cleanProps, buildCSS } from '../core'
|
|
11
14
|
import { STORE_FORM_KEY } from '../context/AppProvider'
|
|
12
15
|
import useDispatch from '../hooks/useDispatch'
|
|
13
16
|
import useStore from '../hooks/useStore'
|
|
@@ -15,7 +18,102 @@ import {
|
|
|
15
18
|
UPDATE_FORM_FIELD
|
|
16
19
|
} from '../actions'
|
|
17
20
|
|
|
18
|
-
const
|
|
21
|
+
const SIZING_STYLE = [
|
|
22
|
+
'borderBottomWidth',
|
|
23
|
+
'borderLeftWidth',
|
|
24
|
+
'borderRightWidth',
|
|
25
|
+
'borderTopWidth',
|
|
26
|
+
'boxSizing',
|
|
27
|
+
'fontFamily',
|
|
28
|
+
'fontSize',
|
|
29
|
+
'fontStyle',
|
|
30
|
+
'fontWeight',
|
|
31
|
+
'letterSpacing',
|
|
32
|
+
'lineHeight',
|
|
33
|
+
'paddingBottom',
|
|
34
|
+
'paddingLeft',
|
|
35
|
+
'paddingRight',
|
|
36
|
+
'paddingTop',
|
|
37
|
+
// non-standard
|
|
38
|
+
'tabSize',
|
|
39
|
+
'textIndent',
|
|
40
|
+
// non-standard
|
|
41
|
+
'textRendering',
|
|
42
|
+
'textTransform',
|
|
43
|
+
'width',
|
|
44
|
+
'wordBreak',
|
|
45
|
+
] as const;
|
|
46
|
+
type SizingProps = Extract<
|
|
47
|
+
(typeof SIZING_STYLE)[number],
|
|
48
|
+
keyof CSSStyleDeclaration
|
|
49
|
+
>;
|
|
50
|
+
|
|
51
|
+
export const noop = () => {};
|
|
52
|
+
|
|
53
|
+
const pick = <Obj extends { [key: string]: any }, Key extends keyof Obj>(
|
|
54
|
+
props: Key[],
|
|
55
|
+
obj: Obj,
|
|
56
|
+
): Pick<Obj, Key> =>
|
|
57
|
+
props.reduce((acc, prop) => {
|
|
58
|
+
acc[prop] = obj[prop];
|
|
59
|
+
return acc;
|
|
60
|
+
}, {} as Pick<Obj, Key>);
|
|
61
|
+
|
|
62
|
+
type SizingStyle = Pick<CSSStyleDeclaration, SizingProps>;
|
|
63
|
+
|
|
64
|
+
export type SizingData = {
|
|
65
|
+
sizingStyle: SizingStyle;
|
|
66
|
+
paddingSize: number;
|
|
67
|
+
borderSize: number;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const isIE = !!(document.documentElement as any).currentStyle
|
|
71
|
+
|
|
72
|
+
const getSizingData = (node: HTMLElement): SizingData | null => {
|
|
73
|
+
|
|
74
|
+
const style = window.getComputedStyle(node);
|
|
75
|
+
|
|
76
|
+
if (style === null) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const sizingStyle = pick(SIZING_STYLE as unknown as SizingProps[], style);
|
|
81
|
+
const { boxSizing } = sizingStyle;
|
|
82
|
+
|
|
83
|
+
// probably node is detached from DOM, can't read computed dimensions
|
|
84
|
+
if (boxSizing === '') {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// IE (Edge has already correct behaviour) returns content width as computed width
|
|
89
|
+
// so we need to add manually padding and border widths
|
|
90
|
+
if (isIE && boxSizing === 'border-box') {
|
|
91
|
+
sizingStyle.width =
|
|
92
|
+
parseFloat(sizingStyle.width!) +
|
|
93
|
+
parseFloat(sizingStyle.borderRightWidth!) +
|
|
94
|
+
parseFloat(sizingStyle.borderLeftWidth!) +
|
|
95
|
+
parseFloat(sizingStyle.paddingRight!) +
|
|
96
|
+
parseFloat(sizingStyle.paddingLeft!) +
|
|
97
|
+
'px';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const paddingSize =
|
|
101
|
+
parseFloat(sizingStyle.paddingBottom!) +
|
|
102
|
+
parseFloat(sizingStyle.paddingTop!);
|
|
103
|
+
|
|
104
|
+
const borderSize =
|
|
105
|
+
parseFloat(sizingStyle.borderBottomWidth!) +
|
|
106
|
+
parseFloat(sizingStyle.borderTopWidth!);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
sizingStyle,
|
|
110
|
+
paddingSize,
|
|
111
|
+
borderSize,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const Input = forwardRef((props : { [ key: string ] : any }, ref : Ref<HTMLTextAreaElement|HTMLInputElement>) => {
|
|
19
117
|
|
|
20
118
|
const {
|
|
21
119
|
as,
|
|
@@ -24,6 +122,8 @@ const Input = forwardRef((props : { [ key: string ] : any }, ref : LegacyRef<HTM
|
|
|
24
122
|
onChange,
|
|
25
123
|
onKeyUp,
|
|
26
124
|
onClick,
|
|
125
|
+
onBlur,
|
|
126
|
+
onFocus,
|
|
27
127
|
readOnly,
|
|
28
128
|
type,
|
|
29
129
|
tag,
|
|
@@ -34,7 +134,11 @@ const Input = forwardRef((props : { [ key: string ] : any }, ref : LegacyRef<HTM
|
|
|
34
134
|
onSubmit,
|
|
35
135
|
value,
|
|
36
136
|
defaultValue,
|
|
37
|
-
fref
|
|
137
|
+
fref,
|
|
138
|
+
autoComplete,
|
|
139
|
+
elastic,
|
|
140
|
+
minRows,
|
|
141
|
+
maxRows
|
|
38
142
|
} = props;
|
|
39
143
|
|
|
40
144
|
const dispatch = useDispatch(STORE_FORM_KEY)
|
|
@@ -46,11 +150,36 @@ const Input = forwardRef((props : { [ key: string ] : any }, ref : LegacyRef<HTM
|
|
|
46
150
|
|
|
47
151
|
const _defaultCSS = `width: 100%;border-radius: var(--radius-base);padding-left: 4px;padding-right: 4px;border-style: solid;border-width: 1px;border-color: var(--colors-gray-200);`;
|
|
48
152
|
|
|
153
|
+
const isControlled = props.value !== undefined;
|
|
154
|
+
const measurementsCacheRef = useRef<SizingData>();
|
|
155
|
+
|
|
156
|
+
const handleElastic = () => {
|
|
157
|
+
const node = _ref.current!
|
|
158
|
+
const nodeSizingData = measurementsCacheRef.current
|
|
159
|
+
? measurementsCacheRef.current : getSizingData(node)
|
|
160
|
+
if (!nodeSizingData) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
measurementsCacheRef.current = nodeSizingData;
|
|
164
|
+
|
|
165
|
+
// const [height, rowHeight] = calculateNodeHeight(
|
|
166
|
+
// nodeSizingData,
|
|
167
|
+
// node.value || node.placeholder || 'x',
|
|
168
|
+
// minRows || 6,
|
|
169
|
+
// maxRows || 6,
|
|
170
|
+
// );
|
|
171
|
+
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
useEffect(() => {}, [])
|
|
175
|
+
|
|
49
176
|
return (
|
|
50
177
|
<ClassNames>
|
|
51
178
|
{({ css, cx }) => <El
|
|
179
|
+
{...cleanProps(props)}
|
|
52
180
|
type={type || `text`}
|
|
53
181
|
placeholder={placeholder || undefined}
|
|
182
|
+
autoComplete={autoComplete || undefined}
|
|
54
183
|
name={name || nanoid()}
|
|
55
184
|
multiple={type == 'file' ? multiple : undefined}
|
|
56
185
|
accept={accept || `*`}
|
|
@@ -60,27 +189,36 @@ const Input = forwardRef((props : { [ key: string ] : any }, ref : LegacyRef<HTM
|
|
|
60
189
|
defaultValue={defaultValue || ``}
|
|
61
190
|
onKeyUp={(e : SyntheticEvent) => {
|
|
62
191
|
let k = e['keyCode'] || ['which'];
|
|
63
|
-
if(El != 'textarea' && k == 13
|
|
192
|
+
if(form && onSubmit && El != 'textarea' && k == 13){
|
|
64
193
|
onSubmit(forms[form]);
|
|
194
|
+
}else{
|
|
195
|
+
if(onKeyUp) onKeyUp(e)
|
|
65
196
|
}
|
|
197
|
+
|
|
66
198
|
}}
|
|
67
199
|
onChange={e => {
|
|
68
200
|
let val = type == 'file' ?
|
|
69
201
|
e.currentTarget.files
|
|
70
202
|
: e.currentTarget.value;
|
|
71
|
-
dispatch( dispatch( UPDATE_FORM_FIELD( form || 'orphan', name, val == "" ? null : val, forms ) ) );
|
|
203
|
+
if(form) dispatch( dispatch( UPDATE_FORM_FIELD( form || 'orphan', name, val == "" ? null : val, forms ) ) );
|
|
204
|
+
// if(El == `textarea` && elastic) handleElastic()
|
|
72
205
|
onChange && onChange(val == "" ? null : val)
|
|
73
206
|
}}
|
|
74
207
|
onClick={onClick ? onClick : () => {}}
|
|
75
208
|
readOnly={readOnly || false}
|
|
76
209
|
onBlur={e => {
|
|
210
|
+
if(onBlur) onBlur(e)
|
|
77
211
|
if(touched){}
|
|
78
212
|
}}
|
|
79
|
-
onFocus={ e =>
|
|
213
|
+
onFocus={ e => {
|
|
214
|
+
if(touched == false)dispatch( UPDATE_FORM_FIELD( form || 'orphan', `touched`, true, forms ) )
|
|
215
|
+
if(onFocus) onFocus(e)
|
|
216
|
+
}}
|
|
80
217
|
/>}
|
|
81
218
|
</ClassNames>
|
|
82
219
|
)
|
|
83
|
-
|
|
220
|
+
|
|
221
|
+
|
|
84
222
|
})
|
|
85
223
|
|
|
86
224
|
export default Input
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
LegacyRef
|
|
4
|
+
} from 'react'
|
|
5
|
+
import { ClassNames } from '@emotion/react'
|
|
6
|
+
import { buildCSS } from '../core'
|
|
7
|
+
|
|
8
|
+
const MediaPlayer = forwardRef((props : { [ key: string ] : any }, ref : LegacyRef<HTMLImageElement>) => {
|
|
9
|
+
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export default MediaPlayer;
|
|
@@ -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",
|
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`,`tag`]
|
|
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 => {
|
|
@@ -216,7 +228,7 @@ const filterHTMLProps = (props : any) => {
|
|
|
216
228
|
return filter;
|
|
217
229
|
}
|
|
218
230
|
|
|
219
|
-
const uuid = () => nanoid()
|
|
231
|
+
const uuid = (len=21) => nanoid(len)
|
|
220
232
|
|
|
221
233
|
const addScript = (src: string, callback: () => any) => {
|
|
222
234
|
var s = document.createElement('script')
|
|
@@ -278,10 +290,112 @@ const imgPromiseFactory = ({decode = true, crossOrigin = ''}) =>
|
|
|
278
290
|
})
|
|
279
291
|
}
|
|
280
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
|
+
|
|
323
|
+
const copyToClipboard = (str : string, callback: Function) => {
|
|
324
|
+
const el = document.createElement('textarea');
|
|
325
|
+
let storeContentEditable = el.contentEditable;
|
|
326
|
+
let storeReadOnly = el.readOnly;
|
|
327
|
+
el.value = str;
|
|
328
|
+
el.contentEditable = `true`;
|
|
329
|
+
el.readOnly = false;
|
|
330
|
+
el.setAttribute('readonly', `false`);
|
|
331
|
+
el.setAttribute('contenteditable', `true`);
|
|
332
|
+
el.style.position = 'absolute';
|
|
333
|
+
el.style.left = '-999999999px';
|
|
334
|
+
document.body.appendChild(el);
|
|
335
|
+
const selected =
|
|
336
|
+
document.getSelection().rangeCount > 0
|
|
337
|
+
? document.getSelection().getRangeAt(0)
|
|
338
|
+
: false;
|
|
339
|
+
el.select();
|
|
340
|
+
el.setSelectionRange(0, 999999);
|
|
341
|
+
document.execCommand('copy');
|
|
342
|
+
document.body.removeChild(el);
|
|
343
|
+
if (selected) {
|
|
344
|
+
document.getSelection().removeAllRanges();
|
|
345
|
+
document.getSelection().addRange(selected);
|
|
346
|
+
}
|
|
347
|
+
el.contentEditable = storeContentEditable;
|
|
348
|
+
el.readOnly = storeReadOnly;
|
|
349
|
+
if(callback) callback()
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Format bytes as human-readable text.
|
|
354
|
+
*
|
|
355
|
+
* @param bytes Number of bytes.
|
|
356
|
+
* @param si True to use metric (SI) units, aka powers of 1000. False to use
|
|
357
|
+
* binary (IEC), aka powers of 1024.
|
|
358
|
+
* @param dp Number of decimal places to display.
|
|
359
|
+
*
|
|
360
|
+
* @return Formatted string.
|
|
361
|
+
*/
|
|
362
|
+
const formatSize = (bytes, si=false, dp=1) => {
|
|
363
|
+
const thresh = si ? 1000 : 1024;
|
|
364
|
+
|
|
365
|
+
if (Math.abs(bytes) < thresh) {
|
|
366
|
+
return bytes + ' B';
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const units = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
|
370
|
+
let u = -1;
|
|
371
|
+
const r = 10**dp;
|
|
372
|
+
|
|
373
|
+
do {
|
|
374
|
+
bytes /= thresh;
|
|
375
|
+
++u;
|
|
376
|
+
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
return bytes.toFixed(dp) + ' ' + units[u];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const slugify = (...args: (string | number)[]): string => {
|
|
383
|
+
const value = args.join(' ')
|
|
384
|
+
|
|
385
|
+
return value
|
|
386
|
+
.normalize('NFD') // split an accented letter in the base letter and the acent
|
|
387
|
+
.replace(/[\u0300-\u036f]/g, '') // remove all previously split accents
|
|
388
|
+
.toLowerCase()
|
|
389
|
+
.trim()
|
|
390
|
+
.replace(/[^a-z0-9 ]/g, '') // remove all chars not letters, numbers and spaces (to be replaced)
|
|
391
|
+
.replace(/\s+/g, '-') // separator
|
|
392
|
+
}
|
|
393
|
+
|
|
281
394
|
export {
|
|
282
395
|
addProps,
|
|
283
396
|
addScript,
|
|
284
397
|
buildCSS,
|
|
398
|
+
cleanProps,
|
|
285
399
|
buildFormData,
|
|
286
400
|
byName,
|
|
287
401
|
byId,
|
|
@@ -306,6 +420,12 @@ export {
|
|
|
306
420
|
generateModalRoutes,
|
|
307
421
|
generatePreservedRoutes,
|
|
308
422
|
generateRegularRoutes,
|
|
309
|
-
getHostname
|
|
423
|
+
getHostname,
|
|
424
|
+
parseFilename,
|
|
425
|
+
camelCase,
|
|
426
|
+
getMousePosition,
|
|
427
|
+
formatSize,
|
|
428
|
+
copyToClipboard,
|
|
429
|
+
slugify
|
|
310
430
|
}
|
|
311
431
|
|
package/src/core/styles.ts
CHANGED
|
@@ -3,6 +3,10 @@ const cssProps : { [key: string] : any } = {
|
|
|
3
3
|
"alignContent": "align-content",
|
|
4
4
|
|
|
5
5
|
"aic": "aic",
|
|
6
|
+
"ais": "ais",
|
|
7
|
+
"aie": "aie",
|
|
8
|
+
"nous": "nous",
|
|
9
|
+
"nope": "nope",
|
|
6
10
|
"ai": "align-items",
|
|
7
11
|
"alignItems": "align-items",
|
|
8
12
|
|
|
@@ -278,6 +282,7 @@ const cssProps : { [key: string] : any } = {
|
|
|
278
282
|
"wordBreak": "word-break",
|
|
279
283
|
"wordSpacing": "word-spacing",
|
|
280
284
|
"wrap": "wrap",
|
|
285
|
+
"textWrap": "textWrap",
|
|
281
286
|
"wordWrap": "word-wrap",
|
|
282
287
|
"writingMode": "writing-mode",
|
|
283
288
|
"zIndex": "z-index",
|
|
@@ -317,6 +322,8 @@ const cssPropsDirect : { [key : string] : any } = {
|
|
|
317
322
|
'flex' : 'display: flex;',
|
|
318
323
|
'fwrap' : 'flex-wrap: wrap;',
|
|
319
324
|
'aic' : 'align-items: center;',
|
|
325
|
+
'ais' : 'align-items: flex-start;',
|
|
326
|
+
'aie' : 'align-items: flex-end;',
|
|
320
327
|
'ass' : 'align-self: flex-start;',
|
|
321
328
|
'asc' : 'align-self: center;',
|
|
322
329
|
'ase' : 'align-self: flex-end;',
|
|
@@ -329,13 +336,17 @@ const cssPropsDirect : { [key : string] : any } = {
|
|
|
329
336
|
'block' : 'display: block;',
|
|
330
337
|
'bold' : "font-weight: bold;",
|
|
331
338
|
'wrap' : "word-wrap: break-word;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;max-width: 99%;",
|
|
339
|
+
'textWrap' : "word-wrap: break-word;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;max-width: 99%;",
|
|
332
340
|
'pointer' : "cursor: pointer;",
|
|
333
341
|
'fb' : 'font-family: var(--primary-font-bold);',
|
|
334
342
|
'ph' : 'padding-left: __VALUE__;padding-right: __VALUE__;',
|
|
335
343
|
'pv' : 'padding-bottom: __VALUE__;padding-top: __VALUE__;',
|
|
336
344
|
'mv' : 'margin-bottom: __VALUE__;margin-top: __VALUE__;',
|
|
337
345
|
'mh' : 'margin-left: __VALUE__;margin-right: __VALUE__;',
|
|
338
|
-
'anim' : 'transition:all __VALUE__s linear 0s;'
|
|
346
|
+
'anim' : 'transition:all __VALUE__s linear 0s;',
|
|
347
|
+
'nous' : 'user-select: none;',
|
|
348
|
+
'nope' : 'pointer-events: none;',
|
|
349
|
+
'tdn' : 'text-decoration: none;',
|
|
339
350
|
}
|
|
340
351
|
|
|
341
352
|
const cssPropsIgnore : string[] = [
|
package/src/hooks/index.tsx
CHANGED
|
@@ -5,4 +5,6 @@ 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
|
-
export { default as useLang } from './useLang'
|
|
8
|
+
export { default as useLang } from './useLang'
|
|
9
|
+
export { default as useContextMenu } from './useContextMenu'
|
|
10
|
+
export { default as useRender } from './useRender'
|
|
@@ -0,0 +1,123 @@
|
|
|
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 Menu = ({ ID, hide, e, items, width }) => {
|
|
7
|
+
|
|
8
|
+
const nodeRef = useRef(null);
|
|
9
|
+
const [p, setP] = useState(getMousePosition(e as MouseEvent));
|
|
10
|
+
const [visible, setVisible] = useState(false);
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const checkBoundaries = (x: number, y: number) => {
|
|
14
|
+
if (nodeRef.current) {
|
|
15
|
+
const { innerWidth, innerHeight } = window;
|
|
16
|
+
const { offsetWidth, offsetHeight } = nodeRef.current;
|
|
17
|
+
if(x + offsetWidth > innerWidth) x -= offsetWidth //x + offsetWidth - innerWidth;
|
|
18
|
+
if (y + offsetHeight > innerHeight) y -= offsetHeight;
|
|
19
|
+
}
|
|
20
|
+
setP({ x, y })
|
|
21
|
+
setVisible(true)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
checkBoundaries(p.x, p.y);
|
|
26
|
+
}, [e])
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Box
|
|
30
|
+
bref={nodeRef}
|
|
31
|
+
flex dir={`cols`}
|
|
32
|
+
fixed
|
|
33
|
+
top={p.y}
|
|
34
|
+
left={p.x}
|
|
35
|
+
w={width || 220}
|
|
36
|
+
opacity={visible ? 1 : 0}
|
|
37
|
+
as={`zuz-contextmenu ${ID}`}>
|
|
38
|
+
{(items as Array<any>).map((m, i) => m.id == `line` ? <Box as={`line`} key={`line-${i}-${m.id}`} /> : <button
|
|
39
|
+
key={`cm-${i}-${m.id}`}
|
|
40
|
+
onClick={ev => {
|
|
41
|
+
if(m.onClick){
|
|
42
|
+
m.onClick(ev, m)
|
|
43
|
+
}else{
|
|
44
|
+
console.log(`No onClick eventFound`)
|
|
45
|
+
}
|
|
46
|
+
hide()
|
|
47
|
+
}}>{m.label}</button>)}
|
|
48
|
+
</Box>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const useContextMenu = (
|
|
53
|
+
contextID : string,
|
|
54
|
+
contextWidth: number,
|
|
55
|
+
contextToken = `____uchides`
|
|
56
|
+
) => {
|
|
57
|
+
|
|
58
|
+
const ID = `contextmenu-${contextID}`
|
|
59
|
+
const [visible, setVisible] = useState(false)
|
|
60
|
+
const [root, setRoot] = useState(null)
|
|
61
|
+
|
|
62
|
+
const el = (e : string) => window.document.createElement(e)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
const createRoot = () => {
|
|
66
|
+
if(!window.document.querySelector(`#${ID}`)){
|
|
67
|
+
let div = el(`div`)
|
|
68
|
+
div.id = ID
|
|
69
|
+
window.document.body.appendChild(div)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const hideAll = () => {
|
|
74
|
+
if(window[contextToken]){
|
|
75
|
+
window[contextToken].map((h : Object) => h['ID'] != ID && h['fnc']())
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const _hide = () => {
|
|
80
|
+
try{
|
|
81
|
+
root?.unmount()
|
|
82
|
+
document.querySelector(`#${ID}`).parentNode.removeChild(document.querySelector(`#${ID}`))
|
|
83
|
+
setRoot(null)
|
|
84
|
+
}catch(e){}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const hide = () => {
|
|
88
|
+
_hide()
|
|
89
|
+
hideAll()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const show = (e : MouseEvent, items : Array<any>) => {
|
|
93
|
+
e.preventDefault(); e.stopPropagation();
|
|
94
|
+
hideAll()
|
|
95
|
+
root.render(<Menu e={e} width={contextWidth || 220} items={items} ID={ID} hide={hide} />)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
createRoot()
|
|
100
|
+
if(!root) setRoot(ReactDOM.createRoot(document.getElementById(ID)))
|
|
101
|
+
}, [root])
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if(contextToken in window == false){
|
|
105
|
+
window[contextToken] = []
|
|
106
|
+
}
|
|
107
|
+
if(window[contextToken].findIndex(x => x.ID == ID) == -1){
|
|
108
|
+
window[contextToken].push({ ID: ID, fnc: _hide })
|
|
109
|
+
if(window[contextToken].length > document.querySelectorAll('div[id^="contextmenu-"]').length){
|
|
110
|
+
window[contextToken].shift()
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}, [])
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
show,
|
|
117
|
+
hide,
|
|
118
|
+
hideAll
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export default useContextMenu
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useEffect, useMemo } from "react"
|
|
2
|
+
import { uuid } from "../core"
|
|
3
|
+
|
|
4
|
+
const useMediaPlayer = (
|
|
5
|
+
playerID? : string
|
|
6
|
+
) => {
|
|
7
|
+
|
|
8
|
+
const pid = useMemo(() => playerID || uuid(), [playerID])
|
|
9
|
+
const ID = `media-player-${pid}`
|
|
10
|
+
|
|
11
|
+
const el = (e : string) => window.document.createElement(e)
|
|
12
|
+
|
|
13
|
+
const createRoot = () => {
|
|
14
|
+
if(!window.document.querySelector(`#${ID}`)){
|
|
15
|
+
let div = el(`div`)
|
|
16
|
+
div.id = ID
|
|
17
|
+
window.document.body.appendChild(div)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
|
|
23
|
+
}, [])
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default useMediaPlayer
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useState, useEffect } from "react"
|
|
2
|
+
|
|
3
|
+
const useRender = (isMounted: boolean, delay: number) => {
|
|
4
|
+
|
|
5
|
+
const [canRender, setCanRender] = useState(false)
|
|
6
|
+
|
|
7
|
+
// console.log(
|
|
8
|
+
// `isMounted:`, isMounted,
|
|
9
|
+
// `canRender:`, canRender,
|
|
10
|
+
// )
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
let outID : any
|
|
14
|
+
if(isMounted && !canRender){
|
|
15
|
+
setCanRender(true)
|
|
16
|
+
}else if(isMounted && canRender){
|
|
17
|
+
outID = setTimeout(
|
|
18
|
+
() => setCanRender(false),
|
|
19
|
+
delay * 1000
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
return () => clearTimeout(outID);
|
|
23
|
+
}, [isMounted, delay])
|
|
24
|
+
|
|
25
|
+
return canRender;
|
|
26
|
+
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default useRender
|
package/src/index.tsx
CHANGED
|
@@ -25,7 +25,7 @@ export { default as Spacer } from './comps/spacer'
|
|
|
25
25
|
export { default as Spinner } from './comps/spinner'
|
|
26
26
|
export { default as Text } from './comps/text'
|
|
27
27
|
export { default as Tweet } from './comps/tweet'
|
|
28
|
-
export { default as ContextMenu } from './comps/contextmenu'
|
|
28
|
+
// export { default as ContextMenu } from './comps/contextmenu'
|
|
29
29
|
|
|
30
30
|
export { default as Header } from './kit/Header'
|
|
31
31
|
|
package/src/scss/style.scss
CHANGED
|
@@ -103,4 +103,31 @@
|
|
|
103
103
|
left: 20px;
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.zuz-contextmenu{
|
|
109
|
+
width: 220px;
|
|
110
|
+
border-radius: 5px;
|
|
111
|
+
padding: 4px;
|
|
112
|
+
background: rgba(34,34,34,0.5);
|
|
113
|
+
border: 1px rgba(255,255,255,0.25) solid;
|
|
114
|
+
box-shadow: 0px 1px 3px rgba(0, 0, 0, .45);
|
|
115
|
+
backdrop-filter: blur(20px);
|
|
116
|
+
button{
|
|
117
|
+
border: 0px;
|
|
118
|
+
text-align: left;
|
|
119
|
+
padding: 4px 6px;
|
|
120
|
+
background: rgba(0, 0, 0, 0);
|
|
121
|
+
cursor: pointer;
|
|
122
|
+
color: #fff;
|
|
123
|
+
border-radius: 4px;
|
|
124
|
+
&:hover{
|
|
125
|
+
background: #5183ff;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
.line{
|
|
129
|
+
height: 1px;
|
|
130
|
+
background: rgba(255,255,255,0.25);
|
|
131
|
+
margin: 4px 6px;
|
|
132
|
+
}
|
|
106
133
|
}
|