@zuzjs/ui 0.2.7 → 0.3.0

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 CHANGED
@@ -6,6 +6,7 @@ import Cookies from 'js-cookie';
6
6
  import axios from 'axios';
7
7
  import { nanoid } from 'nanoid';
8
8
  import { ClassNames } from '@emotion/react';
9
+ export { Link } from 'react-router-dom';
9
10
  import styled from '@emotion/styled';
10
11
 
11
12
  const AppContext = createContext({});
@@ -59,20 +60,22 @@ class AppTheme {
59
60
  _AppTheme_darkTheme.set(this, void 0);
60
61
  this.get = () => {
61
62
  let self = this;
62
- // if(self.#mode === "auto"){
63
- // window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
64
- // self.#mode = event.matches ? "dark" : "light";
65
- // self.#listen(self.#mode);
66
- // });
67
- // return window.matchMedia &&
68
- // window.matchMedia('(prefers-color-scheme: dark)').matches ?
69
- // self.#darkTheme : self.#lightTheme;
70
- // }else
71
- if (__classPrivateFieldGet(self, _AppTheme_mode, "f") === "light") {
72
- return __classPrivateFieldGet(self, _AppTheme_lightTheme, "f");
63
+ if (__classPrivateFieldGet(self, _AppTheme_mode, "f") === "auto") {
64
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
65
+ __classPrivateFieldSet(self, _AppTheme_mode, event.matches ? "dark" : "light", "f");
66
+ __classPrivateFieldGet(self, _AppTheme_listen, "f").call(self, __classPrivateFieldGet(self, _AppTheme_mode, "f"));
67
+ });
68
+ return window.matchMedia &&
69
+ window.matchMedia('(prefers-color-scheme: dark)').matches ?
70
+ __classPrivateFieldGet(self, _AppTheme_darkTheme, "f") : __classPrivateFieldGet(self, _AppTheme_lightTheme, "f");
73
71
  }
74
- else if (__classPrivateFieldGet(self, _AppTheme_mode, "f") === "dark") {
75
- return __classPrivateFieldGet(self, _AppTheme_darkTheme, "f");
72
+ else {
73
+ if (__classPrivateFieldGet(self, _AppTheme_mode, "f") === "light") {
74
+ return __classPrivateFieldGet(self, _AppTheme_lightTheme, "f");
75
+ }
76
+ else if (__classPrivateFieldGet(self, _AppTheme_mode, "f") === "dark") {
77
+ return __classPrivateFieldGet(self, _AppTheme_darkTheme, "f");
78
+ }
76
79
  }
77
80
  };
78
81
  const conf = _conf || {};
@@ -88,6 +91,26 @@ class AppTheme {
88
91
  }
89
92
  _AppTheme_mode = new WeakMap(), _AppTheme_listen = new WeakMap(), _AppTheme_lightTheme = new WeakMap(), _AppTheme_darkTheme = new WeakMap();
90
93
 
94
+ var _AppLang_mode, _AppLang_listen, _AppLang_lang;
95
+ class AppLang {
96
+ constructor(_conf) {
97
+ _AppLang_mode.set(this, void 0);
98
+ _AppLang_listen.set(this, void 0);
99
+ _AppLang_lang.set(this, void 0);
100
+ this.get = () => {
101
+ let self = this;
102
+ return __classPrivateFieldGet(self, _AppLang_lang, "f");
103
+ };
104
+ const conf = _conf || {};
105
+ __classPrivateFieldSet(this, _AppLang_listen, "listen" in conf ? conf.listen : (mod) => { console.log(`Lang switched to ${mod}`); }, "f");
106
+ __classPrivateFieldSet(this, _AppLang_mode, conf.mode || "en", "f");
107
+ __classPrivateFieldSet(this, _AppLang_lang, Object.assign({
108
+ //CORE
109
+ tag: "en" }, ("lang" in conf ? Object.assign({}, conf.lang) : {})), "f");
110
+ }
111
+ }
112
+ _AppLang_mode = new WeakMap(), _AppLang_listen = new WeakMap(), _AppLang_lang = new WeakMap();
113
+
91
114
  let isMounted = true;
92
115
  const STORE_KEY = `__zuzjs`;
93
116
  const STORE_FORM_KEY = `${STORE_KEY}forms`;
@@ -102,14 +125,14 @@ const defaultState = {
102
125
  }
103
126
  };
