npm package: @saasira/holi
Description: Modern UI Components Library with i18n, themes, PWA, SW
Latest version: 0.1.9

# Holi

Holi is a template-driven frontend component library focused on progressive enhancement, declarative rendering, and secure expression-based bindings.

It is designed for teams that want reusable UI components without committing to a virtual DOM framework or Shadow DOM component model.

## Philosophy

Holi follows strict architectural principles:

- HTML templates are the source of truth (`<template>` + `<slot>`).
- Component JS must not generate structure using inline HTML strings.
- Rendering is declarative through attributes such as `data-if`, `data-repeat`, and `@{...}` interpolation.
- Components auto-discover and auto-render on page load (with explicit lazy transform opt-in).
- Expression evaluation is secure-by-design (no `eval`, no `new Function`).
- No Shadow DOM.
- Progressive enhancement first.
- Multiple component libraries and pluggable content providers are supported.

Canonical reference: `docs/holi-principles.md`.

## What You Get

- Auto initialization on `DOMContentLoaded` via `HoliApp.init(document)`.
- Three discovery styles for each component:
  - Tag: `<tabs></tabs>`
  - Attribute: `<section component="tabs"></section>`
  - Role: `<section role="tabs"></section>`
- Component-template bundling (`dist/components.html`) and runtime in-memory template registration.
- Template bindings:
  - `@{expression}` interpolation
  - `data-if`, `data-show`, `data-open`, `visible`
  - `data-repeat` loops with contextual item/index
- Lifecycle-aware component registry with DOM mutation observation.
- Content provider pattern for dynamic/lazy content per component.
- jQuery-like utility surface (`Q`) and native HTTP helper (`HTTP`).
- Prebuilt component set including accordion, calendar, carousel, chart, datagrid, datatable, dialog, drawer, dropdown, form controls, gallery, tabs, toast, tree, wizard, and more.

## Declarative Dependencies

Holi supports declarative dependent updates between components.

- Source components publish change notifications through the central runtime.
- Subscriber components declare interest in one or more source components.
- The framework only resolves and delivers the notification.
- The subscriber decides how to react based on its own data source and behavior.

This means "local refresh" vs "remote/PPR reload" is not decided by the framework. It is decided by the subscriber component after it receives the dependency update.

Common attributes:

- Source side: `update`, `render`, `data-ppr-update`
- Subscriber side: `data-ppr-listen`, `data-ppr-source`

Common subscriber hook contract:

- `handlePprUpdate(payload)`
- `refreshPpr(payload)`
- `refresh(payload)`
- `updateView(payload)`

JSF-style target tokens such as `@this`, `@parent`, `@form`, `@all`, and explicit ids are supported by the dependency resolver.

## Quick Start

```bash
npm install
npm run build
npm run serve
```

Main artifacts:

- `dist/holi.js`
- `dist/holi.css`
- `dist/components.html` (component templates)
- `dist/layouts.html` (layout templates)
- `dist/holi.html` (legacy compatibility bundle)
- `dist/layouts/**` (optional runtime-resolved layouts)
- Example site output: `public/examples/**`

## CDN Quick Start

For application pages that consume Holi directly from a CDN, use the published npm package via jsDelivr:

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@saasira/holi@0.1.5/dist/holi.css" />
<script src="https://cdn.jsdelivr.net/npm/@saasira/holi@0.1.5/dist/holi.js"></script>
<link rel="preload" as="fetch" href="https://cdn.jsdelivr.net/npm/@saasira/holi@0.1.5/dist/components.html" crossorigin="anonymous" />
```

Direct file URLs:

- `https://cdn.jsdelivr.net/npm/@saasira/holi@0.1.5/dist/holi.js`
- `https://cdn.jsdelivr.net/npm/@saasira/holi@0.1.5/dist/holi.css`
- `https://cdn.jsdelivr.net/npm/@saasira/holi@0.1.5/dist/components.html`

Pin an exact version in production so releases remain repeatable.

## CDN Usage

Holi can be shipped directly from a free CDN after publishing the package to npm.

