lupine.web 1.0.32 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lupine.web",
3
- "version": "1.0.32",
3
+ "version": "1.1.1",
4
4
  "license": "MIT",
5
5
  "author": "uuware.com",
6
6
  "homepage": "https://github.com/uuware/lupine.js",
@@ -26,10 +26,16 @@ export const bindRef = (type: any, newProps: any, el: Element) => {
26
26
  * @returns Element
27
27
  */
28
28
  newProps['ref'].$ = (selector: string) => {
29
- return el.querySelector(`[${id}] ` + selector);
29
+ if (selector.startsWith('&')) {
30
+ return el.querySelector(`.${id}${selector.substring(1).replace(/&/g, id)}`);
31
+ }
32
+ return el.querySelector(`.${id} ${selector.replace(/&/g, id)}`);
30
33
  };
31
34
  newProps['ref'].$all = (selector: string) => {
32
- return el.querySelectorAll(`[${id}] ` + selector);
35
+ if (selector.startsWith('&')) {
36
+ return el.querySelectorAll(`.${id}${selector.substring(1).replace(/&/g, id)}`);
37
+ }
38
+ return el.querySelectorAll(`.${id} ${selector.replace(/&/g, id)}`);
33
39
  };
34
40
 
35
41
  newProps['ref'].mountInnerComponent = async (content: string | VNode<any>) => {
@@ -36,20 +36,32 @@ const updateOneBlock = (css: string[], cssTemp: string[], className: string, med
36
36
  }
37
37
  };
38
38
 
39
- export const processStyle = (className: string, style: CssProps, mediaQuery?: string): string[] => {
39
+ const processStyleSub = (
40
+ topUniqueClassName: string,
41
+ classSelector: string,
42
+ style: CssProps,
43
+ mediaQuery?: string
44
+ ): string[] => {
45
+ const outClassName = classSelector
46
+ .split(',')
47
+ .map((key0) => key0.trim())
48
+ .map((key0) => {
49
+ return (key0.startsWith('&') ? `${classSelector}${key0.substring(1)}` : key0).replace(/&/g, topUniqueClassName);
50
+ })
51
+ .join(',');
40
52
  const css: string[] = [];
41
53
  const cssTemp: string[] = [];
42
54
  for (let i in style) {
43
55
  const value = style[i];
44
56
  if (value === null || typeof value !== 'object') {
45
57
  if (value !== '' && typeof value !== 'undefined') {
46
- if (!className) {
58
+ if (!classSelector) {
47
59
  console.warn(`No className is defined for: ${camelToHyphens(i)}:${value};`);
48
60
  }
49
61
  cssTemp.push(`${camelToHyphens(i)}:${value};`);
50
62
  }
51
63
  } else {
52
- updateOneBlock(css, cssTemp, className, mediaQuery);
64
+ updateOneBlock(css, cssTemp, outClassName, mediaQuery);
53
65
 
54
66
  if (i.startsWith('@keyframes')) {
55
67
  const cssText = Object.keys(value)
@@ -57,17 +69,22 @@ export const processStyle = (className: string, style: CssProps, mediaQuery?: st
57
69
  .join('');
58
70
  css.push(`${i}{${cssText}}`);
59
71
  } else if (i.startsWith('@media')) {
60
- const ret = processStyle(className, value, i);
72
+ const ret = processStyleSub(topUniqueClassName, classSelector, value, i);
61
73
  css.push(...ret);
62
74
  } else {
63
75
  // '&:hover, &.open': {
64
- // '>.d1, .d2': {
65
- // },
76
+ // '>.d1, .d2': {...},
66
77
  // }, ==>
67
78
  // &:hover >.d1, &:hover >.d2, &.open >.d1, &.open .d2
68
- const newClassName = !className
79
+
80
+ // '.aa': {
81
+ // '&:hover': {...},
82
+ // '.bb': {...},
83
+ // }, ==>
84
+ // .aa:hover, .aa .bb
85
+ const newClassSelector = !classSelector
69
86
  ? i
70
- : className
87
+ : classSelector
71
88
  .split(',')
72
89
  .map((key0) => key0.trim())
73
90
  .map((key0) => {
@@ -77,25 +94,31 @@ export const processStyle = (className: string, style: CssProps, mediaQuery?: st
77
94
  .map((key) => {
78
95
  // not needed to "+" as them share same parents?
79
96
  // return key.split('+').map(key2 => key2.startsWith('&') ? key0 + key2.substring(1) : key0 + ' ' + key2).join('+');
80
- return key.startsWith('&') ? key0 + key.substring(1) : key0 + ' ' + key;
97
+ // return key.startsWith('&') ? key0 + key.substring(1) : key0 + ' ' + key;
98
+ const newKey = key.startsWith('&') ? key0 + key.substring(1) : key0 + ' ' + key;
99
+ return newKey.replace(/&/g, topUniqueClassName);
81
100
  })
82
101
  .join(',');
83
102
  })
84
103
  .join(',');
85
- const ret = processStyle(newClassName, value, mediaQuery);
104
+ const ret = processStyleSub(topUniqueClassName, newClassSelector, value, mediaQuery);
86
105
  css.push(...ret);
87
106
  }
88
107
  }
89
108
  }
90
- updateOneBlock(css, cssTemp, className, mediaQuery);
109
+ updateOneBlock(css, cssTemp, outClassName, mediaQuery);
91
110
  return css;
92
111
  };
112
+ // topUniqueClassName is used to replace '&' in className, and '.' + topUniqueClassName is used as selector in styles ".xxx {}"
113
+ export const processStyle = (topUniqueClassName: string, style: CssProps): string[] => {
114
+ return processStyleSub(topUniqueClassName, topUniqueClassName ? `.${topUniqueClassName}` : '', style);
115
+ };
93
116
 
94
117
  // mount-components has the same name `sty-`
95
- export const updateStyles = (selector: string, style: CssProps) => {
96
- const el = selector && document.querySelector(selector);
118
+ export const updateStyles = (topUniqueClassName: string, style: CssProps) => {
119
+ const el = topUniqueClassName && document.querySelector(`.${topUniqueClassName}`);
97
120
  if (el) {
98
- const cssText = processStyle(selector, style).join('');
121
+ const cssText = processStyle(topUniqueClassName, style).join('');
99
122
  // if the first child is style, then update it
100
123
  if (el.firstChild && el.firstChild.nodeName === 'STYLE') {
101
124
  (el.firstChild as any).innerHTML = cssText;
@@ -106,7 +129,7 @@ export const updateStyles = (selector: string, style: CssProps) => {
106
129
  el.prepend(style);
107
130
  }
108
131
  } else {
109
- console.warn(`Can't find "${selector}" to update styles.`);
132
+ console.warn(`Can't find "${topUniqueClassName}" to update styles.`);
110
133
  }
111
134
  };
112
135
 
@@ -120,21 +143,41 @@ const updateCssDom = (uniqueStyleId: string, cssText: string, cssDom: HTMLElemen
120
143
  };
121
144
 
122
145
  /*
146
+ If selectors in GlobalStyles have '&' at top level, then classSelector is needed, otherwise classSelector can be ''
147
+ This is ok:
148
+ {
149
+ '.aa':{
150
+ '&:hover': {...}
151
+ }
152
+ }
153
+ This needs classSelector:
154
+ {
155
+ 'color': 'red', // will need and be put under .topUniqueClassName
156
+ '&:hover': {...} // & will be replaced by .topUniqueClassName
157
+ }
123
158
  Global styles including theme will not be updated once it's created.
124
- topClassName is a className or a tag name.
159
+ topUniqueClassName is a className or a tag name.
160
+ classSelector is a selector used in styles ".xxx {}"
125
161
  For example, it can be like this for all elements:
126
162
  html { ... } or :root { ... }
163
+
164
+ For themes like [data-theme="dark" i], the topUniqueClassName should be empty
127
165
  */
128
166
  const _globalStyle = new Map();
129
- export const bindGlobalStyles = (uniqueStyleId: string, topClassName: string, style: CssProps, forceUpdate = false) => {
167
+ export const bindGlobalStyles = (
168
+ topUniqueClassName: string,
169
+ style: CssProps,
170
+ forceUpdate = false,
171
+ noTopClassName = false
172
+ ) => {
130
173
  if (typeof document !== 'undefined') {
131
- let cssDom = document.getElementById(`sty-${uniqueStyleId}`);
174
+ let cssDom = document.getElementById(`sty-${topUniqueClassName}`);
132
175
  if (forceUpdate || !cssDom) {
133
- updateCssDom(uniqueStyleId, processStyle(topClassName, style).join(''), cssDom);
176
+ updateCssDom(topUniqueClassName, processStyle(noTopClassName ? '' : topUniqueClassName, style).join(''), cssDom);
134
177
  }
135
- } else if (!_globalStyle.has(uniqueStyleId) || forceUpdate) {
178
+ } else if (!_globalStyle.has(topUniqueClassName) || forceUpdate) {
136
179
  // don't overwrite it to have the same behavior as in the Browser
137
- _globalStyle.set(uniqueStyleId, { topClassName, style });
180
+ _globalStyle.set(topUniqueClassName, { topUniqueClassName, noTopClassName, style });
138
181
  }
139
182
  };
140
183
 
@@ -143,7 +186,11 @@ const generateThemeStyles = () => {
143
186
  const themeCss = [];
144
187
  for (let themeName in currentTheme.themes) {
145
188
  // i is for case-insensitive
146
- themeCss.push(...processStyle(`[data-theme="${themeName}" i]`, currentTheme.themes[themeName]));
189
+ themeCss.push(
190
+ ...processStyle('', {
191
+ [`[data-theme="${themeName}" i]`]: currentTheme.themes[themeName],
192
+ })
193
+ );
147
194
  }
148
195
  return themeCss.join('\n');
149
196
  };
@@ -159,7 +206,7 @@ if (typeof document !== 'undefined') {
159
206
  });
160
207
  }
161
208
 
162
- // 不能清空,在index.tsx中加载的只会被加载一次,清空了就没有了
209
+ // can't clear global styles,because in index.tsx it is only loaded once, clear it it will be gone
163
210
  // const clearGlobalStyles = () => {
164
211
  // // reset unique id
165
212
  // _globalStyle.clear();
@@ -169,10 +216,10 @@ if (typeof document !== 'undefined') {
169
216
  export const generateAllGlobalStyles = () => {
170
217
  const result = [];
171
218
 
172
- result.push(`<style id="sty-theme">${generateThemeStyles()}</style>`);
219
+ result.push(`<style id="sty-${themeCookieName}">${generateThemeStyles()}</style>`);
173
220
 
174
- for (let [uniqueStyleId, { topClassName, style }] of _globalStyle) {
175
- const cssText = processStyle(topClassName, style).join('');
221
+ for (let [uniqueStyleId, { topUniqueClassName, noTopClassName, style }] of _globalStyle) {
222
+ const cssText = processStyle(noTopClassName ? '' : topUniqueClassName, style).join('');
176
223
  result.push(`<style id="sty-${uniqueStyleId}">${cssText}</style>`);
177
224
  }
178
225
 
@@ -11,7 +11,7 @@ export const domUniqueId = uniqueIdGenerator('l'); // l means label
11
11
  // domUniqueId(true);
12
12
  // });
13
13
 
14
- function renderChildren(html: string[], children: any) {
14
+ function renderChildren(html: string[], children: any, uniqueClassName?: string) {
15
15
  if (typeof children === 'string') {
16
16
  html.push(children);
17
17
  } else if (children === false || children === null || typeof children === 'undefined') {
@@ -22,10 +22,10 @@ function renderChildren(html: string[], children: any) {
22
22
  } else if (Array.isArray(children)) {
23
23
  for (let i = 0; i < children.length; i++) {
24
24
  const item = children[i];
25
- renderChildren(html, item);
25
+ renderChildren(html, item, uniqueClassName);
26
26
  }
27
27
  } else if (children.type && children.props) {
28
- renderComponent(children.type, children.props);
28
+ renderComponent(children.type, children.props, uniqueClassName);
29
29
  html.push(...children.props._html);
30
30
  children.props._html.length = 0;
31
31
  } else {
@@ -57,19 +57,16 @@ const genUniqueId = (props: any) => {
57
57
  return props._id;
58
58
  };
59
59
  // data-refid will be assigned with a ref.id
60
- function renderAttribute(type: any, props: any, jsxNodes: any) {
60
+ function renderAttribute(type: any, props: any, jsxNodes: any, uniqueClassName?: string) {
61
61
  const html = [];
62
62
  // data-refid is used for nested components like this:
63
63
  // <div class='class-name' ref={ref} ...>...
64
64
  // <div data-refid={ref}>
65
65
  // then data-refid can be located:
66
66
  // ref.$(`.class-name[data-refid=${ref.id}]`)
67
- if (props._id) {
68
- console.warn('This component reference is used more than once and will have binding issues: ', props);
69
- }
70
- if (props['data-refid'] && props['data-refid'].id) {
71
- props['data-refid'] = props['data-refid'].id;
72
- }
67
+ // if (props['data-refid'] && props['data-refid'].id) {
68
+ // props['data-refid'] = props['data-refid'].id;
69
+ // }
73
70
  for (let i in props) {
74
71
  if (i === 'ref') {
75
72
  if (props[i]) {
@@ -106,11 +103,15 @@ function renderAttribute(type: any, props: any, jsxNodes: any) {
106
103
  html.push(`${i}="${props[i]}"`);
107
104
  }
108
105
  } else if (i === 'class' || i === 'className') {
109
- const className = props[i]
110
- .split(' ')
111
- .filter((item: string) => item && item !== '')
112
- .join(' ');
113
- html.push(`class="${className}"`);
106
+ let classNameList = props[i].split(' ').filter((item: string) => item && item !== '');
107
+ if ((props['css'] || props['ref']) && !classNameList.includes(props._id)) {
108
+ // add as the first
109
+ classNameList.unshift(props._id);
110
+ }
111
+ if (uniqueClassName) {
112
+ classNameList = classNameList.map((item: string) => item.replace(/&/g, uniqueClassName));
113
+ }
114
+ html.push(`class="${classNameList.join(' ')}"`);
114
115
  } else if (i !== 'dangerouslySetInnerHTML') {
115
116
  html.push(`${i}="${props[i]}"`);
116
117
  }
@@ -123,28 +124,12 @@ function renderAttribute(type: any, props: any, jsxNodes: any) {
123
124
  return html.join(' ');
124
125
  }
125
126
 
126
- // assign the same label to all children
127
- // function assignLabels(label: string, children: any) {
128
- // if (Array.isArray(children)) {
129
- // for (let i = 0; i < children.length; i++) {
130
- // const item = children[i];
131
- // if (Array.isArray(item) || (item && item.type && item.props)) {
132
- // assignLabels(label, item);
133
- // }
134
- // }
135
- // } else if (children.type && children.props) {
136
- // if (typeof children.type === 'string') {
137
- // children.props._lb = label;
138
- // }
139
- // }
140
- // }
141
-
142
127
  // The result has only one element
143
- export const renderComponent = (type: any, props: any) => {
128
+ export const renderComponent = (type: any, props: any, uniqueClassName?: string) => {
144
129
  // logger.log("==================renderComponent", type);
145
130
  if (Array.isArray(props)) {
146
131
  const jsxNodes = { type: 'Fragment', props: { children: props } } as any;
147
- renderComponent(jsxNodes.type, jsxNodes.props);
132
+ renderComponent(jsxNodes.type, jsxNodes.props, uniqueClassName);
148
133
  return;
149
134
  }
150
135
 
@@ -162,7 +147,7 @@ export const renderComponent = (type: any, props: any) => {
162
147
  // }
163
148
  // logger.log('==========props._result', props._result);
164
149
  if (typeof props._result.type === 'function') {
165
- renderComponent(props._result.type, props._result.props);
150
+ renderComponent(props._result.type, props._result.props, uniqueClassName);
166
151
  if (props._result.props._html) {
167
152
  props._html.push(...props._result.props._html);
168
153
  props._result.props._html.length = 0;
@@ -177,7 +162,17 @@ export const renderComponent = (type: any, props: any) => {
177
162
  console.log('renderComponent', newType, newProps);
178
163
  }
179
164
  if (typeof newType === 'string') {
180
- const attrs = renderAttribute(newType, newProps, { type, props });
165
+ if (newProps._id) {
166
+ console.warn('This component reference is used more than once and will have binding issues: ', newProps);
167
+ }
168
+ let newUniqueClassName = uniqueClassName;
169
+ if (newProps['css'] || newProps['ref']) {
170
+ newUniqueClassName = genUniqueId(newProps);
171
+ if (!newProps['class'] && !newProps['className']) {
172
+ newProps['class'] = newUniqueClassName;
173
+ }
174
+ }
175
+ const attrs = renderAttribute(newType, newProps, { type, props }, newUniqueClassName);
181
176
  if (selfClosingTags.includes(newType.toLowerCase())) {
182
177
  // for Fragment, only needs this tag when Ref is assigned
183
178
  if (newType !== 'Fragment' || newProps.ref) {
@@ -192,8 +187,8 @@ export const renderComponent = (type: any, props: any) => {
192
187
  }
193
188
 
194
189
  if (newProps['css']) {
195
- const cssText = processStyle(`[${newProps._id}]`, newProps['css']).join('');
196
- props._html.push(`<style id="sty-${newProps._id}">${cssText}</style>`); // sty means style, and updateStyles has the same name
190
+ const cssText = processStyle(newUniqueClassName!, newProps['css']).join('');
191
+ props._html.push(`<style id="sty-${newUniqueClassName}">${cssText}</style>`); // sty means style, and updateStyles has the same name
197
192
  }
198
193
 
199
194
  if (newProps.children) {
@@ -201,7 +196,7 @@ export const renderComponent = (type: any, props: any) => {
201
196
  // assignLabels(newProps._lb, newProps.children);
202
197
  // }
203
198
 
204
- renderChildren(props._html, newProps.children);
199
+ renderChildren(props._html, newProps.children, newUniqueClassName);
205
200
  } else if (newProps['dangerouslySetInnerHTML']) {
206
201
  props._html.push(newProps['dangerouslySetInnerHTML']);
207
202
  } else {
@@ -213,7 +208,7 @@ export const renderComponent = (type: any, props: any) => {
213
208
  }
214
209
  }
215
210
  } else if (newType.name === 'Fragment') {
216
- renderChildren(props._html, newProps.children);
211
+ renderChildren(props._html, newProps.children, uniqueClassName);
217
212
  } else {
218
213
  logger.warn('Unknown type: ', type, props, newType, newProps);
219
214
  }
@@ -25,9 +25,10 @@
25
25
 
26
26
  export function uniqueIdGenerator(preKey: string) {
27
27
  let count = 0;
28
- let lastKey = Math.round(new Date().getTime() / 1000).toString(36);
28
+ const baseTime = Math.round(Date.now() / 1000);
29
+ let lastKey = '';
29
30
  return function (): string {
30
- const key = Math.round(new Date().getTime() / 1000).toString(36);
31
+ const key = Math.round(Date.now() / 1000 - baseTime).toString(36);
31
32
  if (key !== lastKey) {
32
33
  count = 0;
33
34
  lastKey = key;