Browse Source

Clone CSS rules pasted cross components

improve-ts-datasources
Artur Arseniev 3 months ago
parent
commit
f81da2bec4
  1. 14
      packages/core/src/dom_components/model/Component.ts
  2. 52
      packages/core/src/dom_components/model/Components.ts
  3. 1
      packages/core/src/dom_components/types.ts
  4. 24
      packages/core/test/specs/dom_components/model/Component.ts

14
packages/core/src/dom_components/model/Component.ts

@ -2135,10 +2135,15 @@ export default class Component extends StyleableModel<ComponentProperties> {
components: ComponentDefinitionDefined | ComponentDefinitionDefined[], components: ComponentDefinitionDefined | ComponentDefinitionDefined[],
styles: CssRuleJSON[] = [], styles: CssRuleJSON[] = [],
list: ObjectAny = {}, list: ObjectAny = {},
opts: { keepIds?: string[]; idMap?: PrevToNewIdMap } = {}, opts: {
keepIds?: string[];
idMap?: PrevToNewIdMap;
updatedIds?: Record<string, ComponentDefinitionDefined[]>;
} = {},
) { ) {
opts.updatedIds = opts.updatedIds || {};
const comps = isArray(components) ? components : [components]; const comps = isArray(components) ? components : [components];
const { keepIds = [], idMap = {} } = opts; const { keepIds = [], idMap = {}, updatedIds } = opts;
comps.forEach((comp) => { comps.forEach((comp) => {
comp.attributes; comp.attributes;
const { attributes = {}, components } = comp; const { attributes = {}, components } = comp;
@ -2147,6 +2152,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
// Check if we have collisions with current components // Check if we have collisions with current components
if (id && list[id] && keepIds.indexOf(id) < 0) { if (id && list[id] && keepIds.indexOf(id) < 0) {
const newId = Component.getIncrementId(id, list); const newId = Component.getIncrementId(id, list);
updatedIds[id] = updatedIds[id] ? [...updatedIds[id], comp] : [comp];
idMap[id] = newId; idMap[id] = newId;
attributes.id = newId; attributes.id = newId;
// Update passed styles // Update passed styles
@ -2161,5 +2167,9 @@ export default class Component extends StyleableModel<ComponentProperties> {
components && Component.checkId(components, styles, list, opts); components && Component.checkId(components, styles, list, opts);
}); });
return {
updatedIds: opts.updatedIds,
};
} }
} }

52
packages/core/src/dom_components/model/Components.ts

