Skip to content
Merged

v10 #739

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org

root = true


[*]

# Change these settings to your own preference
indent_style = space
indent_size = 2

# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[*.json]
indent_size = 2

[*.{html,js,md}]
block_comment_start = /**
block_comment = *
block_comment_end = */
117 changes: 117 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# CLAUDE.md

## Project Overview

**titanium-elements** is a monorepo that publishes [`@leavittsoftware/web`](packages/web) — Lit 3 + Material Web custom elements used across Leavitt Group front-end applications.

- **`titanium-*`** — general-purpose UI components (drawers, data tables, inputs, snackbars, etc.)
- **`leavitt-*`** — domain-specific components (company/person selects, file explorer, app shell, API helpers); many require authentication and an `ApiService` instance

The package is published to npm and consumed via **deep imports** (no barrel `index.ts` exports).

## Tech Stack

- **Framework:** Lit 3 — web components with Shadow DOM
- **UI base:** Material Web (`@material/web/*`)
- **Language:** TypeScript (strict, decorators enabled)
- **Shared types:** `@leavittsoftware/lg-core-typescript` for OData entity interfaces
- **Monorepo:** npm workspaces + Lerna (independent versioning)
- **Component docs:** Custom Elements Manifest → `packages/leavittbook/custom-elements.json`
- **Peer dependency:** `lit >= 3.0.0`

## Monorepo Structure

```
packages/
web/ # @leavittsoftware/web — all published elements (titanium/ + leavitt/)
leavittbook/ # Local component gallery and demos (not published); see packages/leavittbook/CLAUDE.md
```

Within `packages/web`:

```
titanium/ # General-purpose components, helpers, styles, events
leavitt/ # Domain components, app shell, api-service, auth
```

## Key Scripts

```bash
npm start # tsc watch + web-dev-server + CEM watch
npm run build # tsc + tests + CEM + leavittbook rollup
npm run lint # prettier + eslint + lit-analyzer + tsc
npm run lint-fix # auto-fix formatting and lint issues
npm run cem # regenerate custom-elements-manifest
npm run lerna:publish # build + conventional publish to npm
```

Run `npm run lint` after edits and fix issues in changed files before committing.

## Development Workflow

1. `npm i` then `npm start` — browse component demos in leavittbook
2. **New component:** copy an existing folder under `packages/web`, add a leavittbook demo + route, add a tsconfig path
3. **Conventional Commits** required (`feat:`, `fix:`, `chore:`, etc.)
4. CEM output at `packages/leavittbook/custom-elements.json` helps verify APIs but is **not** shipped with `@leavittsoftware/web`

## Import Convention

Consumers register elements with side-effect imports:

```ts
import '@leavittsoftware/web/titanium/drawer/drawer.js';
import { TitaniumDrawer } from '@leavittsoftware/web/titanium/drawer/drawer.js';
import { h2, p } from '@leavittsoftware/web/titanium/styles/styles.js';
```

Paths mirror the source tree under `packages/web/`. Use `.js` extensions when importing from the published package.

## Component Reference

For per-component API (properties, methods, events, slots, CSS parts, and implementation notes), see [`packages/web/CLAUDE.md`](packages/web/CLAUDE.md).

Most titanium/leavitt elements **extend or compose** `@material/web` components (`MdFilledTextField`, `md-dialog`, etc.). Mixins, validation, and `--md-*` styling often apply to those Material Web bases — see **Material Web foundation** in `packages/web/CLAUDE.md` before assuming a component is a plain `LitElement` wrapper.

After install, consuming projects find the same file at:

```
node_modules/@leavittsoftware/web/CLAUDE.md
```

## Keeping `packages/web/CLAUDE.md` Up to Date

This is a **required** part of every component change. The web-level `CLAUDE.md` is the primary reference for agents in consuming projects; stale docs are worse than no docs.

Update [`packages/web/CLAUDE.md`](packages/web/CLAUDE.md) in the **same PR** whenever you:

- Add, rename, or remove a component or public utility
- Change a component's public API (properties, attributes, methods, events, slots, CSS parts)
- Change cross-cutting behavior documented there (events, controllers, services, loading/snackbar patterns)
- Add or revise implementation gotchas that affect consumers
- Ship or change a **breaking** (`!`) release — add or update the **Upgrade changelog (for agents)** section

**Checklist per change:**

- [ ] Component entry added/updated/removed (primary catalog or internal appendix as appropriate)
- [ ] Cross-cutting section updated if the change affects shared patterns
- [ ] Import path and tag name match the source file
- [ ] **Upgrade changelog** entry added/updated for any `!` commit or semver-major publish (rename `### Unreleased` → `### X.Y.Z` on publish; add a fresh `### Unreleased` stub)

`npm run cem` can help verify property lists against source, but purpose, usage notes, and gotchas must be curated manually.

## Contributing Conventions

