diff --git a/blog/index.html b/blog/index.html index f08457c3..123443e2 100644 --- a/blog/index.html +++ b/blog/index.html @@ -1,4 +1,4 @@ -
So far, every component we’ve seen has worked synchronously, and Crank will respect this as an intentional decision by the developer by keeping the entire process of rendering synchronous from start to finish. However, modern JavaScript includes promises and async
/await
, which allow you to write concurrently executing code as if it were synchronous. To facilitate these features, Crank allows components to be asynchronous functions as well, and we call these components, async function components.
async function IPAddress () {
console.log(document.body.innerHTML); // <div>Your IP Address: 127.0.0.1</div>
})();
When Crank renders an async component anywhere in the tree, the entire process becomes asynchronous. Concretely, this means that renderer.render
or this.refresh
calls return a promise which fulfills when rendering has finished. It also means that no actual DOM updates will be triggered until this moment.
Because async function components can be rerendered while they are still pending, Crank implements a couple rules to make concurrent updates predictable and performant:
console.log(document.body.innerHTML); // "<div>Run 4</div>"
})();
In the preceding example, at no point is there more than one simultaneous call to the Delay
component, despite the fact that it is rerendered concurrently for its second through fourth renders. And because these renderings are enqueued, only the second and fourth renderings have any effect. This is because the element is busy with the second render by the time the third and fourth renderings are requested, and then, only the fourth rendering is actually executed because third rendering’s props are obsolete by the time the component is ready to update again. This behavior allows async components to always be kept up-to-date without producing excess calls to the function.
console.log(document.body.innerHTML); // "<div><span>Fast</span></div>"
})();
As we’ll see later, this ratcheting effect becomes useful for rendering fallback states for async components.
Just as you can write stateful components with sync generator functions, you can also write stateful async components with async generator functions.
console.log(document.body.innerHTML); //<div>The count is now: 2</div>
})();
AsyncLabeledCounter
is an async version of the LabeledCounter
example introduced in the section on props updates. This example demonstrates several key differences between sync and async generator components. Firstly, rather than using while
or for…of
loops as with sync generator components, we now use a for await…of
loop. This is possible because contexts are not just an iterable of props, but also an async iterable of props as well.
Secondly, you’ll notice that the async generator yields multiple times per iteration over this
, once to show a loading message and once to show the actual count. While it is possible for sync generators components to yield multiple times per iteration over this
, it wouldn’t necessarily make sense to do so because generators suspend at each yield, and upon resuming a second time within the same loop, the props would be stale. In contrast, async generator components are continuously resumed. Rather than suspending at each yield, we rely on the for await…of
loop, which suspends at its end until the next update.
The async components we’ve seen so far have been all or nothing, in the sense that Crank can’t show anything until all promises in the tree have fulfilled. This can be a problem when you have an async call which takes longer than expected. It would be nice if parts of the element tree could be shown without waiting, to create responsive user experiences.
However, because loading indicators which show immediately can paradoxically make your app seem less responsive, we use the async rules described previously along with async generator functions to show loading indicators which appear only when certain components take too long.
renderer.render(<RandomDogApp />, document.body);
In this example, the RandomDogLoader
component is an async generator component which races the LoadingIndicator
component with the RandomDog
component. Because the async generator component resumes continuously, both components are rendered, and according to the second rule of async components, the loading indicator only shows if the RandomDog
component takes longer than the LoadingIndicator
component, which fulfills at a fixed interval of one second.
The preceding example hints at how we could abstract this pattern to implement the Suspense
component from React.
);
})();
No special tags are needed for async loading states, and the functionality to write this logic is implemented using the same element diffing algorithm that governs synchronous components. Additionally, this approach is more flexible in the sense that you can extend it; for instance, you can add another yield to the for await…of
loop to show a second fallback state which waits ten seconds, to inform the user that something went wrong or that servers are slow to respond.
No special tags are needed for async loading states, and the functionality to write this logic is implemented using the same element diffing algorithm that governs synchronous components. Additionally, this approach is more flexible in the sense that you can extend it; for instance, you can add another yield to the for await…of
loop to show a second fallback state which waits ten seconds, to inform the user that something went wrong or that servers are slow to respond.
TNode
is the most important type: it is the type of the node associated with each host element. For instance, for the basic DOM renderer, TNode is the DOM Node interface.
TScope
is the type of the scope, a renderer-specific concept for arbitrary data which is passed down the tree between host elements. Scopes are useful for passing contextual information down the tree to be used when nodes are created; for instance, the DOM renderer uses the scope to pass down information about whether we’re currently rendering in an SVG element.
TRoot
is the type of the root node. This is the type of the second parameter passed to the Renderer.render
method, and the root
prop passed to Portal
elements. It is usually the same type as TNode
but can vary according to renderer requirements.
TResult
describes the type of values made visible to renderer consumers. Any time Crank exposes an internal node, for instance, via the crank-ref
callback, or as the result of yield expressions in generator components, the renderer can intercept this access and provide something other than the internal nodes, allowing renderers to hide implementation details and provide results which make more sense for a specific environment.
For example, the HTML string renderer has an internal node representation, but converts these nodes to strings before they’re exposed to consumers. This is because the internal nodes must be a referentially unique object which is mutated during rendering, while JavaScript strings are referentially transparent and immutable. Therefore, the TResult
type of the HTML renderer is string
.
The following is a description of the signatures of internal renderer methods and when they’re executed. When creating a custom renderer, you are expected to override these methods via inheritance.
el: Element<string | symbol>, scope: TScope
): TNode;
The create
method is called for each host element the first time the element is committed. This method is passed the current host element and scope, and should return the node which will be associated with the host element. This node will remain constant for an element for as long as the element is rendered.
By default, this method will throw a Not Implemented
error, so custom renderers should always implement this method.
read(value: Array<TNode | string> | TNode | string | undefined): TResult;
The renderer exposes rendered values in the following places:
render
method.refresh
method.crank-ref
propsschedule
and cleanup
callbacksvalue
getter methodWhen an element or elements are read in this way, we call the read
method to give renderers a final chance to manipulate what is exposed, so as to hide internal implementation details and return something which makes sense for the target environment. The parameter passed to the read
method can be a node, a string, an array of nodes and strings, or undefined
. The return value is what is actually exposed.
This method is optional. By default, read is an identity function which returns the value passed in.
patch(el: Element<string | symbol>, node: TNode): unknown;
The patch
method is called for each host element whenever it is committed. This method is passed the current host element and its related node, and its return value is ignored. This method is usually where you would mutate the internal node according to the props of the host element.
Implementation of this method is optional for renderers.
children: Array<TNode | string>,
): unknown;
The arrange
method is called whenever an element’s children have changed. It is called with the current host element, the host element’s related node, and the rendered values of all the element’s descendants as an array. In addition to when a host element commits, the arrange
method may also be called when a child refreshes or otherwise causes a host element’s rendered children to change. Because the arrange
method is called for every root/portal element, the parent can be of type TRoot
as well as TNode
.
This method is where the magic happens, and is where you connect the nodes of your target environment into an internal tree.
scope(el: Element<string | symbol>, scope: TScope | undefined): TScope;
The scope
method is called for each host or portal element as elements are mounted or updated. Unlike the other custom renderer methods, the scope
method is called during the pre-order traversal of the tree, much as components are. The scope
method is passed the current host element and scope as parameters, and the return value becomes the scope argument passed to the create
and scope
method calls for descendant host elements.
By default, the scope
method returns undefined
, meaning the scope will be undefined
throughout your application.
escape(text: string, scope: TScope): string;
The escape
method is called whenever a string is encountered in the element tree. It is mainly useful when creating string-based renderers like an HTML or XML renderer, because most rendering targets like the DOM provide text node interfaces which sanitize inputs by default.
One important detail is that escape
should not return text nodes or anything besides a string. We defer this step to the arrange
method because this allows the renderer to normalize a host element’s children by concatenating adjacent strings.
By default, the escape
method returns the string which was passed in.
parse(text: string, scope: TScope): TNode | string;
When the renderer encounters a Raw
element whose value
prop is a string, it calls the parse
method with that string and the current scope. The return value should be the parsed node, or it can be a string as well, in which case parse will be handled like a string child by parents.
By default, the parse
method returns the string which was passed in.
dispose(el: Element<string | symbol>, node: TNode): unknown
The dispose
method is called whenever a host element is unmounted. It is called with the host element and its related node. You can use this method to manually release a node or clean up event listeners for garbage collection purposes.
This method is optional and its return value is ignored.
complete(root: TRoot): unknown;
The complete
method is called at the end of every render execution, when all elements have been committed and all other renderer methods have been called. It is useful, for instance, if your rendering target needs some final code to execute before any mutations take effect.
This method is optional and its return value is ignored.
The complete
method is called at the end of every render execution, when all elements have been committed and all other renderer methods have been called. It is useful, for instance, if your rendering target needs some final code to execute before any mutations take effect.
This method is optional and its return value is ignored.
\ No newline at end of file diff --git a/guides/elements/index.html b/guides/elements/index.html index 91e232f0..7be78027 100644 --- a/guides/elements/index.html +++ b/guides/elements/index.html @@ -1,4 +1,4 @@ -When using this method, you do not have to call the removeEventListener()
method if you merely want to remove event listeners when the component is unmounted. This is done automatically.
The context’s addEventListener()
method attaches to the top-level node or nodes which each component renders, so if you want to listen to events on a nested node, you must use event delegation.
addEventListener() method attaches to the top-level node or nodes which each component renders, so if you want to listen to events on a nested node, you must use event delegation.While the
removeEventListener()
method is implemented, you do not have to call theremoveEventListener()
method if you merely want to remove event listeners when the component is unmounted.Because the event listener is attached to the outer
div
, we have to filter events byev.target.tagName
in the listener to make sure we’re not incrementingcount
based on clicks which don’t target thebutton
element.Because the event listener is attached to the outer
div
, we have to filter events byev.target.tagName
in the listener to make sure we’re not incrementingcount
based on clicks which don’t target thebutton
element.Event props vs EventTarget
The props-based event API and the context-based EventTarget API both have their advantages. On the one hand, using event props means you can listen to exactly the element you’d like to listen to.
On the other hand, using the
addEventListener
method allows you to take full advantage of the EventTarget API, which includes registering passive event listeners, or listeners which are dispatched during the capture phase. Additionally, the EventTarget API can be used without referencing or accessing the child elements which a component renders, meaning you can use it to listen to elements nested in other components.Crank supports both API styles for convenience and flexibility.
Dispatching Events
Crank contexts implement the full EventTarget interface, meaning you can use the
dispatchEvent
method and theCustomEvent
class to dispatch custom events to ancestor components: addEventListener method allows you to take full advantage of the EventTarget API, which includes registering passive event listeners, or listeners which are dispatched during the capture phase. Additionally, the EventTarget API can be used without referencing or accessing the child elements which a component renders, meaning you can use it to listen to elements nested in other components.Crank supports both API styles for convenience and flexibility.
Dispatching Events
Crank contexts implement the full EventTarget interface, meaning you can use the
dispatchEvent
method and theCustomEvent
class to dispatch custom events to ancestor components:
MyButton
is a function component which wraps abutton
element. It dispatches aCustomEvent
whose type is"mybuttonclick"
when it is pressed, and whosedetail
property contains data about the ID of the clicked button. This event is not triggered on the underlying DOM nodes; instead, it can be listened for by parent component contexts using event capturing and bubbling, and in the example, the event propagates and is handled by theMyApp
component. Using custom events and event bubbling allows you to encapsulate state transitions within component hierarchies without the need for complex state management solutions used in other frameworks like Redux or VueX.The preceding example also demonstrates a slight difference in the way the
addEventListener
method works in function components compared to generator components. With generator components, listeners stick between renders, and will continue to fire until the component is unmounted. However, with function components, because theaddEventListener
call would be invoked every time the component is rerendered, we remove and add listeners for each render. This allows function components to remain stateless while still listening for and dispatching events.Form Elements
Because Crank uses explicit state updates, it doesn’t have a concept of “controlled” vs “uncontrolled” props like
value
/defaultValue
in React. No refresh means no render. MyButton is a function component which wraps a<button>
element. It dispatches aCustomEvent
whose type is"mybuttonclick"
when it is pressed, and whosedetail
property contains data about the pressed button. This event is not triggered on the underlying DOM nodes; instead, it can be listened for by parent component contexts using event capturing and bubbling, and in the example, the event propagates and is handled by theMyApp
component.Using custom events and event bubbling allows you to encapsulate state transitions within component hierarchies without the need for complex state management solutions used in other frameworks like Redux or VueX.
Form Elements
Because Crank uses explicit state updates, it doesn’t have a concept of “controlled” vs “uncontrolled” props like
value
/defaultValue
in React. No update means the value is uncontrolled.import {renderer} from "@b9g/crank/dom";
function *App() {
function *Form() {
let reset = true;
let reset = false;
this.addEventListener("click", ev => {
const onreset = () => {
if (ev.target.tagName === "BUTTON") {
reset = true;
reset = true;
this.refresh();
this.refresh();
};
}
});
const onsubmit = (ev) => {
ev.preventDefault();
for ({} of this) {
};
yield (
<div>
for ({} of this) {
<input type="text" value="" />
yield (
<p>
<form onsubmit={onsubmit}>
<button>Reset</button>
<input type="text" value="" />
</p>
<p>
</div>
<button onclick={onreset}>Reset</button>
);
</p>
</form>
reset = false;
);
}
renderer.render(<App />, document.body);
Loading...Loading...You can use the
$static
prop to prevent an element from updating.\ No newline at end of file diff --git a/guides/lifecycles/index.html b/guides/lifecycles/index.html index 330b2e44..053593f3 100644 --- a/guides/lifecycles/index.html +++ b/guides/lifecycles/index.html @@ -1,4 +1,4 @@ -\ No newline at end of file diff --git a/guides/jsx-template-tag/index.html b/guides/jsx-template-tag/index.html index bbcdebd8..84fd00d7 100644 --- a/guides/jsx-template-tag/index.html +++ b/guides/jsx-template-tag/index.html @@ -1,4 +1,4 @@ -If your component is updating for other reasons, you can use the special property
$static
to prevent the input element from updating.import {renderer} from "@b9g/crank/dom";
function *
Loading...let reset = false;
const onreset = () => {
reset = true;
this.refresh();
};
const onsubmit = (ev) => {
ev.preventDefault();
};
setInterval(() => {
this.refresh();
}, 1000);
for ({} of this) {
const currentReset = reset;
reset = false;
yield (
<form onsubmit={onsubmit}>
<input type="text" value="" $static={currentReset} />
<p>
<button onclick={onreset}>Reset</button>
</p>
</form>
);
}
}
renderer.render(<Form />, document.body);
Loading...Crank.js | JSX Template Tag A Crank application as a single HTML file. No transpilation required.
Installation
The JSX tag function can be imported from the module
@b9g/crank/standalone
. This module exports everything from the root@b9g/crank
module as well as thejsx
tag function, which is defined in the module@b9g/crank/jsx-tag
.// console.log(jsx === jsx1);
Loading...In the future, we may use environment detection to automatically exports the correct
renderer
, which would make thestandalone
module truly “standalone.”JSX Syntax
The JSX template tag function is designed to replicate as much of JSX syntax and semantics as possible.
Just like JSX syntax, the template version supports components, but they must be explicitly interpolated.
const syntaxEl = <Component />;
const templateEl = jsx`<${Component} />`;
Component closing tags can be done in one of three styles:
import {jsx} from "@b9g/crank/standalone";
Crank.js | Lifecycles You should be careful when writing generator components to make sure that you always place your
yield
operators in afor
orwhile
loop. If you forget and implicitly return from the generator, it will stop updating and nothing will be rendered ever again.renderer.render(<Numbers />, document.body);
console.log(document.body.innerHTML); // ""
Cleaning Up
When a generator component is removed from the tree, Crank calls the
return
method on the component’s generator object. You can think of it as whateveryield
expression your component was suspended on being replaced by areturn
statement. This means any loops your component was in when the generator suspended are broken out of, and code after the yield does not execute.You can take advantage of this behavior by wrapping your
yield
loops in atry
/finally
block to release any resources that your component may have used.// "finally block executed"
console.log(document.body); // ""
The same best practices which apply to
try
/finally
statements in regular functions apply to generator components. In short, you should not yield or return anything in thefinally
block. Crank will not use the yielded or returned values and doing so might cause your components to inadvertently swallow errors or suspend in unexpected locations.Catching Errors
We all make mistakes, and it can be useful to catch errors thrown by our components so that we can show the user something or notify error-logging services. To facilitate this, Crank will catch errors thrown when rendering child elements and throw them back into parent generator components using the
throw
method on the component’s generator object. You can think of it as whateveryield
expression your component was suspended on being replaced with athrow
statement with the error set to whatever was thrown by the component’s children.You can take advantage of this behavior by wrapping your
yield
operations in atry
/catch
block to catch errors caused by children.renderer.render(<Catcher />, document.body);
console.log(document.body.innerHTML); // "<div>Error: Hmmm</div>"
As explained previously, this component “sticks” because it uses a return statement, so that the same error message is shown until the component is unmounted. However, you may also want to recover from errors as well, and you can do this by ignoring or handling the error.
console.log(document.body.innerHTML);
// "<div>I’ll be back</div>"
Accessing Rendered Values
Sometimes, declarative rendering is not enough, and you’ll want to access the actual DOM nodes you’ve rendered, to make measurements or call imperative methods like the
focus
method for form elements, or theplay
method for media elements. To facilitate this, Crank will pass rendered DOM nodes back into the generator using thenext
method. This means that as a developer, you can readyield
expressions to access the actual rendered DOM nodes.}
}
The
FocusingInput
component focuses every time it is rerendered. We use an async generator component here because async generators continuously resume, so theinput.focus
call happens directly after the component is rendered. While we also pass rendered nodes into sync generator components as well, attempting to access them directly after theyield
may lead to surprising results.}
}
The problem is that sync generator components suspend at the point of yield expressions and only resume when updated by the parent or by a call to the
refresh
method. This means that if you were to try to access the rendered value via ayield
expression, your code would not execute until the moment the component rerenders. You can imagine this as the generator function above suspended exactly before theyield
expression is assigned to theinput
variable.To solve this problem, Crank provides an additional method on the context called
schedule
, which takes a callback and calls it with the rendered value after the component executes.}
}
The
schedule
method fires the passed in callback synchronously when component finally renders. However, one unfortunate consequence of using a callback is that we lose the sequential execution of code which makes generator components so elegant and easy to understand. We can recover some of this linearity by using theschedule
method with therefresh
method.}
}
The focusing input now focuses before the children are yielded, but because the same input is yielded, the result is mostly the same. The
schedule
method is designed to work with therefresh
method so that sync generator components can schedule multiple rendering passes which work synchronously.The focusing input now focuses before the children are yielded, but because the same input is yielded, the result is mostly the same. The
\ No newline at end of file diff --git a/guides/reference-for-react-developers/index.html b/guides/reference-for-react-developers/index.html index 5d333909..9b34adc3 100644 --- a/guides/reference-for-react-developers/index.html +++ b/guides/reference-for-react-developers/index.html @@ -1,4 +1,4 @@ -schedule
method is designed to work with therefresh
method so that sync generator components can schedule multiple rendering passes which work synchronously.Crank.js | Reference for React Developers This example is pseudocode which demonstrates where React’s class methods would be called relative to an async generator component. Refer to the guide on lifecycles for more information on using generator functions.
The following are specific equivalents for React methods.
setState and forceUpdate
Crank uses generator functions and local variables for local state. Refer to the section on stateful components.
Crank is not “reactive” in the same sense as React, in that it does not track your component’s local state and rerender when it detects a change. You can either use the context’s
refresh
to manually refresh the component, similar to React’sforceUpdate
method, or you can use async generator components, which refresh automatically whenever the returned async generator yields.defaultProps
Crank doesn’t have a
defaultProps
implementation. Instead, you can provide default values when destructuring props. See the guide on default props.componentWillMount and componentDidMount
Setup code for components can be written at the top of generator components. It will not execute until the component is mounted in the tree.
shouldComponentUpdate
As an alternative to React’s
shouldComponentUpdate
method, you can useCopy
elements to prevent the rerendering of a specific subtree. Refer to the description ofCopy
elements for more information.getDerivedStateFromProps, componentWillUpdate and getSnapshotBeforeUpdate
Code which compares old and new props or state can be written directly in your components. See the section on prop updates for an example of a component which compares old and new props.
componentDidUpdate
To execute code after rendering, you can use async generator components or the
schedule
method. See the guide on accessing rendered values for more information.componentWillUnmount
You can use a
try
/finally
block to run code when a component is unmounted. You can also use thecleanup
method if you’re writing extensions which don’t run in the main execution of the component.componentDidCatch
To catch errors which occur in child components, you can use generator components and wrap
yield
operations in atry
/catch
block. Refer to the relevant guide on catching errors.Hooks
Crank does not implement any APIs similar to React Hooks. The main appeal of hooks for library authors is that you can encapsulate entire APIs in one or two hooks. Refer to the guide on reusable logic for a description of strategies you can use to reuse logic and write library wrappers in Crank.
The following are alternatives to specific hooks.
useState and useReducer
Crank uses generator functions and local variables for local state. Refer to the section on stateful components.
useEffect and useLayoutEffect
Crank does not have any requirements that rendering should be “pure.” In other words, you can trigger side-effects directly while rendering because Crank does not execute components more times than you might expect. Refer to the guide on accessing rendered values for more information on code which executes after rendering.
useMemo and useCallback
Because the execution of generator components is preserved, there is no need to “memoize” or “cache” callbacks or other values. You can simply assign them to a constant variable.
useImperativeHandle
Crank does not have a way to access component instances, and parent components should not access child components directly. A web component wrapper for defining custom elements with imperative APIs is planned.
Suspense and Concurrent Mode
Crank uses async functions and promises for scheduling and coordinating async processes. See the guide on async components for an introduction to async components, as well as a demonstration of how you can implement the
Suspense
component directly in user space.PropTypes
Crank is written in TypeScript, and you can add type checking to components by typing the props parameter of the component function. See the guide on TypeScript for detailed instructions on how to type components.
Array Children
Crank does not restrict children in JSX elements to just arrays. You can interpolate ES6 maps, sets or any other iterable into your Crank elements. Additionally, Crank does not warn you if elements in the iterable are unkeyed.
Fragments
The
Fragment
element works almost exactly as it does in React, except that in Crank you can also use a callback ref to access its contents.React.cloneElement
You can clone elements using the
cloneElement
function.ReactDOM.createPortal
The
createPortal
function is replaced by the specialPortal
element, whose behavior and expected props varies according to the target rendering environment. Refer to the guide on thePortal
element for more information.React.memo
See the guide on
Copy
tags for a demonstration of how you can useCopy
elements to implementReact.memo
in user space.DOM element props
The following are a list of the differences in props APIs for DOM elements.
className and htmlFor
Crank prefers attribute names rather than the JS property DOM equivalents when these names are mismatched.
<label class="my-label" for="my-id">Label</label>
In short, Crank is optimized for easy copy-pasting, which using props like
className
andhtmlFor
does not encourage. See the section on prop naming conventions for more information.style
The
style
prop can be an object of CSS declarations. However, unlike React, CSS property names match the case of their CSS equivalents, and we do not add units to numbers. Additionally, Crank allows the style prop to be a CSS string as well.<div style="color: red"><span style={{"font-size": "16px"}}>Hello</span></div>
Refer to the guide on the style prop for more information.
Event props
Host elements can be listened to using
onevent
props, but the prop name will be all lowercase. Crank also provides anEventTarget
API for components to add and remove event listeners from the top-level node or nodes of each component. In both cases, Crank does not use a synthetic event system or polyfill events in any way. Refer to the guide on event handling for a longer explanation of event handling in Crank.Controlled and Uncontrolled Props
Crank does not have a concept of controlled or uncontrolled props, and does not provide
defaultValue
-style props for DOM elements. See the section on form elements for a detailed description of how Crank handles stateful form elements.dangerouslySetInnerHTML
Host DOM elements accept an
innerHTML
prop; Crank does not provide thedangerouslySetInnerHTML={{__html}}
API like React. Alternatively, you can use the specialRaw
tag to insert HTML strings or even DOM nodes directly into an element tree without a parent. Refer to the sections on theinnerHTML
prop and on theRaw
tag for more information.Keys
Crank provides keyed rendering via the special
crank-key
prop. The prop was renamed because “key” is a common word and because the prop is not erased from the props object passed into components.Keys work similarly to the way they do in React. The main difference is that Crank does not warn about unkeyed elements which appear in arrays or iterables.
Refs
Crank provides the callback-style ref API from React via the
crank-ref
prop. Unlike React, all elements can be read using thecrank-ref
prop, including Fragment elements. See the guide on thecrank-ref
prop.You can also access rendered values in many other ways. Refer to this section for more information.
React Contexts
Because we refer to the
this
keyword of components as “the component’s context” (“controller” would have been three more characters), we refer to the equivalent concept of React’s Context API as “provisions” instead. We use the context methodsprovide
andconsume
to define provisions between ancestor and descendant components. See the guide on provisions for more information.Refer to the guide on the style prop for more information.
Event props
Host elements can be listened to using
onevent
props, but the prop name will be all lowercase. Crank also provides anEventTarget
API for components to add and remove event listeners from the top-level node or nodes of each component. In both cases, Crank does not use a synthetic event system or polyfill events in any way. Refer to the guide on event handling for a longer explanation of event handling in Crank.Controlled and Uncontrolled Props
Crank does not have a concept of controlled or uncontrolled props, and does not provide
defaultValue
-style props for DOM elements. See the section on form elements for a detailed description of how Crank handles stateful form elements.dangerouslySetInnerHTML
Host DOM elements accept an
innerHTML
prop; Crank does not provide thedangerouslySetInnerHTML={{__html}}
API like React. Alternatively, you can use the specialRaw
tag to insert HTML strings or even DOM nodes directly into an element tree without a parent. Refer to the sections on theinnerHTML
prop and on theRaw
tag for more information.Keys
Crank provides keyed rendering via the special
crank-key
prop. The prop was renamed because “key” is a common word and because the prop is not erased from the props object passed into components.Keys work similarly to the way they do in React. The main difference is that Crank does not warn about unkeyed elements which appear in arrays or iterables.
Refs
Crank provides the callback-style ref API from React via the
crank-ref
prop. Unlike React, all elements can be read using thecrank-ref
prop, including Fragment elements. See the guide on thecrank-ref
prop.You can also access rendered values in many other ways. Refer to this section for more information.
React Contexts
Because we refer to the
\ No newline at end of file diff --git a/guides/reusable-logic/index.html b/guides/reusable-logic/index.html index aed6f5d4..07e93d05 100644 --- a/guides/reusable-logic/index.html +++ b/guides/reusable-logic/index.html @@ -1,4 +1,4 @@ -this
keyword of components as “the component’s context” (“controller” would have been three more characters), we refer to the equivalent concept of React’s Context API as “provisions” instead. We use the context methodsprovide
andconsume
to define provisions between ancestor and descendant components. See the guide on provisions for more information.Crank.js | Reusable Logic Provisions allow libraries to define components which interact with their descendants without rigidly defined component hierarchies or requiring data to be passed manually between components via props. This makes them useful, for instance, when writing multiple components which communicate with each other, like custom
select
andoption
form elements, or drag-and-drop components.Anything can be passed as a key to the
provide
andconsume
methods, so you can use a symbol to ensure that the provision you pass between your components are private and do not collide with provisions set by others.Note: Crank does not link “providers” and “consumers” in any way, and doesn’t automatically refresh consumer components when the
provide
method is called. It’s up to you to ensure consumers update when providers update.context.schedule
You can pass a callback to the
schedule
method to listen for when the component renders. Callbacks passed toschedule
fire synchronously after the component commits, with the rendered value of the component as its only argument. Scheduled callbacks fire once per call and callback function (thinkrequestAnimationFrame
, notsetInterval
). This means you have to continuously call theschedule
method for each update if you want to execute some code every time your component commits.context.cleanup
Similarly, you can pass a callback to the
cleanup
method to listen for when the component unmounts. Callbacks passed tocleanup
fire synchronously when the component is unmounted. Each registered callback fires only once. The callback is called with the last rendered value of the component as its only argument.Strategies for Reusing Logic
The following are various patterns you can use to write and reuse logic between components, as well as a description of their tradeoffs. We will be wrapping the global
window.setInterval
function in examples to demonstrate each design pattern.Global Context Extensions
You can import and extend the Context class’s prototype to globally extend all contexts in your application.
}
}
In this example, we define the methods
setInterval
andclearInterval
directly theContext
prototype. ThesetInterval
andclearInterval
methods will now be available to all components.Pros:
- Methods are available to every component automatically.
Cons:
- Globally scoped.
- No way to write setup logic.
- No way to respond to props updates.
Global context extensions are useful for creating Crank-specific wrappers around already global or otherwise well-known APIs like
setInterval
,requestAnimationFrame
orfetch
.Context Helper Utilities
As an alternative to global context extensions, you can write utility functions which are passed contexts to scope reusable logic per component. In the following example, instead of defining the
setInterval
method globally, we define it locally by passing the context of the component into thecreateSetInterval
function.}
Pros:
- Locally scoped.
- Explicitly imported and referenced.
- Setup logic can be written directly in the function.
Cons:
- Naming these functions can be difficult.
- No way to respond to props updates.
Context helper utilities are useful when you want to write locally-scoped utilities, and are especially well-suited for use-cases which requires setup logic. Possible use-cases include wrappers around stateful APIs like mutation observers or HTML drag and drop.
Higher-order Components
Because Crank components are just functions, we can write functions which both take components as parameters and return wrapped component functions.
const Counter = interval((props) => <div>Seconds: {props.seconds}</div>);
The interval function takes a component function and returns a component which passes the number of seconds as a prop. Additionally, it refreshes the returned component whenever the interval is fired.
Pros:
- Locally scoped.
- Explicitly imported and referenced.
- Able to respond to new props within the returned component.
Cons:
- Naming higher-order functions can be difficult.
- JavaScript doesn’t provide an easy syntax for decorating functions.
- Props passed into the component by the wrapper may clash with the component’s own expected props.
The main advantage of higher-order components is that you can respond to props in your utilities just like you would with a component. Higher-order components are most useful when you need reusable logic which responds to prop updates or sets only well-known props. Possible use-cases include styled component or animation libraries.
Async Iterators
Because components can be written as async generator functions, you can integrate utility functions which return async iterators seamlessly with Crank. Async iterators are a great way to model resources because they define a standard way for releasing resources when they’re no longer needed by returning the iterator.
}
}
Pros:
- Async iterator utilities are framework-agnostic.
- Uniform way to hold and release resources.
Cons:
- Promises and async iterators can cause race conditions and deadlocks, without any language-level features to help you debug them.
If you use async iterators/generators already, Crank is the perfect framework for your application.
Pros:
- Async iterator utilities are framework-agnostic.
- Uniform way to hold and release resources.
Cons:
- Promises and async iterators can cause race conditions and deadlocks, without any language-level features to help you debug them.
If you use async iterators/generators already, Crank is the perfect framework for your application.
\ No newline at end of file diff --git a/guides/special-props-and-tags/index.html b/guides/special-props-and-tags/index.html index 14b8e26d..a6cb89de 100644 --- a/guides/special-props-and-tags/index.html +++ b/guides/special-props-and-tags/index.html @@ -1,4 +1,4 @@ -Crank.js | Special Props and Tags Keys are scoped to an element’s children, and can be any JavaScript value. When rendering iterables, it’s useful to key elements of the iterable, because it’s common for the values of rendered iterables to added, moved or removed.
console.log(document.firstChild.firstChild === span); // true
Loading...All elements in the element tree can be keyed. If the element is a component element, the
crank-key
prop is erased from the props object passed to the component.crank-ref
Sometimes, you may want to access the rendered value of a specific element in the element tree. To do this, you can pass a callback as the
crank-ref
prop. This callback is called with the rendered value of the element when the element has committed.renderer.render(<MyPlayer />, document.body);
Loading...Refs can be attached to any element in the element tree, and the value passed to the callback will vary according the type of the element and the specific renderer.
children
The
children
prop passed to components is special because it is not usually set with JSX’skey="value"
prop syntax, but by the contents between the opening and closing tags. Crank places no limitations on the types of values that can be passed into components as children, but patterns like render props from the React community, where a callback is passed as the child of a component, should be avoided.The actual type of the
children
prop will vary according to the number of children passed in. If a component element has no children (<Component/>
), thechildren
prop will be undefined, if it has one child (<Component><Child/></Component>
), thechildren
prop will be set to that child, and if it has multiple children (<Component><Child/><Child/></Component>
), thechildren
prop will be set to an array of those children. We do this to reduce runtime memory costs. All props have to be retained between renders, and most elements contain only zero or one child, so avoiding the allocation of an extra array for every element in the tree can noticeably reduce memory requirements.Therefore, the
children
prop should be treated as a black box, only to be rendered somewhere within a component’s returned or yielded children. Attempting to iterate over or manipulate the passed in children of a component is an anti-pattern, and you should use event dispatch or provisions to coordinate ancestor and descendant components.Special DOM Props
The following props are specific to host elements for the HTML and DOM renderers.
style
The style prop can be used to add inline styles to an element. It can either be a CSS string, in which case it works exactly as it does in HTML, or it can also be an object, in which case CSS properties can be set individually.
<div style="color: red"><span style={{"font-size": "16px"}}>Hello</span></div>
Note: Unlike other JSX frameworks, Crank does not camel-case style names or add pixel units to numbers.
innerHTML
The
innerHTML
prop can be used to set the element’s children with an HTML string.Be careful when using the
innerHTML
prop, as passing unsanitized text inputs can lead to security vulnerabilities.As an alternative, you can also use the special
Raw
element tag, which allows to inject raw HTML or even actual DOM nodes into the element tree, without requiring a parent host element.Prop Naming Conventions
Crank strives to make copying and pasting HTML into your components as easy as possible, and to this extent it allows you to use
class
andfor
as props in your elements instead ofclassName
andhtmlFor
.<label class="my-label" for="my-id">Label</label>
You can still use the
className
andhtmlFor
props as well, but using the former names is preferred. This philosophy also extends to SVG elements, and you can use props likeclip-path
andstroke-width
without having to camel case them.Special Tags
Crank provides four element tags which have special meaning to the renderer, and affect element diffing and rendering output in various ways.
Fragment
Crank provides a
Fragment
tag, which allows you to render multiple children into a parent without wrapping them in another DOM node. Under the hood, iterables which appear in the element tree are also implicitly wrapped in aFragment
element by the renderer.console.log(document.body.innerHTML);
// "<div>Sibling 1</div><div>Sibling 2</div>"
Copy
It‘s often fine to rerender Crank components, because elements are diffed, persistent between renders, and unnecessary mutations usually avoided. However, you might want to prevent a child from updating when the parent rerenders, perhaps because a certain prop hasn’t changed, because you want to batch updates from the parent, or as a performance optimization. To do this, you can use the
Copy
tag to indicate to Crank that you don’t want to update a previously rendered element in that same position.};
}
In this example,
memo
is a higher-order component, a function which takes a component and returns a component. This wrapper component compares old and new props and yields aCopy
element if every prop is shallowly equal. ACopy
element can appear anywhere in an element tree to prevent rerenderings, and the only propsCopy
elements take are thecrank-key
andcrank-ref
props, which work as expected.Portal
Sometimes you may want to render into a DOM node which isn’t the current parent element, or even a part of the currently rendered DOM tree. You can do this with the
Portal
tag, passing in a DOM node as itsroot
prop. The Portal’s children will be rendered into the specified root element, just as if Renderer.render was called with the root value as its second argument.console.log(root2.innerHTML);
// "<div>This div is rendered into root2</div>"
This tag is useful for creating modals or tooltips, which usually need to be rendered into separate DOM elements at the bottom of the page for visibility reasons. Events dispatched from a Portal
element‘s child components via the dispatchEvent
method will still bubble into parent components.
Sometimes, you may want to insert raw HTML or actual DOM nodes directly into the element tree. Crank allows you to do this with the Raw
element. The Raw
element takes a value
prop which is interpreted by the renderer. For the DOM renderer, if value
is an HTML string, the renderer will parse and insert the resulting DOM nodes. If the value is already a DOM node, Crank will insert them in place.
);
}
Be careful when using Raw
elements, as passing unsanitized text inputs can lead to security vulnerabilities.
Be careful when using Raw
elements, as passing unsanitized text inputs can lead to security vulnerabilities.
You’ll often want to add a return type to your components. Crank exports custom types to help you type the return types of components:
}
}
Element
is just the type returned by JSX expressions/createElement
. As you can see, you still have to modify the return type of functions based on whether the function is async or a generator. You can also use the type Child
which represents any valid value in an element tree.
yield <div>Hello world</div>;
}
Anything assignable to Child
can be part of the element tree, and almost anything can be assigned to Child
.
You can type the props object passed to components. This allows JSX elements which use your component as a tag to be type-checked.
const el = <Greeting name="Brian" />; // compiles
const el1 = <Greeting name={1} />; // throws a type error
The children prop can be typed using the Children
type provided by Crank. The Children
type is a broad type which can be Child
or arbitrarily nested iterables of Child
. TypeScript doesn’t really provide a way to prevent functions from being used as the children
prop, but such patterns are strongly discouraged. You should typically treat children
as an opaque value only to be interpolated into JSX because its value can be almost anything.
);
}
If you dispatch custom events, you may want parent event listeners to be typed with the event you bubbled automatically. To do so, you can use module augmentation to extend the EventMap
interface from the global Crank
module.
);
}
When importing the DOM or HTML renderers, the EventMap
will be extended with the GlobalEventHandlersMap
interface.
By default, calls to the context’s provide
and consume
methods will be loosely typed. If you want stricter typings of these methods, you can use module augmentation to extend the ProvisionMap
interface from the global Crank
module.
return <p>{greeting}, {name}</p>;
}
Async generator functions let you write components that are both async and stateful. Crank uses promises wherever it makes sense, and has a rich async execution model which allows you to do things like race components to display loading states.
Async generator functions let you write components that are both async and stateful. Crank uses promises wherever it makes sense, and has a rich async execution model which allows you to do things like racing components to display loading states.
\ No newline at end of file diff --git a/playground/index.html b/playground/index.html index 4e25bae3..9dd95af0 100644 --- a/playground/index.html +++ b/playground/index.html @@ -1,4 +1,4 @@ -Crank.js \ No newline at end of file diff --git a/static/client-Q5HR47TJ.css b/static/client-Q5HR47TJ.css new file mode 100644 index 00000000..7e6cd1bd --- /dev/null +++ b/static/client-Q5HR47TJ.css @@ -0,0 +1,552 @@ +/* node_modules/normalize.css/normalize.css */ +html { + line-height: 1.15; + -webkit-text-size-adjust: 100%; +} +body { + margin: 0; +} +main { + display: block; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} +pre { + font-family: monospace, monospace; + font-size: 1em; +} +a { + background-color: transparent; +} +abbr[title] { + border-bottom: none; + -webkit-text-decoration: underline; + text-decoration: underline; + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} +b, +strong { + font-weight: bolder; +} +code, +kbd, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sub { + bottom: -0.25em; +} +sup { + top: -0.5em; +} +img { + border-style: none; +} +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + font-size: 100%; + line-height: 1.15; + margin: 0; +} +button, +input { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +[type=button], +[type=reset], +[type=submit] { + -webkit-appearance: button; +} +button::-moz-focus-inner, +[type=button]::-moz-focus-inner, +[type=reset]::-moz-focus-inner, +[type=submit]::-moz-focus-inner { + border-style: none; + padding: 0; +} +button:-moz-focusring, +[type=button]:-moz-focusring, +[type=reset]:-moz-focusring, +[type=submit]:-moz-focusring { + outline: 1px dotted ButtonText; +} +fieldset { + padding: 0.35em 0.75em 0.625em; +} +legend { + box-sizing: border-box; + color: inherit; + display: table; + max-width: 100%; + padding: 0; + white-space: normal; +} +progress { + vertical-align: baseline; +} +textarea { + overflow: auto; +} +[type=checkbox], +[type=radio] { + box-sizing: border-box; + padding: 0; +} +[type=number]::-webkit-inner-spin-button, +[type=number]::-webkit-outer-spin-button { + height: auto; +} +[type=search] { + -webkit-appearance: textfield; + outline-offset: -2px; +} +[type=search]::-webkit-search-decoration { + -webkit-appearance: none; +} +::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit; +} +details { + display: block; +} +summary { + display: list-item; +} +template { + display: none; +} +[hidden] { + display: none; +} + +/* src/styles/prism-theme.css */ +:root { + --coldark00: #111b27; + --coldark01: #213043; + --coldark02: #3c526d; + --coldark03: #8da1b9; + --coldark04: #d0dae7; + --coldark05: #e3eaf2; + --coldark06: #f0f4f8; + --coldark07: #0b121b; + --coldark08: #66cccc; + --coldark09: #e6d37a; + --coldark10: #6cb8e6; + --coldark11: #91d076; + --coldark12: #f4adf4; + --coldark13: #c699e3; + --coldark14: #e9ae7e; + --coldark15: #cd6660; +} +.color-scheme-light { + --coldark00: #e3eaf2; + --coldark01: #d0dae7; + --coldark02: #8da1b9; + --coldark03: #3c526d; + --coldark04: #213043; + --coldark05: #111b27; + --coldark06: #0b121b; + --coldark07: #f0f4f8; + --coldark08: #006d6d; + --coldark09: #755f00; + --coldark10: #005a8e; + --coldark11: #116b00; + --coldark12: #af00af; + --coldark13: #7c00aa; + --coldark14: #a04900; + --coldark15: #c22f2e; +} +code[class*=language-], +pre[class*=language-] { + color: #e3eaf2; + color: var(--coldark05); + background: none; + font-family: + Consolas, + Monaco, + "Andale Mono", + "Ubuntu Mono", + monospace; + text-align: left; + white-space: pre; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; + -webkit-hyphens: none; + hyphens: none; +} +pre[class*=language-]::-moz-selection, +pre[class*=language-] ::-moz-selection, +code[class*=language-]::-moz-selection, +code[class*=language-] ::-moz-selection { + background: #3c526d; + background: var(--coldark02); +} +pre[class*=language-]::-moz-selection, +pre[class*=language-] ::-moz-selection, +code[class*=language-]::-moz-selection, +code[class*=language-] ::-moz-selection { + background: #3c526d; + background: var(--coldark02); +} +pre[class*=language-]::selection, +pre[class*=language-] ::selection, +code[class*=language-]::selection, +code[class*=language-] ::selection { + background: #3c526d; + background: var(--coldark02); +} +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #8da1b9; + color: var(--coldark03); +} +.token.punctuation { + color: #e3eaf2; + color: var(--coldark05); +} +.token.delimiter.important, +.token.selector .parent, +.token.tag, +.token.tag .token.punctuation { + color: #66cccc; + color: var(--coldark08); +} +.token.attr-name, +.token.boolean, +.token.boolean.important, +.token.number, +.token.constant, +.token.selector .token.attribute { + color: #e6d37a; + color: var(--coldark09); +} +.token.class-name, +.token.key, +.token.parameter, +.token.property, +.token.property-access, +.token.variable { + color: #6cb8e6; + color: var(--coldark10); +} +.token.attr-value, +.token.inserted, +.token.color, +.token.selector .token.value, +.token.string, +.token.string .token.url-link { + color: #91d076; + color: var(--coldark11); +} +.token.builtin, +.token.keyword-array, +.token.package, +.token.regex { + color: #f4adf4; + color: var(--coldark12); +} +.token.function, +.token.selector .token.class, +.token.selector .token.id { + color: #c699e3; + color: var(--coldark13); +} +.token.atrule .token.rule, +.token.combinator, +.token.keyword, +.token.operator, +.token.pseudo-class, +.token.pseudo-element, +.token.selector, +.token.unit { + color: #e9ae7e; + color: var(--coldark14); +} +.token.deleted, +.token.important { + color: #cd6660; + color: var(--coldark15); +} +.token.keyword-this, +.token.this { + color: #6cb8e6; + color: var(--coldark10); +} +.token.important, +.token.keyword-this, +.token.this, +.token.bold { + font-weight: bold; +} +.token.delimiter.important { + font-weight: inherit; +} +.token.italic { + font-style: italic; +} +.token.entity { + cursor: help; +} +.language-markdown .token.title, +.language-markdown .token.title .token.punctuation { + color: #6cb8e6; + color: var(--coldark10); + font-weight: bold; +} +.language-markdown .token.blockquote.punctuation { + color: #f4adf4; + color: var(--coldark12); +} +.language-markdown .token.code { + color: #66cccc; + color: var(--coldark08); +} +.language-markdown .token.hr.punctuation { + color: #6cb8e6; + color: var(--coldark10); +} +.language-markdown .token.url .token.content { + color: #91d076; + color: var(--coldark11); +} +.language-markdown .token.url-link { + color: #e6d37a; + color: var(--coldark09); +} +.language-markdown .token.list.punctuation { + color: #f4adf4; + color: var(--coldark12); +} +.language-markdown .token.table-header { + color: #e3eaf2; + color: var(--coldark05); +} +.language-json .token.operator { + color: #e3eaf2; + color: var(--coldark05); +} +.language-scss .token.variable { + color: #66cccc; + color: var(--coldark08); +} + +/* src/styles/client.css */ +:root { + --bg-color: #0a0e1f; + --text-color: #f5f9ff; + --highlight-color: #dbb368; +} +.color-scheme-light { + --bg-color: #e7f4f5; + --text-color: #0a0e1f; +} +@supports ((-webkit-backdrop-filter: blur(4px)) or (backdrop-filter: blur(4px))) { + .blur-background { + background-color: rgba(10, 14, 31, 0.5) !important; + } + .blur-background { + -webkit-backdrop-filter: blur(4px); + backdrop-filter: blur(4px); + } +} +@supports ((-webkit-backdrop-filter: blur(4px)) or (backdrop-filter: blur(4px))) { + .color-scheme-light .blur-background { + background-color: rgba(231, 244, 245, 0.5) !important; + } + .color-scheme-light .blur-background { + -webkit-backdrop-filter: blur(4px); + backdrop-filter: blur(4px); + } +} +@supports ((-webkit-backdrop-filter: blur(8px)) or (backdrop-filter: blur(8px))) { + .blur-background-2 { + background-color: rgba(10, 14, 31, 0.5) !important; + } + .blur-background-2 { + -webkit-backdrop-filter: blur(8px); + backdrop-filter: blur(8px); + } +} +@supports ((-webkit-backdrop-filter: blur(8px)) or (backdrop-filter: blur(8px))) { + .color-scheme-light .blur-background-2 { + background-color: rgba(231, 244, 245, 0.5) !important; + } + .color-scheme-light .blur-background-2 { + -webkit-backdrop-filter: blur(8px); + backdrop-filter: blur(8px); + } +} +* { + color: #f5f9ff; + color: var(--text-color); + box-sizing: border-box; +} +::-moz-selection { + background: dodgerblue; +} +::selection { + background: dodgerblue; +} +html { + scroll-behavior: smooth; +} +body { + background-color: #0a0e1f; + background-color: var(--bg-color); + color: #f5f9ff; + color: var(--text-color); + font-family: sans-serif; +} +p { + line-height: 1.4; + margin: 1.5em 0; +} +img { + display: block; + margin: 0 auto; + background: #e1e4ea; + max-width: 100%; +} +@media screen and (min-width: 1100px) { + img { + max-width: 900px; + } +} +button { + background-color: transparent; + border-radius: 0; + border: 1px solid currentcolor; + padding: 0.3em; +} +h1, +h2, +h3, +h4, +h5, +h6 { + padding: 0; + margin: 0 0 1.4em; +} +h1 a, +h2 a, +h3 a, +h4 a, +h5 a, +h6 a { + color: inherit; + -webkit-text-decoration: none; + text-decoration: none; +} +h1, +h2, +h3 { + color: #dbb368; + color: var(--highlight-color); +} +blockquote { + border-left: 2px solid #dbb368; + border-left: 2px solid var(--highlight-color); + margin: 1.5em 0; + padding: 0 0 0 .8em; +} +blockquote p { + margin: 0; +} +a { + color: inherit; +} +a[aria-current] { + color: #dbb368; + color: var(--highlight-color); +} +li { + margin: 0 0 1em; +} +pre { + margin: 0; + padding: 5px; +} +@media screen and (min-width: 800px) { + pre { + padding: 1em; + } +} +pre { + font-size: 14px; + color: #f5f9ff; + color: var(--text-color); + line-height: 1.4; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; + outline: none; +} +code { + padding: .1em .2em; + margin: 0; + font-size: .9em; + background: var(--coldark01); +} +pre code { + padding: 0; + margin: 0; + font-size: 14px; + background: none; +} +select, +input, +textarea { + background-color: #0a0e1f; + background-color: var(--bg-color); + color: #f5f9ff; + color: var(--text-color); + border: 1px solid #f5f9ff; + border: 1px solid var(--text-color); +} +/** + * Adapted from Coldark Theme for Prism.js to support light/dark mode + * + * Tested with HTML, CSS, JS, JSON, PHP, YAML, Bash script + * @author Armand Philippot+ * @homepage https://github.com/ArmandPhilippot/coldark-prism + * @license MIT + */ +/*! Bundled license information: + +normalize.css/normalize.css: + (*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css *) +*/ +/*# sourceMappingURL=client-Q5HR47TJ.css.map */ diff --git a/static/client-Q5HR47TJ.css.map b/static/client-Q5HR47TJ.css.map new file mode 100644 index 00000000..2b53d47a --- /dev/null +++ b/static/client-Q5HR47TJ.css.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../node_modules/normalize.css/node_modules/normalize.css/normalize.css", "styles/src/styles/prism-theme.css", "styles/src/styles/client.css", "styles/ "], + "sourcesContent": ["/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */\n\n/* Document\n ========================================================================== */\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n line-height: 1.15; /* 1 */\n -webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n ========================================================================== */\n\n/**\n * Remove the margin in all browsers.\n */\n\nbody {\n margin: 0;\n}\n\n/**\n * Render the `main` element consistently in IE.\n */\n\nmain {\n display: block;\n}\n\n/**\n * Correct the font size and margin on `h1` elements within `section` and\n * `article` contexts in Chrome, Firefox, and Safari.\n */\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n/* Grouping content\n ========================================================================== */\n\n/**\n * 1. Add the correct box sizing in Firefox.\n * 2. Show the overflow in Edge and IE.\n */\n\nhr {\n box-sizing: content-box; /* 1 */\n height: 0; /* 1 */\n overflow: visible; /* 2 */\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\npre {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/* Text-level semantics\n ========================================================================== */\n\n/**\n * Remove the gray background on active links in IE 10.\n */\n\na {\n background-color: transparent;\n}\n\n/**\n * 1. Remove the bottom border in Chrome 57-\n * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n */\n\nabbr[title] {\n border-bottom: none; /* 1 */\n text-decoration: underline; /* 2 */\n text-decoration: underline dotted; /* 2 */\n}\n\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\n\nb,\nstrong {\n font-weight: bolder;\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\ncode,\nkbd,\nsamp {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/**\n * Add the correct font size in all browsers.\n */\n\nsmall {\n font-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in\n * all browsers.\n */\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -0.25em;\n}\n\nsup {\n top: -0.5em;\n}\n\n/* Embedded content\n ========================================================================== */\n\n/**\n * Remove the border on images inside links in IE 10.\n */\n\nimg {\n border-style: none;\n}\n\n/* Forms\n ========================================================================== */\n\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n font-family: inherit; /* 1 */\n font-size: 100%; /* 1 */\n line-height: 1.15; /* 1 */\n margin: 0; /* 2 */\n}\n\n/**\n * Show the overflow in IE.\n * 1. Show the overflow in Edge.\n */\n\nbutton,\ninput { /* 1 */\n overflow: visible;\n}\n\n/**\n * Remove the inheritance of text transform in Edge, Firefox, and IE.\n * 1. Remove the inheritance of text transform in Firefox.\n */\n\nbutton,\nselect { /* 1 */\n text-transform: none;\n}\n\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\n/**\n * Remove the inner border and padding in Firefox.\n */\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n border-style: none;\n padding: 0;\n}\n\n/**\n * Restore the focus styles unset by the previous rule.\n */\n\nbutton:-moz-focusring,\n[type=\"button\"]:-moz-focusring,\n[type=\"reset\"]:-moz-focusring,\n[type=\"submit\"]:-moz-focusring {\n outline: 1px dotted ButtonText;\n}\n\n/**\n * Correct the padding in Firefox.\n */\n\nfieldset {\n padding: 0.35em 0.75em 0.625em;\n}\n\n/**\n * 1. Correct the text wrapping in Edge and IE.\n * 2. Correct the color inheritance from `fieldset` elements in IE.\n * 3. Remove the padding so developers are not caught out when they zero out\n * `fieldset` elements in all browsers.\n */\n\nlegend {\n box-sizing: border-box; /* 1 */\n color: inherit; /* 2 */\n display: table; /* 1 */\n max-width: 100%; /* 1 */\n padding: 0; /* 3 */\n white-space: normal; /* 1 */\n}\n\n/**\n * Add the correct vertical alignment in Chrome, Firefox, and Opera.\n */\n\nprogress {\n vertical-align: baseline;\n}\n\n/**\n * Remove the default vertical scrollbar in IE 10+.\n */\n\ntextarea {\n overflow: auto;\n}\n\n/**\n * 1. Add the correct box sizing in IE 10.\n * 2. Remove the padding in IE 10.\n */\n\n[type=\"checkbox\"],\n[type=\"radio\"] {\n box-sizing: border-box; /* 1 */\n padding: 0; /* 2 */\n}\n\n/**\n * Correct the cursor style of increment and decrement buttons in Chrome.\n */\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n\n[type=\"search\"] {\n -webkit-appearance: textfield; /* 1 */\n outline-offset: -2px; /* 2 */\n}\n\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n\n::-webkit-file-upload-button {\n -webkit-appearance: button; /* 1 */\n font: inherit; /* 2 */\n}\n\n/* Interactive\n ========================================================================== */\n\n/*\n * Add the correct display in Edge, IE 10+, and Firefox.\n */\n\ndetails {\n display: block;\n}\n\n/*\n * Add the correct display in all browsers.\n */\n\nsummary {\n display: list-item;\n}\n\n/* Misc\n ========================================================================== */\n\n/**\n * Add the correct display in IE 10+.\n */\n\ntemplate {\n display: none;\n}\n\n/**\n * Add the correct display in IE 10.\n */\n\n[hidden] {\n display: none;\n}\n", "/**\n * Adapted from Coldark Theme for Prism.js to support light/dark mode\n *\n * Tested with HTML, CSS, JS, JSON, PHP, YAML, Bash script\n * @author Armand Philippot \n * @homepage https://github.com/ArmandPhilippot/coldark-prism\n * @license MIT\n */\n:root {\n\t--coldark00: #111b27;\n\t--coldark01: #213043;\n\t--coldark02: #3c526d;\n\t--coldark03: #8da1b9;\n\t--coldark04: #d0dae7;\n\t--coldark05: #e3eaf2;\n\t--coldark06: #f0f4f8;\n\t--coldark07: #0b121b;\n\t--coldark08: #66cccc;\n\t--coldark09: #e6d37a;\n\t--coldark10: #6cb8e6;\n\t--coldark11: #91d076;\n\t--coldark12: #f4adf4;\n\t--coldark13: #c699e3;\n\t--coldark14: #e9ae7e;\n\t--coldark15: #cd6660;\n}\n\n.color-scheme-light {\n\t--coldark00: #e3eaf2;\n\t--coldark01: #d0dae7;\n\t--coldark02: #8da1b9;\n\t--coldark03: #3c526d;\n\t--coldark04: #213043;\n\t--coldark05: #111b27;\n\t--coldark06: #0b121b;\n\t--coldark07: #f0f4f8;\n\t--coldark08: #006d6d;\n\t--coldark09: #755f00;\n\t--coldark10: #005a8e;\n\t--coldark11: #116b00;\n\t--coldark12: #af00af;\n\t--coldark13: #7c00aa;\n\t--coldark14: #a04900;\n\t--coldark15: #c22f2e;\n}\n\ncode[class*=\"language-\"],\npre[class*=\"language-\"] {\n\tcolor: var(--coldark05);\n\tbackground: none;\n\tfont-family: Consolas, Monaco, \"Andale Mono\", \"Ubuntu Mono\", monospace;\n\ttext-align: left;\n\twhite-space: pre;\n\t-moz-tab-size: 2;\n\t-o-tab-size: 2;\n\ttab-size: 2;\n\t-webkit-hyphens: none;\n\t-moz-hyphens: none;\n\t-ms-hyphens: none;\n\thyphens: none;\n}\n\npre[class*=\"language-\"]::-moz-selection,\npre[class*=\"language-\"] ::-moz-selection,\ncode[class*=\"language-\"]::-moz-selection,\ncode[class*=\"language-\"] ::-moz-selection {\n\tbackground: var(--coldark02);\n}\n\npre[class*=\"language-\"]::selection,\npre[class*=\"language-\"] ::selection,\ncode[class*=\"language-\"]::selection,\ncode[class*=\"language-\"] ::selection {\n\tbackground: var(--coldark02);\n}\n\n/*\n:not(pre) > code[class*=\"language-\"],\npre[class*=\"language-\"] {\n\tbackground: var(--coldark00);\n}\n*/\n\n.token.comment,\n.token.prolog,\n.token.doctype,\n.token.cdata {\n\tcolor: var(--coldark03);\n}\n\n.token.punctuation {\n\tcolor: var(--coldark05);\n}\n\n.token.delimiter.important,\n.token.selector .parent,\n.token.tag,\n.token.tag .token.punctuation {\n\tcolor: var(--coldark08);\n}\n\n.token.attr-name,\n.token.boolean,\n.token.boolean.important,\n.token.number,\n.token.constant,\n.token.selector .token.attribute {\n\tcolor: var(--coldark09);\n}\n\n.token.class-name,\n.token.key,\n.token.parameter,\n.token.property,\n.token.property-access,\n.token.variable {\n\tcolor: var(--coldark10);\n}\n\n.token.attr-value,\n.token.inserted,\n.token.color,\n.token.selector .token.value,\n.token.string,\n.token.string .token.url-link {\n\tcolor: var(--coldark11);\n}\n\n.token.builtin,\n.token.keyword-array,\n.token.package,\n.token.regex {\n\tcolor: var(--coldark12);\n}\n\n.token.function,\n.token.selector .token.class,\n.token.selector .token.id {\n\tcolor: var(--coldark13);\n}\n\n.token.atrule .token.rule,\n.token.combinator,\n.token.keyword,\n.token.operator,\n.token.pseudo-class,\n.token.pseudo-element,\n.token.selector,\n.token.unit {\n\tcolor: var(--coldark14);\n}\n\n.token.deleted,\n.token.important {\n\tcolor: var(--coldark15);\n}\n\n.token.keyword-this,\n.token.this {\n\tcolor: var(--coldark10);\n}\n\n.token.important,\n.token.keyword-this,\n.token.this,\n.token.bold {\n\tfont-weight: bold;\n}\n\n.token.delimiter.important {\n\tfont-weight: inherit;\n}\n\n.token.italic {\n\tfont-style: italic;\n}\n\n.token.entity {\n\tcursor: help;\n}\n\n.language-markdown .token.title,\n.language-markdown .token.title .token.punctuation {\n\tcolor: var(--coldark10);\n\tfont-weight: bold;\n}\n\n.language-markdown .token.blockquote.punctuation {\n\tcolor: var(--coldark12);\n}\n\n.language-markdown .token.code {\n\tcolor: var(--coldark08);\n}\n\n.language-markdown .token.hr.punctuation {\n\tcolor: var(--coldark10);\n}\n\n.language-markdown .token.url .token.content {\n\tcolor: var(--coldark11);\n}\n\n.language-markdown .token.url-link {\n\tcolor: var(--coldark09);\n}\n\n.language-markdown .token.list.punctuation {\n\tcolor: var(--coldark12);\n}\n\n.language-markdown .token.table-header {\n\tcolor: var(--coldark05);\n}\n\n.language-json .token.operator {\n\tcolor: var(--coldark05);\n}\n\n.language-scss .token.variable {\n\tcolor: var(--coldark08);\n}\n", "@import \"normalize.css/normalize.css\";\n@import \"./prism-theme.css\";\n\n:root {\n\t--bg-color: #0a0e1f;\n\t--text-color: #f5f9ff;\n\t--highlight-color: #dbb368;\n}\n\n.color-scheme-light {\n\t--bg-color: #e7f4f5;\n\t--text-color: #0a0e1f;\n}\n\n.blur-background {\n\t@supports (backdrop-filter: blur(4px)) {\n\t\tbackdrop-filter: blur(4px);\n\t\tbackground-color: rgba(10, 14, 31, 0.5) !important;\n\t}\n}\n\n/* colors have to match background or it looks bad */\n.color-scheme-light {\n\t.blur-background {\n\t\t@supports (backdrop-filter: blur(4px)) {\n\t\t\tbackdrop-filter: blur(4px);\n\t\t\tbackground-color: rgba(231, 244, 245, 0.5) !important;\n\t\t}\n\t}\n}\n\n.blur-background-2 {\n\t@supports (backdrop-filter: blur(8px)) {\n\t\tbackdrop-filter: blur(8px);\n\t\tbackground-color: rgba(10, 14, 31, 0.5) !important;\n\t}\n}\n\n.color-scheme-light {\n\t.blur-background-2 {\n\t\t@supports (backdrop-filter: blur(8px)) {\n\t\t\tbackdrop-filter: blur(8px);\n\t\t\tbackground-color: rgba(231, 244, 245, 0.5) !important;\n\t\t}\n\t}\n}\n\n* {\n\tcolor: var(--text-color);\n\tbox-sizing: border-box;\n}\n\n::selection {\n\tbackground: dodgerblue;\n}\n\nhtml {\n\tscroll-behavior: smooth;\n}\n\nbody {\n\tbackground-color: var(--bg-color);\n\tcolor: var(--text-color);\n\tfont-family: sans-serif;\n}\n\np {\n\tline-height: 1.4;\n\tmargin: 1.5em 0;\n}\n\nimg {\n\tdisplay: block;\n\tmargin: 0 auto;\n\tbackground: #e1e4ea;\n\tmax-width: 100%;\n\t@media screen and (min-width: 1100px) {\n\t\tmax-width: 900px;\n\t}\n}\n\nbutton {\n\tbackground-color: transparent;\n\tborder-radius: 0;\n\tborder: 1px solid currentcolor;\n\tpadding: 0.3em;\n}\n\nh1, h2, h3, h4, h5, h6 {\n\tpadding: 0;\n\tmargin: 0 0 1.4em;\n\ta {\n\t\tcolor: inherit;\n\t\ttext-decoration: none;\n\t}\n}\n\nh1, h2, h3 {\n\tcolor: var(--highlight-color);\n}\n\nblockquote {\n\tborder-left: 2px solid var(--highlight-color);\n\tmargin: 1.5em 0;\n\tpadding: 0 0 0 .8em;\n\n\tp {\n\t\tmargin: 0;\n\t}\n}\n\na {\n\tcolor: inherit;\n\n\t&[aria-current] {\n\t\tcolor: var(--highlight-color);\n\t}\n}\n\nli {\n\tmargin: 0 0 1em;\n}\n\npre {\n\tmargin: 0;\n\tpadding: 5px;\n\t@media screen and (min-width: 800px) {\n\t\tpadding: 1em;\n\t}\n\n\tfont-size: 14px;\n\tcolor: var(--text-color);\n\tline-height: 1.4;\n\ttab-size: 2;\n\toutline: none;\n}\n\ncode {\n\tpadding: .1em .2em;\n\tmargin: 0;\n\tfont-size: .9em;\n\tbackground: var(--coldark01);\n}\n\npre code {\n\tpadding: 0;\n\tmargin: 0;\n\tfont-size: 14px;\n\tbackground: none;\n}\n\nselect, input, textarea {\n background-color: var(--bg-color);\n\tcolor: var(--text-color);\n\tborder: 1px solid var(--text-color);\n}\n", null], + "mappings": ";AAUA;AACE;AACA;;AAUF;AACE;;AAOF;AACE;;AAQF;AACE;AACA;;AAWF;AACE;AACA;AACA;;AAQF;AACE;AACA;;AAUF;AACE;;AAQF;AACE;AACA;AAAA;AACA;AAAA;AAAA;;AAOF;;AAEE;;AAQF;;;AAGE;AACA;;AAOF;AACE;;AAQF;;AAEE;AACA;AACA;AACA;;AAGF;AACE;;AAGF;AACE;;AAUF;AACE;;AAWF;;;;;AAKE;AACA;AACA;AACA;;AAQF;;AAEE;;AAQF;;AAEE;;AAOF;;;;AAIE;;AAOF;;;;AAIE;AACA;;AAOF;;;;AAIE;;AAOF;AACE;;AAUF;AACE;AACA;AACA;AACA;AACA;AACA;;AAOF;AACE;;AAOF;AACE;;AAQF;;AAEE;AACA;;AAOF;;AAEE;;AAQF;AACE;AACA;;AAOF;AACE;;AAQF;AACE;AACA;;AAUF;AACE;;AAOF;AACE;;AAUF;AACE;;AAOF;AACE;;;;ACnVF;AACC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGD;AACC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGD;;AAEC;AAAA;AACA;AACA;;;;;;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;;AAGD;;;;AAIC;AAAA;;AAGD;;;;AAIC;AAAA;;AAJD;;;;AAIC;AAAA;;AAUD;;;;AAIC;AAAA;;AAGD;AACC;AAAA;;AAGD;;;;AAIC;AAAA;;AAGD;;;;;;AAMC;AAAA;;AAGD;;;;;;AAMC;AAAA;;AAGD;;;;;;AAMC;AAAA;;AAGD;;;;AAIC;AAAA;;AAGD;;;AAGC;AAAA;;AAGD;;;;;;;;AAQC;AAAA;;AAGD;;AAEC;AAAA;;AAGD;;AAEC;AAAA;;AAGD;;;;AAIC;;AAGD;AACC;;AAGD;AACC;;AAGD;AACC;;AAGD;;AAEC;AAAA;AACA;;AAGD;AACC;AAAA;;AAGD;AACC;AAAA;;AAGD;AACC;AAAA;;AAGD;AACC;AAAA;;AAGD;AACC;AAAA;;AAGD;AACC;AAAA;;AAGD;AACC;AAAA;;AAGD;AACC;AAAA;;AAGD;AACC;AAAA;;;;ACzND;AACC;AACA;AACA;;AAGD;AACC;AACA;;AAIA;AADD;AAGE;;AAHF;AAEE;AAAA;;;AAQA;AADD;AAGE;;AAHF;AAEE;AAAA;;;AAOF;AADD;AAGE;;AAHF;AAEE;AAAA;;;AAOA;AADD;AAGE;;AAHF;AAEE;AAAA;;;AAMH;AACC;AAAA;AACA;;AAGD;AACC;;AADD;AACC;;AAGD;AACC;;AAGD;AACC;AAAA;AACA;AAAA;AACA;;AAGD;AACC;AACA;;AAGD;AACC;AACA;AACA;AACA;;AACA;AALD;AAME;;;AAIF;AACC;AACA;AACA;AACA;;AAGD;;;;;;AACC;AACA;;AACA;;;;;;AACC;AACA;AAAA;;AAIF;;;AACC;AAAA;;AAGD;AACC;AAAA;AACA;AACA;;AAEA;AACC;;AAIF;AACC;;AAEA;AACC;AAAA;;AAIF;AACC;;AAGD;AACC;AACA;;AACA;AAHD;AAIE;;;AC/HF;ADkIC;AACA;AAAA;AACA;AACA;AAAA;AAAA;AACA;;AAGD;AACC;AACA;AACA;AACA;;AAGD;AACC;AACA;AACA;AACA;;AAGD;;;AACE;AAAA;AACD;AAAA;AACA;AAAA;;", + "names": [] +}