@cagovweb/cal-ds-banner
Version:
cal-ds-banner top banner
325 lines (269 loc) • 10.9 kB
JavaScript
var CssBaseStyleString = ":host{font-family:system-ui,-apple-system,\"Segoe UI\",Roboto,\"Helvetica Neue\",\"Noto Sans\",\"Liberation Sans\",Arial,sans-serif;--gov-header:#071645;--brand-primary:#13394b;--brand-secondary:#0070a3;--banner-light:#d5dbde;--banner-dark:#09202b;--brand-hover-light:#3ca3d3;--brand-hover-dark:#246c8e;--background-primary-light:#d9d8cf;--background-secondary-light:#f5f3eb;--background-primary-dark:#43433f;--background-secondary-dark:#65645f;--action-primary:#fec335;--action-secondary:#fae2ad;--text-white:#fff;--grey:#ccc;--grey-text:#323232;--grey-background:#eee;--gutter-gap:1.674vw;--flow-space:1rem;--radius:1rem;--shadow:0.2rem 0.2rem 0.5rem 0 rgba(0 0 0 / 25%);--shadow-focus:0.1rem 0.1rem 0.25rem 0 rgba(0 0 0 / 10%);--shadow-active:0.2rem 0.2rem 0.5rem 0 rgba(0 0 0 / 60%);--font-size:1rem;--ratio:1.24;--s0:calc(var(--font-size) - 0.3rem) + 0.2vw;--st:calc(var(--s0) * var(--ratio));--base:calc(var(--st) * var(--ratio));--lt:calc(var(--base) * var(--ratio));--h6:calc(var(--s0) * var(--ratio));--h5:calc(var(--h6) * var(--ratio));--h4:calc(var(--h5) * var(--ratio));--h3:calc(var(--h4) * var(--ratio));--h2:calc(var(--h3) * var(--ratio));--h1:calc(var(--h2) * var(--ratio) * var(--ratio));}";
//@ts-check
/**
* Options for ca-eureka components
* @typedef {object} cal_ds_options
* @property {boolean} [shadow] - Create a shadow DOM?
* @property {string} [css] - CSS to apply to component
* @property {string} [global_css] - CSS to merge into the main DOM
* @property {string} [html] - HTML to apply to component (Event configurable)
* @property {() => void} [connectedCallback] - Callback to use when the component is added to DOM
* @property {() => void} [templateChangedCallback] - callback to use when the TEMPLATE content changes
* @property {(name:string,oldValue:string,newValue:string) => void} [attributeChangedCallback] - Callback to use when attribute changes
*/
/**
* @typedef {"cal_ds_connectedCallback_end"
* | "cal_ds_connectedCallback_start"
* | "cal_ds_shadow_constructed_start"
* | "cal_ds_shadow_constructed_end"
* | "cal_ds_attributeChangedCallback_start"
* | "cal_ds_attributeChangedCallback_end"
* } cal_ds_events
*/
/** @typedef {(target:cal_ds_base,e:Event) => void} cal_ds_event_handler */
/**
* @abstract
*/
class cal_ds_base extends HTMLElement {
/**
*
* @param {cal_ds_options} [options]
*/
constructor(options = {}) {
super();
/**
* @type {(() => void) | undefined}
* @private
*/
this._connectedCallback = options.connectedCallback;
/**
* @type {((name:string,oldValue:string,newValue:string)=> void) | undefined}
* @private
*/
this._attributeChangedCallback = options.attributeChangedCallback;
/**
* Sets the HTML Template String that will be used to render the component
* change this in the `cal_ds_shadow_constructed_start` event if you want to update the source HTML
* @public
* @type {string | undefined}
*/
this.HTMLTemplateString = options.html;
if (options.shadow) {
//Shadow Dom requested
const shadow = this.attachShadow({ mode: "open" });
this.dispatchComponentEvent("cal_ds_shadow_constructed_start");
this.addStyle(CssBaseStyleString); //Adds the base style for ALL components
if (options.css) {
this.addStyle(options.css);
}
if (this.HTMLTemplateString) {
const myTemplate = document.createElement("template");
myTemplate.innerHTML = this.HTMLTemplateString;
// Check ram savings by using console.log(window.performance.memory.usedJSHeapSize/1000000);
shadow.appendChild(myTemplate.content.cloneNode(true));
}
this.dispatchComponentEvent("cal_ds_shadow_constructed_end");
}
if (options.global_css) {
//TODO: make this only happen one per unique sheet
// Create a new style node
const styleSheet = document.createElement("style");
styleSheet.innerText = options.global_css;
// Append the style node to the document head
document.head.appendChild(styleSheet);
}
/***
* The TEMPLATE tag inside the component
* @public
* @type {DocumentFragment | undefined}
*/
this.UserTemplate = this.querySelector("template")?.content;
const templateChangedCallback = options.templateChangedCallback;
if (this.UserTemplate && templateChangedCallback) {
// Create an observer instance linked to the callback function
const observer = new MutationObserver(mutationsList =>
mutationsList.forEach(templateChangedCallback)
);
// Start observing the target node for configured mutations
observer.observe(this.UserTemplate, {
attributes: true,
childList: true,
subtree: true,
characterData: true
});
}
}
/**
* Used with `attributeChangedCallback` to track changes to attributes
* @abstract
* @protected
* @type {string[]}
* @example //@protected //@readonly //@override
* static get observedAttributes() { return ["data-summary", "data-expanded"]; }
*/
static get observedAttributes() {
return []; //Should never see
}
/**
* Get the tagName this class will use
* @abstract
* @protected
*/
static get tagName() {
return ""; //Should never see
}
/**
* Private Hashtable for style objects
* @private
* @readonly
*/
static _styles = {};
/**
* Dispatch a bubbling event for the page to listen for
* @protected
* @param {cal_ds_events} type
*/
dispatchComponentEvent(type) {
this.dispatchEvent(
new Event(type, {
bubbles: true
})
);
}
/**
* Add a window event handler for a component event (choose from enum)
* @overload
* @param {cal_ds_events} EventName
* @param {cal_ds_event_handler} handler
* @returns {void}
*/
/**
* Add a window event handler for a component event (string)
* @overload
* @param {string} EventName
* @param {cal_ds_event_handler} handler
* @returns {void}
*/
/**
* @param {cal_ds_events | string} EventName
* @param {cal_ds_event_handler} handler
*/
static addCEventListener(EventName, handler) {
window.addEventListener(EventName, e =>
handler(/** @type {cal_ds_base} **/ (e.target), e)
);
}
/**
* Add a cachable stylestring to a shadow root
* @param {string} styleString css to add
* @public
* @example myComponent.addStyle("p{background-color:pink}");
*/
addStyle(styleString) {
if (!this.shadowRoot)
throw new Error("AddStyle only works with open shadowRoots");
// Hash Algorithm
const hash = [...styleString].reduce(
(a, b) => (a = (a << 5) - a + b.charCodeAt(0)) & a,
0
);
/** @type {CSSStyleSheet} */
let style = cal_ds_base._styles[hash];
if (!style) {
style = new CSSStyleSheet();
style.replaceSync(styleString);
cal_ds_base._styles[hash] = style;
}
this.shadowRoot.adoptedStyleSheets.push(style);
}
/**
* Base class connectedCallback
* @protected
* @readonly
*/
connectedCallback() {
this.dispatchComponentEvent("cal_ds_connectedCallback_start");
if (this._connectedCallback) {
this._connectedCallback();
}
this.dispatchComponentEvent("cal_ds_connectedCallback_end");
}
/**
* Used with `observedAttributes` to track attribute changes
* @param {string} _name
* @param {string} _oldValue
* @param {string} _newValue
* @protected
*/
attributeChangedCallback(_name, _oldValue, _newValue) {
this.dispatchComponentEvent("cal_ds_attributeChangedCallback_start");
if (this._attributeChangedCallback) {
this._attributeChangedCallback(_name, _oldValue, _newValue);
}
this.dispatchComponentEvent("cal_ds_attributeChangedCallback_end");
}
}
var css = ":host > div{background-color:#fdbc5b;min-height:2rem;> div{display:flex;align-items:baseline;gap:0.3rem;margin:0 auto;padding:0.5rem 1rem;width:100%;max-width:1820px;font-family:\"Noto Sans\",system-ui,-apple-system,\"Segoe UI\",Roboto,\"Helvetica Neue\",sans-serif;> p{margin:0;font-size:1.144rem;a{color:#203673;&:hover,&:focus{text-decoration:none;}&:focus{outline:2px solid #004972;}}}@media (width >= 576px){max-width:576px!important;}@media (width >= 768px){max-width:768px!important;}@media (width >= 992px){max-width:992px!important;}@media (width >= 1200px){max-width:1200px!important;}@media (width >= 1280px){max-width:1280px!important;}@media (width >= 1400px){max-width:1820px!important;}}}";
var html = "<div> <div> <svg aria-hidden=\"true\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 18 17.6\" style=\"width: 1rem; height: 1rem;\"> <g> <radialGradient id=\"SVGID_1_\" cx=\"7.6553\" cy=\"6.5485\" r=\"7.5719\" gradientUnits=\"userSpaceOnUse\"> <stop offset=\"0.4356\" style=\"stop-color:#FF0000\"/> <stop offset=\"0.8402\" style=\"stop-color:#AB0000\"/> </radialGradient> <circle style=\"fill:url(#SVGID_1_);\" cx=\"9\" cy=\"8.7\" r=\"8\"/> </g> </svg> <p></p> </div> </div> ";
// from
// https://www.cssscript.com/create-a-multi-level-drop-down-menu-with-pure-css/
class my extends cal_ds_base {
/** @override */
static get tagName() {
return "cal-ds-banner";
}
/**
* @protected
* @override
*/
static get observedAttributes() {
return ["data-target"];
}
constructor() {
/**
* @param {string} name
*/
const attributeChangedCallback = name => {
switch (name) {
case my.observedAttributes[0]: //"data-target":
_contentChanged();
break;
}
};
const _contentChanged = () => {
if (this.UserTemplate && this.shadowRoot && this.HTMLTemplateString) {
this.shadowRoot.innerHTML = this.HTMLTemplateString;
const ul = /** @type {HTMLElement} */ (
this.shadowRoot.querySelector("p")
);
const dom = /** @type {DocumentFragment} */ (
this.UserTemplate.cloneNode(true)
);
ul.appendChild(dom);
}
const target = document.querySelector(
this.dataset.target || ":scope > body"
);
if (!target)
throw new Error(`Can't find data-target - ${this.dataset.target}`);
//Move itself to the target
if (target.firstElementChild !== this) target.prepend(this);
};
super({
shadow: true,
css,
html,
connectedCallback: _contentChanged,
templateChangedCallback: _contentChanged,
attributeChangedCallback
});
}
}
//@ts-check
//comment out any elements you are not using
//Definition order matters!!! Code will run in this order
const my_bundle = [my];
for (const c of my_bundle) {
//sync "for", to ensure define order
window.customElements.define(c.tagName, c);
}