Skip to content

Commit

Permalink
Show style applied on tag (p, span, div) and private selectors as par…
Browse files Browse the repository at this point in the history
…ent rules instead of hiding them (#5890)

* GrapesJS can now returns multiple CSS rules class as parents and CSS rule applied to tag name

* Take last version of grape.
Fix bug with private selectors that stay invisible in style manager when another class was applied.

* Fix reviews;

* Simplify code to work only with single tag name;

---------

Co-authored-by: Artur Arseniev <[email protected]>
  • Loading branch information
Dobby85 and artf authored Jun 11, 2024
1 parent 679e23c commit 3f5056d
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 4 deletions.
32 changes: 28 additions & 4 deletions src/style_manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,8 @@ export default class StyleManager extends ItemManagerModule<
const cmp = target.toHTML ? target : target.getComponent();
const optsSel = { array: true } as const;
let cmpRules: CssRule[] = [];
let tagNameRules: CssRule[] = [];
let invisibleAndOtherRules: CssRule[] = [];
let otherRules: CssRule[] = [];
let rules: CssRule[] = [];

Expand All @@ -605,19 +607,41 @@ export default class StyleManager extends ItemManagerModule<
? []
: cssC.getRules().filter(rule => {
const rSels = rule.getSelectors().map(s => s.getFullName());
return !!rSels.length && rSels.every(rSel => values.indexOf(rSel) >= 0);

// rSels is equal to 0 when rule selectors contain tagName like : p {}, .logo path {}, ul li {}
if (rSels.length === 0) {
return false;
}

return rSels.every(rSel => values.indexOf(rSel) >= 0);
});
};

const rulesByTagName = (tagName: string) => {
return !tagName ? [] : cssC.getRules().filter(rule => rule.selectorsToString() === tagName);
};

// Componente related rule
if (cmp) {
cmpRules = cssC.getRules(`#${cmp.getId()}`);
tagNameRules = rulesByTagName(cmp.get('tagName'));
otherRules = sel ? rulesBySelectors(sel.getSelectors().getFullName(optsSel)) : [];
rules = otherRules.concat(cmpRules);
rules = otherRules.concat(tagNameRules).concat(cmpRules);
} else {
cmpRules = sel ? cssC.getRules(`#${sel.getId()}`) : [];
otherRules = rulesBySelectors(target.getSelectors().getFullName(optsSel));
rules = cmpRules.concat(otherRules);
tagNameRules = rulesByTagName(sel?.get('tagName') || '');
// Get rules set on invisible selectors like private one
const allCmpClasses = sel?.getSelectors().getFullName(optsSel) || [];
const invisibleSel = allCmpClasses.filter(
(item: string) =>
target
.getSelectors()
.getFullName(optsSel)
.findIndex(sel => sel === item) === -1
);
// Get rules set on active and visible selectors
invisibleAndOtherRules = rulesBySelectors(invisibleSel.concat(target.getSelectors().getFullName(optsSel)));
rules = tagNameRules.concat(cmpRules).concat(invisibleAndOtherRules);
}

const all = rules
Expand Down
66 changes: 66 additions & 0 deletions test/specs/style_manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,72 @@ describe('StyleManager', () => {
expect(obj.getSelectedParents()).toEqual([rule1]);
});

test('With multiple classes, should both be in parents list', () => {
const cmp = domc.addComponent('<div class="cls cls2"></div>');
const [rule1, rule2] = cssc.addRules(`
.cls { color: red; }
.cls2 { color: blue; }
`);
em.setSelected(cmp);
obj.__upSel();
expect(obj.getSelected()).not.toBe(rule1);
expect(obj.getSelected()).not.toBe(rule2);
expect(obj.getSelectedParents()).toEqual([rule2, rule1]);
});

test('With tagName + class, class first', () => {
const cmp = domc.addComponent('<div class="cls" id="id-test"></div>');
const [rule1, rule2] = cssc.addRules(`
.cls { color: red; }
div { color: yellow; }
`);
em.setSelected(cmp);
obj.__upSel();
expect(obj.getSelected()).toBe(rule1);
expect(obj.getSelectedParents()).toEqual([rule2]);
});

test('Should ignore rules with tagName in the selector path but the rule is not apply on the tagName', () => {
const cmp = domc.addComponent('<div class="cls" id="id-test"></div>');
const [rule1, rule2] = cssc.addRules(`
.cls { color: red; }
div { color: yellow; }
div .child { padding: 10px; }
`);
em.setSelected(cmp);
obj.__upSel();
// getSelectedParents should only have 1 rule as the third one is not applied on the div
expect(obj.getSelected()).toBe(rule1);
expect(obj.getSelectedParents()).toEqual([rule2]);
});

test('Should tagName rules if the selectors does not contain only the tagNale', () => {
const cmp = domc.addComponent('<div class="cls" id="id-test"></div>');
const [rule1, rule2] = cssc.addRules(`
.cls { color: red; }
div { color: yellow; }
.child div { padding: 10px; }
`);
em.setSelected(cmp);
obj.__upSel();
// getSelectedParents should only have 1 rule as the third one is not applied on the div
expect(obj.getSelected()).toBe(rule1);
expect(obj.getSelectedParents()).toEqual([rule2]);
});

test('With tagName + ID + class, class first, ID second', () => {
const cmp = domc.addComponent('<div class="cls" id="id-test"></div>');
const [rule1, rule2, rule3] = cssc.addRules(`
.cls { color: red; }
div { color: yellow; }
#id-test { color: blue; }
`);
em.setSelected(cmp);
obj.__upSel();
expect(obj.getSelected()).toBe(rule1);
expect(obj.getSelectedParents()).toEqual([rule3, rule2]);
});

test('With ID + class, multiple devices', () => {
sm.setComponentFirst(true);
const cmp = domc.addComponent('<div class="cls" id="id-test"></div>');
Expand Down

0 comments on commit 3f5056d

Please sign in to comment.