@social.io/catalog 2.1.0 → 2.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/.smartconfig.json +5 -1
- package/dist_bundle/bundle.js +183 -183
- package/dist_bundle/sio-logo-render.js +319 -0
- package/dist_bundle/sio-logo-worker.js +38 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/sio-logo-render.d.ts +2 -1
- package/dist_ts_web/elements/sio-logo-render.js +17 -4
- package/dist_ts_web/elements/sio-logo-worker.d.ts +1 -0
- package/dist_ts_web/elements/sio-logo-worker.js +38 -0
- package/dist_ts_web/elements/sio-logo.d.ts +27 -6
- package/dist_ts_web/elements/sio-logo.js +258 -74
- package/package.json +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/sio-logo-render.ts +26 -5
- package/ts_web/elements/sio-logo-worker.ts +67 -0
- package/ts_web/elements/sio-logo.ts +278 -62
|
@@ -11,6 +11,10 @@ export interface ISioLogoRenderOptions {
|
|
|
11
11
|
light: boolean;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export type TSioLogoRenderContext =
|
|
15
|
+
| CanvasRenderingContext2D
|
|
16
|
+
| OffscreenCanvasRenderingContext2D;
|
|
17
|
+
|
|
14
18
|
interface ISioLogoPoint {
|
|
15
19
|
x: number;
|
|
16
20
|
y: number;
|
|
@@ -159,9 +163,26 @@ const easeInBack = (progress: number): number => {
|
|
|
159
163
|
return (coefficient + 1) * progress * progress * progress - coefficient * progress * progress;
|
|
160
164
|
};
|
|
161
165
|
|
|
166
|
+
type TSioLogoFrameHandle = number | ReturnType<typeof setTimeout>;
|
|
167
|
+
|
|
168
|
+
const requestFrame = (callback: FrameRequestCallback): TSioLogoFrameHandle => {
|
|
169
|
+
if (typeof globalThis.requestAnimationFrame === 'function') {
|
|
170
|
+
return globalThis.requestAnimationFrame(callback);
|
|
171
|
+
}
|
|
172
|
+
return setTimeout(() => callback(performance.now()), 1000 / 60);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const cancelFrame = (handle: TSioLogoFrameHandle): void => {
|
|
176
|
+
if (typeof globalThis.cancelAnimationFrame === 'function' && typeof handle === 'number') {
|
|
177
|
+
globalThis.cancelAnimationFrame(handle);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
clearTimeout(handle);
|
|
181
|
+
};
|
|
182
|
+
|
|
162
183
|
export class SioLogoRenderer {
|
|
163
184
|
private options: ISioLogoRenderOptions;
|
|
164
|
-
private frameHandle:
|
|
185
|
+
private frameHandle: TSioLogoFrameHandle | null = null;
|
|
165
186
|
private phase: TSioLogoAnimationPhase | null = null;
|
|
166
187
|
private startedAt = 0;
|
|
167
188
|
private stagger = 70;
|
|
@@ -178,7 +199,7 @@ export class SioLogoRenderer {
|
|
|
178
199
|
private opacities: number[] = [];
|
|
179
200
|
|
|
180
201
|
constructor(
|
|
181
|
-
private readonly context:
|
|
202
|
+
private readonly context: TSioLogoRenderContext,
|
|
182
203
|
options: ISioLogoRenderOptions,
|
|
183
204
|
) {
|
|
184
205
|
this.options = normalizeOptions(options);
|
|
@@ -220,14 +241,14 @@ export class SioLogoRenderer {
|
|
|
220
241
|
this.settle();
|
|
221
242
|
return;
|
|
222
243
|
}
|
|
223
|
-
this.frameHandle =
|
|
244
|
+
this.frameHandle = requestFrame(drawFrame);
|
|
224
245
|
};
|
|
225
|
-
this.frameHandle =
|
|
246
|
+
this.frameHandle = requestFrame(drawFrame);
|
|
226
247
|
}
|
|
227
248
|
|
|
228
249
|
public stop(): void {
|
|
229
250
|
if (this.frameHandle !== null) {
|
|
230
|
-
|
|
251
|
+
cancelFrame(this.frameHandle);
|
|
231
252
|
this.frameHandle = null;
|
|
232
253
|
}
|
|
233
254
|
this.phase = null;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SioLogoRenderer,
|
|
3
|
+
type ISioLogoRenderOptions,
|
|
4
|
+
type TSioLogoAnimationPhase,
|
|
5
|
+
} from './sio-logo-render.js';
|
|
6
|
+
|
|
7
|
+
interface ISioLogoWorkerInitMessage {
|
|
8
|
+
type: 'init';
|
|
9
|
+
canvas: OffscreenCanvas;
|
|
10
|
+
options: ISioLogoRenderOptions;
|
|
11
|
+
phase: TSioLogoAnimationPhase | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ISioLogoWorkerOptionsMessage {
|
|
15
|
+
type: 'options';
|
|
16
|
+
options: ISioLogoRenderOptions;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ISioLogoWorkerPhaseMessage {
|
|
20
|
+
type: 'phase';
|
|
21
|
+
phase: TSioLogoAnimationPhase | 'stop' | 'clear';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type TSioLogoWorkerMessage =
|
|
25
|
+
| ISioLogoWorkerInitMessage
|
|
26
|
+
| ISioLogoWorkerOptionsMessage
|
|
27
|
+
| ISioLogoWorkerPhaseMessage;
|
|
28
|
+
|
|
29
|
+
interface ISioLogoWorkerScope {
|
|
30
|
+
onmessage: ((event: MessageEvent<TSioLogoWorkerMessage>) => void) | null;
|
|
31
|
+
postMessage: (message: { type: 'ready' | 'initialized' }) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const workerScope = globalThis as unknown as ISioLogoWorkerScope;
|
|
35
|
+
let renderer: SioLogoRenderer | null = null;
|
|
36
|
+
|
|
37
|
+
workerScope.onmessage = (event): void => {
|
|
38
|
+
const message = event.data;
|
|
39
|
+
if (message.type === 'init') {
|
|
40
|
+
const context = message.canvas.getContext('2d');
|
|
41
|
+
if (!context) throw new Error('Unable to create the sio-logo worker canvas context.');
|
|
42
|
+
renderer = new SioLogoRenderer(context, message.options);
|
|
43
|
+
if (message.phase) renderer.start(message.phase);
|
|
44
|
+
else renderer.drawStatic();
|
|
45
|
+
workerScope.postMessage({ type: 'initialized' });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!renderer) return;
|
|
50
|
+
if (message.type === 'options') {
|
|
51
|
+
renderer.setOptions(message.options);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (message.phase === 'stop') {
|
|
56
|
+
renderer.stop();
|
|
57
|
+
renderer.drawStatic();
|
|
58
|
+
} else if (message.phase === 'clear') {
|
|
59
|
+
renderer.clear();
|
|
60
|
+
} else {
|
|
61
|
+
renderer.start(message.phase);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// The canvas is transferred only after the module and its renderer dependency
|
|
66
|
+
// have loaded, leaving the HTMLCanvasElement intact for pre-transfer fallback.
|
|
67
|
+
workerScope.postMessage({ type: 'ready' });
|
|
@@ -2,15 +2,31 @@ import {
|
|
|
2
2
|
DeesElement,
|
|
3
3
|
css,
|
|
4
4
|
customElement,
|
|
5
|
+
directives,
|
|
5
6
|
html,
|
|
6
7
|
property,
|
|
8
|
+
state,
|
|
7
9
|
type TemplateResult,
|
|
8
10
|
} from '@design.estate/dees-element';
|
|
9
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
SioLogoRenderer,
|
|
13
|
+
sioLogoAspect,
|
|
14
|
+
type ISioLogoRenderOptions,
|
|
15
|
+
type TSioLogoAnimationPhase,
|
|
16
|
+
} from './sio-logo-render.js';
|
|
10
17
|
import { sioElementStyles } from './tokens.js';
|
|
11
18
|
|
|
12
19
|
export type TSioLogoTheme = 'auto' | 'light' | 'dark';
|
|
13
20
|
|
|
21
|
+
type TSioLogoBackendState =
|
|
22
|
+
| 'idle'
|
|
23
|
+
| 'worker-loading'
|
|
24
|
+
| 'worker-transferred'
|
|
25
|
+
| 'worker'
|
|
26
|
+
| 'main';
|
|
27
|
+
|
|
28
|
+
type TSioLogoPhaseCommand = TSioLogoAnimationPhase | 'stop' | 'clear';
|
|
29
|
+
|
|
14
30
|
declare global {
|
|
15
31
|
interface HTMLElementTagNameMap {
|
|
16
32
|
'sio-logo': SioLogo;
|
|
@@ -20,9 +36,9 @@ declare global {
|
|
|
20
36
|
/**
|
|
21
37
|
* The social.io faceted S brand mark from the v8 design prototype.
|
|
22
38
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
39
|
+
* Geometry and animation run on an OffscreenCanvas in a module worker when the
|
|
40
|
+
* browser and consumer bundle support it. The same renderer is used on the
|
|
41
|
+
* main thread as a safe fallback.
|
|
26
42
|
*/
|
|
27
43
|
@customElement('sio-logo')
|
|
28
44
|
export class SioLogo extends DeesElement {
|
|
@@ -71,6 +87,9 @@ export class SioLogo extends DeesElement {
|
|
|
71
87
|
@property({ type: Boolean, attribute: 'no-hover' })
|
|
72
88
|
public accessor noHover = false;
|
|
73
89
|
|
|
90
|
+
@state()
|
|
91
|
+
private accessor canvasGeneration = 0;
|
|
92
|
+
|
|
74
93
|
public static styles = [
|
|
75
94
|
...sioElementStyles,
|
|
76
95
|
css`
|
|
@@ -85,29 +104,43 @@ export class SioLogo extends DeesElement {
|
|
|
85
104
|
`,
|
|
86
105
|
];
|
|
87
106
|
|
|
107
|
+
private backendState: TSioLogoBackendState = 'idle';
|
|
108
|
+
private worker: Worker | null = null;
|
|
88
109
|
private renderer: SioLogoRenderer | null = null;
|
|
110
|
+
private canvasTransferred = false;
|
|
111
|
+
private forceMainThread = false;
|
|
112
|
+
private initializationTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
113
|
+
private initializationGeneration = 0;
|
|
114
|
+
private lifecycleGeneration = 0;
|
|
115
|
+
private initialUpdateHandled = false;
|
|
116
|
+
private queuedOptions = false;
|
|
117
|
+
private queuedPhase: TSioLogoPhaseCommand | null = null;
|
|
89
118
|
private themeObserver: MutationObserver | null = null;
|
|
90
|
-
private colorSchemeMedia: MediaQueryList | null = null;
|
|
91
119
|
private reducedMotionMedia: MediaQueryList | null = null;
|
|
92
120
|
private lastDevicePixelRatio = 0;
|
|
93
|
-
private
|
|
121
|
+
private lastLight: boolean | null = null;
|
|
94
122
|
|
|
95
123
|
private readonly handlePointerEnter = (): void => {
|
|
96
124
|
if (!this.noHover && !this.loading && !this.prefersReducedMotion()) this.wave();
|
|
97
125
|
};
|
|
98
126
|
|
|
99
127
|
private readonly handleAmbientChange = (): void => {
|
|
100
|
-
|
|
128
|
+
if (this.theme !== 'auto') return;
|
|
129
|
+
const light = this.isLightBackdrop();
|
|
130
|
+
if (light === this.lastLight) return;
|
|
131
|
+
this.lastLight = light;
|
|
132
|
+
this.syncRendererOptions();
|
|
101
133
|
};
|
|
102
134
|
|
|
103
135
|
private readonly handleReducedMotionChange = (): void => {
|
|
104
|
-
this.
|
|
136
|
+
if (this.prefersReducedMotion()) this.sendPhase('stop');
|
|
137
|
+
else if (this.loading) this.sendPhase('loading');
|
|
105
138
|
};
|
|
106
139
|
|
|
107
140
|
private readonly handleResize = (): void => {
|
|
108
|
-
const
|
|
109
|
-
if (
|
|
110
|
-
this.
|
|
141
|
+
const devicePixelRatio = window.devicePixelRatio || 1;
|
|
142
|
+
if (devicePixelRatio === this.lastDevicePixelRatio) return;
|
|
143
|
+
this.syncRendererOptions();
|
|
111
144
|
};
|
|
112
145
|
|
|
113
146
|
public async connectedCallback(): Promise<void> {
|
|
@@ -118,106 +151,258 @@ export class SioLogo extends DeesElement {
|
|
|
118
151
|
window.addEventListener('resize', this.handleResize);
|
|
119
152
|
this.observeReducedMotion();
|
|
120
153
|
await connection;
|
|
154
|
+
await this.updateComplete;
|
|
121
155
|
if (!this.isConnected || generation !== this.lifecycleGeneration) return;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
this.syncRenderer(this.loading ? 'loading' : null);
|
|
125
|
-
}
|
|
156
|
+
this.observeAmbientTheme();
|
|
157
|
+
if (this.hasUpdated && this.backendState === 'idle') this.initializeCanvas();
|
|
126
158
|
}
|
|
127
159
|
|
|
128
160
|
public async disconnectedCallback(): Promise<void> {
|
|
129
161
|
const generation = ++this.lifecycleGeneration;
|
|
130
|
-
this.cleanupOwnedResources();
|
|
162
|
+
this.cleanupOwnedResources(true);
|
|
131
163
|
await super.disconnectedCallback();
|
|
132
164
|
if (!this.isConnected && generation === this.lifecycleGeneration) {
|
|
133
|
-
this.cleanupOwnedResources();
|
|
165
|
+
this.cleanupOwnedResources(true);
|
|
134
166
|
}
|
|
135
167
|
}
|
|
136
168
|
|
|
137
|
-
private cleanupOwnedResources(): void {
|
|
169
|
+
private cleanupOwnedResources(replaceTransferredCanvas: boolean): void {
|
|
138
170
|
this.removeEventListener('mouseenter', this.handlePointerEnter);
|
|
139
171
|
window.removeEventListener('resize', this.handleResize);
|
|
140
172
|
this.stopAmbientThemeObservation();
|
|
141
173
|
this.stopReducedMotionObservation();
|
|
142
|
-
this.
|
|
174
|
+
this.destroyBackend(replaceTransferredCanvas);
|
|
143
175
|
}
|
|
144
176
|
|
|
145
177
|
public render(): TemplateResult {
|
|
146
178
|
const height = this.normalizedSize;
|
|
147
179
|
const width = this.square ? height : Math.round(height * sioLogoAspect);
|
|
148
|
-
return html
|
|
180
|
+
return html`${directives.keyed(
|
|
181
|
+
this.canvasGeneration,
|
|
182
|
+
html`<canvas aria-hidden="true" style="width:${width}px;height:${height}px"></canvas>`,
|
|
183
|
+
)}`;
|
|
149
184
|
}
|
|
150
185
|
|
|
151
186
|
public firstUpdated(changedProperties: Map<PropertyKey, unknown>): void {
|
|
152
187
|
super.firstUpdated(changedProperties);
|
|
153
|
-
const canvas = this.shadowRoot?.querySelector<HTMLCanvasElement>('canvas');
|
|
154
|
-
const context = canvas?.getContext('2d');
|
|
155
|
-
if (!context) return;
|
|
156
|
-
this.renderer = new SioLogoRenderer(context, this.getRendererOptions());
|
|
157
|
-
if (!this.isConnected) {
|
|
158
|
-
this.renderer.drawStatic();
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
188
|
this.observeAmbientTheme();
|
|
162
|
-
|
|
163
|
-
else if (!this.noEntrance && !this.prefersReducedMotion()) this.renderer.start('in');
|
|
164
|
-
else this.renderer.drawStatic();
|
|
189
|
+
this.initializeCanvas();
|
|
165
190
|
}
|
|
166
191
|
|
|
167
192
|
public updated(changedProperties: Map<PropertyKey, unknown>): void {
|
|
168
|
-
if (!this.
|
|
169
|
-
this.
|
|
170
|
-
this.renderer?.stop();
|
|
193
|
+
if (!this.initialUpdateHandled) {
|
|
194
|
+
this.initialUpdateHandled = true;
|
|
171
195
|
return;
|
|
172
196
|
}
|
|
173
|
-
|
|
197
|
+
if (!this.isConnected) return;
|
|
198
|
+
if (changedProperties.has('canvasGeneration')) {
|
|
199
|
+
this.initializeCanvas();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const shapeProperties: PropertyKey[] = [
|
|
174
204
|
'size',
|
|
175
205
|
'gap',
|
|
176
206
|
'radius',
|
|
177
207
|
'square',
|
|
178
208
|
'background',
|
|
179
209
|
'mono',
|
|
180
|
-
'theme',
|
|
181
210
|
];
|
|
182
|
-
if (
|
|
183
|
-
this.
|
|
211
|
+
if (shapeProperties.some((propertyName) => changedProperties.has(propertyName))) {
|
|
212
|
+
this.syncRendererOptions();
|
|
184
213
|
}
|
|
185
214
|
if (changedProperties.has('theme')) {
|
|
186
215
|
this.stopAmbientThemeObservation();
|
|
187
216
|
this.observeAmbientTheme();
|
|
217
|
+
this.syncRendererOptions();
|
|
188
218
|
}
|
|
189
219
|
if (changedProperties.has('loading')) {
|
|
190
|
-
this.
|
|
220
|
+
this.sendPhase(
|
|
221
|
+
this.loading && !this.prefersReducedMotion() ? 'loading' : 'stop',
|
|
222
|
+
);
|
|
191
223
|
}
|
|
192
224
|
}
|
|
193
225
|
|
|
194
226
|
protected themeChanged(): void {
|
|
195
|
-
this.
|
|
227
|
+
this.handleAmbientChange();
|
|
196
228
|
}
|
|
197
229
|
|
|
198
230
|
public play(): void {
|
|
199
|
-
if (!this.
|
|
200
|
-
|
|
201
|
-
else this.renderer.start('in');
|
|
231
|
+
if (!this.isConnected) return;
|
|
232
|
+
this.sendPhase(this.prefersReducedMotion() ? 'stop' : 'in');
|
|
202
233
|
}
|
|
203
234
|
|
|
204
235
|
public wave(): void {
|
|
205
|
-
if (!this.
|
|
206
|
-
|
|
207
|
-
else this.renderer.start('wave');
|
|
236
|
+
if (!this.isConnected) return;
|
|
237
|
+
this.sendPhase(this.prefersReducedMotion() ? 'stop' : 'wave');
|
|
208
238
|
}
|
|
209
239
|
|
|
210
240
|
public dismiss(): void {
|
|
211
|
-
if (!this.
|
|
212
|
-
|
|
213
|
-
|
|
241
|
+
if (!this.isConnected) return;
|
|
242
|
+
this.sendPhase(this.prefersReducedMotion() ? 'clear' : 'out');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private initializeCanvas(): void {
|
|
246
|
+
if (!this.isConnected || this.backendState !== 'idle') return;
|
|
247
|
+
const canvas = this.shadowRoot?.querySelector<HTMLCanvasElement>('canvas');
|
|
248
|
+
if (!canvas) return;
|
|
249
|
+
const generation = ++this.initializationGeneration;
|
|
250
|
+
const supportsWorker =
|
|
251
|
+
!this.forceMainThread &&
|
|
252
|
+
typeof Worker === 'function' &&
|
|
253
|
+
typeof canvas.transferControlToOffscreen === 'function';
|
|
254
|
+
|
|
255
|
+
if (!supportsWorker) {
|
|
256
|
+
this.initializeMainThread(canvas, generation);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const worker = new Worker(new URL('./sio-logo-worker.js', import.meta.url), {
|
|
262
|
+
type: 'module',
|
|
263
|
+
name: 'sio-logo',
|
|
264
|
+
});
|
|
265
|
+
this.worker = worker;
|
|
266
|
+
this.backendState = 'worker-loading';
|
|
267
|
+
worker.onmessage = (event: MessageEvent<{ type?: string }>): void => {
|
|
268
|
+
if (event.data?.type === 'ready') {
|
|
269
|
+
this.handleWorkerReady(worker, canvas, generation);
|
|
270
|
+
} else if (event.data?.type === 'initialized') {
|
|
271
|
+
this.handleWorkerInitialized(worker, generation);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
worker.onerror = (): void => this.handleWorkerFailure(worker, canvas, generation);
|
|
275
|
+
worker.onmessageerror = (): void => this.handleWorkerFailure(worker, canvas, generation);
|
|
276
|
+
this.armInitializationTimeout(worker, canvas, generation);
|
|
277
|
+
} catch {
|
|
278
|
+
this.forceMainThread = true;
|
|
279
|
+
this.initializeMainThread(canvas, generation);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private handleWorkerReady(
|
|
284
|
+
worker: Worker,
|
|
285
|
+
canvas: HTMLCanvasElement,
|
|
286
|
+
generation: number,
|
|
287
|
+
): void {
|
|
288
|
+
if (!this.isCurrentWorker(worker, generation) || this.backendState !== 'worker-loading') return;
|
|
289
|
+
this.clearInitializationTimeout();
|
|
290
|
+
try {
|
|
291
|
+
const offscreenCanvas = canvas.transferControlToOffscreen();
|
|
292
|
+
this.canvasTransferred = true;
|
|
293
|
+
this.backendState = 'worker-transferred';
|
|
294
|
+
this.lastLight = this.isLightBackdrop();
|
|
295
|
+
worker.postMessage(
|
|
296
|
+
{
|
|
297
|
+
type: 'init',
|
|
298
|
+
canvas: offscreenCanvas,
|
|
299
|
+
options: this.getRendererOptions(),
|
|
300
|
+
phase: this.getInitialPhase(),
|
|
301
|
+
},
|
|
302
|
+
[offscreenCanvas],
|
|
303
|
+
);
|
|
304
|
+
this.queuedOptions = false;
|
|
305
|
+
this.armInitializationTimeout(worker, canvas, generation);
|
|
306
|
+
} catch {
|
|
307
|
+
this.handleWorkerFailure(worker, canvas, generation);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private handleWorkerInitialized(worker: Worker, generation: number): void {
|
|
312
|
+
if (!this.isCurrentWorker(worker, generation) || this.backendState !== 'worker-transferred') return;
|
|
313
|
+
this.clearInitializationTimeout();
|
|
314
|
+
this.backendState = 'worker';
|
|
315
|
+
if (this.queuedOptions) {
|
|
316
|
+
this.queuedOptions = false;
|
|
317
|
+
this.syncRendererOptions();
|
|
318
|
+
}
|
|
319
|
+
if (this.queuedPhase) {
|
|
320
|
+
const phase = this.queuedPhase;
|
|
321
|
+
this.queuedPhase = null;
|
|
322
|
+
this.sendPhase(phase);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private handleWorkerFailure(
|
|
327
|
+
worker: Worker,
|
|
328
|
+
canvas: HTMLCanvasElement,
|
|
329
|
+
generation: number,
|
|
330
|
+
): void {
|
|
331
|
+
if (!this.isCurrentWorker(worker, generation)) return;
|
|
332
|
+
const requiresNewCanvas = this.canvasTransferred;
|
|
333
|
+
this.forceMainThread = true;
|
|
334
|
+
this.destroyBackend(requiresNewCanvas);
|
|
335
|
+
if (!requiresNewCanvas && this.isConnected) {
|
|
336
|
+
this.initializeMainThread(canvas, generation);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private initializeMainThread(canvas: HTMLCanvasElement, generation: number): void {
|
|
341
|
+
if (!this.isConnected || generation !== this.initializationGeneration) return;
|
|
342
|
+
const context = canvas.getContext('2d');
|
|
343
|
+
if (!context) return;
|
|
344
|
+
this.backendState = 'main';
|
|
345
|
+
const options = this.getRendererOptions();
|
|
346
|
+
this.lastLight = options.light;
|
|
347
|
+
this.renderer = new SioLogoRenderer(context, options);
|
|
348
|
+
const phase = this.getInitialPhase();
|
|
349
|
+
if (phase) this.renderer.start(phase);
|
|
350
|
+
else this.renderer.drawStatic();
|
|
351
|
+
this.queuedOptions = false;
|
|
352
|
+
if (this.queuedPhase) {
|
|
353
|
+
const queuedPhase = this.queuedPhase;
|
|
354
|
+
this.queuedPhase = null;
|
|
355
|
+
this.sendPhase(queuedPhase);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private armInitializationTimeout(
|
|
360
|
+
worker: Worker,
|
|
361
|
+
canvas: HTMLCanvasElement,
|
|
362
|
+
generation: number,
|
|
363
|
+
): void {
|
|
364
|
+
this.clearInitializationTimeout();
|
|
365
|
+
this.initializationTimeout = setTimeout(() => {
|
|
366
|
+
this.handleWorkerFailure(worker, canvas, generation);
|
|
367
|
+
}, 2000);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private clearInitializationTimeout(): void {
|
|
371
|
+
if (this.initializationTimeout === null) return;
|
|
372
|
+
clearTimeout(this.initializationTimeout);
|
|
373
|
+
this.initializationTimeout = null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private isCurrentWorker(worker: Worker, generation: number): boolean {
|
|
377
|
+
return this.isConnected &&
|
|
378
|
+
this.worker === worker &&
|
|
379
|
+
generation === this.initializationGeneration;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private destroyBackend(replaceTransferredCanvas: boolean): void {
|
|
383
|
+
const transferred = this.canvasTransferred;
|
|
384
|
+
this.clearInitializationTimeout();
|
|
385
|
+
if (this.worker) {
|
|
386
|
+
this.worker.onmessage = null;
|
|
387
|
+
this.worker.onerror = null;
|
|
388
|
+
this.worker.onmessageerror = null;
|
|
389
|
+
this.worker.terminate();
|
|
390
|
+
}
|
|
391
|
+
this.worker = null;
|
|
392
|
+
this.renderer?.stop();
|
|
393
|
+
this.renderer = null;
|
|
394
|
+
this.backendState = 'idle';
|
|
395
|
+
this.canvasTransferred = false;
|
|
396
|
+
this.queuedOptions = false;
|
|
397
|
+
this.queuedPhase = null;
|
|
398
|
+
if (replaceTransferredCanvas && transferred) this.canvasGeneration += 1;
|
|
214
399
|
}
|
|
215
400
|
|
|
216
401
|
private get normalizedSize(): number {
|
|
217
402
|
return Number.isFinite(this.size) ? Math.max(1, this.size) : 40;
|
|
218
403
|
}
|
|
219
404
|
|
|
220
|
-
private getRendererOptions() {
|
|
405
|
+
private getRendererOptions(): ISioLogoRenderOptions {
|
|
221
406
|
this.lastDevicePixelRatio = window.devicePixelRatio || 1;
|
|
222
407
|
return {
|
|
223
408
|
size: this.normalizedSize,
|
|
@@ -231,14 +416,41 @@ export class SioLogo extends DeesElement {
|
|
|
231
416
|
};
|
|
232
417
|
}
|
|
233
418
|
|
|
234
|
-
private
|
|
235
|
-
if (!this.
|
|
236
|
-
this.
|
|
237
|
-
this.
|
|
238
|
-
|
|
239
|
-
|
|
419
|
+
private getInitialPhase(): TSioLogoAnimationPhase | null {
|
|
420
|
+
if (!this.isConnected || this.prefersReducedMotion()) return null;
|
|
421
|
+
if (this.loading) return 'loading';
|
|
422
|
+
return this.noEntrance ? null : 'in';
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
private syncRendererOptions(): void {
|
|
426
|
+
const options = this.getRendererOptions();
|
|
427
|
+
this.lastLight = options.light;
|
|
428
|
+
if (this.backendState === 'worker') {
|
|
429
|
+
this.worker?.postMessage({ type: 'options', options });
|
|
430
|
+
} else if (this.backendState === 'main') {
|
|
431
|
+
this.renderer?.setOptions(options);
|
|
432
|
+
} else {
|
|
433
|
+
this.queuedOptions = true;
|
|
240
434
|
}
|
|
241
|
-
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
private sendPhase(phase: TSioLogoPhaseCommand): void {
|
|
438
|
+
if (this.backendState === 'worker') {
|
|
439
|
+
this.worker?.postMessage({ type: 'phase', phase });
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
if (this.backendState === 'main' && this.renderer) {
|
|
443
|
+
if (phase === 'stop') {
|
|
444
|
+
this.renderer.stop();
|
|
445
|
+
this.renderer.drawStatic();
|
|
446
|
+
} else if (phase === 'clear') {
|
|
447
|
+
this.renderer.clear();
|
|
448
|
+
} else {
|
|
449
|
+
this.renderer.start(phase);
|
|
450
|
+
}
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
this.queuedPhase = phase;
|
|
242
454
|
}
|
|
243
455
|
|
|
244
456
|
private prefersReducedMotion(): boolean {
|
|
@@ -253,6 +465,13 @@ export class SioLogo extends DeesElement {
|
|
|
253
465
|
|
|
254
466
|
let element: Element | null = this;
|
|
255
467
|
while (element) {
|
|
468
|
+
if (element === document.body || element === document.documentElement) {
|
|
469
|
+
const pageTheme =
|
|
470
|
+
document.documentElement.getAttribute('data-sio-theme') ||
|
|
471
|
+
document.documentElement.getAttribute('data-theme');
|
|
472
|
+
if (pageTheme === 'light') return true;
|
|
473
|
+
if (pageTheme === 'dark') return false;
|
|
474
|
+
}
|
|
256
475
|
const color = getComputedStyle(element).backgroundColor;
|
|
257
476
|
const match = color.match(
|
|
258
477
|
/rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:\s*[,/]\s*([\d.]+))?\s*\)/,
|
|
@@ -271,7 +490,8 @@ export class SioLogo extends DeesElement {
|
|
|
271
490
|
const root = element.getRootNode();
|
|
272
491
|
element = element.parentElement || (root instanceof ShadowRoot ? root.host : null);
|
|
273
492
|
}
|
|
274
|
-
|
|
493
|
+
|
|
494
|
+
return false;
|
|
275
495
|
}
|
|
276
496
|
|
|
277
497
|
private observeAmbientTheme(): void {
|
|
@@ -281,15 +501,11 @@ export class SioLogo extends DeesElement {
|
|
|
281
501
|
attributes: true,
|
|
282
502
|
attributeFilter: ['data-sio-theme', 'data-theme', 'class', 'style'],
|
|
283
503
|
});
|
|
284
|
-
this.colorSchemeMedia = window.matchMedia?.('(prefers-color-scheme: light)') ?? null;
|
|
285
|
-
this.colorSchemeMedia?.addEventListener('change', this.handleAmbientChange);
|
|
286
504
|
}
|
|
287
505
|
|
|
288
506
|
private stopAmbientThemeObservation(): void {
|
|
289
507
|
this.themeObserver?.disconnect();
|
|
290
508
|
this.themeObserver = null;
|
|
291
|
-
this.colorSchemeMedia?.removeEventListener('change', this.handleAmbientChange);
|
|
292
|
-
this.colorSchemeMedia = null;
|
|
293
509
|
}
|
|
294
510
|
|
|
295
511
|
private observeReducedMotion(): void {
|