Recommended jsDelivr links for `v0.1.5`:

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@saasira/holi@0.1.5/dist/holi.css" />
<script src="https://cdn.jsdelivr.net/npm/@saasira/holi@0.1.5/dist/holi.js"></script>
<link rel="preload" as="fetch" href="https://cdn.jsdelivr.net/npm/@saasira/holi@0.1.5/dist/components.html" crossorigin="anonymous" />
```

Fallback unpkg links:

```html
<link rel="stylesheet" href="https://unpkg.com/@saasira/holi@0.1.5/dist/holi.css" />
<script src="https://unpkg.com/@saasira/holi@0.1.5/dist/holi.js"></script>
<link rel="preload" as="fetch" href="https://unpkg.com/@saasira/holi@0.1.5/dist/components.html" crossorigin="anonymous" />
```

Repository setup details for automated npm publishing are documented in `docs/CDN.md`.

## Minimal Usage

```html
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="/dist/holi.css" />
    <script src="/dist/holi.js"></script>
  </head>
  <body>
    <tabs provider="demo" data-source="@{demo.simple}"></tabs>

    <script>
      const tabData = [{ label: "Overview", content: "Overview content" }];
      window.contentProviders = window.contentProviders || {};
      window.contentProviders.demo = class {
        resolve(expr) {
          return expr === "@{demo.simple}" ? tabData : [];
        }
        async getContent(item) {
          return item?.content || "";
        }
      };
    </script>
  </body>
