@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rws-framework/client",
3
3
  "private": false,
4
- "version": "2.23.7",
4
+ "version": "2.24.0",
5
5
  "main": "src/index.ts",
6
6
  "scripts": {
7
7
  "docs": "typedoc --tsconfig ./tsconfig.json"
@@ -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
- private static FORCE_INJECT_STYLES?: string[] = [];
62
- private static FORCE_INJECT_MODE?: CSSInjectMode = 'adopted';
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
- if (!CSSInjectionManager.STYLES_OWNER_COMPONENT) {
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 loadPromise = new Promise<void>(async (resolve, reject) => {
108
- if (mode === 'legacy' || mode === 'both') {
109
- const link = document.createElement('link');
110
- link.rel = 'stylesheet';
111
- link.href = styleLink;
112
- component.shadowRoot!.appendChild(link);
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
- console.log(`System saved stylesheet: ${styleLink} to IndexedDB`)
142
- }
143
-
144
- const sheet = new CSSStyleSheet();
145
- await sheet.replace(cssText);
146
-
147
- // Cache the stylesheet for future use
148
- CSSInjectionManager.CACHED_STYLES.set(styleLink, sheet);
149
-
150
- adoptedSheets.push(sheet);
151
-
152
- if(mode === 'adopted' || mode === 'both'){
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
- if (doneAdded) {
174
- // Set opacity to 1 to fade in the component
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
- component.$emit(domEvents.loadedLinkedStyles);
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,