Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .distignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ phpunit.xml.dist

# Build / tooling config
tailwind.config.js
tailwind.scoped-forms.js
postcss.config.js
webpack.config.js
package.json
Expand Down
6 changes: 5 additions & 1 deletion src/assets/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@
logs: false;
}

@plugin "@tailwindcss/forms";
/* The forms reset, scoped to the weDocs container (see tailwind.scoped-forms.js),
so it stops restyling the checkboxes and fields of the rest of wp-admin. */
@plugin "../../../tailwind.scoped-forms.js" {
selector: .wedocs-document;
}

/*
* Force a light color-scheme for all weDocs UI (modals, blocks, settings all
Expand Down
7 changes: 6 additions & 1 deletion src/blocks/Breadcrumb/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@
daisyUI component or theme colour, and registering it emitted the whole
component set into every one of these four stylesheets. */

@plugin "@tailwindcss/forms";
/* The forms reset, scoped to the weDocs container (see tailwind.scoped-forms.js).
The bare `@tailwindcss/forms` plugin restyled every checkbox and radio on
the page, not just the ones inside weDocs markup. */
@plugin "../../../tailwind.scoped-forms.js" {
selector: .wedocs-document;
}

/* Breadcrumb Block Specific Styles */
.wp-block-wedocs-breadcrumbs {
Expand Down
7 changes: 6 additions & 1 deletion src/blocks/DocNavigation/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@
daisyUI component or theme colour, and registering it emitted the whole
component set into every one of these four stylesheets. */

@plugin "@tailwindcss/forms";
/* The forms reset, scoped to the weDocs container (see tailwind.scoped-forms.js).
The bare `@tailwindcss/forms` plugin restyled every checkbox and radio on
the page, not just the ones inside weDocs markup. */
@plugin "../../../tailwind.scoped-forms.js" {
selector: .wedocs-document;
}

.components-grid.components-tools-panel.block-editor-panel-color-gradient-settings {
padding: 0;
Expand Down
7 changes: 6 additions & 1 deletion src/blocks/QuickSearch/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@
daisyUI component or theme colour, and registering it emitted the whole
component set into every one of these four stylesheets. */

@plugin "@tailwindcss/forms";
/* The forms reset, scoped to the weDocs container (see tailwind.scoped-forms.js).
The bare `@tailwindcss/forms` plugin restyled every checkbox and radio on
the page, not just the ones inside weDocs markup. */
@plugin "../../../tailwind.scoped-forms.js" {
selector: .wedocs-document;
}

.components-grid.components-tools-panel.block-editor-panel-color-gradient-settings {
padding: 0;
Expand Down
7 changes: 6 additions & 1 deletion src/blocks/Sidebar/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@
daisyUI component or theme colour, and registering it emitted the whole
component set into every one of these four stylesheets. */

@plugin "@tailwindcss/forms";
/* The forms reset, scoped to the weDocs container (see tailwind.scoped-forms.js).
The bare `@tailwindcss/forms` plugin restyled every checkbox and radio on
the page, not just the ones inside weDocs markup. */
@plugin "../../../tailwind.scoped-forms.js" {
selector: .wedocs-document;
}

/* Section styling for frontend */
.wedocs-section {
Expand Down
101 changes: 101 additions & 0 deletions tailwind.scoped-forms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* `@tailwindcss/forms`, scoped to a container.
*
* The forms plugin writes its reset as bare element rules
* (`input:where([type='checkbox']) { appearance: none; ... }`). Neither the
* `important` scope nor `tailwindcss-scoped-preflight` touches plugin base
* rules, so wherever a weDocs stylesheet loaded, every checkbox, radio, select
* and text field on the page was restyled, the theme's and other plugins'
* included.
*
* This runs the forms plugin unchanged and rewrites each selector it hands to
* `addBase` so it only matches inside the container, the same
* `:where(<selector>, <selector> *)` form the scoped preflight uses. `:where()`
* adds no specificity, so the rules keep the weight they had before.
*
* Usage, in a Tailwind 4 stylesheet:
*
* @plugin "../../tailwind.scoped-forms.js" {
* selector: .wedocs-document;
* }
*
* `selector` takes a comma-separated list when the markup lives in more than
* one container.
*/
const plugin = require( 'tailwindcss/plugin' );
const forms = require( '@tailwindcss/forms' );

// Split on commas that are not inside parentheses or brackets.
const splitSelectorList = ( list ) => {
const parts = [];
let depth = 0;
let current = '';

for ( const char of list ) {
if ( '(' === char || '[' === char ) {
depth++;
} else if ( ')' === char || ']' === char ) {
depth--;
}

if ( ',' === char && 0 === depth ) {
parts.push( current.trim() );
current = '';
} else {
current += char;
}
}

parts.push( current.trim() );

return parts.filter( Boolean );
};

// Append the scope to one selector, ahead of any pseudo-element, which has to
// stay last.
const scopeSelector = ( selector, scope ) => {
const pseudo = selector.indexOf( '::' );

if ( -1 === pseudo ) {
return `${ selector }${ scope }`;
}

return `${ selector.slice( 0, pseudo ) }${ scope }${ selector.slice( pseudo ) }`;
};

const scopeRules = ( rules, scope ) => {
const scoped = {};

for ( const [ key, value ] of Object.entries( rules ) ) {
if ( key.startsWith( '@' ) ) {
// @media / @supports: scope the rules inside, keep the at-rule.
scoped[ key ] = scopeRules( value, scope );
} else if ( value && 'object' === typeof value && ! Array.isArray( value ) ) {
const selector = splitSelectorList( key )
.map( ( part ) => scopeSelector( part, scope ) )
.join( ',' );

scoped[ selector ] = value;
} else {
// A declaration at this level; nothing to scope.
scoped[ key ] = value;
}
}

return scoped;
};

module.exports = plugin.withOptions( ( options = {} ) => ( api ) => {
// One container or a comma-separated list of them.
const containers = splitSelectorList( String( options.selector || '.wedocs-document' ) );
const scope = `:where(${ containers.map( ( c ) => `${ c }, ${ c } *` ).join( ', ' ) })`;

forms( { strategy: options.strategy } ).handler( {
...api,
addBase: ( rules ) => {
const list = Array.isArray( rules ) ? rules : [ rules ];

list.forEach( ( rule ) => api.addBase( scopeRules( rule, scope ) ) );
},
} );
} );
Loading