Skip to content

Conversation

@geoffrich
Copy link
Member

I'm not sure when this was added, but I discovered that's it's possible to bind to the open state of a <details> element. This PR updates the docs accordingly.

Should there be a new tutorial section for this as well?

Before submitting the PR, please make sure you do the following

  • [ ] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • Prefix your PR title with [feat], [fix], [chore], or [docs].
  • This message body should clearly illustrate what problems it solves.
  • [ ] Ideally, include a test that fails without this PR but passes with it.

Tests

  • Run the tests with npm test and lint the project with npm run lint

@geoffrich
Copy link
Member Author

Screenshot of updated docs:

image

@geoffrich
Copy link
Member Author

For reference, here's a REPL of bind:open in action.

@johannchopin
Copy link

Like mentioned in the doc, you can bind a variable to almost all properties of a html tag. IMO it's not necessary to add a documentation section to all the binding possibilities.

@geoffrich
Copy link
Member Author

Like mentioned in the doc, you can bind a variable to almost all properties of a html tag.

I don't think that's true? At least that isn't an assumption I've made, and I don't see where in the docs it says you can bind to almost any property. The Svelte compiler needs to define what event to listen to to make the binding work. There are also open issues about adding bindings to the readyState property of a media element (#6666) and the complete property of an img (#5105), so it's clearly not any property.

All the bindings are validated here...

this.bindings.forEach(binding => {
const { name } = binding;
if (name === 'value') {
if (
this.name !== 'input' &&
this.name !== 'textarea' &&
this.name !== 'select'
) {
return component.error(binding, compiler_errors.invalid_binding_elements(this.name, 'value'));
}
if (this.name === 'select') {
const attribute = this.attributes.find(
(attribute: Attribute) => attribute.name === 'multiple'
);
if (attribute && !attribute.is_static) {
return component.error(attribute, compiler_errors.dynamic_multiple_attribute);
}
} else {
check_type_attribute();
}
} else if (name === 'checked' || name === 'indeterminate') {
if (this.name !== 'input') {
return component.error(binding, compiler_errors.invalid_binding_elements(this.name, name));
}
const type = check_type_attribute();
if (type !== 'checkbox') {
return component.error(binding, compiler_errors.invalid_binding_no_checkbox(name, type === 'radio'));
}
} else if (name === 'group') {
if (this.name !== 'input') {
return component.error(binding, compiler_errors.invalid_binding_elements(this.name, 'group'));
}
const type = check_type_attribute();
if (type !== 'checkbox' && type !== 'radio') {
return component.error(binding, compiler_errors.invalid_binding_element_with('<input type="checkbox"> or <input type="radio">', 'group'));
}
} else if (name === 'files') {
if (this.name !== 'input') {
return component.error(binding, compiler_errors.invalid_binding_elements(this.name, 'files'));
}
const type = check_type_attribute();
if (type !== 'file') {
return component.error(binding, compiler_errors.invalid_binding_element_with('<input type="file">', 'files'));
}
} else if (name === 'open') {
if (this.name !== 'details') {
return component.error(binding, compiler_errors.invalid_binding_element_with('<details>', name));
}
} else if (
name === 'currentTime' ||
name === 'duration' ||
name === 'paused' ||
name === 'buffered' ||
name === 'seekable' ||
name === 'played' ||
name === 'volume' ||
name === 'muted' ||
name === 'playbackRate' ||
name === 'seeking' ||
name === 'ended'
) {
if (this.name !== 'audio' && this.name !== 'video') {
return component.error(binding, compiler_errors.invalid_binding_element_with('audio> or <video>', name));
}
} else if (
name === 'videoHeight' ||
name === 'videoWidth'
) {
if (this.name !== 'video') {
return component.error(binding, compiler_errors.invalid_binding_element_with('<video>', name));
}
} else if (dimensions.test(name)) {
if (this.name === 'svg' && (name === 'offsetWidth' || name === 'offsetHeight')) {
return component.error(binding, compiler_errors.invalid_binding_on(binding.name, `<svg>. Use '${name.replace('offset', 'client')}' instead`));
} else if (svg.test(this.name)) {
return component.error(binding, compiler_errors.invalid_binding_on(binding.name, 'SVG elements'));
} else if (is_void(this.name)) {
return component.error(binding, compiler_errors.invalid_binding_on(binding.name, `void elements like <${this.name}>. Use a wrapper element instead`));
}
} else if (
name === 'textContent' ||
name === 'innerHTML'
) {
const contenteditable = this.attributes.find(
(attribute: Attribute) => attribute.name === 'contenteditable'
);
if (!contenteditable) {
return component.error(binding, compiler_errors.missing_contenteditable_attribute);
} else if (contenteditable && !contenteditable.is_static) {
return component.error(contenteditable, compiler_errors.dynamic_contenteditable_attribute);
}
} else if (name !== 'this') {
return component.error(binding, compiler_errors.invalid_binding(binding.name));
}
});

... and pretty much all of them are explicitly mentioned in the docs and have their own tutorial chapters. So I don't think it's a stretch to document the details binding -- I certainly didn't know it existed before I stumbled on it in the source code.

@Conduitry Conduitry merged commit 7e156c0 into sveltejs:master Sep 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants