Skip to content

πŸ“ A stupidly small component-driven, node-based, serverless-CMS static site framework

Notifications You must be signed in to change notification settings

shailen-naidoo/millimeter

Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

24 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ“ Millimeter

⭐️ If you find this useful please star it on Github ⭐️

An super-basic, 96 line, anti-framework

Examples

Code

Simply copy-and-paste the following into a JavaScript file:

export const html = (staticArray, ...dynamic) => {
  const stringArray = staticArray || [];
  
  const array = (dynamic || []).map((singleDynamic, index) => {
    const singleDynamicString = Array.isArray(singleDynamic) ? singleDynamic.join('') : singleDynamic
    return `${stringArray[index] || ""}${singleDynamicString || ""}`;
  });

  const lastIndexStatic = stringArray.length - 1;
  return `${array.join("")}${stringArray[lastIndexStatic]}`;
};

/** @param {Record<string, boolean>} conditions */
export const classList = (conditions) => {
  const keys = Object.keys(conditions)
  const map = (isValid, index) => isValid && keys[index]
  return Object.values(conditions).map(map).filter(Boolean).join(' ')
}

export const router = (home, prefix) => {
  const array = window.location.hash.slice(1).split('/').filter(Boolean)
  const composites = array.reduce((result, value) => [`${result[0]}-${value}`, ...result], [prefix || 'path'])
  const componentName = composites.reduce((result, name) => result || !window.customElements.get(name) ? result : name, null)
  document.querySelector('[app]').innerHTML = html`<${componentName || home}></${componentName || home}>`
}

/**
 * @template {string} K
 * @template {string} A
 * @property {{Record<K, Record<string, (event: Event) => void | Promise<void>>>}} handlers
 * @property {{Record<K, HTMLElement[]>}} elements
 * @property {string[]} listeners
 * @property {() => string} render
 * @property {(type: string, any) => void} dispatch
 */
export class Millimeter extends HTMLElement {
  shadow = this.attachShadow({ mode: 'closed' })
  render = () => { throw new Error('"render" method is required') }

  /**
   * @param {Record<A, Record<string, (event: Event) => void | Promise<void>>>} [handlers]
   * @param {Record<K, (attribute: string) => any>} [handlers]
   */
  constructor(attributes, handlers) {
    super()

    this.data = Object.keys(attributes).reduce((result, key) => ({
      ...result,
      [key]: attributes[key](this.getAttribute(key)),
    }), {})

    this.handlers = handlers || {}
  }

  dispatch = (type, payload) => {
    this.dispatchEvent(new CustomEvent(type, { composed: true, bubbles: true, cancelable: true, detail: payload }));
  };

  callHandler = (event) => {
    if (event.type === 'submit') event.preventDefault()

    event.path.forEach(node => {
      const key = node.getAttribute && node.getAttribute('key')
      if (key && this.handlers[key] && this.handlers[key][event.type]) this.handlers[key][event.type](event)
    })
  }

  connectedCallback() {
    this.shadow.innerHTML = `<style>* { box-sizing: border-box }</style> ${this.render(this.data)}`

    const elements = Array
      .from(this.shadow.querySelectorAll('[key]'))
      .reduce((result, node) => ({ ...result, [node.getAttribute('key')]: node }), {})

    const listeners = Object.keys(elements).reduce((result, key) => {
      let innerListeners = []
      const obj = this.handlers[key];

      if (obj) Object.keys(obj).forEach((innerKey) => {
        if (!result.includes(innerKey)) { innerListeners = [...innerListeners, innerKey] }
      })

      return [...result, ...innerListeners]
    }, [])

    if (this.handlers.host && this.handlers.host.mount) this.handlers.host.mount(new Event('mount'))
    listeners.forEach((type) => this.shadow.addEventListener(type, this.callHandler))
    this.elements = elements
    this.listeners = listeners
  }

  disconnectedCallback() {
    if (this.handlers.host && this.handlers.host.unmount) this.handlers.host.mount(new Event('unmount'))
    this.listeners.forEach((type) => this.shadow.addEventListener(type, this.callHandler))
  }
}

About

πŸ“ A stupidly small component-driven, node-based, serverless-CMS static site framework

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 90.3%
  • HTML 4.1%
  • CSS 4.0%
  • TypeScript 1.6%