-
Notifications
You must be signed in to change notification settings - Fork 787
/
styles.ts
199 lines (182 loc) · 7.55 KB
/
styles.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
import { BUILD } from '@app-data';
import { doc, plt, styles, supportsConstructableStylesheets, supportsShadow } from '@platform';
import { CMP_FLAGS, queryNonceMetaTagContent } from '@utils';
import type * as d from '../declarations';
import { createTime } from './profile';
import { HYDRATED_STYLE_ID, NODE_TYPE, SLOT_FB_CSS } from './runtime-constants';
const rootAppliedStyles: d.RootAppliedStyleMap = /*@__PURE__*/ new WeakMap();
/**
* Register the styles for a component by creating a stylesheet and then
* registering it under the component's scope ID in a `WeakMap` for later use.
*
* If constructable stylesheet are not supported or `allowCS` is set to
* `false` then the styles will be registered as a string instead.
*
* @param scopeId the scope ID for the component of interest
* @param cssText styles for the component of interest
* @param allowCS whether or not to use a constructable stylesheet
*/
export const registerStyle = (scopeId: string, cssText: string, allowCS: boolean) => {
let style = styles.get(scopeId);
if (supportsConstructableStylesheets && allowCS) {
style = (style || new CSSStyleSheet()) as CSSStyleSheet;
if (typeof style === 'string') {
style = cssText;
} else {
style.replaceSync(cssText);
}
} else {
style = cssText;
}
styles.set(scopeId, style);
};
/**
* Attach the styles for a given component to the DOM
*
* If the element uses shadow or is already attached to the DOM then we can
* create a stylesheet inside of its associated document fragment, otherwise
* we'll stick the stylesheet into the document head.
*
* @param styleContainerNode the node within which a style element for the
* component of interest should be added
* @param cmpMeta runtime metadata for the component of interest
* @param mode an optional current mode
* @returns the scope ID for the component of interest
*/
export const addStyle = (styleContainerNode: any, cmpMeta: d.ComponentRuntimeMeta, mode?: string) => {
const scopeId = getScopeId(cmpMeta, mode);
const style = styles.get(scopeId);
if (!BUILD.attachStyles) {
return scopeId;
}
// if an element is NOT connected then getRootNode() will return the wrong root node
// so the fallback is to always use the document for the root node in those cases
styleContainerNode = styleContainerNode.nodeType === NODE_TYPE.DocumentFragment ? styleContainerNode : doc;
if (style) {
if (typeof style === 'string') {
styleContainerNode = styleContainerNode.head || (styleContainerNode as HTMLElement);
let appliedStyles = rootAppliedStyles.get(styleContainerNode);
let styleElm;
if (!appliedStyles) {
rootAppliedStyles.set(styleContainerNode, (appliedStyles = new Set()));
}
if (!appliedStyles.has(scopeId)) {
if (
BUILD.hydrateClientSide &&
styleContainerNode.host &&
(styleElm = styleContainerNode.querySelector(`[${HYDRATED_STYLE_ID}="${scopeId}"]`))
) {
// This is only happening on native shadow-dom, do not needs CSS var shim
styleElm.innerHTML = style;
} else {
styleElm = doc.createElement('style');
styleElm.innerHTML = style;
// Apply CSP nonce to the style tag if it exists
const nonce = plt.$nonce$ ?? queryNonceMetaTagContent(doc);
if (nonce != null) {
styleElm.setAttribute('nonce', nonce);
}
if (
(BUILD.hydrateServerSide || BUILD.hotModuleReplacement) &&
cmpMeta.$flags$ & CMP_FLAGS.scopedCssEncapsulation
) {
styleElm.setAttribute(HYDRATED_STYLE_ID, scopeId);
}
/**
* attach styles at the end of the head tag if we render scoped components
*/
if (!(cmpMeta.$flags$ & CMP_FLAGS.shadowDomEncapsulation)) {
if (styleContainerNode.nodeName === 'HEAD') {
/**
* if the page contains preconnect links, we want to insert the styles
* after the last preconnect link to ensure the styles are preloaded
*/
const preconnectLinks = styleContainerNode.querySelectorAll('link[rel=preconnect]');
const referenceNode =
preconnectLinks.length > 0
? preconnectLinks[preconnectLinks.length - 1].nextSibling
: document.querySelector('style');
(styleContainerNode as HTMLElement).insertBefore(styleElm, referenceNode);
} else if ('host' in styleContainerNode) {
/**
* if a scoped component is used within a shadow root, we want to insert the styles
* at the beginning of the shadow root node
*/
(styleContainerNode as HTMLElement).prepend(styleElm);
} else {
styleContainerNode.append(styleElm);
}
}
/**
* attach styles at the beginning of a shadow root node if we render shadow components
*/
if (cmpMeta.$flags$ & CMP_FLAGS.shadowDomEncapsulation && styleContainerNode.nodeName !== 'HEAD') {
styleContainerNode.insertBefore(styleElm, null);
}
}
// Add styles for `slot-fb` elements if we're using slots outside the Shadow DOM
if (cmpMeta.$flags$ & CMP_FLAGS.hasSlotRelocation) {
styleElm.innerHTML += SLOT_FB_CSS;
}
if (appliedStyles) {
appliedStyles.add(scopeId);
}
}
} else if (BUILD.constructableCSS && !styleContainerNode.adoptedStyleSheets.includes(style)) {
styleContainerNode.adoptedStyleSheets = [...styleContainerNode.adoptedStyleSheets, style];
}
}
return scopeId;
};
/**
* Add styles for a given component to the DOM, optionally handling 'scoped'
* encapsulation by adding an appropriate class name to the host element.
*
* @param hostRef the host reference for the component of interest
*/
export const attachStyles = (hostRef: d.HostRef) => {
const cmpMeta = hostRef.$cmpMeta$;
const elm = hostRef.$hostElement$;
const flags = cmpMeta.$flags$;
const endAttachStyles = createTime('attachStyles', cmpMeta.$tagName$);
const scopeId = addStyle(
BUILD.shadowDom && supportsShadow && elm.shadowRoot ? elm.shadowRoot : (elm.getRootNode() as ShadowRoot),
cmpMeta,
hostRef.$modeName$,
);
if (
(BUILD.shadowDom || BUILD.scoped) &&
BUILD.cssAnnotations &&
flags & CMP_FLAGS.needsScopedEncapsulation &&
flags & CMP_FLAGS.scopedCssEncapsulation
) {
// only required when we're NOT using native shadow dom (slot)
// or this browser doesn't support native shadow dom
// and this host element was NOT created with SSR
// let's pick out the inner content for slot projection
// create a node to represent where the original
// content was first placed, which is useful later on
// DOM WRITE!!
elm['s-sc'] = scopeId;
elm.classList.add(scopeId + '-h');
if (BUILD.scoped && flags & CMP_FLAGS.scopedCssEncapsulation) {
elm.classList.add(scopeId + '-s');
}
}
endAttachStyles();
};
/**
* Get the scope ID for a given component
*
* @param cmp runtime metadata for the component of interest
* @param mode the current mode (optional)
* @returns a scope ID for the component of interest
*/
export const getScopeId = (cmp: d.ComponentRuntimeMeta, mode?: string) =>
'sc-' + (BUILD.mode && mode && cmp.$flags$ & CMP_FLAGS.hasMode ? cmp.$tagName$ + '-' + mode : cmp.$tagName$);
declare global {
export interface CSSStyleSheet {
replaceSync(cssText: string): void;
replace(cssText: string): Promise<CSSStyleSheet>;
}
}