Generate /doc using documentation.js; drop redundant docs from readme

This commit is contained in:
Rockwell Schrock
2025-12-18 16:37:45 -05:00
parent 57450222ae
commit f2576d32bc
5 changed files with 3957 additions and 222 deletions
+4 -198
View File
@@ -2,11 +2,11 @@
A lightweight JavaScript library for managing TRMNL device model and palette selection.
This was extracted from our Core web app for [BYOS](https://docs.usetrmnl.com/go/diy/byos) (Bring Your Own Server) and other applications to take advantage of.
This was extracted from our Core web app so that [BYOS](https://docs.usetrmnl.com/go/diy/byos) (Bring Your Own Server) and other applications can take advantage of it.
## Live Demo
Try the interactive demo: [https://usetrmnl.github.io/trmnl-picker/example/](https://usetrmnl.github.io/trmnl-picker/example/)
Try the interactive demo at [https://usetrmnl.github.io/trmnl-picker/example/](https://usetrmnl.github.io/trmnl-picker/example/)
## Installation
@@ -56,33 +56,11 @@ The library expects a form with specific data-* attributes. Apply your favorite
### 2. Initialize Picker
You have two options: let the library fetch from the TRMNL API automatically, or provide your own data.
#### Option A: Automatic API Fetching (Recommended)
The library can automatically fetch models and palettes from the TRMNL API:
```javascript
const picker = await TRMNLPicker.create('picker-form')
```
Data is fetched from these API endpoints:
- `https://usetrmnl.com/api/models`
- `https://usetrmnl.com/api/palettes`
See https://usetrmnl.com/api-docs/ for complete API documentation.
#### Option B: Provide Your Own Data
If you already have the data, or need to customize it, you can pass `models` and/or `palettes` as options to the constructor:
```javascript
const models = [...]
const palettes = [...]
const picker = await TRMNLPicker.create('picker-form', { models, palettes })
```
Data is automatically fetched from the TRMNL API.
#### With localStorage Persistence
@@ -95,11 +73,6 @@ const picker = await TRMNLPicker.create('picker-form', {
})
```
The picker will automatically:
- Load the last selected preferences from localStorage on initialization
- Save any changes to localStorage whenever the user makes a selection
- Fall back to defaults if no saved state exists
### 3. Listen for Changes
```javascript
@@ -115,175 +88,8 @@ document.getElementById('picker-form').addEventListener('trmnl:change', (event)
## API Reference
### Static Constructor Method
See the [API documentation](https://usetrmnl.github.io/trmnl-picker/doc/) for complete information.
#### `TRMNLPicker.create(formIdOrElement, options)`
```javascript
// Fetch both models and palettes from TRMNL API
const picker = await TRMNLPicker.create('picker-form')
// Or pass a DOM element directly
const formElement = document.getElementById('picker-form')
const picker = await TRMNLPicker.create(formElement)
// Or provide custom data
const picker = await TRMNLPicker.create('picker-form', { models, palettes })
```
**Parameters:**
- `formIdOrElement` (string | Element, required): Form element ID or DOM element itself
- `options` (object, optional): Configuration options
- `options.models` (array, optional): Array of model objects. If not provided, fetches from `https://usetrmnl.com/api/models`
- See the [TRMNL API docs](https://usetrmnl.com/api-docs/index.html) for model object schema
- `options.palettes` (array, optional): Array of palette objects. If not provided, fetches from `https://usetrmnl.com/api/palettes`
- See the [TRMNL API docs](https://usetrmnl.com/api-docs/index.html) for palette object schema
- `options.localStorageKey` (string, optional): localStorage key for persisting picker state across page reloads
**Returns:** `Promise<TRMNLPicker>` - Promise that resolves to the picker instance
**Throws:** Error if API fetch fails or data is invalid
### Methods and Properties
#### `setParams(params)`
Programmatically update picker state.
```javascript
picker.setParams({
modelName: 'og_plus', // Model name to select
paletteId: '123', // Palette ID to select
isPortrait: true, // Set portrait orientation
isDarkMode: false // Set dark mode
})
```
**Parameters:**
- `params.modelName` (string, optional): Model name to select
- `params.paletteId` (string, optional): Palette ID to select
- `params.isPortrait` (boolean, optional): Set portrait orientation
- `params.isDarkMode` (boolean, optional): Enable/disable dark mode
**Note:** Changing the model automatically resets the palette to the first valid palette for that model.
#### `state` (getter)
Get current picker state with full model and palette objects.
```javascript
const { model, palette, isPortrait, isDarkMode } = picker.state
```
**Returns:**
```javascript
{
model: { ... },
palette: { ... },
isPortrait: false,
isDarkMode: false
}
```
#### `params` (getter)
Get serializable parameters (useful for persistence or API calls).
```javascript
const params = picker.params
// { modelName: 'og_plus', paletteId: '123', isPortrait: false, isDarkMode: false }
// Save to localStorage
localStorage.setItem('picker', JSON.stringify(picker.params))
```
**Returns:** Object with `modelName`, `paletteId`, `isPortrait`, and `isDarkMode`
#### `screenClasses` (getter)
Get CSS classes for the current configuration.
```javascript
const classes = picker.screenClasses
// ['screen', 'screen--1bit', 'screen--v2', 'screen--md', 'screen--1x']
```
**Returns:** `Array<string>` - Array of CSS class names in priority order
#### `destroy()`
Clean up event listeners and references. Call this when removing the picker.
```javascript
picker.destroy()
```
### Events
#### `trmnl:change` Event
Emitted when:
- User changes any selection (model, palette, orientation, or dark mode)
- `setParams()` is called programmatically
- **On initialization** - allows you to get the initial state immediately
```javascript
document.getElementById('picker-form').addEventListener('trmnl:change', (event) => {
const { origin, screenClasses, model, palette, isPortrait, isDarkMode } = event.detail
// Handle the change
})
```
**Important:** The event is emitted immediately after initialization, so you can apply initial screen classes without waiting for user interaction.
**Event Detail Structure:**
```javascript
{
origin: 'constructor' | 'form' | 'setParams', // What triggered the change
screenClasses: ['screen', 'screen--1bit', 'screen--v2', 'screen--md', 'screen--1x'],
model: { name, label, width, height, kind, css: {...} },
palette: { id, name, framework_class },
isPortrait: false,
isDarkMode: false
}
```
**Origin Values:**
- `'constructor'` - Initial state or loaded from localStorage
- `'form'` - User interaction with form controls
- `'setParams'` - Programmatic update via `setParams()` method
## Form Elements
The library expects the following elements within the form using data-* attributes:
### Required Elements
- `[data-model-select]` - Model dropdown (typically a `<select>` element)
- `[data-palette-select]` - Palette dropdown (typically a `<select>` element)
### Optional Elements
- `[data-orientation-toggle]` - Button to toggle portrait/landscape
- `[data-dark-mode-toggle]` - Button to toggle dark mode
- `[data-reset-button]` - Button to reset to defaults (first palette, landscape orientation, light mode)
- `[data-orientation-text]` - Text element showing current orientation
- `[data-dark-mode-text]` - Text element showing current mode
## Examples
See [example/index.html](example/index.html) for a complete working example with styling.
### Applying Classes to Screen Elements
```javascript
document.getElementById('picker-form').addEventListener('trmnl:change', (event) => {
const { screenClasses } = event.detail
// Apply to all elements with class 'screen'
document.querySelectorAll('.screen').forEach(screen => {
screen.className = screenClasses.join(' ')
})
})
```
## Development
+3860
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -12,10 +12,11 @@
"LICENSE"
],
"scripts": {
"build": "npm run build:esm && npm run build:browser && npm run build:browser:min",
"build": "npm run build:esm && npm run build:browser && npm run build:browser:min && npm run build:docs",
"build:esm": "esbuild src/index.js --bundle --format=esm --outfile=dist/trmnl-picker.esm.js --sourcemap",
"build:browser": "esbuild src/index.js --bundle --format=iife --global-name=TRMNLPicker --outfile=dist/trmnl-picker.js --sourcemap --footer:js=\"TRMNLPicker=TRMNLPicker.default;\"",
"build:browser:min": "esbuild src/index.js --bundle --format=iife --global-name=TRMNLPicker --minify --outfile=dist/trmnl-picker.min.js --sourcemap --footer:js=\"TRMNLPicker=TRMNLPicker.default;\"",
"build:docs": "documentation build src/index.js -f html -o doc --project-name '@trmnl/picker' --project-description 'JavaScript library for TRMNL device model and palette selection' --shallow && node scripts/expand-docs-nav.js",
"watch": "npm run build:browser -- --watch",
"prepublishOnly": "npm run build"
},
@@ -30,6 +31,7 @@
"author": "TRMNL",
"license": "MIT",
"devDependencies": {
"documentation": "^14.0.3",
"esbuild": "^0.20.0"
},
"repository": {
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env node
/**
* Automatically expand all accordion elements in documentation navigation
*/
const fs = require('fs');
const path = require('path');
const docsIndexPath = path.join(__dirname, '../doc/index.html');
if (!fs.existsSync(docsIndexPath)) {
console.error('Error: doc/index.html not found');
process.exit(1);
}
let html = fs.readFileSync(docsIndexPath, 'utf8');
// Remove 'display-none' from all toggle-target elements (handles both single and double quotes, and different class orderings)
html = html.replace(/class=['"]([^'"]*\s)?display-none\s+toggle-target([^'"]*)['"]/g, "class='$1toggle-target$2'");
html = html.replace(/class=['"]([^'"]*\s)?toggle-target\s+display-none([^'"]*)['"]/g, "class='$1toggle-target$2'");
// Change the toggle icon from ▸ (collapsed) to ▾ (expanded)
html = html.replace(/<span class='icon'>▸<\/span>/g, "<span class='icon'>▾</span>");
fs.writeFileSync(docsIndexPath, html);
console.log('✓ Expanded all navigation accordions in documentation');
+62 -23
View File
@@ -1,8 +1,29 @@
/**
* Default model to select when no localStorage state is found
* @private
* @constant {string}
*/
const DEFAULT_MODEL_NAME = 'og_plus'
const _DEFAULT_MODEL_NAME = 'og_plus'
/**
* Event fired when picker state changes
* @event TRMNLPicker#trmnl:change
* @type {CustomEvent}
* @property {Object} detail - Event details
* @property {string} detail.origin - What triggered the change: 'constructor', 'form', or 'setParams'
* @property {Array<string>} detail.screenClasses - Array of CSS classes for Framework CSS rendering
* @property {Object} detail.model - Current model object with name, label, width, height, kind, css properties
* @property {Object} detail.palette - Current palette object with id, name, framework_class properties
* @property {boolean} detail.isPortrait - Portrait orientation flag
* @property {boolean} detail.isDarkMode - Dark mode flag
*
* @example
* picker.formElement.addEventListener('trmnl:change', (event) => {
* const { origin, screenClasses, model, palette } = event.detail
* console.log(`Changed via ${origin}`)
* console.log('Classes:', screenClasses)
* })
*/
/**
* TRMNLPicker - Vanilla JS library for TRMNL device and palette selection
@@ -10,6 +31,12 @@ const DEFAULT_MODEL_NAME = 'og_plus'
* Provides a reactive picker component that manages device models, color palettes,
* orientation, and display mode. Emits 'trmnl:change' events with current state
* and CSS classes for rendering.
*
* **[View a live demo →](https://usetrmnl.github.io/trmnl-picker/example/)**
*
* **Note:** Using the constructor directly is not recommended. Use the static
* {@link TRMNLPicker.create} method instead, which automatically fetches
* models and palettes from the TRMNL API if not provided.
*
* @class TRMNLPicker
* @param {string|Element} formIdOrElement - Form element ID or DOM element reference
@@ -18,21 +45,40 @@ const DEFAULT_MODEL_NAME = 'og_plus'
* @param {Array<Object>} options.palettes - Array of palette objects from TRMNL API
* @param {string} [options.localStorageKey] - Optional key for persisting state to localStorage
*
* @fires trmnl:change - Emitted when picker state changes, includes origin, state, and screenClasses
* @fires TRMNLPicker#trmnl:change
*
* @example
* // HTML Structure - Required form with data-* attributes
* // <form id="picker-form">
* // <!-- Required: Model selector -->
* // <select data-model-select></select>
* //
* // <!-- Required: Palette selector -->
* // <select data-palette-select></select>
* //
* // <!-- Optional: Orientation toggle -->
* // <button type="button" data-orientation-toggle>
* // <span data-orientation-text>Landscape</span>
* // </button>
* //
* // <!-- Optional: Dark mode toggle -->
* // <button type="button" data-dark-mode-toggle>
* // <span data-dark-mode-text>Light Mode</span>
* // </button>
* //
* // <!-- Optional: Reset button -->
* // <button type="button" data-reset-button>Reset</button>
* // </form>
*
* // Create with element ID
* const picker = new TRMNLPicker('screen-picker', { models, palettes })
* const picker = new TRMNLPicker('picker-form', { models, palettes })
*
* // Create with DOM element
* const element = document.getElementById('picker')
* const element = document.getElementById('picker-form')
* const picker = new TRMNLPicker(element, { models, palettes })
*
* // Listen for changes
* picker.formElement.addEventListener('trmnl:change', (event) => {
* console.log(event.detail.origin) // 'form', 'setParams', 'constructor'
* console.log(event.detail.model)
* console.log(event.detail.palette)
* console.log(event.detail.screenClasses)
* })
*/
@@ -293,7 +339,7 @@ class TRMNLPicker {
if (savedParams) {
this._setParams('constructor', savedParams)
} else {
const defaultModel = sortedModels.find(m => m.name === DEFAULT_MODEL_NAME) || sortedModels[0]
const defaultModel = sortedModels.find(m => m.name === _DEFAULT_MODEL_NAME) || sortedModels[0]
const defaultPaletteId = this._getFirstValidPaletteId(defaultModel)
this._setParams('constructor', {
@@ -334,15 +380,7 @@ class TRMNLPicker {
* Emit 'trmnl:change' event with current state and screen classes
* @private
* @param {string} origin - Source of the change ('constructor', 'form', 'setParams')
* @fires trmnl:change
*
* Event detail includes:
* - origin: string - What triggered the change
* - model: Object - Current model object with name, label, size, etc.
* - palette: Object - Current palette object with id, name, framework_class
* - isPortrait: boolean - Portrait orientation flag
* - isDarkMode: boolean - Dark mode flag
* - screenClasses: Array<string> - CSS classes for rendering
* @fires TRMNLPicker#trmnl:change
*/
_emitChangeEvent(origin) {
// Save to localStorage if key is configured
@@ -568,7 +606,7 @@ class TRMNLPicker {
* @param {string} [params.paletteId] - Palette ID to select
* @param {boolean} [params.isPortrait] - Portrait orientation
* @param {boolean} [params.isDarkMode] - Dark mode enabled
* @fires trmnl:change - If any parameter changed successfully
* @fires TRMNLPicker#trmnl:change
* @throws {Error} If params is not an object
*
* @example
@@ -660,11 +698,12 @@ class TRMNLPicker {
/**
* Get complete picker state including full model and palette objects
* @public
* @returns {Object} Current state
* @returns {Object} return.model - Full model object from API
* @returns {Object} return.palette - Full palette object from API
* @returns {boolean} return.isPortrait - Portrait orientation flag
* @returns {boolean} return.isDarkMode - Dark mode flag
* @returns {{
* model: Object,
* palette: Object,
* isPortrait: boolean,
* isDarkMode: boolean
* }} State object containing model (full model object from API), palette (full palette object from API), isPortrait flag, and isDarkMode flag
*
* @example
* const state = picker.state