I am adding style to an accordion item, but when the calcite-accordion-item is first added to the calcite-accordion container, the shadowRoot ".header" is not available yet. What is another way to add style to this?
Accordion item styled.
Only works when shadowRoot is built.
document.querySelectorAll('calcite-accordion-item').forEach((node) => {
let el = node.shadowRoot.querySelector('.header');
if (el) {
el.style.setProperty('background-color', 'rgb(27, 54, 93)');
el.style.setProperty('border', '1px solid rgb(23, 134, 180)');
// add bottom margin
el.style.setProperty('margin-bottom', '2px');
}
el = node.shadowRoot.querySelector('.heading');
if (el)
el.style.setProperty('color', 'rgb(255, 200, 69');
});
Solved! Go to Solution.
Apologies, thought you were using a custom-defined header class, and not the component's header class.
Since it is part of the Shadow DOM, the header class' markup structure, style, and behavior is hidden and separated through encapsulation.
The Shadow DOM hides and separates a portion of the component's DOM elements, so they are rendered in the browser, but cannot be modified by you. The encapsulation creates persistent styling across your applications that won't conflict or compete with other sources. Also, enabling your users to have a consistent user experience.
That being said, we have an open enhancement request to expose the CSS variable for styling that could be incorporated in the future. Just linked the request to this Community thread so the team is aware of usage and intent to assist in prioritizing the enhancement. If you have a specific use case, please do add any comments to the issue for additional context as well.
Great question, we'll be providing documentation on this in the very near future on the ArcGIS Developers site.
Some background context for why the content isn't available with the above code:
Calcite Components must hydrate before their properties can be initialized. The component starts as a plain HTML element, and is upgraded as soon as their implementation is loaded and defined in the browser. To await for hydrated components using JavaScript, you can use the whenDefined() method of the customElementRegistry interface, which returns a Promise when the specified element is defined.
Here's a Codepen with a working example, and a JavaScript code snippet below:
(async () => {
await customElements.whenDefined("calcite-accordion-item");
await document.querySelectorAll("calcite-accordion-item").forEach((node) => {
node.style.setProperty("background-color", "rgb(27, 54, 93)");
node.style.setProperty("border", "1px solid rgb(23, 134, 180)");
// add bottom margin
node.style.setProperty("margin-bottom", "2px");
node.style.setProperty("color", "rgb(255, 200, 69");
});
})();
Thanks, Kitty. I'm not familiar with CustomElementRegistry. You have given me something new to read tonight! The codepen was helpful. Though .header is still not available.
Apologies, thought you were using a custom-defined header class, and not the component's header class.
Since it is part of the Shadow DOM, the header class' markup structure, style, and behavior is hidden and separated through encapsulation.
The Shadow DOM hides and separates a portion of the component's DOM elements, so they are rendered in the browser, but cannot be modified by you. The encapsulation creates persistent styling across your applications that won't conflict or compete with other sources. Also, enabling your users to have a consistent user experience.
That being said, we have an open enhancement request to expose the CSS variable for styling that could be incorporated in the future. Just linked the request to this Community thread so the team is aware of usage and intent to assist in prioritizing the enhancement. If you have a specific use case, please do add any comments to the issue for additional context as well.
Access to header and even title would be great. I'll keep an eye on this open enhancement group. Is my original code fragment bad practice? It does style the header once the accordion item is rendered...or is that just by accident? I'll try using the styling in your fragment above and find a color that isn't so dark when the accordion is expanded. That will be fine as long as the accordion item stands out for the user. Thanks.
Here's my solution until\if header is exposed later on. Thank you, Kitty.
const delay = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms));
};
/**
* TODO: When Esri adds this feature, add style at create time
* Expose CSS variable to style accordion item header in active state #4060
* https://github.com/Esri/calcite-components/issues/4060
* @param {*} id
*/
const styleAccordionItem = async () => {
await customElements.whenDefined("calcite-accordion-item");
await delay(125);
let node = document.querySelector(`#${activeDataAccordionItem}`);
if (node) {
let el = node.shadowRoot.querySelector('.header');
if (el) {
el.style.setProperty('background-color', 'rgb(27, 54, 93)');
el.style.setProperty('border', '1px solid rgb(23, 134, 180)');
// add bottom margin
el.style.setProperty('margin-bottom', '2px');
}
el = node.shadowRoot.querySelector('.heading');
if (el)
el.style.setProperty('color', 'rgb(255, 200, 69');
}
};
Good to hear you have a workaround in the meantime, will see the feasibility and prioritization with the team and keep you posted if we're able to incorporate the enhancement in. Thanks for reaching out!
Hi @KittyHurley, any updates on whether this new enhancement will be made available for accordions? My team and I spent a long time digging around for options this morning before coming across this post. Thanks for your help!
@GregoryBologna, would you be willing to share a generic example of how you incorporated your workaround into the accordion formatting? I'm still learning my way around HTML and calcite formatting, and I'm not clear on where the workaround would live within a code block. Thanks for sharing your workaround with community!
Take a look at styleAccordionItem() and changeHeadingStyle()
You can see it in action here. Click 2nd widget from bottom shown in screenshot.
https://www.manateepao.gov/parcel/?parid=1014100000
/**
* Create the calcite accordian
* @returns
*/
function buildAccordionContainer() {
return new Promise((resolve, reject) => {
let block = document.getElementById('pao-accordion-block');
if (block) {
let container = document.getElementById(
'pao-accordion-data-container'
);
if (!container) {
// Hide label
document.getElementById('pao-accordion-label').hidden = true;
let accordion = document.createElement('calcite-accordion');
accordion.setAttribute('id', 'pao-accordion-data-container');
// Single mode only with single dataTable.
accordion.setAttribute('selection-mode', 'single');
accordion.classList.add('accordion');
// Adds tool bar to accordion block header.
// Add to setupTableBuffer if you want tool bar on accordion item.
let toolbar = buildToolbar();
if (toolbar) accordion.appendChild(toolbar);
block.appendChild(accordion);
// Scroll point element kept at bottom of data container.
let scrollto = document.createElement('div');
scrollto.setAttribute('id', 'pao-scrollto-bottom');
scrollto.classList.add('d-none');
block.appendChild(scrollto);
}
styleAccordionItem();
setupTableBuffer().then(() => {
resolve(true);
});
}
}).catch((error) => {
doLogException('buildAccordionContainer', error);
resolve(false);
});
}
/**
* Called each time a new accordion item is added to pao-accordion-data-container
* TODO: When Esri adds this feature, add style at create time
* Expose CSS variable to style accordion item header in active state #4060
* https://github.com/Esri/calcite-components/issues/4060
* https://developers.arcgis.com/calcite-design-system/foundations/colors/#more-esri-colors
* @param {*} id
*/
const styleAccordionItem = async () => {
try {
await customElements.whenDefined('calcite-accordion-item');
await delay(250);
if (activeDataAccordionItem?.length > 0) {
let node = document.querySelector(`#${activeDataAccordionItem}`);
if (node) {
let el = node.shadowRoot.querySelector('.header');
if (el) {
el.style.setProperty('background-color', 'rgb(27, 54, 93)');
el.style.setProperty('border', '1px solid rgb(23, 134, 180)');
// add bottom margin
el.style.setProperty('margin-bottom', '2px');
}
el = node.shadowRoot.querySelector('.heading');
if (el) el.style.setProperty('color', 'var(--calcite-ui-warning)');
// Style font color
el = node.shadowRoot.querySelector('.description');
if (el)
el.style.setProperty('color', 'rgb(255, 255, 255)');
}
}
} catch (error) {
doLogException('styleAccordionItem', error);
}
};
/**
* Use to remove outline from specific block button.
* Usage: removeButtonOutline('pao-taxparceltypes-block');
* @param {*} id
*/
const removeButtonOutline = async (id) => {
await customElements.whenDefined('calcite-block');
await delay(200);
let node = document.querySelector(`#${id}`);
if (node) {
let el = node.shadowRoot.querySelector('.header-container>button');
if (el) {
el.style.setProperty('outline', 'none');
el = node.shadowRoot.querySelector('.header-container>button.toggle');
if (el)
el.style.setProperty('background-color', 'var(--calcite-ui-foreground-1)');
}
}
};
/**
* Block heading font color only available in shadowroot.
* @param {*} id
*/
const changeHeadingStyle = async (id, color_var) => {
await customElements.whenDefined('calcite-block');
await delay(200);
let node = document.querySelector(`#${id}`);
if (node) {
let el = node.shadowRoot.querySelector('.heading');
if (el) {
el.style.setProperty('color', `var(${color_var})`);
}
}
};
/**
* Use to remove outline from all block buttons only available in shadowroot.
*/
const removeAllButtonOutline = async () => {
await customElements.whenDefined('calcite-block');
await delay(200);
let nodes = document.querySelectorAll('calcite-block');
if (nodes) {
nodes.forEach((p) => {
let el = p.shadowRoot.querySelector('.header-container>button');
if (el)
el.style.setProperty('outline', 'none');
});
}
};
/**
* Updates accordion header and subtitle.
* Usage: await updateTablePageInfoHeader(arr-parids);
* @param {*} distinctParids a parid object of distinct parids
*/
const updateTablePageInfoHeader = async (distinctParids) => {
try {
if (dgData[activeDataAccordionItem]) {
let distinct_parids_count = distinctParids.parids?.length;
// Heading messge to indicate max gis records returned.
// Default heading message is used when no max.
let max_part = '';
if (distinct_parids_count >= MAX_QUERY_RECORDCOUNT) {
let m = new Intl.NumberFormat('en-IN').format(MAX_QUERY_RECORDCOUNT);
max_part = `Max ${m} subject parcels reached.`;
}
// Count of distinct parids, including in subject property and\or buffer.
distinct_parids_count = new Intl.NumberFormat('en-IN').format(distinct_parids_count);
// Get required accordion item and set heading and sub-title description text.
let accordion_item = document.getElementById(activeDataAccordionItem);
if (accordion_item) {
// DataTable rows for this item include subject and buffered parcels.
let item_row_count = dgData[activeDataAccordionItem]?.rows.length;
// Get distance unit type for this item.
let unit_type_abbrv = dgData[activeDataAccordionItem].unitTypeAbbrv;
// DataTable subject parcel count.
let subject_parcel_count = subjectParcels?.rows?.length;
// Absolute buffer size for current accordion item.
let fixed_buffer_size = fixedBufferSizeScale(bufferSize);
// Buffer results heading can be replaced with an alternative value.
// Change heading value or use default.
// Set font color before text.
let header = document.getElementById('pao-accordion-block');
if (header?.hasAttribute('heading')) {
if (max_part?.length > 0) {
await changeHeadingStyle('pao-accordion-block', '--calcite-ui-danger');
header.attributes['heading'].value = max_part;
} else if (header.attributes['heading'].value !== BUFFER_RESULTS_HEADING) {
await changeHeadingStyle('pao-accordion-block', '--calcite-ui-text-2');
header.attributes['heading'].value = BUFFER_RESULTS_HEADING;
}
// Subject property part.
let selected_part = '';
let included_part = '';
let unique_part = '';
let included_word_part = '';
// total_subjects_count exact subject parcels included in table
// when some subject properties get filtered in tax parcel type list.
let total_subjects_count = 0;
//if (subject_parcel_count > 0) {
// Always include subject part.
selected_part = `Selected ${distinct_parids_count} parcel${distinct_parids_count === 0 || distinct_parids_count > 1 ? 's' : ''
}`;
// This part of header prints subject parcels in all buffered parcels.
if (includeSubjectParcelsOn) {
included_word_part = ', including ';
}
total_subjects_count = await visibleSubjectParcelsCount();
// This part of header prints the excluded subject parcels in all buffered parcels.
if (!includeSubjectParcelsOn) {
included_word_part = ', excluding ';
}
// Build the included\excluded part
if (included_word_part.length > 0) {
included_part = `${total_subjects_count} subject propert${total_subjects_count === 0 || total_subjects_count > 1 ? 'ies' : 'y'
}`;
}
// Build the unique mailing addresses part
// Get count of addresses from queried distinct parids, include parent node parid.
let json = JSON.stringify(distinctParids);
// Expects int.
let queried_parids_count = await distinctAddressCount(json);
queried_parids_count = new Intl.NumberFormat('en-IN').format(queried_parids_count);
unique_part = `; ${queried_parids_count} unique mailing address${queried_parids_count > 1 ? 'es' : ''}.`
//}
// Assemble the header.
let punc1 = '.'; // sometimes period, do other punctuation above.
if (included_word_part?.length === 0 && unique_part?.length > 0) {
punc1 = '';
}
if (included_word_part?.length > 0) {
punc1 = '';
}
if (unique_part?.length === 0)
unique_part = '.';
header.description = `${selected_part}${punc1}${included_word_part}${included_part}${unique_part}`;
}
}
}
} catch (error) {
doLogException('updateTablePageInfoHeader', error);
}
};