lupine.web 1.0.31 → 1.1.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/package.json +1 -1
- package/src/core/bind-ref.ts +8 -5
- package/src/core/bind-styles.ts +68 -26
- package/src/core/render-component.ts +34 -36
- package/src/lib/unique-id.ts +3 -2
package/package.json
CHANGED
package/src/core/bind-ref.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { VNode } from '../jsx';
|
|
2
|
-
import { bindAttributes } from './bind-attributes';
|
|
3
|
-
import { bindLinks } from './bind-links';
|
|
4
2
|
import { mountInnerComponent, mountOuterComponent } from './mount-component';
|
|
5
|
-
import { renderComponent } from './render-component';
|
|
6
3
|
import { replaceInnerhtml } from './replace-innerhtml';
|
|
7
4
|
|
|
8
5
|
export const bindRef = (type: any, newProps: any, el: Element) => {
|
|
@@ -29,10 +26,16 @@ export const bindRef = (type: any, newProps: any, el: Element) => {
|
|
|
29
26
|
* @returns Element
|
|
30
27
|
*/
|
|
31
28
|
newProps['ref'].$ = (selector: string) => {
|
|
32
|
-
|
|
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)}`);
|
|
33
33
|
};
|
|
34
34
|
newProps['ref'].$all = (selector: string) => {
|
|
35
|
-
|
|
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)}`);
|
|
36
39
|
};
|
|
37
40
|
|
|
38
41
|
newProps['ref'].mountInnerComponent = async (content: string | VNode<any>) => {
|
package/src/core/bind-styles.ts
CHANGED
|
@@ -36,20 +36,32 @@ const updateOneBlock = (css: string[], cssTemp: string[], className: string, med
|
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
|
|
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 (!
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
79
|
+
|
|
80
|
+
// '.aa': {
|
|
81
|
+
// '&:hover': {...},
|
|
82
|
+
// '.bb': {...},
|
|
83
|
+
// }, ==>
|
|
84
|
+
// .aa:hover, .aa .bb
|
|
85
|
+
const newClassSelector = !classSelector
|
|
69
86
|
? i
|
|
70
|
-
:
|
|
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 =
|
|
104
|
+
const ret = processStyleSub(topUniqueClassName, newClassSelector, value, mediaQuery);
|
|
86
105
|
css.push(...ret);
|
|
87
106
|
}
|
|
88
107
|
}
|
|
89
108
|
}
|
|
90
|
-
updateOneBlock(css, cssTemp,
|
|
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 = (
|
|
96
|
-
const el =
|
|
118
|
+
export const updateStyles = (topUniqueClassName: string, style: CssProps) => {
|
|
119
|
+
const el = topUniqueClassName && document.querySelector(`.${topUniqueClassName}`);
|
|
97
120
|
if (el) {
|
|
98
|
-
const cssText = processStyle(
|
|
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 "${
|
|
132
|
+
console.warn(`Can't find "${topUniqueClassName}" to update styles.`);
|
|
110
133
|
}
|
|
111
134
|
};
|
|
112
135
|
|
|
@@ -120,21 +143,36 @@ 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
|
-
|
|
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 = (
|
|
167
|
+
export const bindGlobalStyles = (topUniqueClassName: string, style: CssProps, forceUpdate = false, isTheme = false) => {
|
|
130
168
|
if (typeof document !== 'undefined') {
|
|
131
|
-
let cssDom = document.getElementById(`sty-${
|
|
169
|
+
let cssDom = document.getElementById(`sty-${topUniqueClassName}`);
|
|
132
170
|
if (forceUpdate || !cssDom) {
|
|
133
|
-
updateCssDom(
|
|
171
|
+
updateCssDom(topUniqueClassName, processStyle(isTheme ? '' : topUniqueClassName, style).join(''), cssDom);
|
|
134
172
|
}
|
|
135
|
-
} else if (!_globalStyle.has(
|
|
173
|
+
} else if (!_globalStyle.has(topUniqueClassName) || forceUpdate) {
|
|
136
174
|
// don't overwrite it to have the same behavior as in the Browser
|
|
137
|
-
_globalStyle.set(
|
|
175
|
+
_globalStyle.set(topUniqueClassName, { topUniqueClassName, style });
|
|
138
176
|
}
|
|
139
177
|
};
|
|
140
178
|
|
|
@@ -143,7 +181,11 @@ const generateThemeStyles = () => {
|
|
|
143
181
|
const themeCss = [];
|
|
144
182
|
for (let themeName in currentTheme.themes) {
|
|
145
183
|
// i is for case-insensitive
|
|
146
|
-
themeCss.push(
|
|
184
|
+
themeCss.push(
|
|
185
|
+
...processStyle('', {
|
|
186
|
+
[`[data-theme="${themeName}" i]`]: currentTheme.themes[themeName],
|
|
187
|
+
})
|
|
188
|
+
);
|
|
147
189
|
}
|
|
148
190
|
return themeCss.join('\n');
|
|
149
191
|
};
|
|
@@ -159,7 +201,7 @@ if (typeof document !== 'undefined') {
|
|
|
159
201
|
});
|
|
160
202
|
}
|
|
161
203
|
|
|
162
|
-
//
|
|
204
|
+
// can't clear global styles,because in index.tsx it is only loaded once, clear it it will be gone
|
|
163
205
|
// const clearGlobalStyles = () => {
|
|
164
206
|
// // reset unique id
|
|
165
207
|
// _globalStyle.clear();
|
|
@@ -169,10 +211,10 @@ if (typeof document !== 'undefined') {
|
|
|
169
211
|
export const generateAllGlobalStyles = () => {
|
|
170
212
|
const result = [];
|
|
171
213
|
|
|
172
|
-
result.push(`<style id="sty
|
|
214
|
+
result.push(`<style id="sty-${themeCookieName}">${generateThemeStyles()}</style>`);
|
|
173
215
|
|
|
174
|
-
for (let [uniqueStyleId, {
|
|
175
|
-
const cssText = processStyle(
|
|
216
|
+
for (let [uniqueStyleId, { topUniqueClassName, style }] of _globalStyle) {
|
|
217
|
+
const cssText = processStyle(topUniqueClassName, style).join('');
|
|
176
218
|
result.push(`<style id="sty-${uniqueStyleId}">${cssText}</style>`);
|
|
177
219
|
}
|
|
178
220
|
|
|
@@ -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,16 +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['data-refid'] && props['data-refid'].id) {
|
|
68
|
-
|
|
69
|
-
}
|
|
67
|
+
// if (props['data-refid'] && props['data-refid'].id) {
|
|
68
|
+
// props['data-refid'] = props['data-refid'].id;
|
|
69
|
+
// }
|
|
70
70
|
for (let i in props) {
|
|
71
71
|
if (i === 'ref') {
|
|
72
72
|
if (props[i]) {
|
|
@@ -103,11 +103,15 @@ function renderAttribute(type: any, props: any, jsxNodes: any) {
|
|
|
103
103
|
html.push(`${i}="${props[i]}"`);
|
|
104
104
|
}
|
|
105
105
|
} else if (i === 'class' || i === 'className') {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
.
|
|
110
|
-
|
|
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(' ')}"`);
|
|
111
115
|
} else if (i !== 'dangerouslySetInnerHTML') {
|
|
112
116
|
html.push(`${i}="${props[i]}"`);
|
|
113
117
|
}
|
|
@@ -120,28 +124,12 @@ function renderAttribute(type: any, props: any, jsxNodes: any) {
|
|
|
120
124
|
return html.join(' ');
|
|
121
125
|
}
|
|
122
126
|
|
|
123
|
-
// assign the same label to all children
|
|
124
|
-
// function assignLabels(label: string, children: any) {
|
|
125
|
-
// if (Array.isArray(children)) {
|
|
126
|
-
// for (let i = 0; i < children.length; i++) {
|
|
127
|
-
// const item = children[i];
|
|
128
|
-
// if (Array.isArray(item) || (item && item.type && item.props)) {
|
|
129
|
-
// assignLabels(label, item);
|
|
130
|
-
// }
|
|
131
|
-
// }
|
|
132
|
-
// } else if (children.type && children.props) {
|
|
133
|
-
// if (typeof children.type === 'string') {
|
|
134
|
-
// children.props._lb = label;
|
|
135
|
-
// }
|
|
136
|
-
// }
|
|
137
|
-
// }
|
|
138
|
-
|
|
139
127
|
// The result has only one element
|
|
140
|
-
export const renderComponent = (type: any, props: any) => {
|
|
128
|
+
export const renderComponent = (type: any, props: any, uniqueClassName?: string) => {
|
|
141
129
|
// logger.log("==================renderComponent", type);
|
|
142
130
|
if (Array.isArray(props)) {
|
|
143
131
|
const jsxNodes = { type: 'Fragment', props: { children: props } } as any;
|
|
144
|
-
renderComponent(jsxNodes.type, jsxNodes.props);
|
|
132
|
+
renderComponent(jsxNodes.type, jsxNodes.props, uniqueClassName);
|
|
145
133
|
return;
|
|
146
134
|
}
|
|
147
135
|
|
|
@@ -159,7 +147,7 @@ export const renderComponent = (type: any, props: any) => {
|
|
|
159
147
|
// }
|
|
160
148
|
// logger.log('==========props._result', props._result);
|
|
161
149
|
if (typeof props._result.type === 'function') {
|
|
162
|
-
renderComponent(props._result.type, props._result.props);
|
|
150
|
+
renderComponent(props._result.type, props._result.props, uniqueClassName);
|
|
163
151
|
if (props._result.props._html) {
|
|
164
152
|
props._html.push(...props._result.props._html);
|
|
165
153
|
props._result.props._html.length = 0;
|
|
@@ -174,7 +162,17 @@ export const renderComponent = (type: any, props: any) => {
|
|
|
174
162
|
console.log('renderComponent', newType, newProps);
|
|
175
163
|
}
|
|
176
164
|
if (typeof newType === 'string') {
|
|
177
|
-
|
|
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);
|
|
178
176
|
if (selfClosingTags.includes(newType.toLowerCase())) {
|
|
179
177
|
// for Fragment, only needs this tag when Ref is assigned
|
|
180
178
|
if (newType !== 'Fragment' || newProps.ref) {
|
|
@@ -189,8 +187,8 @@ export const renderComponent = (type: any, props: any) => {
|
|
|
189
187
|
}
|
|
190
188
|
|
|
191
189
|
if (newProps['css']) {
|
|
192
|
-
const cssText = processStyle(
|
|
193
|
-
props._html.push(`<style id="sty-${
|
|
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
|
|
194
192
|
}
|
|
195
193
|
|
|
196
194
|
if (newProps.children) {
|
|
@@ -198,7 +196,7 @@ export const renderComponent = (type: any, props: any) => {
|
|
|
198
196
|
// assignLabels(newProps._lb, newProps.children);
|
|
199
197
|
// }
|
|
200
198
|
|
|
201
|
-
renderChildren(props._html, newProps.children);
|
|
199
|
+
renderChildren(props._html, newProps.children, newUniqueClassName);
|
|
202
200
|
} else if (newProps['dangerouslySetInnerHTML']) {
|
|
203
201
|
props._html.push(newProps['dangerouslySetInnerHTML']);
|
|
204
202
|
} else {
|
|
@@ -210,7 +208,7 @@ export const renderComponent = (type: any, props: any) => {
|
|
|
210
208
|
}
|
|
211
209
|
}
|
|
212
210
|
} else if (newType.name === 'Fragment') {
|
|
213
|
-
renderChildren(props._html, newProps.children);
|
|
211
|
+
renderChildren(props._html, newProps.children, uniqueClassName);
|
|
214
212
|
} else {
|
|
215
213
|
logger.warn('Unknown type: ', type, props, newType, newProps);
|
|
216
214
|
}
|
package/src/lib/unique-id.ts
CHANGED
|
@@ -25,9 +25,10 @@
|
|
|
25
25
|
|
|
26
26
|
export function uniqueIdGenerator(preKey: string) {
|
|
27
27
|
let count = 0;
|
|
28
|
-
|
|
28
|
+
const baseTime = Math.round(Date.now() / 1000);
|
|
29
|
+
let lastKey = '';
|
|
29
30
|
return function (): string {
|
|
30
|
-
const key = Math.round(
|
|
31
|
+
const key = Math.round(Date.now() / 1000 - baseTime).toString(36);
|
|
31
32
|
if (key !== lastKey) {
|
|
32
33
|
count = 0;
|
|
33
34
|
lastKey = key;
|