@ -25,7 +25,7 @@ export interface ResetCommonUpdateProps {
} }
export interface ResetFromStringOptions { export interface ResetFromStringOptions {
visitedCmps?: Record<string, Component[]>; visitedCmps?: Record<string, ComponentDefinitionDefined[]>;
keepIds?: string[]; keepIds?: string[];
updateOptions?: { updateOptions?: {
onAttributes?: (props: ResetCommonUpdateProps & { attributes: Record<string, any> }) => void; onAttributes?: (props: ResetCommonUpdateProps & { attributes: Record<string, any> }) => void;
@ -161,32 +161,11 @@ Component> {
resetFromString(input = '', opts: ResetFromStringOptions = {}) { resetFromString(input = '', opts: ResetFromStringOptions = {}) {
opts.keepIds = getComponentIds(this); opts.keepIds = getComponentIds(this);
const { domc, em, parent } = this; const { domc, em, parent } = this;
const cssc = em?.Css;
const allByID = domc?.allById() || {}; const allByID = domc?.allById() || {};
const parsed = this.parseString(input, opts); const parsed = this.parseString(input, { ...opts, cloneRules: true });
const fromDefOpts = { skipViewUpdate: true, ...opts }; const fromDefOpts = { skipViewUpdate: true, ...opts };
const newCmps = getComponentsFromDefs(parsed, allByID, fromDefOpts); const newCmps = getComponentsFromDefs(parsed, allByID, fromDefOpts);
const { visitedCmps = {} } = fromDefOpts; Components.cloneCssRules(em, fromDefOpts.visitedCmps);
// Clone styles for duplicated components
Object.keys(visitedCmps).forEach((id) => {
const cmps = visitedCmps[id];
if (cmps.length) {
// Get all available rules of the component
const rulesToClone = cssc?.getRules(`#${id}`) || [];
if (rulesToClone.length) {
cmps.forEach((cmp) => {
rulesToClone.forEach((rule) => {
const newRule = rule.clone();
// @ts-ignore
newRule.set('selectors', [`#${cmp.attributes.id}`]);
cssc!.getAll().add(newRule);
});
});
}
}
});
this.reset(newCmps, opts as any); this.reset(newCmps, opts as any);
em?.trigger('component:content', parent, opts, input); em?.trigger('component:content', parent, opts, input);
@ -318,7 +297,8 @@ Component> {
} }
// We need this to avoid duplicate IDs // We need this to avoid duplicate IDs
Component.checkId(components, parsed.css, domc!.componentsById, opt); const result = Component.checkId(components, parsed.css, domc!.componentsById, opt);
opt.cloneRules && Components.cloneCssRules(em, result.updatedIds);
if (parsed.css && cssc && !opt.temporary) { if (parsed.css && cssc && !opt.temporary) {
const { at, ...optsToPass } = opt; const { at, ...optsToPass } = opt;
@ -447,4 +427,26 @@ Component> {
} }
} }
} }
static cloneCssRules(em: EditorModel, cmpsMap: Record<string, ComponentDefinitionDefined[]> = {}) {
const { Css } = em;
Object.keys(cmpsMap).forEach((id) => {
const cmps = cmpsMap[id];
if (cmps.length) {
// Get all available rules of the component
const rulesToClone = (Css.getRules(`#${id}`) || []).filter((rule) => !isEmpty(rule.attributes.style));
if (rulesToClone.length) {
const rules = Css.getAll();
cmps.forEach((cmp) => {
rulesToClone.forEach((rule) => {
const newRule = rule.clone();
newRule.set('selectors', [`#${cmp.attributes.id}`] as any);
rules.add(newRule);
});
});
}
}
});
}
} }

1
packages/core/src/dom_components/types.ts

@ -19,6 +19,7 @@ export interface SymbolInfo {
export interface ParseStringOptions extends AddOptions, OptionAsDocument, WithHTMLParserOptions { export interface ParseStringOptions extends AddOptions, OptionAsDocument, WithHTMLParserOptions {
keepIds?: string[]; keepIds?: string[];
cloneRules?: boolean;
} }
export enum ComponentsEvents { export enum ComponentsEvents {

24
packages/core/test/specs/dom_components/model/Component.ts

@ -460,6 +460,30 @@ describe('Component', () => {
); );
}); });
test('Ensure duplicated component clones the rules also cross components', () => {
const idName = 'test';
const cmp = dcomp.addComponent(`
<div>
<div id="${idName}">Comp 1</div>
</div>
<style>
#test { color: red; }
@media (max-width: 992px) {
#test { color: blue; }
}
</style>
`) as Component;
const cmp2 = dcomp.addComponent(`<div>Text</div>`) as Component;
cmp2.components().resetFromString(`<div id="${idName}">Comp 2</div>`);
const newId = cmp2.components().at(0).getId();
expect(em.getHtml()).toBe(
`<body><div><div id="test">Comp 1</div></div><div><div id="test-2">Comp 2</div></div></body>`,
);
expect(em.getCss()).toBe(
`#test{color:red;}#${newId}{color:red;}@media (max-width: 992px){#test{color:blue;}#${newId}{color:blue;}}`,
);
});
test('Ability to stop/change propagation chain', () => { test('Ability to stop/change propagation chain', () => {
obj.append({ obj.append({
removable: false, removable: false,

Loading…
Cancel
Save