feat: document binding

fullscreenElement and visibilityState binding
pull/8507/head
ThaUnknown 3 years ago
parent 9425f18e52
commit 43ab54bc4e

@ -1078,6 +1078,11 @@ export interface SvelteMediaTimeRange {
end: number; end: number;
} }
export interface SvelteDocumentAttributes extends HTMLAttributes<Document> {
readonly 'bind:fullscreenElement'?: Document['fullscreenElement'] | undefined | null;
readonly 'bind:visibilityState'?: Document['visibilityState'] | undefined | null;
}
export interface SvelteWindowAttributes extends HTMLAttributes<Window> { export interface SvelteWindowAttributes extends HTMLAttributes<Window> {
readonly 'bind:innerWidth'?: Window['innerWidth'] | undefined | null; readonly 'bind:innerWidth'?: Window['innerWidth'] | undefined | null;
readonly 'bind:innerHeight'?: Window['innerHeight'] | undefined | null; readonly 'bind:innerHeight'?: Window['innerHeight'] | undefined | null;
@ -1591,7 +1596,7 @@ export interface SvelteHTMLElements {
// Svelte specific // Svelte specific
'svelte:window': SvelteWindowAttributes; 'svelte:window': SvelteWindowAttributes;
'svelte:document': HTMLAttributes<Document>; 'svelte:document': SvelteDocumentAttributes;
'svelte:body': HTMLAttributes<HTMLElement>; 'svelte:body': HTMLAttributes<HTMLElement>;
'svelte:fragment': { slot?: string }; 'svelte:fragment': { slot?: string };
'svelte:options': { [name: string]: any }; 'svelte:options': { [name: string]: any };

@ -1756,6 +1756,9 @@ All except `scrollX` and `scrollY` are readonly.
```sv ```sv
<svelte:document on:event={handler}/> <svelte:document on:event={handler}/>
``` ```
```sv
<svelte:document bind:prop={value}/>
```
--- ---
@ -1770,6 +1773,15 @@ As with `<svelte:window>`, this element may only appear the top level of your co
/> />
``` ```
---
You can also bind to the following properties:
* `visibilityState`
* `fullscreenElement`
All except are readonly.
### `<svelte:body>` ### `<svelte:body>`
```sv ```sv

@ -1,14 +1,23 @@
import Node from './shared/Node'; import Node from './shared/Node';
import Binding from './Binding';
import EventHandler from './EventHandler'; import EventHandler from './EventHandler';
import fuzzymatch from '../../utils/fuzzymatch';
import Action from './Action'; import Action from './Action';
import Component from '../Component'; import Component from '../Component';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { Element } from '../../interfaces'; import { Element } from '../../interfaces';
import compiler_warnings from '../compiler_warnings'; import compiler_warnings from '../compiler_warnings';
import compiler_errors from '../compiler_errors';
const valid_bindings = [
'fullscreenElement',
'visibilityState'
];
export default class Document extends Node { export default class Document extends Node {
type: 'Document'; type: 'Document';
handlers: EventHandler[] = []; handlers: EventHandler[] = [];
bindings: Binding[] = [];
actions: Action[] = []; actions: Action[] = [];
constructor(component: Component, parent: Node, scope: TemplateScope, info: Element) { constructor(component: Component, parent: Node, scope: TemplateScope, info: Element) {
@ -17,6 +26,16 @@ export default class Document extends Node {
info.attributes.forEach((node) => { info.attributes.forEach((node) => {
if (node.type === 'EventHandler') { if (node.type === 'EventHandler') {
this.handlers.push(new EventHandler(component, this, scope, node)); this.handlers.push(new EventHandler(component, this, scope, node));
} else if (node.type === 'Binding') {
if (!~valid_bindings.indexOf(node.name)) {
if (fuzzymatch(node.name, valid_bindings)) {
return component.error(node, compiler_errors.invalid_binding_on(node.name, '<svelte:document>', ` (did you mean '${match}'?)`));
} else {
return component.error(node, compiler_errors.invalid_binding_on(node.name, '<svelte:document>', ` — valid bindings are ${list(valid_bindings)}`));
}
}
this.bindings.push(new Binding(component, this, scope, node));
} else if (node.type === 'Action') { } else if (node.type === 'Action') {
this.actions.push(new Action(component, this, scope, node)); this.actions.push(new Action(component, this, scope, node));
} else { } else {

@ -1,6 +1,6 @@
import Block from '../Block'; import Block from '../Block';
import Wrapper from './shared/Wrapper'; import Wrapper from './shared/Wrapper';
import { x } from 'code-red'; import { b, x } from 'code-red';
import Document from '../../nodes/Document'; import Document from '../../nodes/Document';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
import EventHandler from './Element/EventHandler'; import EventHandler from './Element/EventHandler';
@ -9,6 +9,16 @@ import { TemplateNode } from '../../../interfaces';
import Renderer from '../Renderer'; import Renderer from '../Renderer';
import add_actions from './shared/add_actions'; import add_actions from './shared/add_actions';
const associated_events = {
fullscreenElement: 'fullscreenchange',
visibilityState: 'visibilitychange'
};
const readonly = new Set([
'fullscreenElement',
'visibilityState'
]);
export default class DocumentWrapper extends Wrapper { export default class DocumentWrapper extends Wrapper {
node: Document; node: Document;
handlers: EventHandler[]; handlers: EventHandler[];
@ -19,7 +29,64 @@ export default class DocumentWrapper extends Wrapper {
} }
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
const { renderer } = this;
const { component } = renderer;
const events: Record<string, Array<{ name: string; value: string }>> = {};
const bindings: Record<string, string> = {};
add_event_handlers(block, x`@_document`, this.handlers); add_event_handlers(block, x`@_document`, this.handlers);
add_actions(block, x`@_document`, this.node.actions); add_actions(block, x`@_document`, this.node.actions);
this.node.bindings.forEach(binding => {
// TODO: what if it's a MemberExpression?
const binding_name = (binding.expression.node as Identifier).name;
// in dev mode, throw if read-only values are written to
if (readonly.has(binding.name)) {
renderer.readonly.add(binding_name);
}
bindings[binding.name] = binding_name;
const associated_event = associated_events[binding.name];
const property = binding.name;
if (!events[associated_event]) events[associated_event] = [];
events[associated_event].push({
name: binding_name,
value: property
});
});
Object.keys(events).forEach(event => {
const id = block.get_unique_name(`ondocument${event}`);
const props = events[event];
renderer.add_to_context(id.name);
const fn = renderer.reference(id.name);
props.forEach(prop => {
renderer.meta_bindings.push(
b`this._state.${prop.name} = @_document.${prop.value};`
);
});
block.event_listeners.push(x`
@listen(@_document, "${event}", ${fn})
`);
component.partly_hoisted.push(b`
function ${id}() {
${props.map(prop => renderer.invalidate(prop.name, x`${prop.name} = @_document.${prop.value}`))}
}
`);
block.chunks.init.push(b`
@add_render_callback(${fn});
`);
component.has_reactive_assignments = true;
});
} }
} }

Loading…
Cancel
Save