css-houdini-drafts icon indicating copy to clipboard operation
css-houdini-drafts copied to clipboard

What if we could define custom, pseudo-classes?

Open CITguy opened this issue 5 years ago • 1 comments

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...

  1. require a prefix
    • similar to how custom elements must have a dash/hyphen in their name
  2. 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:

  1. open a prefix/namespace registration
  2. define custom selectors within that prefix/namespace
  3. close registration (commit changes)
    • once the namespace has been closed, further modification should be impossible, to prevent external modification/tampering

CITguy avatar Sep 02 '20 00:09 CITguy

Thoughts on API...

Define Namespace

CSS.selectorNamesapces.define( name )

  • returns: CSSSelectorNamespace object (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 undefined if 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) { /* ... */ })

CITguy avatar Sep 02 '20 00:09 CITguy