Skip to content

Commit be0119f

Browse files
authored
[labs] Adds initial implementation of @lit-labs/context (lit#1955)
1 parent 4f1b923 commit be0119f

29 files changed

+2701
-10
lines changed

.changeset/tiny-lies-grin.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@lit-labs/context': minor
3+
---
4+
5+
Initial release

.eslintignore

+6
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ packages/labs/analyzer/**/index.js.map
138138
packages/labs/analyzer/**/index.d.ts
139139
packages/labs/analyzer/**/index.d.ts.map
140140

141+
packages/labs/context/development/
142+
packages/labs/context/test/
143+
packages/labs/context/node_modules/
144+
packages/labs/context/lib/
145+
packages/labs/context/index.*
146+
packages/labs/context/context.*
141147
packages/labs/eleventy-plugin-lit/development/
142148
packages/labs/eleventy-plugin-lit/demo/_site/
143149
packages/labs/eleventy-plugin-lit/demo/_js/*.bundle.js

.prettierignore

+6
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ packages/labs/analyzer/**/index.js.map
124124
packages/labs/analyzer/**/index.d.ts
125125
packages/labs/analyzer/**/index.d.ts.map
126126

127+
packages/labs/context/development/
128+
packages/labs/context/test/
129+
packages/labs/context/node_modules/
130+
packages/labs/context/lib/
131+
packages/labs/context/index.*
132+
packages/labs/context/context.*
127133
packages/labs/eleventy-plugin-lit/development/
128134
packages/labs/eleventy-plugin-lit/demo/_site/
129135
packages/labs/eleventy-plugin-lit/demo/_js/*.bundle.js

package-lock.json

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
"name": "lit-monorepo",
33
"private": true,
44
"type": "module",
5+
"engines": {
6+
"npm": "^7.0.0"
7+
},
58
"scripts": {
69
"benchmarks": "cd packages/benchmarks && npm run benchmarks",
710
"bootstrap": "lerna bootstrap --ci",

packages/labs/analyzer/CHANGELOG.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# @lit-labs/analyzer
22

33
## 0.1.0
4-
### Minor Changes
5-
64

5+
### Minor Changes
76

87
- [#2676](https://github.com/lit/lit/pull/2676) [`df10f6d3`](https://github.com/lit/lit/commit/df10f6d34bd59b736cb723250e0b02b2cc5012e3) - Add initial Analyzer class

packages/labs/context/.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/development/
2+
/test/
3+
/node_modules/
4+
/lib/
5+
/index.*
6+
/context.*

packages/labs/context/LICENSE

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
BSD 3-Clause License
2+
3+
Copyright (c) 2021 Google LLC. All rights reserved.
4+
5+
Redistribution and use in source and binary forms, with or without
6+
modification, are permitted provided that the following conditions are met:
7+
8+
1. Redistributions of source code must retain the above copyright notice, this
9+
list of conditions and the following disclaimer.
10+
11+
2. Redistributions in binary form must reproduce the above copyright notice,
12+
this list of conditions and the following disclaimer in the documentation
13+
and/or other materials provided with the distribution.
14+
15+
3. Neither the name of the copyright holder nor the names of its
16+
contributors may be used to endorse or promote products derived from
17+
this software without specific prior written permission.
18+
19+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

packages/labs/context/README.md

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# @lit-labs/context
2+
3+
## Overview
4+
5+
This module defines an implementation of controllers and decorators for using the [Context Protocol](https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md) as defined by the Web Components Community Group.
6+
7+
This protocol facilitates the communication between components lower in the DOM hierarchy with their ancestors, allowing data to be passed down the tree without having to be passed via 'prop drilling' where each element in the path passes on the data.
8+
9+
For further explanation of the Context Protocol please see the [community protocol documentation](https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md).
10+
11+
## Usage
12+
13+
There are several different usages of the Context API.
14+
15+
### Creating a Context
16+
17+
First lets define a context key we can use elsewhere in our examples:
18+
19+
#### **`logger.ts`**:
20+
21+
```ts
22+
import {createContext} from '@lit-labs/context';
23+
24+
export interface Logger {
25+
log: (msg: string) => void;
26+
}
27+
28+
export const loggerContext = createContext<Logger>('logger');
29+
```
30+
31+
### Consuming a Context
32+
33+
Now we can define a consumer for this context - some component in our app needs the logger.
34+
35+
Here we're using the `@contextRequest` property decorator to make a `ContextConsumer` controller
36+
and update its value when the context changes:
37+
38+
#### **`my-element.ts`**:
39+
40+
```ts
41+
import {contextRequest} from '@lit-labs/context';
42+
import {LitElement, property} from 'lit';
43+
import {Logger, loggerContext} from './logger.js';
44+
45+
export class MyElement extends LitElement {
46+
@contextRequest({context: loggerContext, subscribe: true})
47+
@property({attribute: false})
48+
public logger?: Logger;
49+
50+
private doThing() {
51+
this.logger?.log('a thing was done');
52+
}
53+
}
54+
```
55+
56+
Another way we can use a context in a component is via the `ContextConsumer` controller directly:
57+
58+
#### **`my-element.ts`**:
59+
60+
```ts
61+
import {ContextConsumer, property} from '@lit-labs/context';
62+
import {LitElement} from 'lit';
63+
import {Logger, loggerContext} from './logger.js';
64+
65+
export class MyElement extends LitElement {
66+
public logger = new ContextConsumer(
67+
this,
68+
loggerContext,
69+
undefined, // don't need to pass a callback
70+
true // pass true to get updates if the logger changes
71+
);
72+
73+
private doThing() {
74+
this.logger.value?.log('a thing was done');
75+
}
76+
}
77+
```
78+
79+
### Providing a Context
80+
81+
Finally we want to be able to provide this context from somewhere higher in the DOM.
82+
83+
Here we're using a `@contextProvider` property decorator to make a `ContextProvider`
84+
controller and update its value when the property value changes.
85+
86+
#### **`my-app.ts`**:
87+
88+
```ts
89+
import {LitElement} from 'lit';
90+
import {contextProvider} from '@lit-labs/context';
91+
import {loggerContext, Logger} from './my-logger.js';
92+
93+
export class MyApp extends LitElement {
94+
@contextProvider({context: loggerContext})
95+
@property({attribute: false})
96+
public logger: Logger = {
97+
log: (msg) => {
98+
console.log(`[my-app] ${msg}`);
99+
},
100+
});
101+
102+
protected render(): TemplateResult {
103+
return html`<my-thing></my-thing>`;
104+
}
105+
}
106+
```
107+
108+
We can also use the `ContextProvider` controller directly:
109+
110+
#### **`my-app.ts`**:
111+
112+
```ts
113+
import {LitElement} from 'lit';
114+
import {ContextProvider} from '@lit-labs/context';
115+
import {loggerContext, Logger} from './my-logger.js';
116+
117+
export class MyApp extends LitElement {
118+
// create a provider controller and a default logger
119+
private provider = new ContextProvider(this, loggerContext, {
120+
log: (msg) => {
121+
console.log(`[my-app] ${msg}`);
122+
},
123+
});
124+
125+
protected render(): TemplateResult {
126+
return html`<my-thing></my-thing>`;
127+
}
128+
129+
public setLogger(newLogger: Logger) {
130+
// update the provider with a new logger value
131+
this.provider.setValue(newLogger);
132+
}
133+
}
134+
```
135+
136+
## Known Issues
137+
138+
### Late upgraded Context Providers
139+
140+
In some cases you might have a context providing element that is upgraded late. LightDOM content below this provider may end up requesting a context that is currently not provided by any provider.
141+
142+
To solve this case we provide a `ContextRoot` class which can intercept and track unsatisfied `context-request` events and then redispatch these requests when providers are updated.
143+
144+
Example usage:
145+
146+
#### **`index.ts`**:
147+
148+
```ts
149+
import {ContextRoot} from '@lit-labs/context';
150+
const root = new ContextRoot();
151+
root.attach(document.body);
152+
```
153+
154+
The `ContextRoot` can be attached to any element and it will gather a list of any context requests which are received at the attached element. The `ContextProvider` controllers will emit `context-provider` events when they are connected to the DOM. These events act as triggers for the `ContextRoot` to redispatch these `context-request` events from their sources.
155+
156+
This solution has a small overhead, in that if a provider is not within the DOM hierarchy of the unsatisfied requests we are unnecessarily refiring these requests, but this approach is safest and most correct in that it is very hard to manage stable DOM hierarchies with the semantics of slotting and reparenting that is common in web components implementations.
157+
158+
## Contributing
159+
160+
Please see [CONTRIBUTING.md](../../../CONTRIBUTING.md).

0 commit comments

Comments
 (0)