- Extend `LitElement` or the appropriate `@material/web` base (e.g. `MdFilledTextField`); register with `@customElement('kebab-case-name')` (must include a hyphen)
- Use `@property() accessor` / `@state() accessor` (omit `accessor` on `@provide` / `@consume` context properties)
- Side-effect imports for element registration; deep paths mirror source tree
- Use `promiseTracking` for loading state on page components (`LoadWhile` mixin removed; `titanium-data-table-core.loadWhile()` remains as a deprecated alias for `trackLoadingPromise`)
- Pair `ShowSnackbarEvent` with `titanium-snackbar-stack`; `PendingStateEvent` with loading indicators
- Multi-word reflected attributes use **kebab-case** (`local-storage-key`, not `localStorageKey`)
- Any PR with `feat!`, `fix!`, or `refactor!` must include an upgrade-changelog entry in `packages/web/CLAUDE.md` with grep targets and replacements

## What NOT to Do

- Don't add barrel `index.ts` exports
- Don't break semver without a conventional commit + `lerna publish`
- Don't duplicate `@leavittsoftware/lg-core-typescript` entity types in components
- Don't merge component API changes without updating `packages/web/CLAUDE.md`
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright 2025 Leavitt Software Solutions
Copyright 2026 Leavitt Software Solutions

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,14 @@ Types enable JavaScript developers to use highly-productive development tools an

### Create the leavittbook story

- [ ] Copy an existing demo/rename it/write your demo code
- [ ] Update my-app inside of leavittbook
- Add a route
- Add a menu item
- Add your component tag
See [`packages/leavittbook/CLAUDE.md`](packages/leavittbook/CLAUDE.md) for gallery conventions.

- [ ] Copy an existing leavittbook demo (e.g. [`packages/leavittbook/src/demos/leavitt-error-page-demo.ts`](packages/leavittbook/src/demos/leavitt-error-page-demo.ts)) and rename it
- [ ] Update [`packages/leavittbook/src/my-app.ts`](packages/leavittbook/src/my-app.ts)
- Add a `page('/route', …)` handler
- Add a drawer `<md-list-item href="…">` (label must match `level1Text` on the demo header)
- Add a conditional render tag in `<main-content>`
- [ ] Add `requires-auth` on `<story-header>` if the demo calls api3

### Important