104
127
  const rootReducer = (state, action) => (Object.assign(Object.assign({}, state), action.payload));
105
- const AppProvider = ({ children, initialState = {}, theme = {} }) => {
128
+ const AppProvider = ({ children, initialState = {}, theme = {}, lang = {} }) => {
106
129
  useEffect(() => {
107
130
  isMounted = true;
108
131
  return () => {
109
132
  isMounted = false;
110
133
  };
111
134
  }, []);
112
- const rootState = useMemo(() => (Object.assign(Object.assign(Object.assign({}, defaultState), initialState), { theme: new AppTheme({ theme }).get() })), [initialState]);
135
+ const rootState = useMemo(() => (Object.assign(Object.assign(Object.assign({}, defaultState), initialState), { theme: new AppTheme({ theme }).get(), lang: new AppLang({ lang }).get() })), [initialState]);
113
136
  const [state, _dispatch] = useReducer(rootReducer, rootState);
114
137
  const dispatch = useCallback((args) => {
115
138
  if (isMounted) {
@@ -135,12 +158,14 @@ const AppProvider = ({ children, initialState = {}, theme = {} }) => {
135
158
  };
136
159
  AppProvider.defaultProps = {
137
160
  theme: {},
161
+ lang: {},
138
162
  initialState: {},
139
163
  };
140
164
  AppProvider.propTypes = {
141
165
  children: PropTypes.node.isRequired,
142
166
  initialState: PropTypes.instanceOf(Object),
143
- theme: PropTypes.instanceOf(Object)
167
+ theme: PropTypes.instanceOf(Object),
168
+ lang: PropTypes.instanceOf(Object)
144
169
  };
145
170
 
146
171
  const createSlice = ({ name, initialState, reducers }) => {
@@ -556,14 +581,14 @@ const UPDATE_FORM_FIELD = (formName, field, value, forms) => {
556
581
  };
557
582
 
558
583
  const Input = forwardRef((props, ref) => {
559
- const { as, accept, multiple, onChange, onKeyUp, readOnly, type, tag, placeholder, name, form, touched, onSubmit, defaultValue, fref } = props;
584
+ const { as, accept, multiple, onChange, onKeyUp, onClick, readOnly, type, tag, placeholder, name, form, touched, onSubmit, value, defaultValue, fref } = props;
560
585
  const dispatch = useDispatch(STORE_FORM_KEY);
561
586
  const { forms } = useStore(state => state[STORE_FORM_KEY], false);
562
587
  let Tag = tag || 'input';
563
588
  const El = Tag;
564
589
  const _ref = fref || useRef();
565
590
  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);`;
566
- return (jsx(ClassNames, { children: ({ css, cx }) => jsx(El, { type: type || `text`, placeholder: placeholder || undefined, name: name || nanoid(), multiple: type == 'file' ? multiple : undefined, accept: accept || `*`, className: `${as ? `${as} ` : ``}f ${cx(css `${_defaultCSS}${buildCSS(props)}&:hover {${buildCSS(props.hover || {})}} &:focus {${buildCSS(props.focus || {})}}`)}`, ref: _ref, defaultValue: defaultValue || ``, onKeyUp: (e) => {
591
+ return (jsx(ClassNames, { children: ({ css, cx }) => jsx(El, { type: type || `text`, placeholder: placeholder || undefined, name: name || nanoid(), multiple: type == 'file' ? multiple : undefined, accept: accept || `*`, className: `${as ? `${as} ` : ``}f ${cx(css `${_defaultCSS}${buildCSS(props)}&:hover {${buildCSS(props.hover || {})}} &:focus {${buildCSS(props.focus || {})}}`)}`, ref: _ref, value: value || undefined, defaultValue: defaultValue || ``, onKeyUp: (e) => {
567
592
  let k = e['keyCode'] || ['which'];
568
593
  if (El != 'textarea' && k == 13 && form && onSubmit) {
569
594
  onSubmit(forms[form]);
@@ -574,7 +599,7 @@ const Input = forwardRef((props, ref) => {
574
599
  : e.currentTarget.value;
575
600
  dispatch(dispatch(UPDATE_FORM_FIELD(form || 'orphan', name, val == "" ? null : val, forms)));
576
601
  onChange && onChange(val == "" ? null : val);
577
- }, readOnly: readOnly || false, onBlur: e => {
602
+ }, onClick: onClick ? onClick : () => { }, readOnly: readOnly || false, onBlur: e => {
578
603
  }, onFocus: e => touched == false && dispatch(UPDATE_FORM_FIELD(form || 'orphan', `touched`, true, forms)) }) }));
579
604
  });
580
605
 
@@ -601,45 +626,70 @@ const useTheme = (mod = 'auto') => {
601
626
  return state['theme'] || {};
602
627
  };
603
628
 
604
- const useImage = (url, crossOrigin, referrerpolicy) => {
605
- const statusRef = useRef('loading');
606
- const imageRef = useRef();
607
- const [_, setStateToken] = useState(0);
608
- const oldUrl = useRef();
609
- const oldCrossOrigin = useRef();
610
- const oldReferrerPolicy = useRef();
611
- if (oldUrl.current !== url || oldCrossOrigin.current !== crossOrigin || oldReferrerPolicy.current !== referrerpolicy) {
612
- statusRef.current = 'loading';
613
- imageRef.current = undefined;
614
- oldUrl.current = url;
615
- oldCrossOrigin.current = crossOrigin;
616
- oldReferrerPolicy.current = referrerpolicy;
617
- }
618
- useLayoutEffect(function () {
619
- if (!url)
620
- return;
621
- var img = document.createElement('img');
622
- function onload() {
623
- statusRef.current = 'loaded';
624
- imageRef.current = img;
625
- setStateToken(Math.random());
626
- }
627
- function onerror() {
628
- statusRef.current = 'failed';
629
- imageRef.current = undefined;
630
- setStateToken(Math.random());
631
- }
632
- img.addEventListener('load', onload);
633
- img.addEventListener('error', onerror);
634
- crossOrigin && (img.crossOrigin = crossOrigin);
635
- referrerpolicy && (img.referrerPolicy = referrerpolicy);
636
- img.src = url;
637
- return function cleanup() {
638
- img.removeEventListener('load', onload);
639
- img.removeEventListener('error', onerror);
629
+ const removeBlankArrayElements = (a) => a.filter((x) => x);
630
+ const stringToArray = (x) => (Array.isArray(x) ? x : [x]);
631
+ const cache = {};
632
+ // sequential map.find for promises
633
+ const promiseFind = (arr, promiseFactory) => {
634
+ let done = false;
635
+ return new Promise((resolve, reject) => {
636
+ const queueNext = (src) => {
637
+ return promiseFactory(src).then(() => {
638
+ done = true;
639
+ resolve(src);
640
+ });
640
641
  };
641
- }, [url, crossOrigin, referrerpolicy]);
642
- return [imageRef.current, statusRef.current];
642
+ arr
643
+ .reduce((p, src) => {
644
+ // ensure we aren't done before enqueuing the next source
645
+ return p.catch(() => {
646
+ if (!done)
647
+ return queueNext(src);
648
+ });
649
+ }, queueNext(arr.shift()))
650
+ .catch(reject);
651
+ });
652
+ };
653
+ const useImage = ({ srcList, imgPromise = imgPromiseFactory({ decode: true }), useSuspense = true, }) => {
654
+ const [, setIsSettled] = useState(false);
655
+ const sourceList = removeBlankArrayElements(stringToArray(srcList));
656
+ const sourceKey = sourceList.join('');
657
+ if (!cache[sourceKey]) {
658
+ // create promise to loop through sources and try to load one
659
+ cache[sourceKey] = {
660
+ promise: promiseFind(sourceList, imgPromise),
661
+ cache: 'pending',
662
+ error: null,
663
+ };
664
+ }
665
+ // when promise resolves/reject, update cache & state
666
+ if (cache[sourceKey].cache === 'resolved') {
667
+ return { src: cache[sourceKey].src, isLoading: false, error: null };
668
+ }
669
+ if (cache[sourceKey].cache === 'rejected') {
670
+ if (useSuspense)
671
+ throw cache[sourceKey].error;
672
+ return { isLoading: false, error: cache[sourceKey].error, src: undefined };
673
+ }
674
+ cache[sourceKey].promise
675
+ // if a source was found, update cache
676
+ // when not using suspense, update state to force a rerender
677
+ .then((src) => {
678
+ cache[sourceKey] = Object.assign(Object.assign({}, cache[sourceKey]), { cache: 'resolved', src });
679
+ if (!useSuspense)
680
+ setIsSettled(sourceKey);
681
+ })
682
+ // if no source was found, or if another error occurred, update cache
683
+ // when not using suspense, update state to force a rerender
684
+ .catch((error) => {
685
+ cache[sourceKey] = Object.assign(Object.assign({}, cache[sourceKey]), { cache: 'rejected', error });
686
+ if (!useSuspense)
687
+ setIsSettled(sourceKey);
688
+ });
689
+ // cache[sourceKey].cache === 'pending')
690
+ if (useSuspense)
691
+ throw cache[sourceKey].promise;
692
+ return { isLoading: true, src: undefined, error: null };
643
693
  };
644
694
 
645
695
  /** External Dependencies */
@@ -799,13 +849,14 @@ const useDevice = (config, defaultBreakpoint, hydrateInitial = true) => {
799
849
  return currentBreakpoint;
800
850
  };
801
851
 
802
- var _Toaster_container, _Toaster_startTop, _Toaster_tout, _Toaster_defaultTimeLeft;
852
+ var _Toaster_container, _Toaster_startTop, _Toaster_tout, _Toaster_defaultTimeLeft, _Toaster_root;
803
853
  class Toaster {
804
854
  constructor(config) {
805
855
  _Toaster_container.set(this, void 0);
806
856
  _Toaster_startTop.set(this, void 0);
807
857
  _Toaster_tout.set(this, void 0);
808
858
  _Toaster_defaultTimeLeft.set(this, void 0);
859
+ _Toaster_root.set(this, void 0);
809
860
  this.show = (message, duration) => {
810
861
  let self = this;
811
862
  self.toast({
@@ -816,12 +867,13 @@ class Toaster {
816
867
  __classPrivateFieldSet(this, _Toaster_startTop, 20, "f");
817
868
  __classPrivateFieldSet(this, _Toaster_defaultTimeLeft, 4, "f");
818
869
  const rootID = (config === null || config === void 0 ? void 0 : config.root) || `toast-container`;
819
- if (document.querySelector(`#${rootID}`))
870
+ __classPrivateFieldSet(this, _Toaster_root, rootID, "f");
871
+ if (window.document.querySelector(`#${rootID}`))
820
872
  return;
821
- var root = document.createElement('div');
873
+ var root = window.document.createElement('div');
822
874
  root.id = rootID;
823
- document.body.appendChild(root);
824
- __classPrivateFieldSet(this, _Toaster_container, document.querySelector(`#${rootID}`), "f");
875
+ window.document.body.appendChild(root);
876
+ __classPrivateFieldSet(this, _Toaster_container, window.document.querySelector(`#${rootID}`), "f");
825
877
  }
826
878
  dismiss(ID) {
827
879
  let self = this;
@@ -841,7 +893,7 @@ class Toaster {
841
893
  }
842
894
  toast(config) {
843
895
  var self = this;
844
- var ID = 'toast-' + nanoid(), toast = document.createElement('div'); document.createElement('div');
896
+ var ID = 'toast-' + nanoid(), toast = window.document.createElement('div'); window.document.createElement('div');
845
897
  toast.id = ID;
846
898
  toast.style.backgroundColor = `#111`;
847
899
  toast.style.color = `#fff`;
@@ -856,7 +908,7 @@ class Toaster {
856
908
  toast.classList.add('fixed');
857
909
  toast.classList.add('f');
858
910
  toast.innerHTML = config.message || `You haven't passed "message" in this toast`;
859
- __classPrivateFieldGet(self, _Toaster_container, "f").appendChild(toast);
911
+ (__classPrivateFieldGet(self, _Toaster_container, "f") || window.document.querySelector(`#${__classPrivateFieldGet(self, _Toaster_root, "f")}`)).appendChild(toast);
860
912
  self.arrangeToasts()
861
913
  .then(() => {
862
914
  setTimeout(() => {
@@ -883,13 +935,18 @@ class Toaster {
883
935
  });
884
936
  }
885
937
  }
886
- _Toaster_container = new WeakMap(), _Toaster_startTop = new WeakMap(), _Toaster_tout = new WeakMap(), _Toaster_defaultTimeLeft = new WeakMap();
938
+ _Toaster_container = new WeakMap(), _Toaster_startTop = new WeakMap(), _Toaster_tout = new WeakMap(), _Toaster_defaultTimeLeft = new WeakMap(), _Toaster_root = new WeakMap();
887
939
 
888
940
  const useToast = () => {
889
941
  const toaster = useMemo(() => new Toaster(), []);
890
942
  return toaster;
891
943
  };
892
944
 
945
+ const useLang = (mod = 'en') => {
946
+ const state = useContext(AppContext);
947
+ return state['lang'] || {};
948
+ };
949
+
893
950
  const Button = forwardRef((props, ref) => {
894
951
  const { forms } = useStore(state => state[STORE_FORM_KEY], false);
895
952
  if (props.html) {
@@ -1037,7 +1094,7 @@ const randstr = function (len) {
1037
1094
  }
1038
1095
  return text;
1039
1096
  };
1040
- const setCookie = (key, value, expiry) => Cookies.set(key, value, { expires: expiry || 7 });
1097
+ const setCookie = (key, value, expiry, host) => Cookies.set(key, value, { expires: expiry || 7, domain: host || window.location.host });
1041
1098
  const getCookie = (key) => Cookies.get(key) || null;
1042
1099
  const removeCookie = (key) => Cookies.remove(key);
1043
1100
  const buildFormData = (data) => {
@@ -1190,6 +1247,29 @@ const getUriParams = () => {
1190
1247
  return xny;
1191
1248
  };
1192
1249
  const rgb2hex = rgb => `#${rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/).slice(1).map(n => parseInt(n, 10).toString(16).padStart(2, '0')).join('')}`;
1250
+ const getHostname = url => {
1251
+ if (window.URL) {
1252
+ let u = new window.URL(url);
1253
+ return u.hostname;
1254
+ }
1255
+ else {
1256
+ var a = document.createElement(`a`);
1257
+ a.href = url;
1258
+ return a.hostname.replace("www.", "");
1259
+ }
1260
+ };
1261
+ const imgPromiseFactory = ({ decode = true, crossOrigin = '' }) => (src) => {
1262
+ return new Promise((resolve, reject) => {
1263
+ const i = new Image();
1264
+ if (crossOrigin)
1265
+ i.crossOrigin = crossOrigin;
1266
+ i.onload = () => {
1267
+ decode && i.decode ? i.decode().then(resolve).catch(reject) : resolve();
1268
+ };
1269
+ i.onerror = reject;
1270
+ i.src = src;
1271
+ });
1272
+ };
1193
1273
 
1194
1274
  const AppMain = forwardRef((props, ref) => {
1195
1275
  // const { dispatch } = useStore()
@@ -1338,9 +1418,18 @@ const Icon = forwardRef((props, ref) => {
1338
1418
  return (jsx(Box, Object.assign({ hover: hover || {}, flex: true, bref: ref, as: `icon-${as}`, ai: `c`, jc: `c`, size: size || 24, color: color || `#111111` }, { children: path && Array(path).fill(undefined).map((p, i) => jsx("span", { className: `path${i + 1}` }, `ico-${as}-${i}`)) })));
1339
1419
  });
1340
1420
 
1341
- const Image = forwardRef((props, ref) => {
1342
- const [photo, status] = useImage(props.src, 'anonymous', 'origin');
1343
- return (jsx(ClassNames, { children: ({ css, cx }) => jsxs("picture", Object.assign({ className: cx(css `${buildCSS({ flex: true })}`) }, { children: [status == 'loading' && jsx(Box, { className: `${props.as ? `${props.as} ` : ``}${cx(css `background: #eee;${buildCSS(props)}`)}` }), status == 'loaded' && jsx("img", { src: props.src, className: `${props.as ? `${props.as} ` : ``}${cx(css `${buildCSS(props)}`)}`, ref: ref })] })) }));
1421
+ const Image$1 = forwardRef((props, ref) => {
1422
+ const { src, isLoading, error } = useImage({ srcList: props.src, useSuspense: false });
1423
+ return (jsx(ClassNames, { children: ({ css, cx }) => jsxs(Fragment, { children: [isLoading && jsx(Box, { className: `${props.as ? `${props.as} ` : ``}${cx(css `background: #eee;${buildCSS(props)}`)}` }), !isLoading && jsx("img", { src: src, className: `${props.as ? `${props.as} ` : ``}${cx(css `${buildCSS(props)}`)}`, ref: ref })] }) })
1424
+ // <ClassNames>
1425
+ // {({ css, cx }) => <picture className={cx(css`${buildCSS({ flex: true })}`)}>
1426
+ // {status == 'loading' && <Box className={`${props.as ? `${props.as} ` : ``}${cx(css`background: #eee;${buildCSS(props)}`)}`} />}
1427
+ // {status == 'loaded' && <img src={props.src}
1428
+ // className={`${props.as ? `${props.as} ` : ``}${cx(css`${buildCSS(props)}`)}`}
1429
+ // ref={ref} />}
1430
+ // </picture>}
1431
+ // </ClassNames>
1432
+ );
1344
1433
  });
1345
1434
 
1346
1435
  const DEFAULT_COLUMNS = 2;
@@ -1548,4 +1637,4 @@ const Header = forwardRef((props, ref) => {
1548
1637
  return (jsx(Fragment, { children: buildComponent(data) }));
1549
1638
  });
1550
1639
 
1551
- export { App, Component as Block, Box, Button, Checkbox, ContextMenu, Cover, Form, Header, Heading, Icon, Image, Input, Masonry, Placeholder, AppProvider as Provider, Select, Spacer, Spinner, Text, Toaster, Tweet, addProps, addScript, buildCSS, buildFormData, byId, byName, createSlice, el, filterHTMLProps, filterStyleProps, generateModalRoutes, generatePreservedRoutes, generateRegularRoutes, getCookie, getUriParams, grab, isEmail, isIPv4, isUrl, randstr, removeCookie, rgb2hex, setCSSVar, setCookie, shuffleArray, ucfirst, useDevice, useDispatch, useImage, useResizeObserver, useStore, useTheme, useToast, uuid };
1640
+ export { App, Component as Block, Box, Button, Checkbox, ContextMenu, Cover, Form, Header, Heading, Icon, Image$1 as Image, Input, Masonry, Placeholder, AppProvider as Provider, Select, Spacer, Spinner, Text, Toaster, Tweet, addProps, addScript, buildCSS, buildFormData, byId, byName, createSlice, el, filterHTMLProps, filterStyleProps, generateModalRoutes, generatePreservedRoutes, generateRegularRoutes, getCookie, getHostname, getUriParams, grab, imgPromiseFactory, isEmail, isIPv4, isUrl, randstr, removeCookie, rgb2hex, setCSSVar, setCookie, shuffleArray, ucfirst, useDevice, useDispatch, useImage, useLang, useResizeObserver, useStore, useTheme, useToast, uuid };
package/dist/styles.css CHANGED
@@ -312,6 +312,50 @@ button {
312
312
  font-size: 72px;
313
313
  }
314
314
 
315
+ /*
316
+ BoldSize
317
+ It will generate .b400 { font-weight: 400; } from '400'
318
+ */
319
+ .bold {
320
+ font-weight: bold;
321
+ }
322
+
323
+ .b100 {
324
+ font-weight: 100;
325
+ }
326
+
327
+ .b200 {
328
+ font-weight: 200;
329
+ }
330
+
331
+ .b300 {
332
+ font-weight: 300;
333
+ }
334
+
335
+ .b400 {
336
+ font-weight: 400;
337
+ }
338
+
339
+ .b500 {
340
+ font-weight: 500;
341
+ }
342
+
343
+ .b600 {
344
+ font-weight: 600;
345
+ }
346
+
347
+ .b700 {
348
+ font-weight: 700;
349
+ }
350
+
351
+ .b800 {
352
+ font-weight: 800;
353
+ }
354
+
355
+ .b900 {
356
+ font-weight: 900;
357
+ }
358
+
315
359
  @-webkit-keyframes rotating /* Safari and Chrome */ {
316
360
  from {
317
361
  -webkit-transform: rotate(0deg);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zuzjs/ui",
3
- "version": "0.2.7",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -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 [photo, status] = useImage(props.src, 'anonymous', 'origin');
12
+ const { src, isLoading, error } = useImage({ srcList: props.src, useSuspense: false });
13
13
 
14
14
  return (
15
15
  <ClassNames>
16
- {({ css, cx }) => <picture className={cx(css`${buildCSS({ flex: true })}`)}>
17
- {status == 'loading' && <Box className={`${props.as ? `${props.as} ` : ``}${cx(css`background: #eee;${buildCSS(props)}`)}`} />}
18
- {status == 'loaded' && <img src={props.src}
19
- className={`${props.as ? `${props.as} ` : ``}${cx(css`${buildCSS(props)}`)}`}
20
- ref={ref} />}
21
- </picture>}
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
 
@@ -23,6 +23,7 @@ const Input = forwardRef((props : { [ key: string ] : any }, ref : LegacyRef<HTM
23
23
  multiple,
24
24
  onChange,
25
25
  onKeyUp,
26
+ onClick,
26
27
  readOnly,
27
28
  type,
28
29
  tag,
@@ -31,6 +32,7 @@ const Input = forwardRef((props : { [ key: string ] : any }, ref : LegacyRef<HTM
31
32
  form,
32
33
  touched,
33
34
  onSubmit,
35
+ value,
34
36
  defaultValue,
35
37
  fref
36
38
  } = props;
@@ -54,6 +56,7 @@ const Input = forwardRef((props : { [ key: string ] : any }, ref : LegacyRef<HTM
54
56
  accept={accept || `*`}
55
57
  className={`${as ? `${as} ` : ``}f ${cx(css`${_defaultCSS}${buildCSS(props)}&:hover {${buildCSS(props.hover || {})}} &:focus {${buildCSS(props.focus || {})}}`)}`}
56
58
  ref={_ref}
59
+ value={value || undefined}
57
60
  defaultValue={defaultValue || ``}
58
61
  onKeyUp={(e : SyntheticEvent) => {
59
62
  let k = e['keyCode'] || ['which'];
@@ -68,6 +71,7 @@ const Input = forwardRef((props : { [ key: string ] : any }, ref : LegacyRef<HTM
68
71
  dispatch( dispatch( UPDATE_FORM_FIELD( form || 'orphan', name, val == "" ? null : val, forms ) ) );
69
72
  onChange && onChange(val == "" ? null : val)
70
73
  }}
74
+ onClick={onClick ? onClick : () => {}}
71
75
  readOnly={readOnly || false}
72
76
  onBlur={e => {
73
77
  if(touched){}
@@ -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
- if(document.querySelector(`#${rootID}`)) return;
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
@@ -31,19 +31,20 @@ class AppTheme {
31
31
 
32
32
  get = () => {
33
33
  let self = this;
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;
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
 
@@ -67,19 +67,20 @@ class AppTheme {
67
67
 
68
68
  get = () => {
69
69
  let self = this;
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;
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
@@ -78,7 +78,7 @@ const randstr = function(len? : number){
78
78
  return text;
79
79
  }
80
80
 
81
- const setCookie = (key : string, value : any, expiry? : number) => Cookies.set(key, value, { expires: expiry || 7 })
81
+ const setCookie = (key : string, value : any, expiry? : number, host? : string) => Cookies.set(key, value, { expires: expiry || 7, domain: host || window.location.host })
82
82
 
83
83
  const getCookie = (key : string) => Cookies.get(key) || null;
84
84
 
@@ -254,6 +254,30 @@ const getUriParams = () => {
254
254
 
255
255
  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
256
 
257
+ const getHostname = url => {
258
+ if(window.URL){
259
+ let u = new window.URL(url);
260
+ return u.hostname
261
+ }else{
262
+ var a = document.createElement(`a`)
263
+ a.href = url
264
+ return a.hostname.replace("www.", "")
265
+ }
266
+ }
267
+
268
+ const imgPromiseFactory = ({decode = true, crossOrigin = ''}) =>
269
+ (src): Promise<void> => {
270
+ return new Promise((resolve, reject) => {
271
+ const i = new Image()
272
+ if (crossOrigin) i.crossOrigin = crossOrigin
273
+ i.onload = () => {
274
+ decode && i.decode ? i.decode().then(resolve).catch(reject) : resolve()
275
+ }
276
+ i.onerror = reject
277
+ i.src = src
278
+ })
279
+ }
280
+
257
281
  export {
258
282
  addProps,
259
283
  addScript,
@@ -266,6 +290,7 @@ export {
266
290
  isEmail,
267
291
  isIPv4,
268
292
  isUrl,
293
+ imgPromiseFactory,
269
294
  randstr,
270
295
  setCSSVar,
271
296
  getCookie,
@@ -280,6 +305,7 @@ export {
280
305
  rgb2hex,
281
306
  generateModalRoutes,
282
307
  generatePreservedRoutes,
283
- generateRegularRoutes
308
+ generateRegularRoutes,
309
+ getHostname
284
310
  }
285
311
 
@@ -5,4 +5,4 @@ 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 useRouter } from './useRouter'
8
+ export { default as useLang } from './useLang'
@@ -1,58 +1,84 @@
1
- import React, { useRef, useLayoutEffect, useState } from 'react'
1
+ import React, {useState} from 'react'
2
+ import { imgPromiseFactory } from '../core'
2
3
 
3
- const useImage = (url, crossOrigin, referrerpolicy) => {
4
-
5
- const statusRef = useRef('loading')
6
- const imageRef = useRef<HTMLImageElement>();
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
- const [_, setStateToken] = useState(0);
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
- const oldUrl = useRef();
11
- const oldCrossOrigin = useRef();
12
- const oldReferrerPolicy = useRef();
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
- if(oldUrl.current !== url || oldCrossOrigin.current !== crossOrigin || oldReferrerPolicy.current !== referrerpolicy){
15
- statusRef.current = 'loading';
16
- imageRef.current = undefined;
17
- oldUrl.current = url;
18
- oldCrossOrigin.current = crossOrigin;
19
- oldReferrerPolicy.current = referrerpolicy;
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
- useLayoutEffect(
23
- function(){
24
-
25
- if(!url) return;
26
- var img = document.createElement('img');
27
-
28
- function onload(){
29
- statusRef.current = 'loaded';
30
- imageRef.current = img;
31
- setStateToken(Math.random());
32
- }
33
-
34
- function onerror(){
35
- statusRef.current = 'failed';
36
- imageRef.current = undefined;
37
- setStateToken(Math.random());
38
- }
39
-
40
- img.addEventListener('load', onload);
41
- img.addEventListener('error', onerror);
42
- crossOrigin && (img.crossOrigin = crossOrigin);
43
- referrerpolicy && (img.referrerPolicy = referrerpolicy);
44
- img.src = url;
45
-
46
- return function cleanup() {
47
- img.removeEventListener('load', onload);
48
- img.removeEventListener('error', onerror);
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;
@@ -0,0 +1,9 @@
1
+ import { useContext, useEffect } from 'react'
2
+ import AppContext from '../context'
3
+
4
+ const useLang = (mod = 'en') => {
5
+ const state = useContext(AppContext)
6
+ return state['lang'] || {};
7
+ }
8
+
9
+ export default useLang
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'
@@ -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
  }