const editor = grapesjs.init({ container: '#gjs', fromElement: true, height: '100%', storageManager: false, noticeOnUnload: false, selectorManager: { custom: true, componentFirst: true, }, }); // Load the Vue app const sm = editor.Selectors; const app = new Vue({ el: '.selectors-wrp', data: { selectors: [], states: [], state: null, targets: '' }, mounted() { editor.on('selector:custom', this.handleCustom); }, destroyed() { editor.off('selector:custom', this.handleCustom); }, methods: { handleCustom(props) { if (props.container) { props.container.appendChild(this.$el); } this.selectors = sm.getSelected(); this.states = sm.getStates(); this.state = sm.getState(); this.targets = sm.getSelectedTargets() .map(target => target.getSelectorsString()).join(', '); }, changeState(ev) { sm.setState(ev.target.value); }, remove(selector) { sm.removeSelected(selector); }, addNew() { const len = sm.getAll().length + 1; sm.addSelected({ name: `new-${len}`, label: `New ${len}` }); }, } });
<div id="gjs"> <div style="padding: 25px">Custom Selector Manager</div> <div class="class-a">Element A</div> <div class="class-a class-b">Element A-B</div> <div class="class-a class-b class-c">Element A-B-C</div> <style> .class-a { color: red } .class-b { color: green } .class-c { color: blue } </style> </div> <div style="display: none;"> <!-- Vue app --> <div class="selectors-wrp"> <div class="selectors-head"> <div class="selectors-title">Classes</div> <div class="selectors-states"> <select @change="changeState" :value="state"> <option value="">- State -</option> <option v-for="state in states" :key="state.getName()"> {{ state.getLabel() }} </option> </select> </div> </div> <div class="selectors"> <div class="selector-add" @click="addNew">+</div> <div v-for="sel in selectors" :key="sel.toString()" class="selector"> <div class="selector-name">{{ sel.getLabel() }}</div> <div class="selector-rm" @click="remove(sel)">⨯</div> </div> </div> <div class="selectors-targets"> Selected: {{ targets }} </div> </div> </div>
body, html { margin: 0; height: 100%; } .selectors-wrp { padding: 10px 5px; } .selectors-head { display: flex; align-items: center; } .selectors-title { flex-grow: 1; text-align: left; } .selectors { display: flex; flex-wrap: wrap; background-color: rgb(0 0 0 / 15%); padding: 3px 5px; border-radius: 3px; margin: 10px 0; gap: 5px; } .selector, .selector-add { display: flex; gap: 3px; background-color: #8f4a79; color: white; padding: 2px 5px 3px; border-radius: 3px; align-items: center; white-space: nowrap; } .selector-add { font-size: 1rem; padding: 2px 8px 3px; background-color: rgb(255 255 255 / 25%); cursor: pointer; } .selector-rm { font-size: 1rem; cursor: pointer; } .selectors-targets { text-align: left; }