Expand Down
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default [
ecmaVersion: 2020,
sourceType: 'module',
parserOptions: {
project: true,
project: ['./tsconfig.json', './tsconfig.spec.json'],
tsconfigRootDir: import.meta.dirname,
},
},
Expand Down
50 changes: 25 additions & 25 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,53 +7,53 @@
"packages/*"
],
"dependencies": {
"@leavittsoftware/lg-core-typescript": "^5.1208.0",
"@leavittsoftware/lg-core-typescript": "^5.1342.0",
"@material/web": "^2.4.1",
"api-viewer-element": "^1.0.0-pre.10",
"lit": "3.3.2",
"countup.js": "^2.10.0",
"lit": "3.3.3",
"page": "^1.11.6",
"promise-parallel-throttle": "^3.5.0",
"tslib": "^2.8.1"
},
"devDependencies": {
"@custom-elements-manifest/analyzer": "^0.11.0",
"@rollup/plugin-babel": "^6.1.0",
"@eslint/eslintrc": "^3.3.5",
"@eslint/js": "^10.0.1",
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-terser": "^0.4.4",
"@types/dom-navigation": "^1.0.7",
"@rollup/plugin-terser": "^1.0.0",
"@types/google.maps": "*",
"@types/jasmine": "^6.0.0",
"@types/page": "^1.11.9",
"@typescript-eslint/eslint-plugin": "^8.56.1",
"@typescript-eslint/parser": "^8.56.1",
"@typescript-eslint/eslint-plugin": "^8.62.0",
"@typescript-eslint/parser": "^8.62.0",
"@web/dev-server": "^0.4.6",
"@web/rollup-plugin-html": "^3.1.0",
"@web/rollup-plugin-import-meta-assets": "^2.3.2",
"concurrently": "^9.2.1",
"deepmerge": "^4.3.1",
"eslint": "^9.39.1",
"eslint-plugin-lit": "^2.2.1",
"@web/rollup-plugin-import-meta-assets": "^2.3.3",
"concurrently": "^10.0.3",
"eslint": "^10.5.0",
"eslint-plugin-lit": "^2.3.1",
"eslint-plugin-wc": "^3.1.0",
"happy-dom": "^20.7.0",
"jasmine": "^6.1.0",
"jasmine-ts": "^0.4.0",
"lerna": "^9.0.4",
"jasmine": "^6.3.0",
"lerna": "^9.0.7",
"lit-analyzer": "^2.0.3",
"patch-package": "^8.0.1",
"prettier": "^3.8.1",
"replace": "^1.2.2",
"prettier": "^3.8.4",
"rimraf": "^6.1.3",
"rollup": "^4.59.0",
"rollup-plugin-copy": "^3.5.0",
"rollup": "^4.62.2",
"rollup-plugin-workbox": "^8.1.3",
"tslib": "^2.8.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.56.1",
"typescript": "^6.0.3",
"vscode-css-languageservice": "^6.3.10"
},
"overrides": {
"lit-analyzer": {
"vscode-css-languageservice": "latest"
}
},
"dompurify": "^3.4.11",
"js-yaml": "^4.2.0",
"rollup-plugin-workbox": {
"esbuild": "^0.28.1"
},
"tar": "^7.5.17"
},
"scripts": {
"start": "concurrently --kill-others --names tsc,wds,cem \"tsc --watch\" \"web-dev-server\" \"npm run cem:watch\"",
Expand Down
87 changes: 87 additions & 0 deletions packages/leavittbook/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Leavittbook

Local component gallery for `@leavittsoftware/web`. Not published to npm.

## Purpose

- Live demos of `titanium-*` and `leavitt-*` elements
- CEM-generated API docs via `api-viewer-element` and [`custom-elements.json`](../custom-elements.json)
- [`story-header`](src/shared/story-header.ts) for class/tag metadata

Component APIs live in [`packages/web/CLAUDE.md`](../web/CLAUDE.md).

## Adding a demo

1. Copy an existing demo (e.g. [`src/demos/leavitt-error-page-demo.ts`](src/demos/leavitt-error-page-demo.ts)) → `src/demos/<name>-demo.ts`
2. Register in [`src/my-app.ts`](src/my-app.ts):
- `page('/route', …)` lazy import
- `<md-list-item href="…">` in the drawer (`level1Text` must match the nav label)
- `${this.page === 'route-key' ? html`<…-demo></…-demo>` : nothing}` in `<main-content>`
3. Run `npm start` (CEM watch keeps `custom-elements.json` current)

## Page layout

```
leavitt-app-main-content-container (.pendingStateElement=${this})
main
leavitt-app-navigation-header (level1Text matches drawer label, level1Href="/route")
leavitt-app-width-limiter
story-header
…variants…
api-docs (src="./custom-elements.json" selected="element-tag")
```

- Add `leavitt-app-navigation-footer` **only** when it contains real actions (Save/Cancel, bulk actions, pagination). Do not add empty footers.
- Import shared typography from `@leavittsoftware/web/titanium/styles/styles` before local font rules.

## Routing (intentional divergence from skeleton)

Leavittbook **removes** inactive demo pages from the DOM on navigation (`${this.page === 'x' ? html`…` : nothing}`). Production apps scaffolded from skeleton keep pages mounted with `?hidden` + `.isActive`. The gallery resets demo state on each visit.

Use `connectedCallback` / `disconnectedCallback` for per-visit setup and teardown.

## Auth

The gallery is public. Demos that call **api3** require Leavitt login:

| Demo | Route |
| --------------------- | -------------------------------- |
| Company select | `/leavitt-company-select` |
| Person select | `/leavitt-person-select` |
| Person company select | `/leavitt-person-company-select` |
| Person group select | `/leavitt-person-group-select` |
| File explorer | `/leavitt-file-explorer` |
| Email history viewer | `/leavitt-email-history-viewer` |

Drawer links for these demos show a trailing `passkey` icon.

Pattern:

- Gate demo content with `UserManager.identity` inline in `render()` — `${UserManager.identity ? html\`…\` : nothing}` (do not cache in demo state)
- `requires-auth` on `<story-header>` — shows an **Authentication is required for this demo** filled tonal button when not authenticated; calls `UserManager.authenticate()` (redirect handles login)

No Auth0 redirect until the user clicks that button. Shell uses `UserManager.initialize()` only (profile menu, feedback dialogs). No whole-app Auth0 gate.

## Toolbar search

After each route change, `my-app` sets `showSearch` from the active page’s `searchController` (see skeleton pattern). Demos that need the shared toolbar search expose `searchController` (usually via `TitaniumSiteSearchTextFieldController`).

## Demo styling conventions

- Pair `background` / accent tokens with matching `on-*` foreground tokens
- Use `labelStyles` for checkbox/radio/switch labels when present
- Minimum 13px font-size in component-local CSS
- `@property()` / `@state()` use `accessor` (Lit 3)
- `promiseTracking` + `PendingStateEvent` for async work; `ShowSnackbarEvent` for errors

## Scaffolding sync (manual)

When drift-checking against [skeleton.leavitt.com](https://github.com/LeavittSoftware/skeleton.leavitt.com) `develop`:

| Skeleton | Leavittbook |
| ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `theme.css`, `theme-custom.css` | `packages/leavittbook/` |
| `src/styles/my-app-styles.ts`, `form-styles.ts`, `hero-styles.ts`, `input-styles.ts`, `label-styles.ts`, `nice-badge-styles.ts` | `packages/leavittbook/src/styles/` (keep `story-styles.ts`) |
| `.editorconfig`, `.vscode/settings.json` | monorepo root |

Do **not** copy skeleton’s `my-app.ts` routing model wholesale — see routing section above.
Loading
Loading