Skip to content

Commit cb01010

Browse files
authored
Merge branch 'staging/chansey' into feat/code-block/extensions
2 parents 48a9c8c + 0be8bc1 commit cb01010

File tree

8 files changed

+85
-97
lines changed

8 files changed

+85
-97
lines changed

.changeset/light-rice-warn.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@rhds/elements": patch
3+
---
4+
5+
Context: aligned context implementation with updated [protocol defintions](https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md#definitions)

lib/context/color/consumer.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
type ColorContextOptions
77
} from './controller.js';
88

9-
import { ContextEvent } from '../event.js';
9+
import { ContextRequestEvent } from '../event.js';
1010

1111
/**
1212
* A Color theme is a context-specific restriction on the available color palettes
@@ -52,13 +52,14 @@ export class ColorContextConsumer<
5252
#override: ColorTheme | null = null;
5353

5454
constructor(host: T, private options?: ColorContextConsumerOptions<T>) {
55-
super(host, options);
55+
super(host);
5656
this.#propertyName = options?.propertyName ?? 'on' as keyof T;
5757
}
5858

5959
/** When a consumer connects, it requests colour context from the closest provider. */
6060
async hostConnected() {
61-
const event = new ContextEvent(this.context, e => this.#contextCallback(e), true);
61+
const { context } = ColorContextController;
62+
const event = new ContextRequestEvent(context, e => this.#contextCallback(e), true);
6263
this.#override = this.#propertyValue;
6364
contextEvents.set(this.host, event);
6465
await this.host.updateComplete;

lib/context/color/controller.ts

+20-26
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,9 @@ import type { ReactiveController, ReactiveElement } from 'lit';
33

44
import { StyleController } from '@patternfly/pfe-core/controllers/style-controller.js';
55

6-
import {
7-
createContext,
8-
ContextEvent,
9-
type Context,
10-
type UnknownContext,
11-
} from '../event.js';
6+
import { createContext, type ContextRequestEvent } from '../event.js';
127

13-
import CONTEXT_BASE_STYLES from './context-color.css';
8+
import COLOR_CONTEXT_BASE_STYLES from './context-color.css';
149

1510
export interface ColorContextOptions<T extends ReactiveElement> {
1611
prefix?: string;
@@ -28,12 +23,15 @@ export interface ColorContextOptions<T extends ReactiveElement> {
2823
* ```html
2924
* <early-provider>
3025
* <late-provider>
31-
* <eager-consumer>
26+
* <eager-consumer></eager-consumer>
3227
* </late-provider>
3328
* </early-provider>
3429
* ```
3530
*/
36-
export const contextEvents = new Map<ReactiveElement, ContextEvent<UnknownContext>>();
31+
export const contextEvents = new Map<
32+
ReactiveElement,
33+
ContextRequestEvent<typeof ColorContextController.context>
34+
>();
3735

3836
/**
3937
* Color context is derived from the `--context` css custom property,
@@ -47,26 +45,22 @@ export const contextEvents = new Map<ReactiveElement, ContextEvent<UnknownContex
4745
export abstract class ColorContextController<
4846
T extends ReactiveElement
4947
> implements ReactiveController {
50-
abstract update(next?: ColorTheme | null): void;
48+
/** The context object which acts as the key for providers and consumers */
49+
public static readonly context = createContext<ColorTheme | null>(Symbol('rh-color-context'));
5150

52-
/** The context object which describes the host's colour context */
53-
protected context: Context<ColorTheme | null>;
51+
/** The style controller which provides the necessary CSS. */
52+
protected styleController: StyleController;
5453

55-
/** The style controller which provides the necessary CSS. */
56-
protected styleController: StyleController;
54+
/** The last-known color context on the host */
55+
protected last: ColorTheme | null = null;
5756

58-
/** Prefix for colour context. Set this in Options to create a separate context */
59-
protected prefix = 'rh-';
57+
hostUpdate?(): void
6058

61-
/** The last-known color context on the host */
62-
protected last: ColorTheme | null = null;
59+
/** callback which updates the context value on consumers */
60+
abstract update(next?: ColorTheme | null): void;
6361

64-
hostUpdate?(): void
65-
66-
constructor(protected host: T, options?: ColorContextOptions<T>) {
67-
this.prefix = options?.prefix ?? 'rh';
68-
this.context = createContext(`${this.prefix}-color-context`);
69-
this.styleController = new StyleController(host, CONTEXT_BASE_STYLES);
70-
host.addController(this);
71-
}
62+
constructor(protected host: T) {
63+
this.styleController = new StyleController(host, COLOR_CONTEXT_BASE_STYLES);
64+
host.addController(this);
65+
}
7266
}

lib/context/color/provider.ts

+12-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { ReactiveController, ReactiveElement } from 'lit';
2-
import type { Context, ContextCallback, ContextEvent, UnknownContext } from '../event.js';
2+
import type { ContextCallback, ContextRequestEvent, UnknownContext } from '../event.js';
33

44
import {
55
contextEvents,
@@ -85,9 +85,11 @@ export class ColorContextProvider<
8585
}
8686

8787
constructor(host: T, options?: ColorContextProviderOptions<T>) {
88-
const { attribute = 'color-palette', ...rest } = options ?? {};
89-
super(host, rest);
90-
this.#consumer = new ColorContextConsumer(host, { callback: value => this.update(value) });
88+
const { attribute = 'color-palette' } = options ?? {};
89+
super(host);
90+
this.#consumer = new ColorContextConsumer(host, {
91+
callback: value => this.update(value),
92+
});
9193
this.#logger = new Logger(host);
9294
this.#style = window.getComputedStyle(host);
9395
this.#attribute = attribute;
@@ -102,7 +104,7 @@ export class ColorContextProvider<
102104
* in case this context provider upgraded after and is closer to a given consumer.
103105
*/
104106
async hostConnected() {
105-
this.host.addEventListener('context-request', e => this.#onChildContextEvent(e));
107+
this.host.addEventListener('context-request', e => this.#onChildContextRequestEvent(e));
106108
this.#mo.observe(this.host, { attributes: true, attributeFilter: [this.#attribute] });
107109
for (const [host, fired] of contextEvents) {
108110
host.dispatchEvent(fired);
@@ -129,20 +131,17 @@ export class ColorContextProvider<
129131

130132
/** Was the context event fired requesting our colour-context context? */
131133
#isColorContextEvent(
132-
event: ContextEvent<UnknownContext>
133-
): event is ContextEvent<Context<ColorTheme | null>> {
134-
return (
135-
event.target !== this.host &&
136-
event.context.name === this.context.name
137-
);
134+
event: ContextRequestEvent<UnknownContext>
135+
): event is ContextRequestEvent<typeof ColorContextController.context> {
136+
return event.target !== this.host && event.context === ColorContextController.context;
138137
}
139138

140139
/**
141140
* Provider part of context API
142141
* When a child connects, claim its context-request event
143142
* and add its callback to the Set of children if it requests multiple updates
144143
*/
145-
async #onChildContextEvent(event: ContextEvent<UnknownContext>) {
144+
async #onChildContextRequestEvent(event: ContextRequestEvent<UnknownContext>) {
146145
// only handle ContextEvents relevant to colour context
147146
if (this.#isColorContextEvent(event)) {
148147
// claim the context-request event for ourselves (required by context protocol)
@@ -152,7 +151,7 @@ export class ColorContextProvider<
152151
event.callback(this.value);
153152

154153
// Cache the callback for future updates, if requested
155-
if (event.multiple) {
154+
if (event.subscribe) {
156155
this.#callbacks.add(event.callback);
157156
}
158157
}

lib/context/event.ts

+22-24
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,35 @@
44
*/
55

66
/**
7-
* A Context object defines an optional initial value for a Context, as well as a name identifier for debugging purposes.
7+
* A context key.
8+
*
9+
* A context key can be any type of object, including strings and symbols. The
10+
* Context type brands the key type with the `__context__` property that
11+
* carries the type of the value the context references.
812
*/
9-
export type Context<T> = {
10-
name: string;
11-
initialValue?: T;
13+
export type Context<KeyType, ValueType> = KeyType & {
14+
__context__: ValueType;
1215
};
1316

1417
/**
15-
* An unknown context typeU
18+
* An unknown context type
1619
*/
17-
export type UnknownContext = Context<unknown>;
20+
export type UnknownContext = Context<unknown, unknown>;
1821

1922
/**
2023
* A helper type which can extract a Context value type from a Context type
2124
*/
22-
export type ContextType<T extends UnknownContext> = T extends Context<infer Y>
23-
? Y
25+
export type ContextType<T extends UnknownContext> = T extends Context<infer _, infer V>
26+
? V
2427
: never;
2528

2629
/**
2730
* A function which creates a Context value object
2831
*/
29-
export function createContext<T>(
30-
name: string,
31-
initialValue?: T
32-
): Readonly<Context<T>> {
33-
return {
34-
name,
35-
initialValue,
36-
};
32+
export function createContext<ValueType>(
33+
key: unknown,
34+
): Readonly<Context<typeof key, ValueType>> {
35+
return key as Context<typeof key, ValueType>;
3736
}
3837

3938
/**
@@ -42,7 +41,7 @@ export function createContext<T>(
4241
*/
4342
export type ContextCallback<ValueType> = (
4443
value: ValueType,
45-
dispose?: () => void
44+
unsubscribe?: () => void
4645
) => void;
4746

4847
/**
@@ -51,15 +50,15 @@ export type ContextCallback<ValueType> = (
5150
* A provider should inspect the `context` property of the event to determine if it has a value that can
5251
* satisfy the request, calling the `callback` with the requested value if so.
5352
*
54-
* If the requested context event contains a truthy `multiple` value, then a provider can call the callback
55-
* multiple times if the value is changed, if this is the case the provider should pass a `dispose`
56-
* method to the callback which requesters can invoke to indicate they no longer wish to receive these updates.
53+
* If the requested context event contains a truthy `subscribe` value, then a provider can call the callback
54+
* multiple times if the value is changed, if this is the case the provider should pass an `unsubscribe`
55+
* function to the callback which requesters can invoke to indicate they no longer wish to receive these updates.
5756
*/
58-
export class ContextEvent<T extends UnknownContext> extends Event {
57+
export class ContextRequestEvent<T extends UnknownContext> extends Event {
5958
public constructor(
6059
public readonly context: T,
6160
public readonly callback: ContextCallback<ContextType<T>>,
62-
public readonly multiple?: boolean
61+
public readonly subscribe?: boolean
6362
) {
6463
super('context-request', { bubbles: true, composed: true });
6564
}
@@ -71,7 +70,6 @@ declare global {
7170
* A 'context-request' event can be emitted by any element which desires
7271
* a context value to be injected by an external provider.
7372
*/
74-
'context-request': ContextEvent<UnknownContext>;
73+
'context-request': ContextRequestEvent<Context<unknown, unknown>>;
7574
}
7675
}
77-

lib/context/headings/consumer.ts

+8-10
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
1-
import {
2-
ContextEvent,
3-
type Context,
4-
} from '../event.js';
1+
import { ContextRequestEvent } from '../event.js';
52

6-
import {
7-
contextEvents,
8-
HeadingLevelController,
9-
} from './controller.js';
3+
import { contextEvents, HeadingLevelController } from './controller.js';
104

115
export interface HeadingTemplateOptions {
126
id?: string;
@@ -23,8 +17,12 @@ export class HeadingLevelContextConsumer extends HeadingLevelController {
2317

2418
/** When a consumer connects, it requests context from the closest provider. */
2519
hostConnected() {
26-
const event = new ContextEvent<Context<number>>(this.context, e =>
27-
this.#contextCallback(e), true);
20+
const { context } = HeadingLevelController;
21+
const event = new ContextRequestEvent<typeof context>(
22+
context,
23+
e => this.#contextCallback(e),
24+
true,
25+
);
2826
this.host.dispatchEvent(event);
2927
contextEvents.set(this.host, event);
3028
}

lib/context/headings/controller.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ifDefined } from 'lit/directives/if-defined.js';
44

55
import {
66
createContext,
7-
type ContextEvent,
7+
type ContextRequestEvent,
88
type UnknownContext,
99
} from '../event.js';
1010

@@ -41,24 +41,22 @@ export interface HeadingLevelContextOptions {
4141
* ```html
4242
* <early-provider>
4343
* <late-provider>
44-
* <eager-consumer>
44+
* <eager-consumer></eager-consumer>
4545
* </late-provider>
4646
* </early-provider>
4747
* ```
4848
*/
49-
export const contextEvents = new Map<ReactiveElement, ContextEvent<UnknownContext>>();
49+
export const contextEvents = new Map<ReactiveElement, ContextRequestEvent<UnknownContext>>();
5050

5151
/**
5252
* Determines which heading level immediately precedes the host element,
5353
* and provides templates for shadow headings.
5454
*/
5555
export class HeadingLevelController implements ReactiveController {
56-
static get CONTEXT() { return 'rh-heading-levels'; }
56+
public static readonly context = createContext<number>(Symbol('rh-heading-level-context'));
5757

5858
public offset: number;
5959

60-
protected context = createContext<number>(HeadingLevelController.CONTEXT);
61-
6260
#level = 1;
6361

6462
get level(): number { return this.#level; }

lib/context/headings/provider.ts

+10-15
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { contextEvents, HeadingLevelController } from './controller.js';
22

33
import {
4-
ContextEvent,
5-
type Context,
4+
ContextRequestEvent,
65
type UnknownContext,
76
type ContextCallback,
87
} from '../event.js';
@@ -36,8 +35,7 @@ export class HeadingLevelContextProvider extends HeadingLevelController {
3635
#callbacks = new Set<ContextCallback<number>>();
3736

3837
hostConnected() {
39-
this.host.addEventListener('context-request', e =>
40-
this.#onChildContextEvent(e as ContextEvent<UnknownContext>));
38+
this.host.addEventListener('context-request', e => this.#onChildContextRequestEvent(e));
4139
for (const [host, fired] of contextEvents) {
4240
host.dispatchEvent(fired);
4341
}
@@ -63,26 +61,23 @@ export class HeadingLevelContextProvider extends HeadingLevelController {
6361
}
6462

6563
/** Was the context event fired requesting our colour-context context? */
66-
#isHeadingContextEvent(
67-
event: ContextEvent<UnknownContext>
68-
): event is ContextEvent<Context<number>> {
69-
return (
70-
event.target !== this.host &&
71-
event.context.name === this.context.name
72-
);
64+
#isHeadingContextRequestEvent(
65+
event: ContextRequestEvent<UnknownContext>
66+
): event is ContextRequestEvent<typeof HeadingLevelController.context> {
67+
return event.target !== this.host && event.context === HeadingLevelController.context;
7368
}
7469

75-
async #onChildContextEvent(event: ContextEvent<UnknownContext>) {
76-
// only handle ContextEvents relevant to colour context
77-
if (this.#isHeadingContextEvent(event)) {
70+
async #onChildContextRequestEvent(event: ContextRequestEvent<UnknownContext>) {
71+
// only handle ContextRequestEvents relevant to colour context
72+
if (this.#isHeadingContextRequestEvent(event)) {
7873
// claim the context-request event for ourselves (required by context protocol)
7974
event.stopPropagation();
8075

8176
// Run the callback to initialize the child's value
8277
event.callback(this.level);
8378

8479
// Cache the callback for future updates, if requested
85-
if (event.multiple) {
80+
if (event.subscribe) {
8681
this.#callbacks.add(event.callback);
8782
}
8883
}

0 commit comments

Comments
 (0)