css-houdini-drafts
css-houdini-drafts copied to clipboard
What if we could define custom, pseudo-classes?
There are a handful of use cases where it'd be useful to register a custom pseudo-class for simplification or even extension of CSS capabilities.
Use Cases
:visible
Target visible elements (i.e., non-zero height and width, non-zero opacity, not visibility: hidden;, not display: none;, etc.).
Example: applying margin between non-text button children
:touched
Target elements that have emitted a blur event.
Useful to apply :invalid styling to form controls, but only after the user has interacted with the control (i.e., the user had the opportunity to enter a valid value, but left it invalid).
Example:
input:invalid {
/*
Don't apply invalid styles!
Newly rendered inputs that don't pass validation (e.g., input[required] without a value)
should not be displayed as invalid. This results in a suboptimal user experience, because
a user will see invalid inputs immediately after the page loads (before they've had a chance
to interact with the form).
*/
}
input:invalid:touched {
/*
Apply invalid styles!
The user has entered the field (focused) and left it (blurred) in an invalid state.
*/
}
:changed
Target a form control whose current value differs from its initial value.
- Display an icon next to fields that have been modified, but not persisted/saved.
- Display a notification banner, communicating unsaved changes in a form.
Compatibility
Naturally, you'd want to avoid conflicts with pre-existing pseudo-classes.
As such, custom pseudo-classes...
- require a prefix
- similar to how custom elements must have a dash/hyphen in their name
- may not be defined more than once
- avoid overwriting third-party custom pseudo-classes, but allow browser vendors to define their own prefixed pseudo classes through the same API
There may need to be some sort of mechanism for prefix registration, so that library authors can:
- open a prefix/namespace registration
- define custom selectors within that prefix/namespace
- close registration (commit changes)
- once the namespace has been closed, further modification should be impossible, to prevent external modification/tampering
Thoughts on API...
Define Namespace
CSS.selectorNamesapces.define( name )
- returns:
CSSSelectorNamespaceobject (see below)
/* create a namespace for prefixed selector definitions */
let fooNs = CSS.selectorNamespaces.define('foo')
// throws an error, because 'foo' namespace has already been defined
let fooNsToo = CSS.selectorNamespaces.define('foo')
Retrieve Namespace
CSS.selectorNamespaces.get( name )
- returns: CSSSelectorNamespace object (see below) or
undefinedif namespace isn't registered
let fooNs = CSS.selectorNamespaces.get('foo') // CSSSelectorNamespace
let barNs = CSS.selectorNamespaces.get('bar') // undefined
Selector Registration
CSSSelectorNamespace.prototype.addPseudoClass( name, callback )
/*
registers the ":-foo-visible" pseudo-class selector
callback function determines whether or not the node in question matches the selector
(similar to Array.prototype.filter)
*/
fooNs.addPseudoClass(':visible', function (node) {
let meetsCriteria = false
/* ... */
return meetsCriteria
})
// throws an error, because ":-foo-visible" has already been registered
fooNs.addPseudoClass(':visible', function (node) { /* ... */ })
Commit Namespace
CSSSelectorNamespace.prototype.commit()
- Similar to Object.freeze()...
fooNs.commit()
// fooNs cannot be modified from this point forward
// throws error, because namespace is no longer open to modification
fooNs.addPseudoClass(':blah', function (node) { /* ... */ })