</html>
```

## Project Structure Contracts

- Component scripts: `src/scripts/components/<componentname>.js`
- Component styles: `src/styles/components/<componentname>.css`
- Component templates: `src/templates/components/<componentname>.html`
- Shared utilities: `src/scripts/utils/<utilityname>.js`
- Optional layout templates:
  - library layouts: `src/templates/layouts/*.html`
  - example/app layouts in examples: `src/examples/layouts/*.html`
- Example sources:
  - `src/examples/pages/*.html`
  - `src/examples/styles/*.css`
  - `src/examples/scripts/*.js`
- Built examples: `public/examples/**` (referencing `dist/holi.js` and `dist/holi.css`)

## Page Layout Composition

Holi supports runtime page composition with a layout template plus named placeholders.

Supported structure:

```html
<page layout="3x9" layouts-base="/examples/layouts/">
  <block name="head">
    <region name="meta">
      <meta name="page-layout-demo" content="true" />
    </region>
    <region name="styles">
      <style>.page-title { letter-spacing: 0.02em; }</style>
    </region>
    <region name="scripts">
      <script>window.pageLayoutExampleLoaded = true;</script>
    </region>
  </block>

  <block name="header">
    <h1 class="page-title">Project Dashboard</h1>
  </block>

  <block name="main">
    <region name="lhs"><div>Navigation</div></region>
    <region name="middle"><div>Primary content</div></region>
    <region name="rhs"><div>Context tools</div></region>
  </block>
</page>
```

Layout template example:

```html
<template id="page-layout-3x9" data-layout="3x9">
  <layout-head data-layout-head="true">
    <meta name="example-layout" content="3x9" />
    <slot name="meta"></slot>
    <slot name="styles"></slot>
    <slot name="scripts"></slot>
  </layout-head>

  <header><slot name="header"></slot></header>
  <slot name="main">
    <main>
      <aside><slot name="lhs"></slot></aside>
      <section><slot name="middle"></slot></section>
      <aside><slot name="rhs"></slot></aside>
    </main>
  </slot>
</template>
```

Rules:

- Top-level `block[name]` fills a matching layout slot.
- Direct `region[name]` inside a block fills matching nested slots inside that block's assigned subtree.
- Missing slots are empty by default.
- Set `inherit-missing="true"` on `page` to keep fallback slot content.
- Use `renderer="browser"` on source pages to show the built-in placeholder until runtime composition completes.
- After browser or compiler rendering completes, both `renderer` and `rendered` should be removed from the final page node.
- Use `layout-src` for an explicit layout file or `layouts-base` for a layout folder.
- Page-level attributes `title`, `description`, `canonical`, and `lang` are applied to the real document metadata.
- Page-level `theme` is applied to the real `<body>` element for theme scoping and switching.
- Use `<layout-head data-layout-head="true">` for shared head assets in layouts.
- Use `<tail data-layout-tail="true">` for deferred body-end assets in layouts.
- Non-script nodes from `layout-head` go to the real HTML `<head>`.
- Nodes from `tail` are appended near the end of `<body>`.
- Built-in region `meta` always targets the real HTML `<head>`.
- Built-in regions `styles` and `page-styles` always target the real HTML `<head>`, even when the layout does not declare those slots.
- Built-in regions `scripts` and `page-scripts` always target the end of `<body>`, even when the layout does not declare those slots.
- Use `styles` for external asset nodes such as `<link rel="stylesheet" href="...">`.
- Use `scripts` for external asset nodes such as `<script src="..."></script>`.
- Use `page-styles` for inline `<style>` tags.
- Use `page-scripts` for inline `<script>` tags.

## Holi vs React / Angular / Vue

| Area | Holi | React | Angular | Vue |
|---|---|---|---|---|
| Primary model | HTML template + declarative attrs | Component functions + JSX | Framework with DI, modules, templates | SFC/templates + reactivity |
| Runtime style | Direct DOM + template clone/bind | Virtual DOM | Framework-managed change detection | Virtual DOM + compiler/runtime |
| Auto discovery | Yes (tag/component/role selectors) | No (explicit mount) | No (bootstrapped app) | No (explicit mount) |
| Progressive enhancement | First-class | Possible, not default | Usually app-shell centric | Possible, typically app-centric |
| Shadow DOM | Not used | Not required | Optional with Angular Elements | Not required |
| Secure expression engine | Built-in `@{...}` parser (no eval) | N/A (JS expressions in render code) | Template parser | Template parser |
| Best fit | Multi-page apps, server-rendered pages, declarative component islands | Large SPAs and highly interactive app UIs | Enterprise apps needing full framework conventions | Progressive SPAs and mixed-complexity apps |

Practical summary:

- Choose Holi when your HTML-first architecture, declarative enhancement, and low-framework runtime footprint are priorities.
- Choose React/Angular/Vue when you need the broader ecosystem around SPA routing/state tooling, compile-time optimizations, and framework-level DX conventions.

## Development Scripts

- `npm run build`: Build library and examples.
- `npm run build:dev`: Development build and examples.
- `npm run build:examples`: Build example pages into `public/examples`.
- `npm run serve`: Build examples and start webpack dev server.
- `npm run smoke:examples`: Run example smoke checks.
- `npm run ci:smoke`: Full build + smoke.

## Notes

- Holi exports `window.HoliApp` / `window.Holi`.
- Auto init can be disabled with `window.HoliAutoInit = false` before loading `dist/holi.js`.
- Component templates are loaded from `dist/components.html` (with runtime fallback paths).
- Layout templates are loaded from `dist/layouts.html` (with runtime fallback paths).

## OfflineIndicator

`OfflineIndicator` provides a template-driven connectivity banner with automatic polling and queue count display.

Supported declaration styles:

- `<offline></offline>`
- `<section component="offline"></section>`
- `<section role="offline"></section>`
- `[data-offline]`

Useful attributes:

- `scope="page|block|inline"`
- `position="top-left|top-right|bottom-left|bottom-right|top-center|bottom-center"`
- `host="#selector"` for `scope="block"`
- `ping-url="/api/ping"`
- `heartbeat-ms="2500"`
- `probe-timeout-ms="1800"`
- `duration="1200"`
- `max-attempts="4"`

Instance/static helpers:

- `el.offlineIndicator.retryConnection()`
- `el.offlineIndicator.simulateOffline()`
- `el.offlineIndicator.simulateOnline()`
- `window.OfflineIndicator.queue(payload)`
- `window.OfflineIndicator.clearQueue()`
- `window.OfflineIndicator.create(options)`

See [offline example](/D:/Work/Self/Holi/src/examples/pages/offline.html) for page-scoped, block-scoped, and queue-sync scenarios.