@silexlabs/grapesjs-advanced-selector
Version:
A GrapesJS plugin for managing advanced CSS selectors with a visual interface
188 lines (174 loc) • 6.02 kB
text/typescript
import { property } from 'lit/decorators.js'
import StylableElement from '../StylableElement'
import { css, html, TemplateResult } from 'lit'
import './resize-input'
import { SimpleSelector, SimpleSelectorType } from '../model/SimpleSelector'
import { ComplexSelector, toString } from '../model/ComplexSelector'
import { Operator, OPERATORS } from '../model/Operator'
import InlineSelectComponent from './inline-select'
import CompoundSelectorComponent from './compound-selector'
import { CompoundSelector } from '../model/CompoundSelector'
import { FOCUS_VISIBLE } from '../styles'
import './inline-select'
import './compound-selector'
/**
* A component to display and edit a complex selector
* A complex selector is a main selector, an operator, and a related selector
* @emits change
* @emits rename (when a simple selector in the main compound selector is renamed)
*/
export default class ComplexSelectorComponent extends StylableElement {
// /////////////////
// Attributes
/**
* The selector to display
*/
public get value(): ComplexSelector | undefined {
return this._value
}
public set value(value: ComplexSelector | string | undefined) {
try {
this._value = typeof value === 'string' ? JSON.parse(value) : value
} catch (error) {
console.error('Error parsing value for selector', { value, error })
}
this.requestUpdate()
}
private _value: ComplexSelector | undefined
/**
* A list of all the classes, IDs, tags, custom tags, attributes, custom attributes
* that are available in the document, applicable to the current selection
*/
public suggestions: SimpleSelector[] = []
/**
* A list of all the classes, IDs, tags, custom tags, attributes, custom attributes
* that are available to the related selector
*/
public relations: SimpleSelector[] = []
public hasBody: boolean = false
// /////////////////
// Element overrides
static override styles = css`
:host {
${ FOCUS_VISIBLE }
& {
display: block;
text-align: left;
padding: 0.5rem 0;
}
button:hover, a:hover {
transform: translateX(1px);
font-weight: bold;
}
button.asm__add-inline {
font-size: 0.8rem;
background: transparent;
}
}
`
// override dispatchEvent(event: Event): boolean {
// console.info('[COMPLEX] Dispatching event', event)
// return super.dispatchEvent(event)
// }
override render(): TemplateResult {
return html`
<compound-selector
.t=${ this.t }
.value=${ this.value?.mainSelector }
.suggestions=${ this.suggestions }
=${ (event: CustomEvent) => {
const selector: SimpleSelector | undefined = event.detail
if (selector?.type === SimpleSelectorType.CLASS) {
// Forward the event when it's a class
// so that the plugin can remove it from the component
this.dispatchEvent(new CustomEvent('removeClass', { detail: event.detail }))
}
}}
=${ (event: CustomEvent) => {
const target = event.target as CompoundSelectorComponent
this.value = {
...this.value!,
mainSelector: target.value as CompoundSelector,
}
event.stopPropagation()
this.dispatchEvent(new CustomEvent('change', { detail: this.value }))
}}
=${ (event: CustomEvent) => this.dispatchEvent(new CustomEvent('rename', { detail: event.detail })) }
></compound-selector>
${ this.hasBody ? html`` : html`
${ this.value?.operator ? html`
<inline-select
.t=${ this.t }
.value=${ this.value?.operator }
.options=${ OPERATORS }
placeholder=""
=${ this.changeOperator }
></inline-select>
` : html`
<button
class="gjs-btn-prim asm__add-inline"
=${ this.addOperator }
>\u2192 ${ this.t('Relation') }</button>
`}
`}
${ this.value?.operator ? html`
<compound-selector
.t=${ this.t }
.value=${ this.value?.relatedSelector }
.suggestions=${ this.relations }
?disable-pseudo-class=${ this.value?.operator.isCombinator === false }
=${ this.changeRelatedSelector }
></compound-selector>
` : ''}
`
}
// /////////////////
// Methods
private changeOperator(event: CustomEvent<Operator>) {
const target = event.target as InlineSelectComponent
if ((target.value as Operator)?.isCombinator === false) {
// Make sure we don't have a pseudo class as a param of a pseudo class
delete this.value?.relatedSelector?.pseudoClass
}
this.value = {
...this.value!,
operator: target.value as Operator,
}
if (!target.value) {
delete this.value?.relatedSelector
delete this.value?.operator
}
event.stopPropagation()
this.dispatchEvent(new CustomEvent('change', { detail: this.value }))
}
private addOperator() {
this.requestUpdate()
this.value = {
...this.value!,
operator: OPERATORS[0],
relatedSelector: {
selectors: [],
},
} as ComplexSelector
this.dispatchEvent(new CustomEvent('change', { detail: this.value }))
}
private changeRelatedSelector(event: CustomEvent<CompoundSelector>) {
const target = event.target as CompoundSelectorComponent
this.value = {
...this.value!,
relatedSelector: target.value as CompoundSelector,
}
event.stopPropagation()
this.dispatchEvent(new CustomEvent('change', { detail: this.value }))
}
override toString(): string {
return toString(this.value!)
}
}
if (!customElements.get('complex-selector')) {
customElements.define('complex-selector', ComplexSelectorComponent)
}