@dxos/web-context 0.0.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/LICENSE +8 -0
- package/README.md +49 -0
- package/package.json +31 -0
- package/src/index.ts +5 -0
- package/src/protocol.test.ts +312 -0
- package/src/protocol.ts +115 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
Copyright (c) 2025 DXOS
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
5
|
+
|
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @dxos/web-context
|
|
2
|
+
|
|
3
|
+
Framework-agnostic definitions for the Web Component Context Protocol.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This package provides the core types and event definitions for the [Web Component Context Protocol](https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md). It is intended to be used by framework-specific implementations (providers and consumers) to ensure interoperability.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @dxos/web-context
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Context Definitions
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { createContext } from '@dxos/web-context';
|
|
21
|
+
|
|
22
|
+
export const ThemeContext = createContext<{ color: string }>('theme');
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Requesting Context
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { ContextRequestEvent } from '@dxos/web-context';
|
|
29
|
+
|
|
30
|
+
const event = new ContextRequestEvent(ThemeContext, (value, unsubscribe) => {
|
|
31
|
+
console.log('Context value:', value);
|
|
32
|
+
// Optional: unsubscribe()
|
|
33
|
+
}, { subscribe: true });
|
|
34
|
+
|
|
35
|
+
element.dispatchEvent(event);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Providing Context
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { ContextProviderEvent } from '@dxos/web-context';
|
|
42
|
+
|
|
43
|
+
element.addEventListener('context-request', (event) => {
|
|
44
|
+
if (event.context === ThemeContext) {
|
|
45
|
+
event.stopPropagation();
|
|
46
|
+
event.callback({ color: 'blue' });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dxos/web-context",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Web Component Context Protocol definitions",
|
|
5
|
+
"homepage": "https://dxos.org",
|
|
6
|
+
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "DXOS.org",
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"type": "module",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"source": "./src/index.ts",
|
|
14
|
+
"types": "./dist/types/src/index.d.ts",
|
|
15
|
+
"browser": "./dist/lib/browser/index.mjs",
|
|
16
|
+
"node": "./dist/lib/node-esm/index.mjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"types": "dist/types/src/index.d.ts",
|
|
20
|
+
"typesVersions": {
|
|
21
|
+
"*": {}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"src"
|
|
26
|
+
],
|
|
27
|
+
"devDependencies": {},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
CONTEXT_REQUEST_EVENT,
|
|
9
|
+
type ContextCallback,
|
|
10
|
+
ContextRequestEvent,
|
|
11
|
+
type ContextType,
|
|
12
|
+
type UnknownContext,
|
|
13
|
+
createContext,
|
|
14
|
+
} from './protocol';
|
|
15
|
+
|
|
16
|
+
describe('protocol', () => {
|
|
17
|
+
describe('createContext', () => {
|
|
18
|
+
test('creates a context with string key', () => {
|
|
19
|
+
const ctx = createContext<number>('my-context');
|
|
20
|
+
expect(ctx).toBe('my-context');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('creates a context with symbol key', () => {
|
|
24
|
+
const key = Symbol('my-context');
|
|
25
|
+
const ctx = createContext<string>(key);
|
|
26
|
+
expect(ctx).toBe(key);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('creates a context with object key', () => {
|
|
30
|
+
const key = { name: 'my-context' };
|
|
31
|
+
const ctx = createContext<boolean>(key);
|
|
32
|
+
expect(ctx).toBe(key);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('contexts with same string key are strictly equal', () => {
|
|
36
|
+
const ctx1 = createContext<number>('shared-key');
|
|
37
|
+
const ctx2 = createContext<number>('shared-key');
|
|
38
|
+
expect(ctx1 === ctx2).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('contexts with unique symbols are not equal', () => {
|
|
42
|
+
const ctx1 = createContext<number>(Symbol('unique'));
|
|
43
|
+
const ctx2 = createContext<number>(Symbol('unique'));
|
|
44
|
+
expect(ctx1 === ctx2).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('contexts with Symbol.for are equal', () => {
|
|
48
|
+
const ctx1 = createContext<number>(Symbol.for('shared'));
|
|
49
|
+
const ctx2 = createContext<number>(Symbol.for('shared'));
|
|
50
|
+
expect(ctx1 === ctx2).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('ContextRequestEvent', () => {
|
|
55
|
+
test('creates event with correct type', () => {
|
|
56
|
+
const ctx = createContext<string>('test');
|
|
57
|
+
const target = document.createElement('div');
|
|
58
|
+
const callback = vi.fn();
|
|
59
|
+
const event = new ContextRequestEvent(ctx, callback, { target });
|
|
60
|
+
|
|
61
|
+
expect(event.type).toBe('context-request');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('event bubbles', () => {
|
|
65
|
+
const ctx = createContext<string>('test');
|
|
66
|
+
const target = document.createElement('div');
|
|
67
|
+
const callback = vi.fn();
|
|
68
|
+
const event = new ContextRequestEvent(ctx, callback, { target });
|
|
69
|
+
|
|
70
|
+
expect(event.bubbles).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('event is composed (crosses shadow DOM boundaries)', () => {
|
|
74
|
+
const ctx = createContext<string>('test');
|
|
75
|
+
const target = document.createElement('div');
|
|
76
|
+
const callback = vi.fn();
|
|
77
|
+
const event = new ContextRequestEvent(ctx, callback, { target });
|
|
78
|
+
|
|
79
|
+
expect(event.composed).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('carries context key', () => {
|
|
83
|
+
const ctx = createContext<string>('my-key');
|
|
84
|
+
const target = document.createElement('div');
|
|
85
|
+
const callback = vi.fn();
|
|
86
|
+
const event = new ContextRequestEvent(ctx, callback, { target });
|
|
87
|
+
|
|
88
|
+
expect(event.context).toBe(ctx);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('carries contextTarget', () => {
|
|
92
|
+
const ctx = createContext<string>('test');
|
|
93
|
+
const target = document.createElement('div');
|
|
94
|
+
const callback = vi.fn();
|
|
95
|
+
const event = new ContextRequestEvent(ctx, callback, { target });
|
|
96
|
+
|
|
97
|
+
expect(event.contextTarget).toBe(target);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('carries callback', () => {
|
|
101
|
+
const ctx = createContext<string>('test');
|
|
102
|
+
const target = document.createElement('div');
|
|
103
|
+
const callback = vi.fn();
|
|
104
|
+
const event = new ContextRequestEvent(ctx, callback, { target });
|
|
105
|
+
|
|
106
|
+
expect(event.callback).toBe(callback);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('subscribe defaults to undefined', () => {
|
|
110
|
+
const ctx = createContext<string>('test');
|
|
111
|
+
const target = document.createElement('div');
|
|
112
|
+
const callback = vi.fn();
|
|
113
|
+
const event = new ContextRequestEvent(ctx, callback, { target });
|
|
114
|
+
|
|
115
|
+
expect(event.subscribe).toBeUndefined();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('subscribe can be set to true', () => {
|
|
119
|
+
const ctx = createContext<string>('test');
|
|
120
|
+
const target = document.createElement('div');
|
|
121
|
+
const callback = vi.fn();
|
|
122
|
+
const event = new ContextRequestEvent(ctx, callback, { subscribe: true, target });
|
|
123
|
+
|
|
124
|
+
expect(event.subscribe).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('subscribe can be set to false', () => {
|
|
128
|
+
const ctx = createContext<string>('test');
|
|
129
|
+
const target = document.createElement('div');
|
|
130
|
+
const callback = vi.fn();
|
|
131
|
+
const event = new ContextRequestEvent(ctx, callback, { subscribe: false, target });
|
|
132
|
+
|
|
133
|
+
expect(event.subscribe).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('ContextRequestEvent integration', () => {
|
|
138
|
+
test('event bubbles through DOM', () => {
|
|
139
|
+
const ctx = createContext<string>('test');
|
|
140
|
+
const callback = vi.fn();
|
|
141
|
+
|
|
142
|
+
const parent = document.createElement('div');
|
|
143
|
+
const child = document.createElement('div');
|
|
144
|
+
parent.appendChild(child);
|
|
145
|
+
document.body.appendChild(parent);
|
|
146
|
+
|
|
147
|
+
const handler = vi.fn((e: Event) => {
|
|
148
|
+
const event = e as ContextRequestEvent<typeof ctx>;
|
|
149
|
+
if (event.context === ctx) {
|
|
150
|
+
event.stopImmediatePropagation();
|
|
151
|
+
event.callback('provided-value');
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
parent.addEventListener(CONTEXT_REQUEST_EVENT, handler);
|
|
156
|
+
|
|
157
|
+
const event = new ContextRequestEvent(ctx, callback, { target: child });
|
|
158
|
+
child.dispatchEvent(event);
|
|
159
|
+
|
|
160
|
+
expect(handler).toHaveBeenCalled();
|
|
161
|
+
expect(callback).toHaveBeenCalledWith('provided-value');
|
|
162
|
+
|
|
163
|
+
// Cleanup
|
|
164
|
+
parent.removeEventListener(CONTEXT_REQUEST_EVENT, handler);
|
|
165
|
+
document.body.removeChild(parent);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('stopImmediatePropagation prevents other handlers', () => {
|
|
169
|
+
const ctx = createContext<string>('test');
|
|
170
|
+
const callback = vi.fn();
|
|
171
|
+
|
|
172
|
+
const grandparent = document.createElement('div');
|
|
173
|
+
const parent = document.createElement('div');
|
|
174
|
+
const child = document.createElement('div');
|
|
175
|
+
grandparent.appendChild(parent);
|
|
176
|
+
parent.appendChild(child);
|
|
177
|
+
document.body.appendChild(grandparent);
|
|
178
|
+
|
|
179
|
+
const parentHandler = vi.fn((e: Event) => {
|
|
180
|
+
const event = e as ContextRequestEvent<typeof ctx>;
|
|
181
|
+
if (event.context === ctx) {
|
|
182
|
+
event.stopImmediatePropagation();
|
|
183
|
+
event.callback('parent-value');
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const grandparentHandler = vi.fn((e: Event) => {
|
|
188
|
+
const event = e as ContextRequestEvent<typeof ctx>;
|
|
189
|
+
if (event.context === ctx) {
|
|
190
|
+
event.callback('grandparent-value');
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
parent.addEventListener(CONTEXT_REQUEST_EVENT, parentHandler);
|
|
195
|
+
grandparent.addEventListener(CONTEXT_REQUEST_EVENT, grandparentHandler);
|
|
196
|
+
|
|
197
|
+
const event = new ContextRequestEvent(ctx, callback, { target: child });
|
|
198
|
+
child.dispatchEvent(event);
|
|
199
|
+
|
|
200
|
+
expect(parentHandler).toHaveBeenCalled();
|
|
201
|
+
expect(grandparentHandler).not.toHaveBeenCalled();
|
|
202
|
+
expect(callback).toHaveBeenCalledWith('parent-value');
|
|
203
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
204
|
+
|
|
205
|
+
// Cleanup
|
|
206
|
+
parent.removeEventListener(CONTEXT_REQUEST_EVENT, parentHandler);
|
|
207
|
+
grandparent.removeEventListener(CONTEXT_REQUEST_EVENT, grandparentHandler);
|
|
208
|
+
document.body.removeChild(grandparent);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('provider can invoke callback multiple times for subscriptions', () => {
|
|
212
|
+
const ctx = createContext<number>('counter');
|
|
213
|
+
const callback = vi.fn();
|
|
214
|
+
const unsubscribe = vi.fn();
|
|
215
|
+
|
|
216
|
+
const parent = document.createElement('div');
|
|
217
|
+
const child = document.createElement('div');
|
|
218
|
+
parent.appendChild(child);
|
|
219
|
+
document.body.appendChild(parent);
|
|
220
|
+
|
|
221
|
+
let storedCallback: ContextCallback<number> | null = null;
|
|
222
|
+
|
|
223
|
+
const handler = vi.fn((e: Event) => {
|
|
224
|
+
const event = e as ContextRequestEvent<typeof ctx>;
|
|
225
|
+
if (event.context === ctx) {
|
|
226
|
+
event.stopImmediatePropagation();
|
|
227
|
+
// Provide initial value
|
|
228
|
+
event.callback(0, unsubscribe);
|
|
229
|
+
// Store callback for future updates if subscribing
|
|
230
|
+
if (event.subscribe) {
|
|
231
|
+
storedCallback = event.callback;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
parent.addEventListener(CONTEXT_REQUEST_EVENT, handler);
|
|
237
|
+
|
|
238
|
+
const event = new ContextRequestEvent(ctx, callback, { subscribe: true, target: child });
|
|
239
|
+
child.dispatchEvent(event);
|
|
240
|
+
|
|
241
|
+
expect(callback).toHaveBeenCalledWith(0, unsubscribe);
|
|
242
|
+
|
|
243
|
+
// Simulate value update
|
|
244
|
+
storedCallback!(1, unsubscribe);
|
|
245
|
+
storedCallback!(2, unsubscribe);
|
|
246
|
+
|
|
247
|
+
expect(callback).toHaveBeenCalledTimes(3);
|
|
248
|
+
expect(callback).toHaveBeenLastCalledWith(2, unsubscribe);
|
|
249
|
+
|
|
250
|
+
// Cleanup
|
|
251
|
+
parent.removeEventListener(CONTEXT_REQUEST_EVENT, handler);
|
|
252
|
+
document.body.removeChild(parent);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('context matching uses strict equality', () => {
|
|
256
|
+
const ctx1 = createContext<string>('test');
|
|
257
|
+
const ctx2 = createContext<string>('test'); // Same key, should match
|
|
258
|
+
const ctx3 = createContext<string>('other');
|
|
259
|
+
|
|
260
|
+
const callback = vi.fn();
|
|
261
|
+
const parent = document.createElement('div');
|
|
262
|
+
const child = document.createElement('div');
|
|
263
|
+
parent.appendChild(child);
|
|
264
|
+
document.body.appendChild(parent);
|
|
265
|
+
|
|
266
|
+
const handler = vi.fn((e: Event) => {
|
|
267
|
+
const event = e as ContextRequestEvent<UnknownContext>;
|
|
268
|
+
// Only respond to ctx1 (or ctx2 since they're equal)
|
|
269
|
+
if (event.context === ctx1) {
|
|
270
|
+
event.stopImmediatePropagation();
|
|
271
|
+
event.callback('matched');
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
parent.addEventListener(CONTEXT_REQUEST_EVENT, handler);
|
|
276
|
+
|
|
277
|
+
// Request with ctx2 (equal to ctx1)
|
|
278
|
+
child.dispatchEvent(new ContextRequestEvent(ctx2, callback, { target: child }));
|
|
279
|
+
expect(callback).toHaveBeenCalledWith('matched');
|
|
280
|
+
|
|
281
|
+
callback.mockClear();
|
|
282
|
+
|
|
283
|
+
// Request with ctx3 (different)
|
|
284
|
+
child.dispatchEvent(new ContextRequestEvent(ctx3, callback, { target: child }));
|
|
285
|
+
expect(callback).not.toHaveBeenCalled();
|
|
286
|
+
|
|
287
|
+
// Cleanup
|
|
288
|
+
parent.removeEventListener(CONTEXT_REQUEST_EVENT, handler);
|
|
289
|
+
document.body.removeChild(parent);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe('Type utilities', () => {
|
|
294
|
+
test('ContextType extracts value type', () => {
|
|
295
|
+
const ctx = createContext<{ name: string }>('user');
|
|
296
|
+
|
|
297
|
+
// This is a compile-time check - if it compiles, the types work
|
|
298
|
+
type ExtractedType = ContextType<typeof ctx>;
|
|
299
|
+
const value: ExtractedType = { name: 'test' };
|
|
300
|
+
expect(value.name).toBe('test');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test('Context type carries both key and value type info', () => {
|
|
304
|
+
const stringCtx = createContext<number>('key');
|
|
305
|
+
const symbolCtx = createContext<string>(Symbol('key'));
|
|
306
|
+
|
|
307
|
+
// These are compile-time checks
|
|
308
|
+
expect(typeof stringCtx).toBe('string');
|
|
309
|
+
expect(typeof symbolCtx).toBe('symbol');
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
});
|
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Web Component Context Protocol Implementation
|
|
7
|
+
*
|
|
8
|
+
* Follows the specification at:
|
|
9
|
+
* https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md
|
|
10
|
+
*
|
|
11
|
+
* Also implements extensions from @lit/context for better interop:
|
|
12
|
+
* - contextTarget property on ContextRequestEvent
|
|
13
|
+
* - ContextProviderEvent for late provider registration
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A context key.
|
|
18
|
+
*
|
|
19
|
+
* A context key can be any type of object, including strings and symbols. The
|
|
20
|
+
* Context type brands the key type with the `__context__` property that
|
|
21
|
+
* carries the type of the value the context references.
|
|
22
|
+
*/
|
|
23
|
+
export type Context<KeyType, ValueType> = KeyType & { __context__: ValueType };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* An unknown context type
|
|
27
|
+
*/
|
|
28
|
+
export type UnknownContext = Context<unknown, unknown>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A helper type which can extract a Context value type from a Context type
|
|
32
|
+
*/
|
|
33
|
+
export type ContextType<T extends UnknownContext> = T extends Context<infer _, infer V> ? V : never;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A function which creates a Context value object
|
|
37
|
+
*/
|
|
38
|
+
export const createContext = <ValueType>(key: unknown) => key as Context<typeof key, ValueType>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A callback which is provided by a context requester and is called with the
|
|
42
|
+
* value satisfying the request. This callback can be called multiple times by
|
|
43
|
+
* context providers as the requested value is changed.
|
|
44
|
+
*/
|
|
45
|
+
export type ContextCallback<ValueType> = (value: ValueType, unsubscribe?: () => void) => void;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* An event fired by a context requester to signal it desires a named context.
|
|
49
|
+
*
|
|
50
|
+
* A provider should inspect the `context` property of the event to determine
|
|
51
|
+
* if it has a value that can satisfy the request, calling the `callback` with
|
|
52
|
+
* the requested value if so.
|
|
53
|
+
*
|
|
54
|
+
* If the requested context event contains a truthy `subscribe` value, then a
|
|
55
|
+
* provider can call the callback multiple times if the value is changed, if
|
|
56
|
+
* this is the case the provider should pass an `unsubscribe` function to the
|
|
57
|
+
* callback which requesters can invoke to indicate they no longer wish to
|
|
58
|
+
* receive these updates.
|
|
59
|
+
*/
|
|
60
|
+
export class ContextRequestEvent<T extends UnknownContext> extends Event {
|
|
61
|
+
/**
|
|
62
|
+
* @param context - The context key being requested
|
|
63
|
+
* @param callback - The callback to invoke with the context value
|
|
64
|
+
* @param options - Options for the request:
|
|
65
|
+
* - `subscribe`: Whether to subscribe to future updates.
|
|
66
|
+
* - `target`: The element that originally requested the context.
|
|
67
|
+
* This is preserved when events are re-dispatched for re-parenting.
|
|
68
|
+
*/
|
|
69
|
+
public readonly subscribe?: boolean;
|
|
70
|
+
public readonly contextTarget?: Element;
|
|
71
|
+
|
|
72
|
+
public constructor(
|
|
73
|
+
public readonly context: T,
|
|
74
|
+
public readonly callback: ContextCallback<ContextType<T>>,
|
|
75
|
+
options?: { subscribe?: boolean; target?: Element },
|
|
76
|
+
) {
|
|
77
|
+
super(CONTEXT_REQUEST_EVENT, { bubbles: true, composed: true });
|
|
78
|
+
this.subscribe = options?.subscribe;
|
|
79
|
+
this.contextTarget = options?.target;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The event name for context requests
|
|
85
|
+
*/
|
|
86
|
+
export const CONTEXT_REQUEST_EVENT = 'context-request' as const;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* An event fired by a context provider to signal it is available.
|
|
90
|
+
*
|
|
91
|
+
* This allows ContextRoot implementations to replay pending context requests
|
|
92
|
+
* when providers are registered after consumers, and allows parent providers
|
|
93
|
+
* to re-parent their subscriptions when a closer provider appears.
|
|
94
|
+
*/
|
|
95
|
+
export class ContextProviderEvent<T extends UnknownContext> extends Event {
|
|
96
|
+
/**
|
|
97
|
+
* @param context - The context key this provider can provide
|
|
98
|
+
* @param contextTarget - The element hosting this provider
|
|
99
|
+
*/
|
|
100
|
+
public constructor(
|
|
101
|
+
public readonly context: T,
|
|
102
|
+
public readonly contextTarget: Element,
|
|
103
|
+
) {
|
|
104
|
+
super('context-provider', { bubbles: true, composed: true });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The event name for context provider announcements
|
|
110
|
+
*/
|
|
111
|
+
export const CONTEXT_PROVIDER_EVENT = 'context-provider' as const;
|
|
112
|
+
|
|
113
|
+
// Note: We don't declare the global HTMLElementEventMap augmentation here
|
|
114
|
+
// to avoid conflicts with @lit/context which also declares it.
|
|
115
|
+
// Both follow the same protocol, so they're compatible.
|