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 +1 -1
- package/src/core/bind-ref.ts +8 -2
- package/src/core/bind-styles.ts +73 -26
- package/src/core/render-component.ts +34 -39
- package/src/lib/unique-id.ts +3 -2
package/package.json
CHANGED
package/src/core/bind-ref.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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>) => {
|
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,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
|
-
|
|
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 = (
|
|
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-${
|
|
174
|
+
let cssDom = document.getElementById(`sty-${topUniqueClassName}`);
|
|
132
175
|
if (forceUpdate || !cssDom) {
|
|
133
|
-
updateCssDom(
|
|
176
|
+
updateCssDom(topUniqueClassName, processStyle(noTopClassName ? '' : topUniqueClassName, style).join(''), cssDom);
|
|
134
177
|
}
|
|
135
|
-
} else if (!_globalStyle.has(
|
|
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(
|
|
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(
|
|
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
|
-
//
|
|
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
|
|
219
|
+
result.push(`<style id="sty-${themeCookieName}">${generateThemeStyles()}</style>`);
|
|
173
220
|
|
|
174
|
-
for (let [uniqueStyleId, {
|
|
175
|
-
const cssText = processStyle(
|
|
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.
|
|
68
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
.
|
|
113
|
-
|
|
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
|
-
|
|
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(
|
|
196
|
-
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
|
|
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
|
}
|
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;
|