diff --git a/.travis.yml b/.travis.yml index 862286e395..5aba7e8a73 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,19 @@ language: node_js node_js: - "6" - "node" + env: global: - BUILD_TIMEOUT=10000 -install: npm install + +addons: + apt: + packages: + - xvfb + +install: + - export DISPLAY=':99.0' + - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & + - npm install + after_success: npm run codecov diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f46a44cf4..2391c71a94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Svelte changelog +## 1.39.1 + +* Always use anchors for slotted content ([#822](https://github.com/sveltejs/svelte/issues/822)) +* Prevent ES6 in helpers ([#838](https://github.com/sveltejs/svelte/issues/838)) +* Correctly determine whether to use `timeRangesToArray` ([#837](https://github.com/sveltejs/svelte/pull/837)) + +## 1.39.0 + +* Always attach fragment to shadow root ([#821](https://github.com/sveltejs/svelte/issues/821)) +* Add `buffered`, `seekable`, `played` bindings to media elements ([#819](https://github.com/sveltejs/svelte/pull/819)) +* Quote `class` properties in legacy mode ([#830](https://github.com/sveltejs/svelte/issues/830)) +* Warn on missing `lang` attribute on `` ([#828](https://github.com/sveltejs/svelte/pull/828)) + ## 1.38.0 * Compile-time a11y warnings ([#815](https://github.com/sveltejs/svelte/pull/815)) diff --git a/package.json b/package.json index 02e0e8fcd1..d9a50a0f79 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "1.38.0", + "version": "1.39.1", "description": "The magical disappearing UI framework", "main": "compiler/svelte.js", "files": [ @@ -56,6 +56,7 @@ "locate-character": "^2.0.0", "magic-string": "^0.22.3", "mocha": "^3.2.0", + "nightmare": "^2.10.0", "node-resolve": "^1.3.3", "nyc": "^11.1.0", "prettier": "^1.4.1", @@ -65,6 +66,7 @@ "rollup-plugin-json": "^2.1.0", "rollup-plugin-node-resolve": "^3.0.0", "rollup-plugin-typescript": "^0.8.1", + "rollup-plugin-virtual": "^1.0.1", "rollup-watch": "^4.3.1", "source-map": "^0.5.6", "source-map-support": "^0.4.8", diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 957df6e3f4..bd39842f6e 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -208,29 +208,36 @@ export default function dom( this._fragment = @create_main_fragment(this._state, this); - if (options.target) { - ${generator.hydratable - ? deindent` - var nodes = @children(options.target); - options.hydrate ? this._fragment.claim(nodes) : this._fragment.create(); - nodes.forEach(@detachNode); - ` : - deindent` - ${options.dev && `if (options.hydrate) throw new Error("options.hydrate only works if the component was compiled with the \`hydratable: true\` option");`} - this._fragment.create(); + ${generator.customElement ? deindent` + this._fragment.create(); + this._fragment.${block.hasIntroMethod ? 'intro' : 'mount'}(this.shadowRoot, null); + + if (options.target) this._mount(options.target, options.anchor || null); + ` : deindent` + if (options.target) { + ${generator.hydratable + ? deindent` + var nodes = @children(options.target); + options.hydrate ? this._fragment.claim(nodes) : this._fragment.create(); + nodes.forEach(@detachNode); + ` : + deindent` + ${options.dev && `if (options.hydrate) throw new Error("options.hydrate only works if the component was compiled with the \`hydratable: true\` option");`} + this._fragment.create(); + `} + this._fragment.${block.hasIntroMethod ? 'intro' : 'mount'}(options.target, options.anchor || null); + + ${(generator.hasComponents || generator.hasComplexBindings || templateProperties.oncreate || generator.hasIntroTransitions) && deindent` + ${generator.hasComponents && `this._lock = true;`} + ${(generator.hasComponents || generator.hasComplexBindings) && `@callAll(this._beforecreate);`} + ${(generator.hasComponents || templateProperties.oncreate) && `@callAll(this._oncreate);`} + ${(generator.hasComponents || generator.hasIntroTransitions) && `@callAll(this._aftercreate);`} + ${generator.hasComponents && `this._lock = false;`} `} - ${generator.customElement ? - `this._mount(options.target, options.anchor || null);` : - `this._fragment.${block.hasIntroMethod ? 'intro' : 'mount'}(options.target, options.anchor || null);`} - - ${(generator.hasComponents || generator.hasComplexBindings || templateProperties.oncreate || generator.hasIntroTransitions) && deindent` - ${generator.hasComponents && `this._lock = true;`} - ${(generator.hasComponents || generator.hasComplexBindings) && `@callAll(this._beforecreate);`} - ${(generator.hasComponents || templateProperties.oncreate) && `@callAll(this._oncreate);`} - ${(generator.hasComponents || generator.hasIntroTransitions) && `@callAll(this._aftercreate);`} - ${generator.hasComponents && `this._lock = false;`} - `} - } + } + `} + + `; if (generator.customElement) { @@ -272,7 +279,6 @@ export default function dom( customElements.define("${generator.tag}", ${name}); @assign(${prototypeBase}, ${proto}, { _mount(target, anchor) { - this._fragment.${block.hasIntroMethod ? 'intro' : 'mount'}(this.shadowRoot, null); target.insertBefore(this, anchor); }, diff --git a/src/generators/dom/preprocess.ts b/src/generators/dom/preprocess.ts index b945c902b9..1e71361f31 100644 --- a/src/generators/dom/preprocess.ts +++ b/src/generators/dom/preprocess.ts @@ -412,9 +412,13 @@ function preprocessChildren( lastChild = null; cleaned.forEach((child: Node, i: number) => { + child.parent = node; + const preprocessor = preprocessors[child.type]; if (preprocessor) preprocessor(generator, block, state, child, inEachBlock, elementStack, componentStack, stripWhitespace, cleaned[i + 1] || nextSibling); + if (child.shouldSkip) return; + if (lastChild) lastChild.next = child; child.prev = lastChild; diff --git a/src/generators/dom/visitors/EachBlock.ts b/src/generators/dom/visitors/EachBlock.ts index 333c29ce6b..e0d941511e 100644 --- a/src/generators/dom/visitors/EachBlock.ts +++ b/src/generators/dom/visitors/EachBlock.ts @@ -21,7 +21,7 @@ export default function visitEachBlock( const iterations = block.getUniqueName(`${each_block}_iterations`); const params = block.params.join(', '); - const needsAnchor = node.next ? !isDomNode(node.next) : !state.parentNode; + const needsAnchor = node.next ? !isDomNode(node.next, generator) : !state.parentNode; const anchor = needsAnchor ? block.getUniqueName(`${each_block}_anchor`) : (node.next && node.next.var) || 'null'; diff --git a/src/generators/dom/visitors/Element/Binding.ts b/src/generators/dom/visitors/Element/Binding.ts index 492cebd5b4..edc3767c0e 100644 --- a/src/generators/dom/visitors/Element/Binding.ts +++ b/src/generators/dom/visitors/Element/Binding.ts @@ -8,6 +8,13 @@ import { State } from '../../interfaces'; import getObject from '../../../../utils/getObject'; import getTailSnippet from '../../../../utils/getTailSnippet'; +const readOnlyMediaAttributes = new Set([ + 'duration', + 'buffered', + 'seekable', + 'played' +]); + export default function visitBinding( generator: DomGenerator, block: Block, @@ -25,9 +32,9 @@ export default function visitBinding( state.allUsedContexts.push(context); }); - const eventName = getBindingEventName(node, attribute); + const eventNames = getBindingEventName(node, attribute); const handler = block.getUniqueName( - `${state.parentNode}_${eventName}_handler` + `${state.parentNode}_${eventNames.join('_')}_handler` ); const isMultipleSelect = node.name === 'select' && @@ -38,6 +45,10 @@ export default function visitBinding( const bindingGroup = attribute.name === 'group' ? getBindingGroup(generator, attribute.value) : null; + + const isMediaElement = node.name === 'audio' || node.name === 'video'; + const isReadOnly = isMediaElement && readOnlyMediaAttributes.has(attribute.name) + const value = getBindingValue( generator, block, @@ -45,6 +56,7 @@ export default function visitBinding( node, attribute, isMultipleSelect, + isMediaElement, bindingGroup, type ); @@ -52,10 +64,9 @@ export default function visitBinding( let setter = getSetter(block, name, snippet, state.parentNode, attribute, dependencies, value); let updateElement = `${state.parentNode}.${attribute.name} = ${snippet};`; - const needsLock = node.name !== 'input' || !/radio|checkbox|range|color/.test(type); // TODO others? + const needsLock = !isReadOnly && node.name !== 'input' || !/radio|checkbox|range|color/.test(type); // TODO others? const lock = `#${state.parentNode}_updating`; let updateConditions = needsLock ? [`!${lock}`] : []; - let readOnly = false; if (needsLock) block.addVariable(lock, 'false'); @@ -115,7 +126,7 @@ export default function visitBinding( ); updateElement = `${state.parentNode}.checked = ${condition};`; - } else if (node.name === 'audio' || node.name === 'video') { + } else if (isMediaElement) { generator.hasComplexBindings = true; block.builders.hydrate.addBlock(`#component._root._beforecreate.push(${handler});`); @@ -129,8 +140,6 @@ export default function visitBinding( `; updateConditions.push(`!isNaN(${snippet})`); - } else if (attribute.name === 'duration') { - readOnly = true; } else if (attribute.name === 'paused') { // this is necessary to prevent the audio restarting by itself const last = block.getUniqueName(`${state.parentNode}_paused_value`); @@ -161,21 +170,23 @@ export default function visitBinding( @removeListener(${state.parentNode}, "change", ${handler}); `); } else { - block.builders.hydrate.addLine( - `@addListener(${state.parentNode}, "${eventName}", ${handler});` - ); - - block.builders.destroy.addLine( - `@removeListener(${state.parentNode}, "${eventName}", ${handler});` - ); + eventNames.forEach(eventName => { + block.builders.hydrate.addLine( + `@addListener(${state.parentNode}, "${eventName}", ${handler});` + ); + + block.builders.destroy.addLine( + `@removeListener(${state.parentNode}, "${eventName}", ${handler});` + ); + }); } - if (node.name !== 'audio' && node.name !== 'video') { + if (!isMediaElement) { node.initialUpdate = updateElement; node.initialUpdateNeedsStateObject = !block.contexts.has(name); } - if (!readOnly) { // audio/video duration is read-only, it never updates + if (!isReadOnly) { // audio/video duration is read-only, it never updates if (updateConditions.length) { block.builders.update.addBlock(deindent` if (${updateConditions.join(' && ')}) { @@ -206,15 +217,18 @@ function getBindingEventName(node: Node, attribute: Node) { ); const type = typeAttribute ? typeAttribute.value[0].data : 'text'; // TODO in validation, should throw if type attribute is not static - return type === 'checkbox' || type === 'radio' ? 'change' : 'input'; + return [type === 'checkbox' || type === 'radio' ? 'change' : 'input']; } - if (node.name === 'textarea') return 'input'; - if (attribute.name === 'currentTime') return 'timeupdate'; - if (attribute.name === 'duration') return 'durationchange'; - if (attribute.name === 'paused') return 'pause'; + if (node.name === 'textarea') return ['input']; + if (attribute.name === 'currentTime') return ['timeupdate']; + if (attribute.name === 'duration') return ['durationchange']; + if (attribute.name === 'paused') return ['pause']; + if (attribute.name === 'buffered') return ['progress', 'loadedmetadata']; + if (attribute.name === 'seekable') return ['loadedmetadata']; + if (attribute.name === 'played') return ['timeupdate']; - return 'change'; + return ['change']; } function getBindingValue( @@ -224,6 +238,7 @@ function getBindingValue( node: Node, attribute: Node, isMultipleSelect: boolean, + isMediaElement: boolean, bindingGroup: number, type: string ) { @@ -251,6 +266,10 @@ function getBindingValue( return `@toNumber(${state.parentNode}.${attribute.name})`; } + if (isMediaElement && (attribute.name === 'buffered' || attribute.name === 'seekable' || attribute.name === 'played')) { + return `@timeRangesToArray(${state.parentNode}.${attribute.name})` + } + // everything else return `${state.parentNode}.${attribute.name}`; } @@ -317,4 +336,4 @@ function isComputed(node: Node) { } return false; -} \ No newline at end of file +} diff --git a/src/generators/dom/visitors/IfBlock.ts b/src/generators/dom/visitors/IfBlock.ts index 7f4f80fc18..0deaf6b395 100644 --- a/src/generators/dom/visitors/IfBlock.ts +++ b/src/generators/dom/visitors/IfBlock.ts @@ -80,7 +80,7 @@ export default function visitIfBlock( ) { const name = node.var; - const needsAnchor = node.next ? !isDomNode(node.next) : !state.parentNode; + const needsAnchor = node.next ? !isDomNode(node.next, generator) : !state.parentNode || !isDomNode(node.parent, generator); const anchor = needsAnchor ? block.getUniqueName(`${name}_anchor`) : (node.next && node.next.var) || 'null'; @@ -94,7 +94,7 @@ export default function visitIfBlock( const dynamic = branches[0].hasUpdateMethod; // can use [0] as proxy for all, since they necessarily have the same value const hasOutros = branches[0].hasOutroMethod; - const vars = { name, anchor, params, if_name, hasElse }; + const vars = { name, needsAnchor, anchor, params, if_name, hasElse }; if (node.else) { if (hasOutros) { @@ -137,7 +137,7 @@ function simple( node: Node, branch, dynamic, - { name, anchor, params, if_name } + { name, needsAnchor, anchor, params, if_name } ) { block.builders.init.addBlock(deindent` var ${name} = (${branch.condition}) && ${branch.block}(${params}, #component); @@ -152,7 +152,7 @@ function simple( `if (${name}) ${name}.${mountOrIntro}(${targetNode}, ${anchorNode});` ); - const parentNode = state.parentNode || `${anchor}.parentNode`; + const parentNode = (state.parentNode && !needsAnchor) ? state.parentNode : `${anchor}.parentNode`; const enter = dynamic ? branch.hasIntroMethod @@ -227,7 +227,7 @@ function compound( node: Node, branches, dynamic, - { name, anchor, params, hasElse, if_name } + { name, needsAnchor, anchor, params, hasElse, if_name } ) { const select_block_type = generator.getUniqueName(`select_block_type`); const current_block_type = block.getUniqueName(`current_block_type`); @@ -255,7 +255,7 @@ function compound( `${if_name}${name}.${mountOrIntro}(${targetNode}, ${anchorNode});` ); - const parentNode = state.parentNode || `${anchor}.parentNode`; + const parentNode = (state.parentNode && !needsAnchor) ? state.parentNode : `${anchor}.parentNode`; const changeBlock = deindent` ${hasElse @@ -303,7 +303,7 @@ function compoundWithOutros( node: Node, branches, dynamic, - { name, anchor, params, hasElse } + { name, needsAnchor, anchor, params, hasElse } ) { const select_block_type = block.getUniqueName(`select_block_type`); const current_block_type_index = block.getUniqueName(`current_block_type_index`); @@ -354,7 +354,7 @@ function compoundWithOutros( `${if_current_block_type_index}${if_blocks}[${current_block_type_index}].${mountOrIntro}(${targetNode}, ${anchorNode});` ); - const parentNode = state.parentNode || `${anchor}.parentNode`; + const parentNode = (state.parentNode && !needsAnchor) ? state.parentNode : `${anchor}.parentNode`; const destroyOldBlock = deindent` ${name}.outro(function() { diff --git a/src/generators/dom/visitors/shared/isDomNode.ts b/src/generators/dom/visitors/shared/isDomNode.ts index 256aa65f4e..18304e3ff1 100644 --- a/src/generators/dom/visitors/shared/isDomNode.ts +++ b/src/generators/dom/visitors/shared/isDomNode.ts @@ -1,5 +1,7 @@ +import { DomGenerator } from '../../index'; import { Node } from '../../../../interfaces'; -export default function isDomNode(node: Node) { - return node.type === 'Element' || node.type === 'Text' || node.type === 'MustacheTag'; +export default function isDomNode(node: Node, generator: DomGenerator) { + if (node.type === 'Element') return !generator.components.has(node.name); + return node.type === 'Text' || node.type === 'MustacheTag'; } \ No newline at end of file diff --git a/src/interfaces.ts b/src/interfaces.ts index acb9671499..93d1b608f2 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -55,6 +55,7 @@ export interface CompileOptions { hydratable?: boolean; legacy?: boolean; customElement?: CustomElementOptions | true; + css?: boolean; onerror?: (error: Error) => void; onwarn?: (warning: Warning) => void; diff --git a/src/shared/_build.js b/src/shared/_build.js index 3bbf0298ff..0339ba558c 100644 --- a/src/shared/_build.js +++ b/src/shared/_build.js @@ -16,6 +16,11 @@ fs.readdirSync(__dirname).forEach(file => { ast.body.forEach(node => { if (node.type !== 'ExportNamedDeclaration') return; + // check no ES6+ slipped in + acorn.parse(source.slice(node.declaration.start, node.end), { + ecmaVersion: 5 + }); + const declaration = node.declaration; if (!declaration) return; @@ -34,5 +39,7 @@ fs.readdirSync(__dirname).forEach(file => { fs.writeFileSync( 'src/generators/dom/shared.ts', `// this file is auto-generated, do not edit it -export default ${JSON.stringify(declarations, null, '\t')};` +const shared: Record = ${JSON.stringify(declarations, null, '\t')}; + +export default shared;` ); diff --git a/src/shared/dom.js b/src/shared/dom.js index e3b1676e1c..ffbda89b6d 100644 --- a/src/shared/dom.js +++ b/src/shared/dom.js @@ -102,6 +102,14 @@ export function toNumber(value) { return value === '' ? undefined : +value; } +export function timeRangesToArray(ranges) { + var array = []; + for (var i = 0; i < ranges.length; i += 1) { + array.push({ start: ranges.start(i), end: ranges.end(i) }); + } + return array; +} + export function children (element) { return Array.from(element.childNodes); } diff --git a/src/validate/html/validateElement.ts b/src/validate/html/validateElement.ts index c3243c6e64..72ca2a6698 100644 --- a/src/validate/html/validateElement.ts +++ b/src/validate/html/validateElement.ts @@ -115,7 +115,10 @@ export default function validateElement( } else if ( name === 'currentTime' || name === 'duration' || - name === 'paused' + name === 'paused' || + name === 'buffered' || + name === 'seekable' || + name === 'played' ) { if (node.name !== 'audio' && node.name !== 'video') { validator.error( @@ -218,4 +221,4 @@ function checkTypeAttribute(validator: Validator, node: Node) { function isDynamic(attribute: Node) { return attribute.value.length > 1 || attribute.value[0].type !== 'Text'; -} \ No newline at end of file +} diff --git a/test/custom-elements/assert.js b/test/custom-elements/assert.js new file mode 100644 index 0000000000..3cb3918d50 --- /dev/null +++ b/test/custom-elements/assert.js @@ -0,0 +1,3 @@ +export function equal(a, b, message) { + if (a != b) throw new Error(message || `Expected ${a} to equal ${b}`); +} \ No newline at end of file diff --git a/test/custom-elements/index.js b/test/custom-elements/index.js new file mode 100644 index 0000000000..54aefa3885 --- /dev/null +++ b/test/custom-elements/index.js @@ -0,0 +1,97 @@ +import * as fs from 'fs'; +import * as http from 'http'; +import { rollup } from 'rollup'; +import virtual from 'rollup-plugin-virtual'; +import Nightmare from 'nightmare'; +import { loadSvelte } from "../helpers.js"; + +const page = ` + +
+ + +`; + +const assert = fs.readFileSync('test/custom-elements/assert.js', 'utf-8'); + +describe('custom-elements', function() { + this.timeout(10000); + + const nightmare = new Nightmare({ show: false }); + + let svelte; + let server; + let bundle; + + before(() => { + svelte = loadSvelte(); + + return new Promise((fulfil) => { + server = http.createServer((req, res) => { + if (req.url === '/') { + res.end(page); + } + + if (req.url === '/bundle.js') { + res.end(bundle); + } + }); + + server.listen('6789', () => { + fulfil(); + }); + }); + }); + + after(() => { + server.close(); + }); + + fs.readdirSync('test/custom-elements/samples').forEach(dir => { + if (dir[0] === '.') return; + + it(dir, () => { + return rollup({ + input: `test/custom-elements/samples/${dir}/test.js`, + plugins: [ + { + transform(code, id) { + if (id.endsWith('.html')) { + const compiled = svelte.compile(code, { + customElement: true + }); + + return { + code: compiled.code, + map: compiled.map + }; + } + } + }, + + virtual({ + assert + }) + ] + }) + .then(bundle => bundle.generate({ format: 'iife', name: 'test' })) + .then(generated => { + bundle = generated.code; + + return nightmare + .goto('http://localhost:6789') + .evaluate(() => { + return test(document.querySelector('main')); + }) + .then(result => { + if (result) console.log(result); + }) + .catch(message => { + throw new Error(message); + }); + }); + + + }); + }); +}); \ No newline at end of file diff --git a/test/js/samples/custom-element-slot/input.html b/test/custom-elements/samples/html-slots/main.html similarity index 100% rename from test/js/samples/custom-element-slot/input.html rename to test/custom-elements/samples/html-slots/main.html diff --git a/test/custom-elements/samples/html-slots/test.js b/test/custom-elements/samples/html-slots/test.js new file mode 100644 index 0000000000..87cfd2029d --- /dev/null +++ b/test/custom-elements/samples/html-slots/test.js @@ -0,0 +1,17 @@ +import * as assert from 'assert'; +import './main.html'; + +export default function (target) { + target.innerHTML = ` + + slotted + `; + + const el = target.querySelector('custom-element'); + + const div = el.shadowRoot.children[0]; + const [slot0, slot1] = div.children; + + assert.equal(slot0.assignedNodes()[1], target.querySelector('strong')); + assert.equal(slot1.assignedNodes().length, 0); +} \ No newline at end of file diff --git a/test/js/samples/custom-element-basic/input.html b/test/custom-elements/samples/html/main.html similarity index 100% rename from test/js/samples/custom-element-basic/input.html rename to test/custom-elements/samples/html/main.html diff --git a/test/custom-elements/samples/html/test.js b/test/custom-elements/samples/html/test.js new file mode 100644 index 0000000000..a9aa2c7129 --- /dev/null +++ b/test/custom-elements/samples/html/test.js @@ -0,0 +1,12 @@ +import * as assert from 'assert'; +import './main.html'; + +export default function (target) { + target.innerHTML = ''; + const el = target.querySelector('custom-element'); + + assert.equal(el.get('name'), 'world'); + + const h1 = el.shadowRoot.querySelector('h1'); + assert.equal(h1.textContent, 'Hello world!'); +} \ No newline at end of file diff --git a/test/js/samples/custom-element-styled/input.html b/test/custom-elements/samples/new-styled/main.html similarity index 69% rename from test/js/samples/custom-element-styled/input.html rename to test/custom-elements/samples/new-styled/main.html index 78c7ad685b..4163b0732d 100644 --- a/test/js/samples/custom-element-styled/input.html +++ b/test/custom-elements/samples/new-styled/main.html @@ -1,7 +1,7 @@ -

Hello {{name}}!

+

styled

diff --git a/test/custom-elements/samples/new-styled/test.js b/test/custom-elements/samples/new-styled/test.js new file mode 100644 index 0000000000..6bb37d7a2a --- /dev/null +++ b/test/custom-elements/samples/new-styled/test.js @@ -0,0 +1,19 @@ +import * as assert from 'assert'; +import CustomElement from './main.html'; + +export default function (target) { + target.innerHTML = '

unstyled

'; + + new CustomElement({ + target + }); + + const unstyled = target.querySelector('p'); + const styled = target.querySelector('custom-element').shadowRoot.querySelector('p'); + + assert.equal(unstyled.textContent, 'unstyled'); + assert.equal(styled.textContent, 'styled'); + + assert.equal(getComputedStyle(unstyled).color, 'rgb(0, 0, 0)'); + assert.equal(getComputedStyle(styled).color, 'rgb(255, 0, 0)'); +} \ No newline at end of file diff --git a/test/custom-elements/samples/new/main.html b/test/custom-elements/samples/new/main.html new file mode 100644 index 0000000000..6cc7a9e7af --- /dev/null +++ b/test/custom-elements/samples/new/main.html @@ -0,0 +1,7 @@ +

Hello {{name}}!

+ + \ No newline at end of file diff --git a/test/custom-elements/samples/new/test.js b/test/custom-elements/samples/new/test.js new file mode 100644 index 0000000000..ff27f52130 --- /dev/null +++ b/test/custom-elements/samples/new/test.js @@ -0,0 +1,18 @@ +import * as assert from 'assert'; +import CustomElement from './main.html'; + +export default function (target) { + new CustomElement({ + target, + data: { + name: 'world' + } + }); + + assert.equal(target.innerHTML, ''); + + const el = target.querySelector('custom-element'); + const h1 = el.shadowRoot.querySelector('h1'); + + assert.equal(h1.textContent, 'Hello world!'); +} \ No newline at end of file diff --git a/test/js/samples/custom-element-basic/_config.js b/test/js/samples/custom-element-basic/_config.js deleted file mode 100644 index 735dd07e62..0000000000 --- a/test/js/samples/custom-element-basic/_config.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - options: { - customElement: true - } -}; \ No newline at end of file diff --git a/test/js/samples/custom-element-basic/expected-bundle.js b/test/js/samples/custom-element-basic/expected-bundle.js deleted file mode 100644 index bd1a063877..0000000000 --- a/test/js/samples/custom-element-basic/expected-bundle.js +++ /dev/null @@ -1,262 +0,0 @@ -function noop() {} - -function assign(target) { - var k, - source, - i = 1, - len = arguments.length; - for (; i < len; i++) { - source = arguments[i]; - for (k in source) target[k] = source[k]; - } - - return target; -} - -function appendNode(node, target) { - target.appendChild(node); -} - -function insertNode(node, target, anchor) { - target.insertBefore(node, anchor); -} - -function detachNode(node) { - node.parentNode.removeChild(node); -} - -function createElement(name) { - return document.createElement(name); -} - -function createText(data) { - return document.createTextNode(data); -} - -function destroy(detach) { - this.destroy = noop; - this.fire('destroy'); - this.set = this.get = noop; - - if (detach !== false) this._fragment.unmount(); - this._fragment.destroy(); - this._fragment = this._state = null; -} - -function differs(a, b) { - return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); -} - -function dispatchObservers(component, group, changed, newState, oldState) { - for (var key in group) { - if (!changed[key]) continue; - - var newValue = newState[key]; - var oldValue = oldState[key]; - - var callbacks = group[key]; - if (!callbacks) continue; - - for (var i = 0; i < callbacks.length; i += 1) { - var callback = callbacks[i]; - if (callback.__calling) continue; - - callback.__calling = true; - callback.call(component, newValue, oldValue); - callback.__calling = false; - } - } -} - -function get(key) { - return key ? this._state[key] : this._state; -} - -function fire(eventName, data) { - var handlers = - eventName in this._handlers && this._handlers[eventName].slice(); - if (!handlers) return; - - for (var i = 0; i < handlers.length; i += 1) { - handlers[i].call(this, data); - } -} - -function observe(key, callback, options) { - var group = options && options.defer - ? this._observers.post - : this._observers.pre; - - (group[key] || (group[key] = [])).push(callback); - - if (!options || options.init !== false) { - callback.__calling = true; - callback.call(this, this._state[key]); - callback.__calling = false; - } - - return { - cancel: function() { - var index = group[key].indexOf(callback); - if (~index) group[key].splice(index, 1); - } - }; -} - -function on(eventName, handler) { - if (eventName === 'teardown') return this.on('destroy', handler); - - var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); - handlers.push(handler); - - return { - cancel: function() { - var index = handlers.indexOf(handler); - if (~index) handlers.splice(index, 1); - } - }; -} - -function set(newState) { - this._set(assign({}, newState)); - if (this._root._lock) return; - this._root._lock = true; - callAll(this._root._beforecreate); - callAll(this._root._oncreate); - callAll(this._root._aftercreate); - this._root._lock = false; -} - -function _set(newState) { - var oldState = this._state, - changed = {}, - dirty = false; - - for (var key in newState) { - if (differs(newState[key], oldState[key])) changed[key] = dirty = true; - } - if (!dirty) return; - - this._state = assign({}, oldState, newState); - this._recompute(changed, this._state, oldState, false); - if (this._bind) this._bind(changed, this._state); - dispatchObservers(this, this._observers.pre, changed, this._state, oldState); - this._fragment.update(changed, this._state); - dispatchObservers(this, this._observers.post, changed, this._state, oldState); -} - -function callAll(fns) { - while (fns && fns.length) fns.pop()(); -} - -function _mount(target, anchor) { - this._fragment.mount(target, anchor); -} - -function _unmount() { - this._fragment.unmount(); -} - -var proto = { - destroy: destroy, - get: get, - fire: fire, - observe: observe, - on: on, - set: set, - teardown: destroy, - _recompute: noop, - _set: _set, - _mount: _mount, - _unmount: _unmount -}; - -function create_main_fragment(state, component) { - var h1, text, text_1, text_2; - - return { - create: function() { - h1 = createElement("h1"); - text = createText("Hello "); - text_1 = createText(state.name); - text_2 = createText("!"); - }, - - mount: function(target, anchor) { - insertNode(h1, target, anchor); - appendNode(text, h1); - appendNode(text_1, h1); - appendNode(text_2, h1); - }, - - update: function(changed, state) { - if ( changed.name ) { - text_1.data = state.name; - } - }, - - unmount: function() { - detachNode(h1); - }, - - destroy: noop - }; -} - -class SvelteComponent extends HTMLElement { - constructor(options = {}) { - super(); - this.options = options; - this._state = options.data || {}; - - this._observers = { - pre: Object.create(null), - post: Object.create(null) - }; - - this._handlers = Object.create(null); - - this._root = options._root || this; - this._yield = options._yield; - this._bind = options._bind; - - this.attachShadow({ mode: 'open' }); - - this._fragment = create_main_fragment(this._state, this); - - if (options.target) { - this._fragment.create(); - this._mount(options.target, options.anchor || null); - } - } - - static get observedAttributes() { - return ["name"]; - } - - get name() { - return this.get('name'); - } - - set name(value) { - this.set({ name: value }); - } - - attributeChangedCallback(attr, oldValue, newValue) { - this.set({ [attr]: newValue }); - } -} - -customElements.define("custom-element", SvelteComponent); -assign(SvelteComponent.prototype, proto , { - _mount(target, anchor) { - this._fragment.mount(this.shadowRoot, null); - target.insertBefore(this, anchor); - }, - - _unmount() { - this.parentNode.removeChild(this); - } -}); - -export default SvelteComponent; diff --git a/test/js/samples/custom-element-basic/expected.js b/test/js/samples/custom-element-basic/expected.js deleted file mode 100644 index d7b2514940..0000000000 --- a/test/js/samples/custom-element-basic/expected.js +++ /dev/null @@ -1,91 +0,0 @@ -import { appendNode, assign, createElement, createText, detachNode, insertNode, noop, proto } from "svelte/shared.js"; - -function create_main_fragment(state, component) { - var h1, text, text_1, text_2; - - return { - create: function() { - h1 = createElement("h1"); - text = createText("Hello "); - text_1 = createText(state.name); - text_2 = createText("!"); - }, - - mount: function(target, anchor) { - insertNode(h1, target, anchor); - appendNode(text, h1); - appendNode(text_1, h1); - appendNode(text_2, h1); - }, - - update: function(changed, state) { - if ( changed.name ) { - text_1.data = state.name; - } - }, - - unmount: function() { - detachNode(h1); - }, - - destroy: noop - }; -} - -class SvelteComponent extends HTMLElement { - constructor(options = {}) { - super(); - this.options = options; - this._state = options.data || {}; - - this._observers = { - pre: Object.create(null), - post: Object.create(null) - }; - - this._handlers = Object.create(null); - - this._root = options._root || this; - this._yield = options._yield; - this._bind = options._bind; - - this.attachShadow({ mode: 'open' }); - - this._fragment = create_main_fragment(this._state, this); - - if (options.target) { - this._fragment.create(); - this._mount(options.target, options.anchor || null); - } - } - - static get observedAttributes() { - return ["name"]; - } - - get name() { - return this.get('name'); - } - - set name(value) { - this.set({ name: value }); - } - - attributeChangedCallback(attr, oldValue, newValue) { - this.set({ [attr]: newValue }); - } -} - -customElements.define("custom-element", SvelteComponent); -assign(SvelteComponent.prototype, proto , { - _mount(target, anchor) { - this._fragment.mount(this.shadowRoot, null); - target.insertBefore(this, anchor); - }, - - _unmount() { - this.parentNode.removeChild(this); - } -}); - -export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/custom-element-slot/_config.js b/test/js/samples/custom-element-slot/_config.js deleted file mode 100644 index 735dd07e62..0000000000 --- a/test/js/samples/custom-element-slot/_config.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - options: { - customElement: true - } -}; \ No newline at end of file diff --git a/test/js/samples/custom-element-slot/expected-bundle.js b/test/js/samples/custom-element-slot/expected-bundle.js deleted file mode 100644 index 8667a5d672..0000000000 --- a/test/js/samples/custom-element-slot/expected-bundle.js +++ /dev/null @@ -1,278 +0,0 @@ -function noop() {} - -function assign(target) { - var k, - source, - i = 1, - len = arguments.length; - for (; i < len; i++) { - source = arguments[i]; - for (k in source) target[k] = source[k]; - } - - return target; -} - -function appendNode(node, target) { - target.appendChild(node); -} - -function insertNode(node, target, anchor) { - target.insertBefore(node, anchor); -} - -function detachNode(node) { - node.parentNode.removeChild(node); -} - -function createElement(name) { - return document.createElement(name); -} - -function createText(data) { - return document.createTextNode(data); -} - -function setAttribute(node, attribute, value) { - node.setAttribute(attribute, value); -} - -function destroy(detach) { - this.destroy = noop; - this.fire('destroy'); - this.set = this.get = noop; - - if (detach !== false) this._fragment.unmount(); - this._fragment.destroy(); - this._fragment = this._state = null; -} - -function differs(a, b) { - return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); -} - -function dispatchObservers(component, group, changed, newState, oldState) { - for (var key in group) { - if (!changed[key]) continue; - - var newValue = newState[key]; - var oldValue = oldState[key]; - - var callbacks = group[key]; - if (!callbacks) continue; - - for (var i = 0; i < callbacks.length; i += 1) { - var callback = callbacks[i]; - if (callback.__calling) continue; - - callback.__calling = true; - callback.call(component, newValue, oldValue); - callback.__calling = false; - } - } -} - -function get(key) { - return key ? this._state[key] : this._state; -} - -function fire(eventName, data) { - var handlers = - eventName in this._handlers && this._handlers[eventName].slice(); - if (!handlers) return; - - for (var i = 0; i < handlers.length; i += 1) { - handlers[i].call(this, data); - } -} - -function observe(key, callback, options) { - var group = options && options.defer - ? this._observers.post - : this._observers.pre; - - (group[key] || (group[key] = [])).push(callback); - - if (!options || options.init !== false) { - callback.__calling = true; - callback.call(this, this._state[key]); - callback.__calling = false; - } - - return { - cancel: function() { - var index = group[key].indexOf(callback); - if (~index) group[key].splice(index, 1); - } - }; -} - -function on(eventName, handler) { - if (eventName === 'teardown') return this.on('destroy', handler); - - var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); - handlers.push(handler); - - return { - cancel: function() { - var index = handlers.indexOf(handler); - if (~index) handlers.splice(index, 1); - } - }; -} - -function set(newState) { - this._set(assign({}, newState)); - if (this._root._lock) return; - this._root._lock = true; - callAll(this._root._beforecreate); - callAll(this._root._oncreate); - callAll(this._root._aftercreate); - this._root._lock = false; -} - -function _set(newState) { - var oldState = this._state, - changed = {}, - dirty = false; - - for (var key in newState) { - if (differs(newState[key], oldState[key])) changed[key] = dirty = true; - } - if (!dirty) return; - - this._state = assign({}, oldState, newState); - this._recompute(changed, this._state, oldState, false); - if (this._bind) this._bind(changed, this._state); - dispatchObservers(this, this._observers.pre, changed, this._state, oldState); - this._fragment.update(changed, this._state); - dispatchObservers(this, this._observers.post, changed, this._state, oldState); -} - -function callAll(fns) { - while (fns && fns.length) fns.pop()(); -} - -function _mount(target, anchor) { - this._fragment.mount(target, anchor); -} - -function _unmount() { - this._fragment.unmount(); -} - -var proto = { - destroy: destroy, - get: get, - fire: fire, - observe: observe, - on: on, - set: set, - teardown: destroy, - _recompute: noop, - _set: _set, - _mount: _mount, - _unmount: _unmount -}; - -function create_main_fragment(state, component) { - var div, slot, p, text, text_2, slot_1, p_1, text_3; - - return { - create: function() { - div = createElement("div"); - slot = createElement("slot"); - p = createElement("p"); - text = createText("default fallback content"); - text_2 = createText("\n\n\t"); - slot_1 = createElement("slot"); - p_1 = createElement("p"); - text_3 = createText("foo fallback content"); - this.hydrate(); - }, - - hydrate: function(nodes) { - setAttribute(slot_1, "name", "foo"); - }, - - mount: function(target, anchor) { - insertNode(div, target, anchor); - appendNode(slot, div); - appendNode(p, slot); - appendNode(text, p); - appendNode(text_2, div); - appendNode(slot_1, div); - appendNode(p_1, slot_1); - appendNode(text_3, p_1); - }, - - update: noop, - - unmount: function() { - detachNode(div); - }, - - destroy: noop - }; -} - -class SvelteComponent extends HTMLElement { - constructor(options = {}) { - super(); - this.options = options; - this._state = options.data || {}; - - this._observers = { - pre: Object.create(null), - post: Object.create(null) - }; - - this._handlers = Object.create(null); - - this._root = options._root || this; - this._yield = options._yield; - this._bind = options._bind; - this._slotted = options.slots || {}; - - this.attachShadow({ mode: 'open' }); - - this.slots = {}; - - this._fragment = create_main_fragment(this._state, this); - - if (options.target) { - this._fragment.create(); - this._mount(options.target, options.anchor || null); - } - } - - static get observedAttributes() { - return []; - } - - - - connectedCallback() { - Object.keys(this._slotted).forEach(key => { - this.appendChild(this._slotted[key]); - }); - } - - attributeChangedCallback(attr, oldValue, newValue) { - this.set({ [attr]: newValue }); - } -} - -customElements.define("custom-element", SvelteComponent); -assign(SvelteComponent.prototype, proto , { - _mount(target, anchor) { - this._fragment.mount(this.shadowRoot, null); - target.insertBefore(this, anchor); - }, - - _unmount() { - this.parentNode.removeChild(this); - } -}); - -export default SvelteComponent; diff --git a/test/js/samples/custom-element-slot/expected.js b/test/js/samples/custom-element-slot/expected.js deleted file mode 100644 index b64c7e6838..0000000000 --- a/test/js/samples/custom-element-slot/expected.js +++ /dev/null @@ -1,103 +0,0 @@ -import { appendNode, assign, createElement, createText, detachNode, insertNode, noop, proto, setAttribute } from "svelte/shared.js"; - -function create_main_fragment(state, component) { - var div, slot, p, text, text_2, slot_1, p_1, text_3; - - return { - create: function() { - div = createElement("div"); - slot = createElement("slot"); - p = createElement("p"); - text = createText("default fallback content"); - text_2 = createText("\n\n\t"); - slot_1 = createElement("slot"); - p_1 = createElement("p"); - text_3 = createText("foo fallback content"); - this.hydrate(); - }, - - hydrate: function(nodes) { - setAttribute(slot_1, "name", "foo"); - }, - - mount: function(target, anchor) { - insertNode(div, target, anchor); - appendNode(slot, div); - appendNode(p, slot); - appendNode(text, p); - appendNode(text_2, div); - appendNode(slot_1, div); - appendNode(p_1, slot_1); - appendNode(text_3, p_1); - }, - - update: noop, - - unmount: function() { - detachNode(div); - }, - - destroy: noop - }; -} - -class SvelteComponent extends HTMLElement { - constructor(options = {}) { - super(); - this.options = options; - this._state = options.data || {}; - - this._observers = { - pre: Object.create(null), - post: Object.create(null) - }; - - this._handlers = Object.create(null); - - this._root = options._root || this; - this._yield = options._yield; - this._bind = options._bind; - this._slotted = options.slots || {}; - - this.attachShadow({ mode: 'open' }); - - this.slots = {}; - - this._fragment = create_main_fragment(this._state, this); - - if (options.target) { - this._fragment.create(); - this._mount(options.target, options.anchor || null); - } - } - - static get observedAttributes() { - return []; - } - - - - connectedCallback() { - Object.keys(this._slotted).forEach(key => { - this.appendChild(this._slotted[key]); - }); - } - - attributeChangedCallback(attr, oldValue, newValue) { - this.set({ [attr]: newValue }); - } -} - -customElements.define("custom-element", SvelteComponent); -assign(SvelteComponent.prototype, proto , { - _mount(target, anchor) { - this._fragment.mount(this.shadowRoot, null); - target.insertBefore(this, anchor); - }, - - _unmount() { - this.parentNode.removeChild(this); - } -}); - -export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/custom-element-styled/_config.js b/test/js/samples/custom-element-styled/_config.js deleted file mode 100644 index 735dd07e62..0000000000 --- a/test/js/samples/custom-element-styled/_config.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - options: { - customElement: true - } -}; \ No newline at end of file diff --git a/test/js/samples/custom-element-styled/expected-bundle.js b/test/js/samples/custom-element-styled/expected-bundle.js deleted file mode 100644 index 1ef76c5aae..0000000000 --- a/test/js/samples/custom-element-styled/expected-bundle.js +++ /dev/null @@ -1,263 +0,0 @@ -function noop() {} - -function assign(target) { - var k, - source, - i = 1, - len = arguments.length; - for (; i < len; i++) { - source = arguments[i]; - for (k in source) target[k] = source[k]; - } - - return target; -} - -function appendNode(node, target) { - target.appendChild(node); -} - -function insertNode(node, target, anchor) { - target.insertBefore(node, anchor); -} - -function detachNode(node) { - node.parentNode.removeChild(node); -} - -function createElement(name) { - return document.createElement(name); -} - -function createText(data) { - return document.createTextNode(data); -} - -function destroy(detach) { - this.destroy = noop; - this.fire('destroy'); - this.set = this.get = noop; - - if (detach !== false) this._fragment.unmount(); - this._fragment.destroy(); - this._fragment = this._state = null; -} - -function differs(a, b) { - return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); -} - -function dispatchObservers(component, group, changed, newState, oldState) { - for (var key in group) { - if (!changed[key]) continue; - - var newValue = newState[key]; - var oldValue = oldState[key]; - - var callbacks = group[key]; - if (!callbacks) continue; - - for (var i = 0; i < callbacks.length; i += 1) { - var callback = callbacks[i]; - if (callback.__calling) continue; - - callback.__calling = true; - callback.call(component, newValue, oldValue); - callback.__calling = false; - } - } -} - -function get(key) { - return key ? this._state[key] : this._state; -} - -function fire(eventName, data) { - var handlers = - eventName in this._handlers && this._handlers[eventName].slice(); - if (!handlers) return; - - for (var i = 0; i < handlers.length; i += 1) { - handlers[i].call(this, data); - } -} - -function observe(key, callback, options) { - var group = options && options.defer - ? this._observers.post - : this._observers.pre; - - (group[key] || (group[key] = [])).push(callback); - - if (!options || options.init !== false) { - callback.__calling = true; - callback.call(this, this._state[key]); - callback.__calling = false; - } - - return { - cancel: function() { - var index = group[key].indexOf(callback); - if (~index) group[key].splice(index, 1); - } - }; -} - -function on(eventName, handler) { - if (eventName === 'teardown') return this.on('destroy', handler); - - var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); - handlers.push(handler); - - return { - cancel: function() { - var index = handlers.indexOf(handler); - if (~index) handlers.splice(index, 1); - } - }; -} - -function set(newState) { - this._set(assign({}, newState)); - if (this._root._lock) return; - this._root._lock = true; - callAll(this._root._beforecreate); - callAll(this._root._oncreate); - callAll(this._root._aftercreate); - this._root._lock = false; -} - -function _set(newState) { - var oldState = this._state, - changed = {}, - dirty = false; - - for (var key in newState) { - if (differs(newState[key], oldState[key])) changed[key] = dirty = true; - } - if (!dirty) return; - - this._state = assign({}, oldState, newState); - this._recompute(changed, this._state, oldState, false); - if (this._bind) this._bind(changed, this._state); - dispatchObservers(this, this._observers.pre, changed, this._state, oldState); - this._fragment.update(changed, this._state); - dispatchObservers(this, this._observers.post, changed, this._state, oldState); -} - -function callAll(fns) { - while (fns && fns.length) fns.pop()(); -} - -function _mount(target, anchor) { - this._fragment.mount(target, anchor); -} - -function _unmount() { - this._fragment.unmount(); -} - -var proto = { - destroy: destroy, - get: get, - fire: fire, - observe: observe, - on: on, - set: set, - teardown: destroy, - _recompute: noop, - _set: _set, - _mount: _mount, - _unmount: _unmount -}; - -function create_main_fragment(state, component) { - var h1, text, text_1, text_2; - - return { - create: function() { - h1 = createElement("h1"); - text = createText("Hello "); - text_1 = createText(state.name); - text_2 = createText("!"); - }, - - mount: function(target, anchor) { - insertNode(h1, target, anchor); - appendNode(text, h1); - appendNode(text_1, h1); - appendNode(text_2, h1); - }, - - update: function(changed, state) { - if ( changed.name ) { - text_1.data = state.name; - } - }, - - unmount: function() { - detachNode(h1); - }, - - destroy: noop - }; -} - -class SvelteComponent extends HTMLElement { - constructor(options = {}) { - super(); - this.options = options; - this._state = options.data || {}; - - this._observers = { - pre: Object.create(null), - post: Object.create(null) - }; - - this._handlers = Object.create(null); - - this._root = options._root || this; - this._yield = options._yield; - this._bind = options._bind; - - this.attachShadow({ mode: 'open' }); - this.shadowRoot.innerHTML = ``; - - this._fragment = create_main_fragment(this._state, this); - - if (options.target) { - this._fragment.create(); - this._mount(options.target, options.anchor || null); - } - } - - static get observedAttributes() { - return ["name"]; - } - - get name() { - return this.get('name'); - } - - set name(value) { - this.set({ name: value }); - } - - attributeChangedCallback(attr, oldValue, newValue) { - this.set({ [attr]: newValue }); - } -} - -customElements.define("custom-element", SvelteComponent); -assign(SvelteComponent.prototype, proto , { - _mount(target, anchor) { - this._fragment.mount(this.shadowRoot, null); - target.insertBefore(this, anchor); - }, - - _unmount() { - this.parentNode.removeChild(this); - } -}); - -export default SvelteComponent; diff --git a/test/js/samples/custom-element-styled/expected.js b/test/js/samples/custom-element-styled/expected.js deleted file mode 100644 index 8d53597042..0000000000 --- a/test/js/samples/custom-element-styled/expected.js +++ /dev/null @@ -1,92 +0,0 @@ -import { appendNode, assign, createElement, createText, detachNode, insertNode, noop, proto } from "svelte/shared.js"; - -function create_main_fragment(state, component) { - var h1, text, text_1, text_2; - - return { - create: function() { - h1 = createElement("h1"); - text = createText("Hello "); - text_1 = createText(state.name); - text_2 = createText("!"); - }, - - mount: function(target, anchor) { - insertNode(h1, target, anchor); - appendNode(text, h1); - appendNode(text_1, h1); - appendNode(text_2, h1); - }, - - update: function(changed, state) { - if ( changed.name ) { - text_1.data = state.name; - } - }, - - unmount: function() { - detachNode(h1); - }, - - destroy: noop - }; -} - -class SvelteComponent extends HTMLElement { - constructor(options = {}) { - super(); - this.options = options; - this._state = options.data || {}; - - this._observers = { - pre: Object.create(null), - post: Object.create(null) - }; - - this._handlers = Object.create(null); - - this._root = options._root || this; - this._yield = options._yield; - this._bind = options._bind; - - this.attachShadow({ mode: 'open' }); - this.shadowRoot.innerHTML = ``; - - this._fragment = create_main_fragment(this._state, this); - - if (options.target) { - this._fragment.create(); - this._mount(options.target, options.anchor || null); - } - } - - static get observedAttributes() { - return ["name"]; - } - - get name() { - return this.get('name'); - } - - set name(value) { - this.set({ name: value }); - } - - attributeChangedCallback(attr, oldValue, newValue) { - this.set({ [attr]: newValue }); - } -} - -customElements.define("custom-element", SvelteComponent); -assign(SvelteComponent.prototype, proto , { - _mount(target, anchor) { - this._fragment.mount(this.shadowRoot, null); - target.insertBefore(this, anchor); - }, - - _unmount() { - this.parentNode.removeChild(this); - } -}); - -export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/media-bindings/expected-bundle.js b/test/js/samples/media-bindings/expected-bundle.js new file mode 100644 index 0000000000..980c134d90 --- /dev/null +++ b/test/js/samples/media-bindings/expected-bundle.js @@ -0,0 +1,320 @@ +function noop() {} + +function assign(target) { + var k, + source, + i = 1, + len = arguments.length; + for (; i < len; i++) { + source = arguments[i]; + for (k in source) target[k] = source[k]; + } + + return target; +} + +function insertNode(node, target, anchor) { + target.insertBefore(node, anchor); +} + +function detachNode(node) { + node.parentNode.removeChild(node); +} + +function createElement(name) { + return document.createElement(name); +} + +function addListener(node, event, handler) { + node.addEventListener(event, handler, false); +} + +function removeListener(node, event, handler) { + node.removeEventListener(event, handler, false); +} + +function timeRangesToArray(ranges) { + var array = []; + for (var i = 0; i < ranges.length; i += 1) { + array.push({ start: ranges.start(i), end: ranges.end(i) }); + } + return array; +} + +function destroy(detach) { + this.destroy = noop; + this.fire('destroy'); + this.set = this.get = noop; + + if (detach !== false) this._fragment.unmount(); + this._fragment.destroy(); + this._fragment = this._state = null; +} + +function differs(a, b) { + return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); +} + +function dispatchObservers(component, group, changed, newState, oldState) { + for (var key in group) { + if (!changed[key]) continue; + + var newValue = newState[key]; + var oldValue = oldState[key]; + + var callbacks = group[key]; + if (!callbacks) continue; + + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; + + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; + } + } +} + +function get(key) { + return key ? this._state[key] : this._state; +} + +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; + + for (var i = 0; i < handlers.length; i += 1) { + handlers[i].call(this, data); + } +} + +function observe(key, callback, options) { + var group = options && options.defer + ? this._observers.post + : this._observers.pre; + + (group[key] || (group[key] = [])).push(callback); + + if (!options || options.init !== false) { + callback.__calling = true; + callback.call(this, this._state[key]); + callback.__calling = false; + } + + return { + cancel: function() { + var index = group[key].indexOf(callback); + if (~index) group[key].splice(index, 1); + } + }; +} + +function on(eventName, handler) { + if (eventName === 'teardown') return this.on('destroy', handler); + + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); + + return { + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); + } + }; +} + +function set(newState) { + this._set(assign({}, newState)); + if (this._root._lock) return; + this._root._lock = true; + callAll(this._root._beforecreate); + callAll(this._root._oncreate); + callAll(this._root._aftercreate); + this._root._lock = false; +} + +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + +function callAll(fns) { + while (fns && fns.length) fns.pop()(); +} + +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + +var proto = { + destroy: destroy, + get: get, + fire: fire, + observe: observe, + on: on, + set: set, + teardown: destroy, + _recompute: noop, + _set: _set, + _mount: _mount, + _unmount: _unmount +}; + +function create_main_fragment(state, component) { + var audio, audio_updating = false, audio_animationframe, audio_paused_value = true; + + function audio_progress_loadedmetadata_handler() { + audio_updating = true; + component.set({ buffered: timeRangesToArray(audio.buffered) }); + audio_updating = false; + } + + function audio_loadedmetadata_handler() { + audio_updating = true; + component.set({ seekable: timeRangesToArray(audio.seekable) }); + audio_updating = false; + } + + function audio_timeupdate_handler() { + audio_updating = true; + component.set({ played: timeRangesToArray(audio.played) }); + audio_updating = false; + } + + function audio_timeupdate_handler_1() { + audio_updating = true; + cancelAnimationFrame(audio_animationframe); + if (!audio.paused) audio_animationframe = requestAnimationFrame(audio_timeupdate_handler_1); + component.set({ currentTime: audio.currentTime }); + audio_updating = false; + } + + function audio_durationchange_handler() { + audio_updating = true; + component.set({ duration: audio.duration }); + audio_updating = false; + } + + function audio_pause_handler() { + audio_updating = true; + component.set({ paused: audio.paused }); + audio_updating = false; + } + + return { + create: function() { + audio = createElement("audio"); + addListener(audio, "play", audio_pause_handler); + this.hydrate(); + }, + + hydrate: function(nodes) { + component._root._beforecreate.push(audio_progress_loadedmetadata_handler); + + addListener(audio, "progress", audio_progress_loadedmetadata_handler); + addListener(audio, "loadedmetadata", audio_progress_loadedmetadata_handler); + + component._root._beforecreate.push(audio_loadedmetadata_handler); + + addListener(audio, "loadedmetadata", audio_loadedmetadata_handler); + + component._root._beforecreate.push(audio_timeupdate_handler); + + addListener(audio, "timeupdate", audio_timeupdate_handler); + + component._root._beforecreate.push(audio_timeupdate_handler_1); + + addListener(audio, "timeupdate", audio_timeupdate_handler_1); + + component._root._beforecreate.push(audio_durationchange_handler); + + addListener(audio, "durationchange", audio_durationchange_handler); + + component._root._beforecreate.push(audio_pause_handler); + + addListener(audio, "pause", audio_pause_handler); + }, + + mount: function(target, anchor) { + insertNode(audio, target, anchor); + }, + + update: function(changed, state) { + if (!audio_updating && !isNaN(state.currentTime )) { + audio.currentTime = state.currentTime ; + } + + if (audio_paused_value !== (audio_paused_value = state.paused)) { + audio[audio_paused_value ? "pause" : "play"](); + } + }, + + unmount: function() { + detachNode(audio); + }, + + destroy: function() { + removeListener(audio, "progress", audio_progress_loadedmetadata_handler); + removeListener(audio, "loadedmetadata", audio_progress_loadedmetadata_handler); + removeListener(audio, "loadedmetadata", audio_loadedmetadata_handler); + removeListener(audio, "timeupdate", audio_timeupdate_handler); + removeListener(audio, "timeupdate", audio_timeupdate_handler_1); + removeListener(audio, "durationchange", audio_durationchange_handler); + removeListener(audio, "pause", audio_pause_handler); + removeListener(audio, "play", audio_pause_handler); + } + }; +} + +function SvelteComponent(options) { + this.options = options; + this._state = options.data || {}; + + this._observers = { + pre: Object.create(null), + post: Object.create(null) + }; + + this._handlers = Object.create(null); + + this._root = options._root || this; + this._yield = options._yield; + this._bind = options._bind; + + if (!options._root) { + this._oncreate = []; + this._beforecreate = []; + } + + this._fragment = create_main_fragment(this._state, this); + + if (options.target) { + this._fragment.create(); + this._fragment.mount(options.target, options.anchor || null); + + callAll(this._beforecreate); + } +} + +assign(SvelteComponent.prototype, proto ); + +export default SvelteComponent; diff --git a/test/js/samples/media-bindings/expected.js b/test/js/samples/media-bindings/expected.js new file mode 100644 index 0000000000..52a6a00a53 --- /dev/null +++ b/test/js/samples/media-bindings/expected.js @@ -0,0 +1,141 @@ +import { addListener, assign, callAll, createElement, detachNode, insertNode, proto, removeListener, timeRangesToArray } from "svelte/shared.js"; + +function create_main_fragment(state, component) { + var audio, audio_updating = false, audio_animationframe, audio_paused_value = true; + + function audio_progress_loadedmetadata_handler() { + audio_updating = true; + component.set({ buffered: timeRangesToArray(audio.buffered) }); + audio_updating = false; + } + + function audio_loadedmetadata_handler() { + audio_updating = true; + component.set({ seekable: timeRangesToArray(audio.seekable) }); + audio_updating = false; + } + + function audio_timeupdate_handler() { + audio_updating = true; + component.set({ played: timeRangesToArray(audio.played) }); + audio_updating = false; + } + + function audio_timeupdate_handler_1() { + audio_updating = true; + cancelAnimationFrame(audio_animationframe); + if (!audio.paused) audio_animationframe = requestAnimationFrame(audio_timeupdate_handler_1); + component.set({ currentTime: audio.currentTime }); + audio_updating = false; + } + + function audio_durationchange_handler() { + audio_updating = true; + component.set({ duration: audio.duration }); + audio_updating = false; + } + + function audio_pause_handler() { + audio_updating = true; + component.set({ paused: audio.paused }); + audio_updating = false; + } + + return { + create: function() { + audio = createElement("audio"); + addListener(audio, "play", audio_pause_handler); + this.hydrate(); + }, + + hydrate: function(nodes) { + component._root._beforecreate.push(audio_progress_loadedmetadata_handler); + + addListener(audio, "progress", audio_progress_loadedmetadata_handler); + addListener(audio, "loadedmetadata", audio_progress_loadedmetadata_handler); + + component._root._beforecreate.push(audio_loadedmetadata_handler); + + addListener(audio, "loadedmetadata", audio_loadedmetadata_handler); + + component._root._beforecreate.push(audio_timeupdate_handler); + + addListener(audio, "timeupdate", audio_timeupdate_handler); + + component._root._beforecreate.push(audio_timeupdate_handler_1); + + addListener(audio, "timeupdate", audio_timeupdate_handler_1); + + component._root._beforecreate.push(audio_durationchange_handler); + + addListener(audio, "durationchange", audio_durationchange_handler); + + component._root._beforecreate.push(audio_pause_handler); + + addListener(audio, "pause", audio_pause_handler); + }, + + mount: function(target, anchor) { + insertNode(audio, target, anchor); + }, + + update: function(changed, state) { + if (!audio_updating && !isNaN(state.currentTime )) { + audio.currentTime = state.currentTime ; + } + + if (audio_paused_value !== (audio_paused_value = state.paused)) { + audio[audio_paused_value ? "pause" : "play"](); + } + }, + + unmount: function() { + detachNode(audio); + }, + + destroy: function() { + removeListener(audio, "progress", audio_progress_loadedmetadata_handler); + removeListener(audio, "loadedmetadata", audio_progress_loadedmetadata_handler); + removeListener(audio, "loadedmetadata", audio_loadedmetadata_handler); + removeListener(audio, "timeupdate", audio_timeupdate_handler); + removeListener(audio, "timeupdate", audio_timeupdate_handler_1); + removeListener(audio, "durationchange", audio_durationchange_handler); + removeListener(audio, "pause", audio_pause_handler); + removeListener(audio, "play", audio_pause_handler); + } + }; +} + +function SvelteComponent(options) { + this.options = options; + this._state = options.data || {}; + + this._observers = { + pre: Object.create(null), + post: Object.create(null) + }; + + this._handlers = Object.create(null); + + this._root = options._root || this; + this._yield = options._yield; + this._bind = options._bind; + + if (!options._root) { + this._oncreate = []; + this._beforecreate = []; + } + + this._fragment = create_main_fragment(this._state, this); + + if (options.target) { + this._fragment.create(); + this._fragment.mount(options.target, options.anchor || null); + + callAll(this._beforecreate); + } +} + +assign(SvelteComponent.prototype, proto ); + +export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/media-bindings/input.html b/test/js/samples/media-bindings/input.html new file mode 100644 index 0000000000..07a5cc1bc1 --- /dev/null +++ b/test/js/samples/media-bindings/input.html @@ -0,0 +1 @@ +