lupine.web 1.1.4 → 1.1.5
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/README.md +66 -3
- package/jsx-runtime/index.js +14 -14
- package/jsx-runtime/package.json +16 -16
- package/jsx-runtime/src/index.d.ts +2 -2
- package/package.json +53 -52
- package/src/core/bind-attributes.ts +61 -61
- package/src/core/bind-lang.ts +52 -52
- package/src/core/bind-links.ts +26 -16
- package/src/core/bind-meta.tsx +52 -52
- package/src/core/bind-ref.ts +51 -51
- package/src/core/bind-styles.ts +239 -239
- package/src/core/bind-theme.ts +53 -53
- package/src/core/camel-to-hyphens.ts +3 -3
- package/src/core/export-lupine.ts +80 -80
- package/src/core/index.ts +17 -17
- package/src/core/initialize.ts +116 -116
- package/src/core/mount-component.ts +72 -68
- package/src/core/page-loaded-events.ts +16 -16
- package/src/core/page-router.ts +180 -180
- package/src/core/render-component.ts +230 -233
- package/src/core/replace-innerhtml.ts +23 -23
- package/src/core/server-cookie.ts +24 -24
- package/src/global.d.ts +66 -66
- package/src/index.ts +14 -14
- package/src/jsx.ts +1044 -1043
- package/src/lib/cookie.ts +44 -44
- package/src/lib/debug-watch.ts +32 -32
- package/src/lib/index.ts +7 -7
- package/src/lib/is-frontend.ts +3 -3
- package/src/lib/logger.ts +55 -55
- package/src/lib/unique-id.ts +40 -40
- package/src/lib/web-config.ts +79 -79
- package/src/lib/web-env.ts +99 -99
- package/src/models/index.ts +4 -4
- package/src/models/json-props.ts +8 -8
- package/src/models/simple-storage-props.ts +9 -9
- package/src/models/theme-props.ts +7 -7
- package/src/models/to-client-delivery-props.ts +8 -8
- package/src/styles/css-styles.ts +814 -814
- package/src/styles/index.ts +4 -4
- package/tsconfig.json +113 -113
package/src/core/bind-ref.ts
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
import { VNode } from '../jsx';
|
|
2
|
-
import { mountInnerComponent, mountOuterComponent } from './mount-component';
|
|
3
|
-
import { replaceInnerhtml } from './replace-innerhtml';
|
|
4
|
-
|
|
5
|
-
export const bindRef = (type: any, newProps: any, el: Element) => {
|
|
6
|
-
// console.log('========', newProps, el);
|
|
7
|
-
const id = newProps._id;
|
|
8
|
-
// newProps["ref"].id = id; // this is set at bindAttributes
|
|
9
|
-
newProps['ref'].current = el;
|
|
10
|
-
if (newProps['ref'].onLoad) {
|
|
11
|
-
// setTimeout(() => newProps['ref'].onLoad(el), 0);
|
|
12
|
-
// Promise.resolve().then(fn) will be called in the end of current event loop (quicker than setTimeout)
|
|
13
|
-
const defer = Promise.prototype.then.bind(Promise.resolve());
|
|
14
|
-
defer(() => newProps['ref'].onLoad(el));
|
|
15
|
-
}
|
|
16
|
-
if (newProps['ref'].onUnload) {
|
|
17
|
-
(el as any)._lj = (el as any)._lj || {};
|
|
18
|
-
(el as any)._lj.onUnload = async () => {
|
|
19
|
-
await newProps['ref'].onUnload(el);
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* ref.$('selector')
|
|
25
|
-
* @param selector
|
|
26
|
-
* @returns Element
|
|
27
|
-
*/
|
|
28
|
-
newProps['ref'].$ = (selector: string) => {
|
|
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
|
-
};
|
|
34
|
-
newProps['ref'].$all = (selector: string) => {
|
|
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)}`);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
newProps['ref'].mountInnerComponent = async (content: string | VNode<any>) => {
|
|
42
|
-
if (typeof content === 'object' && content.type && content.props) {
|
|
43
|
-
await mountInnerComponent(el, content);
|
|
44
|
-
} else {
|
|
45
|
-
await replaceInnerhtml(el, content as string);
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
newProps['ref'].mountOuterComponent = async (content: VNode<any>) => {
|
|
49
|
-
await mountOuterComponent(el, content);
|
|
50
|
-
};
|
|
51
|
-
};
|
|
1
|
+
import { VNode } from '../jsx';
|
|
2
|
+
import { mountInnerComponent, mountOuterComponent } from './mount-component';
|
|
3
|
+
import { replaceInnerhtml } from './replace-innerhtml';
|
|
4
|
+
|
|
5
|
+
export const bindRef = (type: any, newProps: any, el: Element) => {
|
|
6
|
+
// console.log('========', newProps, el);
|
|
7
|
+
const id = newProps._id;
|
|
8
|
+
// newProps["ref"].id = id; // this is set at bindAttributes
|
|
9
|
+
newProps['ref'].current = el;
|
|
10
|
+
if (newProps['ref'].onLoad) {
|
|
11
|
+
// setTimeout(() => newProps['ref'].onLoad(el), 0);
|
|
12
|
+
// Promise.resolve().then(fn) will be called in the end of current event loop (quicker than setTimeout)
|
|
13
|
+
const defer = Promise.prototype.then.bind(Promise.resolve());
|
|
14
|
+
defer(() => newProps['ref'].onLoad(el));
|
|
15
|
+
}
|
|
16
|
+
if (newProps['ref'].onUnload) {
|
|
17
|
+
(el as any)._lj = (el as any)._lj || {};
|
|
18
|
+
(el as any)._lj.onUnload = async () => {
|
|
19
|
+
await newProps['ref'].onUnload(el);
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* ref.$('selector')
|
|
25
|
+
* @param selector
|
|
26
|
+
* @returns Element
|
|
27
|
+
*/
|
|
28
|
+
newProps['ref'].$ = (selector: string) => {
|
|
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
|
+
};
|
|
34
|
+
newProps['ref'].$all = (selector: string) => {
|
|
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)}`);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
newProps['ref'].mountInnerComponent = async (content: string | VNode<any>) => {
|
|
42
|
+
if (typeof content === 'object' && content.type && content.props) {
|
|
43
|
+
await mountInnerComponent(el, content);
|
|
44
|
+
} else {
|
|
45
|
+
await replaceInnerhtml(el, content as string);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
newProps['ref'].mountOuterComponent = async (content: VNode<any>) => {
|
|
49
|
+
await mountOuterComponent(el, content);
|
|
50
|
+
};
|
|
51
|
+
};
|
package/src/core/bind-styles.ts
CHANGED
|
@@ -1,239 +1,239 @@
|
|
|
1
|
-
import { uniqueIdGenerator } from 'lupine.components';
|
|
2
|
-
import { CssProps } from '../jsx';
|
|
3
|
-
import { getCurrentTheme, themeCookieName } from './bind-theme';
|
|
4
|
-
import { camelToHyphens } from './camel-to-hyphens';
|
|
5
|
-
// import { bindPageResetEvent } from './page-reset-events';
|
|
6
|
-
import { bindPageLoadedEvent } from './page-loaded-events';
|
|
7
|
-
|
|
8
|
-
const wrapCss = (className: string, cssText: string, mediaQuery?: string) => {
|
|
9
|
-
// if (!className) {
|
|
10
|
-
// console.warn(`No class name is provided for ${cssText}`);
|
|
11
|
-
// }
|
|
12
|
-
let cssTextWrap = className ? `${className}{${cssText}}` : cssText;
|
|
13
|
-
if (mediaQuery) {
|
|
14
|
-
cssTextWrap = `${mediaQuery}{${cssTextWrap}}`;
|
|
15
|
-
}
|
|
16
|
-
return cssTextWrap;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const processStyleValue = (style: CssProps) => {
|
|
20
|
-
return Object.keys(style)
|
|
21
|
-
.map((key) => key.trim())
|
|
22
|
-
.map((key) => {
|
|
23
|
-
const noOutput =
|
|
24
|
-
(style[key] != null && typeof style[key] === 'object') ||
|
|
25
|
-
typeof style[key] === 'undefined' ||
|
|
26
|
-
style[key] === '';
|
|
27
|
-
return noOutput ? '' : `${camelToHyphens(key)}:${style[key]};`;
|
|
28
|
-
})
|
|
29
|
-
.join('');
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const updateOneBlock = (css: string[], cssTemp: string[], className: string, mediaQuery?: string) => {
|
|
33
|
-
if (cssTemp.length > 0) {
|
|
34
|
-
const cssText = wrapCss(className, cssTemp.join(''), mediaQuery);
|
|
35
|
-
css.push(cssText);
|
|
36
|
-
cssTemp.length = 0;
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const processStyleSub = (
|
|
41
|
-
topUniqueClassName: string,
|
|
42
|
-
classSelector: string,
|
|
43
|
-
style: CssProps,
|
|
44
|
-
mediaQuery?: string
|
|
45
|
-
): string[] => {
|
|
46
|
-
const outClassName = classSelector
|
|
47
|
-
.split(',')
|
|
48
|
-
.map((key0) => key0.trim())
|
|
49
|
-
.map((key0) => {
|
|
50
|
-
return (key0.startsWith('&') ? `${classSelector}${key0.substring(1)}` : key0).replace(/&/g, topUniqueClassName);
|
|
51
|
-
})
|
|
52
|
-
.join(',');
|
|
53
|
-
const css: string[] = [];
|
|
54
|
-
const cssTemp: string[] = [];
|
|
55
|
-
for (let i in style) {
|
|
56
|
-
const value = style[i];
|
|
57
|
-
if (value === null || typeof value !== 'object') {
|
|
58
|
-
if (value !== '' && typeof value !== 'undefined') {
|
|
59
|
-
if (!classSelector) {
|
|
60
|
-
console.warn(`No className is defined for: ${camelToHyphens(i)}:${value};`);
|
|
61
|
-
}
|
|
62
|
-
cssTemp.push(`${camelToHyphens(i)}:${value};`);
|
|
63
|
-
}
|
|
64
|
-
} else {
|
|
65
|
-
updateOneBlock(css, cssTemp, outClassName, mediaQuery);
|
|
66
|
-
|
|
67
|
-
if (i.startsWith('@keyframes')) {
|
|
68
|
-
const cssText = Object.keys(value)
|
|
69
|
-
.map((stageKey) => stageKey + '{' + processStyleValue(value[stageKey] as CssProps) + '}')
|
|
70
|
-
.join('');
|
|
71
|
-
css.push(`${i}{${cssText}}`);
|
|
72
|
-
} else if (i.startsWith('@media')) {
|
|
73
|
-
const ret = processStyleSub(topUniqueClassName, classSelector, value, i);
|
|
74
|
-
css.push(...ret);
|
|
75
|
-
} else {
|
|
76
|
-
// '&:hover, &.open': {
|
|
77
|
-
// '>.d1, .d2': {...},
|
|
78
|
-
// }, ==>
|
|
79
|
-
// &:hover >.d1, &:hover >.d2, &.open >.d1, &.open .d2
|
|
80
|
-
|
|
81
|
-
// '.aa': {
|
|
82
|
-
// '&:hover': {...},
|
|
83
|
-
// '.bb': {...},
|
|
84
|
-
// }, ==>
|
|
85
|
-
// .aa:hover, .aa .bb
|
|
86
|
-
const newClassSelector = !classSelector
|
|
87
|
-
? i
|
|
88
|
-
: classSelector
|
|
89
|
-
.split(',')
|
|
90
|
-
.map((key0) => key0.trim())
|
|
91
|
-
.map((key0) => {
|
|
92
|
-
return i
|
|
93
|
-
.split(',')
|
|
94
|
-
.map((key) => key.trim())
|
|
95
|
-
.map((key) => {
|
|
96
|
-
// not needed to "+" as them share same parents?
|
|
97
|
-
// return key.split('+').map(key2 => key2.startsWith('&') ? key0 + key2.substring(1) : key0 + ' ' + key2).join('+');
|
|
98
|
-
// return key.startsWith('&') ? key0 + key.substring(1) : key0 + ' ' + key;
|
|
99
|
-
const newKey = key.startsWith('&') ? key0 + key.substring(1) : key0 + ' ' + key;
|
|
100
|
-
return newKey.replace(/&/g, topUniqueClassName);
|
|
101
|
-
})
|
|
102
|
-
.join(',');
|
|
103
|
-
})
|
|
104
|
-
.join(',');
|
|
105
|
-
const ret = processStyleSub(topUniqueClassName, newClassSelector, value, mediaQuery);
|
|
106
|
-
css.push(...ret);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
updateOneBlock(css, cssTemp, outClassName, mediaQuery);
|
|
111
|
-
return css;
|
|
112
|
-
};
|
|
113
|
-
// topUniqueClassName is used to replace '&' in className, and '.' + topUniqueClassName is used as selector in styles ".xxx {}"
|
|
114
|
-
export const processStyle = (topUniqueClassName: string, style: CssProps): string[] => {
|
|
115
|
-
return processStyleSub(topUniqueClassName, topUniqueClassName ? `.${topUniqueClassName}` : '', style);
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// mount-components has the same name `sty-`
|
|
119
|
-
export const updateStyles = (topUniqueClassName: string, style: CssProps) => {
|
|
120
|
-
const el = topUniqueClassName && document.querySelector(`.${topUniqueClassName}`);
|
|
121
|
-
if (el) {
|
|
122
|
-
const cssText = processStyle(topUniqueClassName, style).join('');
|
|
123
|
-
// if the first child is style, then update it
|
|
124
|
-
if (el.firstChild && el.firstChild.nodeName === 'STYLE') {
|
|
125
|
-
(el.firstChild as any).innerHTML = cssText;
|
|
126
|
-
} else {
|
|
127
|
-
const style = document.createElement('style');
|
|
128
|
-
style.innerHTML = cssText;
|
|
129
|
-
// style.id = `sty-${selector}`; // sty means style, this is different from mount-components.renderComponent
|
|
130
|
-
el.prepend(style);
|
|
131
|
-
}
|
|
132
|
-
} else {
|
|
133
|
-
console.warn(`Can't find "${topUniqueClassName}" to update styles.`);
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const updateCssDom = (uniqueStyleId: string, cssText: string, cssDom: HTMLElement | null) => {
|
|
138
|
-
if (!cssDom) {
|
|
139
|
-
cssDom = document.createElement('style');
|
|
140
|
-
cssDom.id = `sty-${uniqueStyleId}`;
|
|
141
|
-
document.head.appendChild(cssDom);
|
|
142
|
-
}
|
|
143
|
-
cssDom.innerText = cssText;
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
/*
|
|
147
|
-
If selectors in GlobalStyles have '&' at top level, then classSelector is needed, otherwise classSelector can be ''
|
|
148
|
-
This is ok:
|
|
149
|
-
{
|
|
150
|
-
'.aa':{
|
|
151
|
-
'&:hover': {...}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
This needs classSelector:
|
|
155
|
-
{
|
|
156
|
-
'color': 'red', // will need and be put under .topUniqueClassName
|
|
157
|
-
'&:hover': {...} // & will be replaced by .topUniqueClassName
|
|
158
|
-
}
|
|
159
|
-
Global styles including theme will not be updated once it's created.
|
|
160
|
-
topUniqueClassName is a className or a tag name.
|
|
161
|
-
classSelector is a selector used in styles ".xxx {}"
|
|
162
|
-
For example, it can be like this for all elements:
|
|
163
|
-
html { ... } or :root { ... }
|
|
164
|
-
|
|
165
|
-
For themes like [data-theme="dark" i], the topUniqueClassName should be empty
|
|
166
|
-
*/
|
|
167
|
-
export const globalStyleUniqueId = uniqueIdGenerator('g'); // g means global style
|
|
168
|
-
const _globalStyleIds = new Map<CssProps, string>();
|
|
169
|
-
export const getGlobalStylesId = (style: CssProps): string => {
|
|
170
|
-
if (!_globalStyleIds.has(style)) {
|
|
171
|
-
const id = globalStyleUniqueId();
|
|
172
|
-
_globalStyleIds.set(style, id);
|
|
173
|
-
}
|
|
174
|
-
return _globalStyleIds.get(style)!;
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
const _globalStyle = new Map();
|
|
178
|
-
export const bindGlobalStyle = (
|
|
179
|
-
topUniqueClassName: string,
|
|
180
|
-
style: CssProps,
|
|
181
|
-
forceUpdate = false,
|
|
182
|
-
noTopClassName = false
|
|
183
|
-
) => {
|
|
184
|
-
if (typeof document !== 'undefined') {
|
|
185
|
-
let cssDom = document.getElementById(`sty-${topUniqueClassName}`);
|
|
186
|
-
if (forceUpdate || !cssDom) {
|
|
187
|
-
updateCssDom(topUniqueClassName, processStyle(noTopClassName ? '' : topUniqueClassName, style).join(''), cssDom);
|
|
188
|
-
}
|
|
189
|
-
} else if (!_globalStyle.has(topUniqueClassName) || forceUpdate) {
|
|
190
|
-
// don't overwrite it to have the same behavior as in the Browser
|
|
191
|
-
_globalStyle.set(topUniqueClassName, { topUniqueClassName, noTopClassName, style });
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
const generateThemeStyles = () => {
|
|
196
|
-
const currentTheme = getCurrentTheme();
|
|
197
|
-
const themeCss = [];
|
|
198
|
-
for (let themeName in currentTheme.themes) {
|
|
199
|
-
// i is for case-insensitive
|
|
200
|
-
themeCss.push(
|
|
201
|
-
...processStyle('', {
|
|
202
|
-
[`[data-theme="${themeName}" i]`]: currentTheme.themes[themeName],
|
|
203
|
-
})
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
return themeCss.join('\n');
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
if (typeof document !== 'undefined') {
|
|
210
|
-
// Update theme in Browser when no SSR
|
|
211
|
-
bindPageLoadedEvent(() => {
|
|
212
|
-
const uniqueStyleId = themeCookieName;
|
|
213
|
-
let cssDom = document.getElementById(`sty-${uniqueStyleId}`);
|
|
214
|
-
if (!cssDom) {
|
|
215
|
-
updateCssDom(uniqueStyleId, generateThemeStyles(), cssDom);
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// can't clear global styles,because in index.tsx it is only loaded once, clear it it will be gone
|
|
221
|
-
// const clearGlobalStyles = () => {
|
|
222
|
-
// // reset unique id
|
|
223
|
-
// _globalStyle.clear();
|
|
224
|
-
// };
|
|
225
|
-
// bindPageResetEvent(clearGlobalStyles);
|
|
226
|
-
|
|
227
|
-
export const generateAllGlobalStyles = () => {
|
|
228
|
-
const result = [];
|
|
229
|
-
|
|
230
|
-
result.push(`<style id="sty-${themeCookieName}">${generateThemeStyles()}</style>`);
|
|
231
|
-
|
|
232
|
-
for (let [uniqueStyleId, { topUniqueClassName, noTopClassName, style }] of _globalStyle) {
|
|
233
|
-
const cssText = processStyle(noTopClassName ? '' : topUniqueClassName, style).join('');
|
|
234
|
-
result.push(`<style id="sty-${uniqueStyleId}">${cssText}</style>`);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// clearGlobalStyles();
|
|
238
|
-
return result.join('');
|
|
239
|
-
};
|
|
1
|
+
import { uniqueIdGenerator } from 'lupine.components';
|
|
2
|
+
import { CssProps } from '../jsx';
|
|
3
|
+
import { getCurrentTheme, themeCookieName } from './bind-theme';
|
|
4
|
+
import { camelToHyphens } from './camel-to-hyphens';
|
|
5
|
+
// import { bindPageResetEvent } from './page-reset-events';
|
|
6
|
+
import { bindPageLoadedEvent } from './page-loaded-events';
|
|
7
|
+
|
|
8
|
+
const wrapCss = (className: string, cssText: string, mediaQuery?: string) => {
|
|
9
|
+
// if (!className) {
|
|
10
|
+
// console.warn(`No class name is provided for ${cssText}`);
|
|
11
|
+
// }
|
|
12
|
+
let cssTextWrap = className ? `${className}{${cssText}}` : cssText;
|
|
13
|
+
if (mediaQuery) {
|
|
14
|
+
cssTextWrap = `${mediaQuery}{${cssTextWrap}}`;
|
|
15
|
+
}
|
|
16
|
+
return cssTextWrap;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const processStyleValue = (style: CssProps) => {
|
|
20
|
+
return Object.keys(style)
|
|
21
|
+
.map((key) => key.trim())
|
|
22
|
+
.map((key) => {
|
|
23
|
+
const noOutput =
|
|
24
|
+
(style[key] != null && typeof style[key] === 'object') ||
|
|
25
|
+
typeof style[key] === 'undefined' ||
|
|
26
|
+
style[key] === '';
|
|
27
|
+
return noOutput ? '' : `${camelToHyphens(key)}:${style[key]};`;
|
|
28
|
+
})
|
|
29
|
+
.join('');
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const updateOneBlock = (css: string[], cssTemp: string[], className: string, mediaQuery?: string) => {
|
|
33
|
+
if (cssTemp.length > 0) {
|
|
34
|
+
const cssText = wrapCss(className, cssTemp.join(''), mediaQuery);
|
|
35
|
+
css.push(cssText);
|
|
36
|
+
cssTemp.length = 0;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const processStyleSub = (
|
|
41
|
+
topUniqueClassName: string,
|
|
42
|
+
classSelector: string,
|
|
43
|
+
style: CssProps,
|
|
44
|
+
mediaQuery?: string
|
|
45
|
+
): string[] => {
|
|
46
|
+
const outClassName = classSelector
|
|
47
|
+
.split(',')
|
|
48
|
+
.map((key0) => key0.trim())
|
|
49
|
+
.map((key0) => {
|
|
50
|
+
return (key0.startsWith('&') ? `${classSelector}${key0.substring(1)}` : key0).replace(/&/g, topUniqueClassName);
|
|
51
|
+
})
|
|
52
|
+
.join(',');
|
|
53
|
+
const css: string[] = [];
|
|
54
|
+
const cssTemp: string[] = [];
|
|
55
|
+
for (let i in style) {
|
|
56
|
+
const value = style[i];
|
|
57
|
+
if (value === null || typeof value !== 'object') {
|
|
58
|
+
if (value !== '' && typeof value !== 'undefined') {
|
|
59
|
+
if (!classSelector) {
|
|
60
|
+
console.warn(`No className is defined for: ${camelToHyphens(i)}:${value};`);
|
|
61
|
+
}
|
|
62
|
+
cssTemp.push(`${camelToHyphens(i)}:${value};`);
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
updateOneBlock(css, cssTemp, outClassName, mediaQuery);
|
|
66
|
+
|
|
67
|
+
if (i.startsWith('@keyframes')) {
|
|
68
|
+
const cssText = Object.keys(value)
|
|
69
|
+
.map((stageKey) => stageKey + '{' + processStyleValue(value[stageKey] as CssProps) + '}')
|
|
70
|
+
.join('');
|
|
71
|
+
css.push(`${i}{${cssText}}`);
|
|
72
|
+
} else if (i.startsWith('@media')) {
|
|
73
|
+
const ret = processStyleSub(topUniqueClassName, classSelector, value, i);
|
|
74
|
+
css.push(...ret);
|
|
75
|
+
} else {
|
|
76
|
+
// '&:hover, &.open': {
|
|
77
|
+
// '>.d1, .d2': {...},
|
|
78
|
+
// }, ==>
|
|
79
|
+
// &:hover >.d1, &:hover >.d2, &.open >.d1, &.open .d2
|
|
80
|
+
|
|
81
|
+
// '.aa': {
|
|
82
|
+
// '&:hover': {...},
|
|
83
|
+
// '.bb': {...},
|
|
84
|
+
// }, ==>
|
|
85
|
+
// .aa:hover, .aa .bb
|
|
86
|
+
const newClassSelector = !classSelector
|
|
87
|
+
? i
|
|
88
|
+
: classSelector
|
|
89
|
+
.split(',')
|
|
90
|
+
.map((key0) => key0.trim())
|
|
91
|
+
.map((key0) => {
|
|
92
|
+
return i
|
|
93
|
+
.split(',')
|
|
94
|
+
.map((key) => key.trim())
|
|
95
|
+
.map((key) => {
|
|
96
|
+
// not needed to "+" as them share same parents?
|
|
97
|
+
// return key.split('+').map(key2 => key2.startsWith('&') ? key0 + key2.substring(1) : key0 + ' ' + key2).join('+');
|
|
98
|
+
// return key.startsWith('&') ? key0 + key.substring(1) : key0 + ' ' + key;
|
|
99
|
+
const newKey = key.startsWith('&') ? key0 + key.substring(1) : key0 + ' ' + key;
|
|
100
|
+
return newKey.replace(/&/g, topUniqueClassName);
|
|
101
|
+
})
|
|
102
|
+
.join(',');
|
|
103
|
+
})
|
|
104
|
+
.join(',');
|
|
105
|
+
const ret = processStyleSub(topUniqueClassName, newClassSelector, value, mediaQuery);
|
|
106
|
+
css.push(...ret);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
updateOneBlock(css, cssTemp, outClassName, mediaQuery);
|
|
111
|
+
return css;
|
|
112
|
+
};
|
|
113
|
+
// topUniqueClassName is used to replace '&' in className, and '.' + topUniqueClassName is used as selector in styles ".xxx {}"
|
|
114
|
+
export const processStyle = (topUniqueClassName: string, style: CssProps): string[] => {
|
|
115
|
+
return processStyleSub(topUniqueClassName, topUniqueClassName ? `.${topUniqueClassName}` : '', style);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// mount-components has the same name `sty-`
|
|
119
|
+
export const updateStyles = (topUniqueClassName: string, style: CssProps) => {
|
|
120
|
+
const el = topUniqueClassName && document.querySelector(`.${topUniqueClassName}`);
|
|
121
|
+
if (el) {
|
|
122
|
+
const cssText = processStyle(topUniqueClassName, style).join('');
|
|
123
|
+
// if the first child is style, then update it
|
|
124
|
+
if (el.firstChild && el.firstChild.nodeName === 'STYLE') {
|
|
125
|
+
(el.firstChild as any).innerHTML = cssText;
|
|
126
|
+
} else {
|
|
127
|
+
const style = document.createElement('style');
|
|
128
|
+
style.innerHTML = cssText;
|
|
129
|
+
// style.id = `sty-${selector}`; // sty means style, this is different from mount-components.renderComponent
|
|
130
|
+
el.prepend(style);
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
console.warn(`Can't find "${topUniqueClassName}" to update styles.`);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const updateCssDom = (uniqueStyleId: string, cssText: string, cssDom: HTMLElement | null) => {
|
|
138
|
+
if (!cssDom) {
|
|
139
|
+
cssDom = document.createElement('style');
|
|
140
|
+
cssDom.id = `sty-${uniqueStyleId}`;
|
|
141
|
+
document.head.appendChild(cssDom);
|
|
142
|
+
}
|
|
143
|
+
cssDom.innerText = cssText;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/*
|
|
147
|
+
If selectors in GlobalStyles have '&' at top level, then classSelector is needed, otherwise classSelector can be ''
|
|
148
|
+
This is ok:
|
|
149
|
+
{
|
|
150
|
+
'.aa':{
|
|
151
|
+
'&:hover': {...}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
This needs classSelector:
|
|
155
|
+
{
|
|
156
|
+
'color': 'red', // will need and be put under .topUniqueClassName
|
|
157
|
+
'&:hover': {...} // & will be replaced by .topUniqueClassName
|
|
158
|
+
}
|
|
159
|
+
Global styles including theme will not be updated once it's created.
|
|
160
|
+
topUniqueClassName is a className or a tag name.
|
|
161
|
+
classSelector is a selector used in styles ".xxx {}"
|
|
162
|
+
For example, it can be like this for all elements:
|
|
163
|
+
html { ... } or :root { ... }
|
|
164
|
+
|
|
165
|
+
For themes like [data-theme="dark" i], the topUniqueClassName should be empty
|
|
166
|
+
*/
|
|
167
|
+
export const globalStyleUniqueId = uniqueIdGenerator('g'); // g means global style
|
|
168
|
+
const _globalStyleIds = new Map<CssProps, string>();
|
|
169
|
+
export const getGlobalStylesId = (style: CssProps): string => {
|
|
170
|
+
if (!_globalStyleIds.has(style)) {
|
|
171
|
+
const id = globalStyleUniqueId();
|
|
172
|
+
_globalStyleIds.set(style, id);
|
|
173
|
+
}
|
|
174
|
+
return _globalStyleIds.get(style)!;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const _globalStyle = new Map();
|
|
178
|
+
export const bindGlobalStyle = (
|
|
179
|
+
topUniqueClassName: string,
|
|
180
|
+
style: CssProps,
|
|
181
|
+
forceUpdate = false,
|
|
182
|
+
noTopClassName = false
|
|
183
|
+
) => {
|
|
184
|
+
if (typeof document !== 'undefined') {
|
|
185
|
+
let cssDom = document.getElementById(`sty-${topUniqueClassName}`);
|
|
186
|
+
if (forceUpdate || !cssDom) {
|
|
187
|
+
updateCssDom(topUniqueClassName, processStyle(noTopClassName ? '' : topUniqueClassName, style).join(''), cssDom);
|
|
188
|
+
}
|
|
189
|
+
} else if (!_globalStyle.has(topUniqueClassName) || forceUpdate) {
|
|
190
|
+
// don't overwrite it to have the same behavior as in the Browser
|
|
191
|
+
_globalStyle.set(topUniqueClassName, { topUniqueClassName, noTopClassName, style });
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const generateThemeStyles = () => {
|
|
196
|
+
const currentTheme = getCurrentTheme();
|
|
197
|
+
const themeCss = [];
|
|
198
|
+
for (let themeName in currentTheme.themes) {
|
|
199
|
+
// i is for case-insensitive
|
|
200
|
+
themeCss.push(
|
|
201
|
+
...processStyle('', {
|
|
202
|
+
[`[data-theme="${themeName}" i]`]: currentTheme.themes[themeName],
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
return themeCss.join('\n');
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
if (typeof document !== 'undefined') {
|
|
210
|
+
// Update theme in Browser when no SSR
|
|
211
|
+
bindPageLoadedEvent(() => {
|
|
212
|
+
const uniqueStyleId = themeCookieName;
|
|
213
|
+
let cssDom = document.getElementById(`sty-${uniqueStyleId}`);
|
|
214
|
+
if (!cssDom) {
|
|
215
|
+
updateCssDom(uniqueStyleId, generateThemeStyles(), cssDom);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// can't clear global styles,because in index.tsx it is only loaded once, clear it it will be gone
|
|
221
|
+
// const clearGlobalStyles = () => {
|
|
222
|
+
// // reset unique id
|
|
223
|
+
// _globalStyle.clear();
|
|
224
|
+
// };
|
|
225
|
+
// bindPageResetEvent(clearGlobalStyles);
|
|
226
|
+
|
|
227
|
+
export const generateAllGlobalStyles = () => {
|
|
228
|
+
const result = [];
|
|
229
|
+
|
|
230
|
+
result.push(`<style id="sty-${themeCookieName}">${generateThemeStyles()}</style>`);
|
|
231
|
+
|
|
232
|
+
for (let [uniqueStyleId, { topUniqueClassName, noTopClassName, style }] of _globalStyle) {
|
|
233
|
+
const cssText = processStyle(noTopClassName ? '' : topUniqueClassName, style).join('');
|
|
234
|
+
result.push(`<style id="sty-${uniqueStyleId}">${cssText}</style>`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// clearGlobalStyles();
|
|
238
|
+
return result.join('');
|
|
239
|
+
};
|