From 45f09c1a7e095eaa65da00262c5b38219ca47442 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 13 May 2024 18:02:48 -0700 Subject: [PATCH] fix #1327; document resize --- docs/javascript.md | 66 ++++++++++++++++++++++++++++--------- docs/lib/generators.md | 16 +++++---- src/client/stdlib/resize.ts | 6 ++-- 3 files changed, 63 insertions(+), 25 deletions(-) diff --git a/docs/javascript.md b/docs/javascript.md index 4db652717..d4b15a7e3 100644 --- a/docs/javascript.md +++ b/docs/javascript.md @@ -28,7 +28,7 @@ A program block looks like this (note the semicolon): const foo = 1 + 2; ``` -A program block doesn’t display anything by default, but you can call [`display`](#explicit-display) to display something. +A program block doesn’t display anything by default, but you can call [`display`](#display(value)) to display something. JavaScript blocks do not show their code by default. If you want to show the code, use the `echo` directive: @@ -129,7 +129,7 @@ Expressions cannot declare top-level reactive variables. To declare a variable, ## Explicit display -The built-in `display` function displays the specified value. +The built-in [`display` function](#display(value)) displays the specified value. ```js echo const x = Math.random(); @@ -202,7 +202,7 @@ Inputs.button("Click me", {value: 0, reduce: (i) => displayThere(++i)}) ## Implicit display -JavaScript expression fenced code blocks are implicitly wrapped with a call to [`display`](#explicit-display). For example, this arithmetic expression displays implicitly: +JavaScript expression fenced code blocks are implicitly wrapped with a call to [`display`](#display(value)). For example, this arithmetic expression displays implicitly: ```js echo 1 + 2 // implicit display @@ -238,23 +238,17 @@ Implicit display also implicitly awaits promises. ## Responsive display -In Markdown, the built-in `width` reactive variable represents the current width of the main element. This variable is implemented by [`Generators.width`](./lib/generators#width(element)) and backed by a [`ResizeObserver`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver). The reactive width can be a handy thing to pass, say, as the **width** option to [Observable Plot](./lib/plot). +In Markdown, the built-in [`width` reactive variable](#width) represents the current width of the main element. This variable is implemented by [`Generators.width`](./lib/generators#width(element)) and backed by a [`ResizeObserver`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver). The reactive width can be a handy thing to pass, say, as the **width** option to [Observable Plot](./lib/plot). -```html echo -The current width is ${width}. -``` - -```js -import {resize} from "npm:@observablehq/stdlib"; +```js echo +Plot.barX([9, 4, 8, 1, 11, 3, 4, 2, 7, 5]).plot({width}) ``` -For more control, or in a [grid](./css/grid) where you want to respond to either width or height changing, use the built-in `resize` helper. This takes a render function which is called whenever the width or height [changes](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver), and the element returned by the render function is inserted into the DOM. +For non-top-level elements, as when rendering content within an inline expression, use the built-in [`resize` function](#resize(render)) instead. This takes a _render_ function which is called whenever the width or height [changes](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver), and the element returned by the render function is inserted into the DOM. ```html echo -
-
- ${resize((width) => `This card is ${width}px wide.`)} -
+
+ ${resize((width) => Plot.barX([9, 4, 8, 1, 11, 3, 4, 2, 7, 5]).plot({width}))}
``` @@ -272,3 +266,45 @@ If your container defines a height, such as `240px` in the example below, then y ```
If you are using resize with both width and height and see nothing rendered, it may be because your parent container does not have its own height specified. When both arguments are used, the rendered element is implicitly position: absolute to avoid affecting the size of its parent and causing a feedback loop.
+ +## display(*value*) + +Displays the specified *value* in the current context, returning *value*. If *value* is a DOM element or node, it is inserted directly into the page. Otherwise, if the current context is a fenced code block, inspects the specified *value*; or, if the current context is an inline expression, coerces the specified *value* to a string and displays it as text. + +```js echo +display(1 + 2); +``` + +See [Explicit display](#explicit-display) for more. + +## resize(*render*) + +Creates and returns a DIV element to contain responsive content; then, using a [`ResizeObserver`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) to observe changes to the returned element’s size, calls the specified *render* function with the new width and height whenever the size changes. The element returned by the *render* function is inserted into the DIV element, replacing any previously-rendered content. This is useful for responsive charts. + +```js echo +resize((width) => `I am ${width} pixels wide.`) +``` + +If the *render* function returns a promise, the promise is implicitly awaited. If the resulting value is null, the DIV element is cleared; otherwise, if the resulting value is not a DOM element, it is coerced to a string and displayed as text. + +See [Responsive display](#responsive-display) for more. + +## width + +The current width of the main element in pixels as a reactive variable. A fenced code block or inline expression that references `width` will re-run whenever the width of the main element changes, such as when the window is resized; often used for responsive charts. + +```js echo +width +``` + +See [`Generators.width`](./lib/generators#width(element)) for implementation. + +## now + +The current time in milliseconds since Unix epoch as a reactive variable. A fenced code block or inline expression that references `now` will run continuously; often used for simple animations. + +```js echo +now +``` + +See [`Generators.now`](./lib/generators#now()) for implementation. diff --git a/docs/lib/generators.md b/docs/lib/generators.md index db758fb19..485f7d51e 100644 --- a/docs/lib/generators.md +++ b/docs/lib/generators.md @@ -8,7 +8,7 @@ import {Generators} from "npm:@observablehq/stdlib"; ## input(*element*) -Returns an async generator that yields whenever the given *element* emits an *input* event, with the given *element*’s current value. (It’s a bit fancier than that because we special-case a few element types.) The built-in [`view` function](<../reactivity#inputs>) uses this. +[Source](https://github.com/observablehq/framework/blob/main/src/client/stdlib/generators/input.js) · Returns an async generator that yields whenever the given *element* emits an *input* event, with the given *element*’s current value. (It’s a bit fancier than that because we special-case a few element types.) The built-in [`view` function](<../reactivity#inputs>) uses this. ```js echo const nameInput = display(document.createElement("input")); @@ -19,9 +19,9 @@ const name = Generators.input(nameInput); name ``` -## observe(*change*) +## observe(*initialize*) -Returns an async generator that yields whenever the callback function *change* is called, with the value passed. +[Source](https://github.com/observablehq/framework/blob/main/src/client/stdlib/generators/observe.js) · Returns an async generator that immediately invokes the specified *initialize* function, being passed a *change* callback function, and yields the passed value whenever *change* is called. The *initialize* function may optionally return a *dispose* function that will be called when the generator is terminated. ```js echo const hash = Generators.observe((change) => { @@ -37,7 +37,9 @@ hash ## queue(*change*) -Returns an async generator that yields whenever the callback function *change* is called, with the value passed. This is identical to Generators.observe, except that if *change* is called multiple times before the consumer has a chance to process the yielded result, values will not be dropped; use this if you require that the consumer not miss a yielded value. +[Source](https://github.com/observablehq/framework/blob/main/src/client/stdlib/generators/queue.js) · Returns an async generator that immediately invokes the specified *initialize* function, being passed a *change* callback function, and yields the passed value whenever *change* is called. The *initialize* function may optionally return a *dispose* function that will be called when the generator is terminated. + +This is identical to `Generators.observe` except that if *change* is called multiple times before the consumer has a chance to process the yielded result, values will not be dropped; use this if you require that the consumer not miss a yielded value. ```js run=false const hash = Generators.queue((change) => { @@ -53,7 +55,7 @@ hash ## now() -Returns a generator that repeatedly yields `Date.now()`, forever. This generator is available by default as `now` in Markdown. +[Source](https://github.com/observablehq/framework/blob/main/src/client/stdlib/generators/now.js) · Returns a generator that repeatedly yields `Date.now()`, forever. This generator is available by default as `now` in Markdown. ```js run=false const now = Generators.now(); @@ -65,7 +67,7 @@ now ## width(*element*) -Returns an async generator that yields the width of the given target *element*. Using a [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver), the generator will yield whenever the width of the *element* changes. This generator for the `main` element is available by default as `width` in Markdown. +[Source](https://github.com/observablehq/framework/blob/main/src/client/stdlib/generators/width.js) · Returns an async generator that yields the width of the given target *element*. Using a [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver), the generator will yield whenever the width of the *element* changes. This generator for the `main` element is available by default as `width` in Markdown. ```js run=false const width = Generators.width(document.querySelector("main")); @@ -77,7 +79,7 @@ width ## dark() -Returns an async generator that yields a boolean indicating whether the page is currently displayed with a dark [color scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme). +[Source](https://github.com/observablehq/framework/blob/main/src/client/stdlib/generators/dark.js) · Returns an async generator that yields a boolean indicating whether the page is currently displayed with a dark [color scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme). If the page supports both light and dark mode (as with the [default theme](../themes)), the value reflects the user’s [preferred color scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme). The generator will yield a new value if the preferred color changes — as when the user changes their system settings, or if the user’s system adapts automatically to the diurnal cycle — allowing you to update the display as needed without requiring a page reload. diff --git a/src/client/stdlib/resize.ts b/src/client/stdlib/resize.ts index c9aeef0ce..225ed934f 100644 --- a/src/client/stdlib/resize.ts +++ b/src/client/stdlib/resize.ts @@ -1,6 +1,6 @@ // TODO Automatically disconnect the observer when the returned DIV is detached. export function resize( - render: (width: number, height: number) => Node | null | Promise, + render: (width: number, height: number) => Node | string | null | Promise, invalidation?: Promise ): Node { const div = document.createElement("div"); @@ -24,6 +24,6 @@ export function resize( return div; } -function isElement(node: Node): node is HTMLElement { - return node.nodeType === 1; +function isElement(node: Node | string): node is HTMLElement { + return typeof node === "object" && node.nodeType === 1; }