@rws-framework/client 2.23.7 → 2.24.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/components/_component.ts +4 -2
- package/src/components/_css_injection.ts +148 -63
- package/src/index.ts +3 -2
package/package.json
CHANGED
|
@@ -58,8 +58,9 @@ abstract class RWSViewComponent extends FoundationElement implements IRWSViewCom
|
|
|
58
58
|
static _externalAttrs: { [key: string]: string[] } = {};
|
|
59
59
|
static _verbose: boolean = false;
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
static FORCE_INJECT_STYLES?: string[] = [];
|
|
62
|
+
static FORCE_INJECT_MODE?: CSSInjectMode = 'adopted';
|
|
63
|
+
static FORCE_INJECT_MODE_PER_LINK?: Record<string, CSSInjectMode> = {};
|
|
63
64
|
|
|
64
65
|
static _EVENTS = {
|
|
65
66
|
component_define: 'rws:lifecycle:defineComponent',
|
|
@@ -271,6 +272,7 @@ abstract class RWSViewComponent extends FoundationElement implements IRWSViewCom
|
|
|
271
272
|
protected async injectStyles(styleLinks: string[], mode: CSSInjectMode = 'adopted', maxDaysExp?: number) {
|
|
272
273
|
// Create a bridge object that exposes the necessary properties
|
|
273
274
|
const componentBridge = {
|
|
275
|
+
componentElement: this,
|
|
274
276
|
shadowRoot: this.shadowRoot,
|
|
275
277
|
indexedDBService: this.indexedDBService,
|
|
276
278
|
$emit: this.$emit.bind(this)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { domEvents } from '../events';
|
|
2
2
|
import IndexedDBService, { IndexedDBServiceInstance } from '../services/IndexedDBService';
|
|
3
|
+
import RWSViewComponent from './_component';
|
|
3
4
|
|
|
4
|
-
type CSSInjectMode = 'adopted' | 'legacy' | 'both';
|
|
5
|
+
type CSSInjectMode = 'adopted' | 'legacy' | 'both' | 'style-element';
|
|
5
6
|
|
|
6
7
|
const _DEFAULT_INJECT_CSS_CACHE_LIMIT_DAYS = 1;
|
|
7
8
|
|
|
@@ -11,6 +12,7 @@ interface ICSSInjectionOptions {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
interface ICSSInjectionComponent {
|
|
15
|
+
componentElement?: RWSViewComponent;
|
|
14
16
|
shadowRoot: ShadowRoot | null;
|
|
15
17
|
indexedDBService: IndexedDBServiceInstance;
|
|
16
18
|
$emit(eventName: string): void;
|
|
@@ -44,7 +46,6 @@ export class CSSInjectionManager {
|
|
|
44
46
|
styleLinks: string[],
|
|
45
47
|
options: ICSSInjectionOptions = {}
|
|
46
48
|
): Promise<void> {
|
|
47
|
-
const { mode = 'adopted', maxDaysExp } = options;
|
|
48
49
|
|
|
49
50
|
if (!component.shadowRoot) {
|
|
50
51
|
throw new Error('Component must have a shadow root for CSS injection');
|
|
@@ -63,11 +64,35 @@ export class CSSInjectionManager {
|
|
|
63
64
|
transition: opacity 0.3s ease-in-out;
|
|
64
65
|
}
|
|
65
66
|
`);
|
|
67
|
+
|
|
66
68
|
component.shadowRoot.adoptedStyleSheets = [
|
|
67
69
|
transitionSheet,
|
|
68
70
|
...component.shadowRoot.adoptedStyleSheets,
|
|
69
71
|
];
|
|
70
72
|
|
|
73
|
+
const doneAdded = await CSSInjectionManager.addStyleSheets(component, styleLinks, options);
|
|
74
|
+
|
|
75
|
+
if (doneAdded) {
|
|
76
|
+
// Set opacity to 1 to fade in the component
|
|
77
|
+
const opacitySheet = new CSSStyleSheet();
|
|
78
|
+
await opacitySheet.replace(`
|
|
79
|
+
:host {
|
|
80
|
+
opacity: 1 !important;
|
|
81
|
+
}
|
|
82
|
+
`);
|
|
83
|
+
component.shadowRoot.adoptedStyleSheets = [
|
|
84
|
+
opacitySheet,
|
|
85
|
+
...component.shadowRoot.adoptedStyleSheets,
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
component.$emit(domEvents.loadedLinkedStyles);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private static async addStyleSheets(component: ICSSInjectionComponent, styleLinks: string[], options: ICSSInjectionOptions = {}): Promise<boolean>
|
|
93
|
+
{
|
|
94
|
+
const { mode = 'adopted', maxDaysExp } = options;
|
|
95
|
+
|
|
71
96
|
let adoptedSheets: CSSStyleSheet[] = [];
|
|
72
97
|
let doneAdded = false;
|
|
73
98
|
|
|
@@ -75,14 +100,21 @@ export class CSSInjectionManager {
|
|
|
75
100
|
const cachedSheets: CSSStyleSheet[] = [];
|
|
76
101
|
const uncachedLinks: string[] = [];
|
|
77
102
|
|
|
103
|
+
let hasCached = false;
|
|
104
|
+
|
|
78
105
|
for (const styleLink of styleLinks) {
|
|
79
106
|
if (CSSInjectionManager.CACHED_STYLES.has(styleLink)) {
|
|
80
107
|
cachedSheets.push(CSSInjectionManager.CACHED_STYLES.get(styleLink)!);
|
|
108
|
+
hasCached = true;
|
|
81
109
|
} else {
|
|
82
110
|
uncachedLinks.push(styleLink);
|
|
83
111
|
}
|
|
84
112
|
}
|
|
85
113
|
|
|
114
|
+
if(hasCached){
|
|
115
|
+
CSSInjectionManager.setStylesOwner(component);
|
|
116
|
+
}
|
|
117
|
+
|
|
86
118
|
// If we have cached styles, use them immediately
|
|
87
119
|
if (cachedSheets.length > 0) {
|
|
88
120
|
adoptedSheets.push(...cachedSheets);
|
|
@@ -92,9 +124,7 @@ export class CSSInjectionManager {
|
|
|
92
124
|
// Only process uncached styles
|
|
93
125
|
if (uncachedLinks.length > 0) {
|
|
94
126
|
// Set this component as the owner if no owner exists yet
|
|
95
|
-
|
|
96
|
-
CSSInjectionManager.STYLES_OWNER_COMPONENT = component;
|
|
97
|
-
}
|
|
127
|
+
CSSInjectionManager.setStylesOwner(component);
|
|
98
128
|
|
|
99
129
|
const dbName = 'css-cache';
|
|
100
130
|
const storeName = 'styles';
|
|
@@ -104,54 +134,41 @@ export class CSSInjectionManager {
|
|
|
104
134
|
const maxAgeDays = maxAgeMs * maxDaysAge;
|
|
105
135
|
|
|
106
136
|
for (const styleLink of uncachedLinks) {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
link.onload = () => {
|
|
115
|
-
doneAdded = true;
|
|
116
|
-
|
|
117
|
-
if(mode === 'legacy'){
|
|
137
|
+
const linkMode = Object.keys(RWSViewComponent.FORCE_INJECT_MODE_PER_LINK).includes(styleLink) ? RWSViewComponent.FORCE_INJECT_MODE_PER_LINK[styleLink] : mode;
|
|
138
|
+
|
|
139
|
+
const loadPromise = new Promise<void>(async (resolve) => {
|
|
140
|
+
try {
|
|
141
|
+
if (linkMode === 'legacy') {
|
|
142
|
+
await CSSInjectionManager.injectLegacyStyle(component, styleLink, () => {
|
|
143
|
+
doneAdded = true;
|
|
118
144
|
resolve();
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (mode === 'adopted' || mode === 'both') {
|
|
124
|
-
const entry = await component.indexedDBService.getFromDB(db, storeName, styleLink);
|
|
125
|
-
|
|
126
|
-
let cssText: string | null = null;
|
|
127
|
-
|
|
128
|
-
if (entry && typeof entry === 'object' && 'css' in entry && 'timestamp' in entry) {
|
|
129
|
-
const expired = Date.now() - entry.timestamp > maxAgeDays;
|
|
130
|
-
if (!expired) {
|
|
131
|
-
cssText = entry.css;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (!cssText) {
|
|
136
|
-
cssText = await fetch(styleLink).then(res => res.text());
|
|
137
|
-
await component.indexedDBService.saveToDB(db, storeName, styleLink, {
|
|
138
|
-
css: cssText,
|
|
139
|
-
timestamp: Date.now()
|
|
140
145
|
});
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
146
|
+
} else if (linkMode === 'style-element') {
|
|
147
|
+
await CSSInjectionManager.injectStyleElement(component, styleLink, db, storeName, maxAgeDays);
|
|
148
|
+
doneAdded = true;
|
|
149
|
+
resolve();
|
|
150
|
+
} else if (linkMode === 'adopted') {
|
|
151
|
+
const sheet = await CSSInjectionManager.injectAdoptedStyle(component, styleLink, db, storeName, maxAgeDays);
|
|
152
|
+
adoptedSheets.push(sheet);
|
|
153
|
+
doneAdded = true;
|
|
154
|
+
resolve();
|
|
155
|
+
} else if (linkMode === 'both') {
|
|
156
|
+
// Handle both modes
|
|
157
|
+
const [sheet] = await Promise.all([
|
|
158
|
+
CSSInjectionManager.injectAdoptedStyle(component, styleLink, db, storeName, maxAgeDays),
|
|
159
|
+
new Promise<void>((resolveLegacy) => {
|
|
160
|
+
CSSInjectionManager.injectLegacyStyle(component, styleLink, () => {
|
|
161
|
+
resolveLegacy();
|
|
162
|
+
});
|
|
163
|
+
})
|
|
164
|
+
]);
|
|
165
|
+
adoptedSheets.push(sheet);
|
|
166
|
+
doneAdded = true;
|
|
153
167
|
resolve();
|
|
154
168
|
}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error(`Failed to inject styles for ${styleLink}:`, error);
|
|
171
|
+
resolve();
|
|
155
172
|
}
|
|
156
173
|
});
|
|
157
174
|
|
|
@@ -170,23 +187,91 @@ export class CSSInjectionManager {
|
|
|
170
187
|
doneAdded = true;
|
|
171
188
|
}
|
|
172
189
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const opacitySheet = new CSSStyleSheet();
|
|
176
|
-
await opacitySheet.replace(`
|
|
177
|
-
:host {
|
|
178
|
-
opacity: 1 !important;
|
|
179
|
-
}
|
|
180
|
-
`);
|
|
181
|
-
component.shadowRoot.adoptedStyleSheets = [
|
|
182
|
-
opacitySheet,
|
|
183
|
-
...component.shadowRoot.adoptedStyleSheets,
|
|
184
|
-
];
|
|
190
|
+
return doneAdded;
|
|
191
|
+
}
|
|
185
192
|
|
|
186
|
-
|
|
193
|
+
private static async injectLegacyStyle(
|
|
194
|
+
component: ICSSInjectionComponent,
|
|
195
|
+
styleLink: string,
|
|
196
|
+
onLoad: () => void
|
|
197
|
+
): Promise<void> {
|
|
198
|
+
const link = document.createElement('link');
|
|
199
|
+
link.rel = 'stylesheet';
|
|
200
|
+
link.href = styleLink;
|
|
201
|
+
link.onload = onLoad;
|
|
202
|
+
component.shadowRoot!.appendChild(link);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private static async injectStyleElement(
|
|
206
|
+
component: ICSSInjectionComponent,
|
|
207
|
+
styleLink: string,
|
|
208
|
+
db: IDBDatabase,
|
|
209
|
+
storeName: string,
|
|
210
|
+
maxAgeDays: number
|
|
211
|
+
): Promise<void> {
|
|
212
|
+
const cssText = await CSSInjectionManager.getCachedOrFetchCSS(component, styleLink, db, storeName, maxAgeDays);
|
|
213
|
+
|
|
214
|
+
if (component.componentElement) {
|
|
215
|
+
const styleElement = document.createElement('style');
|
|
216
|
+
styleElement.textContent = cssText;
|
|
217
|
+
component.componentElement.appendChild(styleElement);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private static async injectAdoptedStyle(
|
|
222
|
+
component: ICSSInjectionComponent,
|
|
223
|
+
styleLink: string,
|
|
224
|
+
db: IDBDatabase,
|
|
225
|
+
storeName: string,
|
|
226
|
+
maxAgeDays: number
|
|
227
|
+
): Promise<CSSStyleSheet> {
|
|
228
|
+
const cssText = await CSSInjectionManager.getCachedOrFetchCSS(component, styleLink, db, storeName, maxAgeDays);
|
|
229
|
+
|
|
230
|
+
const sheet = new CSSStyleSheet();
|
|
231
|
+
await sheet.replace(cssText);
|
|
232
|
+
|
|
233
|
+
// Cache the stylesheet for future use
|
|
234
|
+
CSSInjectionManager.CACHED_STYLES.set(styleLink, sheet);
|
|
235
|
+
|
|
236
|
+
return sheet;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private static async getCachedOrFetchCSS(
|
|
240
|
+
component: ICSSInjectionComponent,
|
|
241
|
+
styleLink: string,
|
|
242
|
+
db: IDBDatabase,
|
|
243
|
+
storeName: string,
|
|
244
|
+
maxAgeDays: number
|
|
245
|
+
): Promise<string> {
|
|
246
|
+
const entry = await component.indexedDBService.getFromDB(db, storeName, styleLink);
|
|
247
|
+
let cssText: string | null = null;
|
|
248
|
+
|
|
249
|
+
if (entry && typeof entry === 'object' && 'css' in entry && 'timestamp' in entry) {
|
|
250
|
+
const expired = Date.now() - entry.timestamp > maxAgeDays;
|
|
251
|
+
if (!expired) {
|
|
252
|
+
cssText = entry.css;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (!cssText) {
|
|
257
|
+
cssText = await fetch(styleLink).then(res => res.text());
|
|
258
|
+
await component.indexedDBService.saveToDB(db, storeName, styleLink, {
|
|
259
|
+
css: cssText,
|
|
260
|
+
timestamp: Date.now()
|
|
261
|
+
});
|
|
262
|
+
console.log(`System saved stylesheet: ${styleLink} to IndexedDB`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return cssText;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private static setStylesOwner(component: ICSSInjectionComponent): void {
|
|
269
|
+
if (!CSSInjectionManager.STYLES_OWNER_COMPONENT) {
|
|
270
|
+
CSSInjectionManager.STYLES_OWNER_COMPONENT = component;
|
|
271
|
+
console.log({component});
|
|
187
272
|
}
|
|
188
273
|
}
|
|
189
274
|
}
|
|
190
275
|
|
|
191
276
|
export default CSSInjectionManager;
|
|
192
|
-
export { CSSInjectMode, ICSSInjectionOptions, ICSSInjectionComponent };
|
|
277
|
+
export { CSSInjectMode, ICSSInjectionOptions, ICSSInjectionComponent };
|
package/src/index.ts
CHANGED
|
@@ -30,7 +30,7 @@ import type IRWSConfig from './types/IRWSConfig';
|
|
|
30
30
|
import type RWSNotify from './types/RWSNotify';
|
|
31
31
|
import type { NotifyUiType, NotifyLogType } from './types/RWSNotify';
|
|
32
32
|
import * as RWSEvents from './events';
|
|
33
|
-
|
|
33
|
+
import { CSSInjectMode } from './components/_css_injection';
|
|
34
34
|
export default RWSClient;
|
|
35
35
|
|
|
36
36
|
export {
|
|
@@ -75,7 +75,8 @@ export {
|
|
|
75
75
|
RWSEvents
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
-
export type {
|
|
78
|
+
export type {
|
|
79
|
+
CSSInjectMode,
|
|
79
80
|
NotifyUiType,
|
|
80
81
|
NotifyLogType,
|
|
81
82
|
IBackendRoute as IRWSBackendRoute,
|