trmnl_preview 0.5.10 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,656 @@
1
+ var TRMNLPicker = (() => {
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.js
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ default: () => src_default
24
+ });
25
+ var _DEFAULT_MODEL_NAME = "og_plus";
26
+ var _API_CACHE_KEY = "trmnl-picker-api-cache";
27
+ var _CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
28
+ var TRMNLPicker = class _TRMNLPicker {
29
+ static API_BASE_URL = "https://usetrmnl.com";
30
+ /**
31
+ * Get cached API response from localStorage
32
+ * @private
33
+ * @static
34
+ * @returns {{models: Array, palettes: Array} | null} Cached data or null if expired/missing
35
+ */
36
+ static _getCachedApiData() {
37
+ try {
38
+ const cached = localStorage.getItem(_API_CACHE_KEY);
39
+ if (!cached)
40
+ return null;
41
+ const { timestamp, models, palettes } = JSON.parse(cached);
42
+ const now = Date.now();
43
+ if (now - timestamp > _CACHE_TTL_MS) {
44
+ localStorage.removeItem(_API_CACHE_KEY);
45
+ return null;
46
+ }
47
+ return { models, palettes };
48
+ } catch (error) {
49
+ console.warn("TRMNLPicker: Failed to read API cache:", error);
50
+ return null;
51
+ }
52
+ }
53
+ /**
54
+ * Save API response to localStorage cache
55
+ * @private
56
+ * @static
57
+ * @param {Array} models - Models array
58
+ * @param {Array} palettes - Palettes array
59
+ */
60
+ static _setCachedApiData(models, palettes) {
61
+ try {
62
+ const cacheData = {
63
+ timestamp: Date.now(),
64
+ models,
65
+ palettes
66
+ };
67
+ localStorage.setItem(_API_CACHE_KEY, JSON.stringify(cacheData));
68
+ } catch (error) {
69
+ console.warn("TRMNLPicker: Failed to save API cache:", error);
70
+ }
71
+ }
72
+ /**
73
+ * Create a TRMNLPicker instance, fetching data from TRMNL API if not provided
74
+ *
75
+ * Automatically caches API responses in localStorage for 24 hours to reduce network requests.
76
+ *
77
+ * @static
78
+ * @param {string|Element} formIdOrElement - Form element ID or DOM element
79
+ * @param {Object} options - Configuration options
80
+ * @param {Array<Object>} [options.models] - Optional models array (fetched from API if not provided)
81
+ * @param {Array<Object>} [options.palettes] - Optional palettes array (fetched from API if not provided)
82
+ * @param {string} [options.localStorageKey] - Optional key for state persistence
83
+ * @returns {Promise<TRMNLPicker>} Promise resolving to picker instance
84
+ * @throws {Error} If API fetch fails when models or palettes are not provided
85
+ *
86
+ * @example
87
+ * // Fetch models and palettes from API (or use cached data if available)
88
+ * const picker = await TRMNLPicker.create('screen-picker')
89
+ *
90
+ * // Provide your own data
91
+ * const picker = await TRMNLPicker.create('screen-picker', { models, palettes })
92
+ */
93
+ static async create(formId, options = {}) {
94
+ let { models, palettes, localStorageKey } = options;
95
+ if (!models && !palettes) {
96
+ const cached = _TRMNLPicker._getCachedApiData();
97
+ if (cached) {
98
+ models = cached.models;
99
+ palettes = cached.palettes;
100
+ }
101
+ }
102
+ if (!models) {
103
+ try {
104
+ const response = await fetch(`${_TRMNLPicker.API_BASE_URL}/api/models`);
105
+ if (!response.ok) {
106
+ throw new Error(`Failed to fetch models: ${response.status} ${response.statusText}`);
107
+ }
108
+ const data = await response.json();
109
+ models = data.data || data;
110
+ } catch (error) {
111
+ throw new Error(`TRMNLPicker: Failed to fetch models from API: ${error.message}`);
112
+ }
113
+ }
114
+ if (!palettes) {
115
+ try {
116
+ const response = await fetch(`${_TRMNLPicker.API_BASE_URL}/api/palettes`);
117
+ if (!response.ok) {
118
+ throw new Error(`Failed to fetch palettes: ${response.status} ${response.statusText}`);
119
+ }
120
+ const data = await response.json();
121
+ palettes = data.data || data;
122
+ } catch (error) {
123
+ throw new Error(`TRMNLPicker: Failed to fetch palettes from API: ${error.message}`);
124
+ }
125
+ }
126
+ if (!options.models && !options.palettes) {
127
+ _TRMNLPicker._setCachedApiData(models, palettes);
128
+ }
129
+ return new _TRMNLPicker(formId, { models, palettes, localStorageKey });
130
+ }
131
+ constructor(formIdOrElement, options = {}) {
132
+ if (!formIdOrElement) {
133
+ throw new Error("TRMNLPicker: formIdOrElement is required");
134
+ }
135
+ if (typeof formIdOrElement === "string") {
136
+ this.formElement = document.getElementById(formIdOrElement);
137
+ if (!this.formElement) {
138
+ throw new Error(`TRMNLPicker: Form element with id "${formIdOrElement}" not found`);
139
+ }
140
+ } else if (formIdOrElement instanceof Element) {
141
+ this.formElement = formIdOrElement;
142
+ } else {
143
+ throw new Error("TRMNLPicker: formIdOrElement must be a string ID or DOM element");
144
+ }
145
+ const { models, palettes, localStorageKey } = options;
146
+ this.models = models;
147
+ this.palettes = palettes;
148
+ this.localStorageKey = localStorageKey;
149
+ if (this.models && this.palettes) {
150
+ if (!Array.isArray(this.models) || this.models.length === 0) {
151
+ throw new Error("TRMNLPicker: models must be a non-empty array");
152
+ }
153
+ if (!Array.isArray(this.palettes) || this.palettes.length === 0) {
154
+ throw new Error("TRMNLPicker: palettes must be a non-empty array");
155
+ }
156
+ this.models = this._filterValidModels();
157
+ if (this.models.length === 0) {
158
+ throw new Error("TRMNLPicker: no valid models found (all models have palettes with empty framework_class)");
159
+ }
160
+ this._initializeElements();
161
+ this._bindEvents();
162
+ this._setInitialState();
163
+ }
164
+ }
165
+ /**
166
+ * Filter out models where all their palettes have empty framework_class
167
+ * @private
168
+ * @returns {Array<Object>} Filtered models array
169
+ */
170
+ _filterValidModels() {
171
+ return this.models.filter((model) => {
172
+ return model.palette_ids.some((paletteId) => {
173
+ const palette = this.palettes.find((p) => p.id === paletteId);
174
+ return palette && palette.framework_class && palette.framework_class.trim() !== "";
175
+ });
176
+ });
177
+ }
178
+ /**
179
+ * Get the first valid palette ID for a model (one with non-empty framework_class)
180
+ * @private
181
+ * @param {Object} model - Model object
182
+ * @returns {string|null} First valid palette ID or null
183
+ */
184
+ _getFirstValidPaletteId(model) {
185
+ if (!model)
186
+ return null;
187
+ for (const paletteId of model.palette_ids) {
188
+ const palette = this.palettes.find((p) => p.id === paletteId);
189
+ if (palette && palette.framework_class && palette.framework_class.trim() !== "") {
190
+ return paletteId;
191
+ }
192
+ }
193
+ return null;
194
+ }
195
+ /**
196
+ * Find and store references to form elements using data-* attributes
197
+ * @private
198
+ */
199
+ _initializeElements() {
200
+ this.elements = {
201
+ modelSelect: this.formElement.querySelector("[data-model-select]"),
202
+ paletteSelect: this.formElement.querySelector("[data-palette-select]"),
203
+ orientationToggle: this.formElement.querySelector("[data-orientation-toggle]"),
204
+ darkModeToggle: this.formElement.querySelector("[data-dark-mode-toggle]"),
205
+ resetButton: this.formElement.querySelector("[data-reset-button]"),
206
+ // Optional: UI indicator elements
207
+ orientationText: this.formElement.querySelector("[data-orientation-text]"),
208
+ darkModeText: this.formElement.querySelector("[data-dark-mode-text]")
209
+ };
210
+ const required = ["modelSelect", "paletteSelect"];
211
+ for (const key of required) {
212
+ if (!this.elements[key]) {
213
+ throw new Error(`TRMNLPicker: Required element "${key}" not found in form`);
214
+ }
215
+ }
216
+ }
217
+ /**
218
+ * Bind event listeners to form elements
219
+ * @private
220
+ */
221
+ _bindEvents() {
222
+ this.handlers = {
223
+ modelChange: this._handleModelChange.bind(this),
224
+ paletteChange: this._handlePaletteChange.bind(this),
225
+ orientationToggle: this._toggleOrientation.bind(this),
226
+ darkModeToggle: this._toggleDarkMode.bind(this),
227
+ reset: this._resetToModelDefaults.bind(this)
228
+ };
229
+ this.elements.modelSelect.addEventListener("change", this.handlers.modelChange);
230
+ this.elements.paletteSelect.addEventListener("change", this.handlers.paletteChange);
231
+ if (this.elements.orientationToggle) {
232
+ this.elements.orientationToggle.addEventListener("click", this.handlers.orientationToggle);
233
+ }
234
+ if (this.elements.darkModeToggle) {
235
+ this.elements.darkModeToggle.addEventListener("click", this.handlers.darkModeToggle);
236
+ }
237
+ if (this.elements.resetButton) {
238
+ this.elements.resetButton.addEventListener("click", this.handlers.reset);
239
+ }
240
+ }
241
+ /**
242
+ * Set initial state and populate form
243
+ * @private
244
+ */
245
+ _setInitialState() {
246
+ const trmnlModels = this.models.filter((m) => m.kind === "trmnl");
247
+ const byodModels = this.models.filter((m) => m.kind !== "trmnl");
248
+ const sortTRMNL = [...trmnlModels].sort((a, b) => {
249
+ const labelA = (a.label || a.name).toLowerCase();
250
+ const labelB = (b.label || b.name).toLowerCase();
251
+ return labelA.localeCompare(labelB);
252
+ });
253
+ const sortBYOD = [...byodModels].sort((a, b) => {
254
+ const labelA = (a.label || a.name).toLowerCase();
255
+ const labelB = (b.label || b.name).toLowerCase();
256
+ return labelA.localeCompare(labelB);
257
+ });
258
+ this.elements.modelSelect.innerHTML = "";
259
+ if (sortTRMNL.length > 0) {
260
+ const trmnlGroup = document.createElement("optgroup");
261
+ trmnlGroup.label = "TRMNL";
262
+ sortTRMNL.forEach((model) => {
263
+ const option = document.createElement("option");
264
+ option.value = model.name;
265
+ option.textContent = model.label || model.name;
266
+ trmnlGroup.appendChild(option);
267
+ });
268
+ this.elements.modelSelect.appendChild(trmnlGroup);
269
+ }
270
+ if (sortBYOD.length > 0) {
271
+ const byodGroup = document.createElement("optgroup");
272
+ byodGroup.label = "BYOD";
273
+ sortBYOD.forEach((model) => {
274
+ const option = document.createElement("option");
275
+ option.value = model.name;
276
+ option.textContent = model.label || model.name;
277
+ byodGroup.appendChild(option);
278
+ });
279
+ this.elements.modelSelect.appendChild(byodGroup);
280
+ }
281
+ const sortedModels = [...sortTRMNL, ...sortBYOD];
282
+ this._state = {};
283
+ const savedParams = this._loadFromLocalStorage();
284
+ if (savedParams) {
285
+ this._setParams("constructor", savedParams);
286
+ } else {
287
+ const defaultModel = sortedModels.find((m) => m.name === _DEFAULT_MODEL_NAME) || sortedModels[0];
288
+ const defaultPaletteId = this._getFirstValidPaletteId(defaultModel);
289
+ this._setParams("constructor", {
290
+ modelName: defaultModel.name,
291
+ paletteId: defaultPaletteId,
292
+ isPortrait: false,
293
+ isDarkMode: false
294
+ });
295
+ }
296
+ }
297
+ /**
298
+ * Populate palette dropdown based on selected model
299
+ * @private
300
+ */
301
+ _populateModelPalettes() {
302
+ const modelName = this.elements.modelSelect.value;
303
+ const model = this.models.find((m) => m.name === modelName);
304
+ if (!model)
305
+ return;
306
+ this.elements.paletteSelect.innerHTML = "";
307
+ model.palette_ids.forEach((paletteId) => {
308
+ const palette = this.palettes.find((p) => p.id === paletteId);
309
+ if (palette && palette.framework_class && palette.framework_class.trim() !== "") {
310
+ const option = document.createElement("option");
311
+ option.value = palette.id;
312
+ option.textContent = palette.name;
313
+ this.elements.paletteSelect.appendChild(option);
314
+ }
315
+ });
316
+ }
317
+ /**
318
+ * Emit 'trmnl:change' event with current state and screen classes
319
+ * @private
320
+ * @param {string} origin - Source of the change ('constructor', 'form', 'setParams')
321
+ * @fires TRMNLPicker#trmnl:change
322
+ */
323
+ _emitChangeEvent(origin) {
324
+ this._saveToLocalStorage();
325
+ const event = new CustomEvent("trmnl:change", {
326
+ detail: {
327
+ origin,
328
+ ...this.state
329
+ },
330
+ bubbles: true
331
+ });
332
+ this.formElement.dispatchEvent(event);
333
+ }
334
+ /**
335
+ * Load state from localStorage
336
+ * @private
337
+ * @returns {Object|null} Saved state or null if not available
338
+ */
339
+ _loadFromLocalStorage() {
340
+ if (!this.localStorageKey)
341
+ return null;
342
+ try {
343
+ const saved = localStorage.getItem(this.localStorageKey);
344
+ if (saved) {
345
+ return JSON.parse(saved);
346
+ }
347
+ } catch (error) {
348
+ console.warn("TRMNLPicker: Failed to load from localStorage:", error);
349
+ }
350
+ return null;
351
+ }
352
+ /**
353
+ * Save current state to localStorage
354
+ * @private
355
+ */
356
+ _saveToLocalStorage() {
357
+ if (!this.localStorageKey)
358
+ return;
359
+ try {
360
+ localStorage.setItem(this.localStorageKey, JSON.stringify(this.params));
361
+ } catch (error) {
362
+ console.warn("TRMNLPicker: Failed to save to localStorage:", error);
363
+ }
364
+ }
365
+ /**
366
+ * Handle model selection change
367
+ * @private
368
+ */
369
+ _handleModelChange(event) {
370
+ this._setParams("form", { modelName: event.target.value });
371
+ }
372
+ /**
373
+ * Handle palette selection change
374
+ * @private
375
+ */
376
+ _handlePaletteChange(event) {
377
+ this._setParams("form", { paletteId: event.target.value });
378
+ }
379
+ /**
380
+ * Toggle orientation between portrait and landscape
381
+ * @private
382
+ */
383
+ _toggleOrientation() {
384
+ this._setParams("form", { isPortrait: !this._state.isPortrait });
385
+ }
386
+ /**
387
+ * Toggle dark mode on/off
388
+ * @private
389
+ */
390
+ _toggleDarkMode() {
391
+ this._setParams("form", { isDarkMode: !this._state.isDarkMode });
392
+ }
393
+ /**
394
+ * Reset to defaults: first valid palette, landscape orientation, light mode
395
+ * @private
396
+ */
397
+ _resetToModelDefaults() {
398
+ const model = this._state.model;
399
+ if (!model)
400
+ return;
401
+ const firstPaletteId = this._getFirstValidPaletteId(model);
402
+ this._setParams("form", {
403
+ paletteId: firstPaletteId,
404
+ isPortrait: false,
405
+ isDarkMode: false
406
+ });
407
+ }
408
+ /**
409
+ * Update reset button enabled/disabled state
410
+ * Button is disabled only when palette, orientation, and dark mode are all at defaults
411
+ * @private
412
+ */
413
+ _updateResetButton() {
414
+ if (!this.elements.resetButton)
415
+ return;
416
+ const model = this._state.model;
417
+ if (!model)
418
+ return;
419
+ const firstValidPaletteId = this._getFirstValidPaletteId(model);
420
+ const isPaletteDefault = this.elements.paletteSelect.value === String(firstValidPaletteId);
421
+ const isOrientationDefault = this._state.isPortrait === false;
422
+ const isDarkModeDefault = this._state.isDarkMode === false;
423
+ const isAtDefaults = isPaletteDefault && isOrientationDefault && isDarkModeDefault;
424
+ this.elements.resetButton.disabled = isAtDefaults;
425
+ if (isAtDefaults) {
426
+ this.elements.resetButton.classList.add("opacity-50", "cursor-default");
427
+ this.elements.resetButton.setAttribute("aria-disabled", "true");
428
+ } else {
429
+ this.elements.resetButton.classList.remove("opacity-50", "cursor-default");
430
+ this.elements.resetButton.removeAttribute("aria-disabled");
431
+ }
432
+ }
433
+ /**
434
+ * Get CSS classes for the current picker configuration
435
+ * @private
436
+ * @returns {Array<string>} Array of CSS class names for Framework CSS rendering
437
+ *
438
+ * Generated classes (in order):
439
+ * 1. 'screen' - Base class (always present)
440
+ * 2. palette.framework_class - From selected palette (e.g., 'screen--1bit')
441
+ * 3. model.css.classes.device - From model API (e.g., 'screen--v2')
442
+ * 4. model.css.classes.size - From model API (e.g., 'screen--md')
443
+ * 5. 'screen--portrait' - Only when portrait orientation is enabled
444
+ * 6. 'screen--1x' - Scale indicator (always 1x)
445
+ * 7. 'screen--dark-mode' - Only when dark mode is enabled
446
+ *
447
+ * @example
448
+ * const classes = picker.screenClasses
449
+ * // ['screen', 'screen--1bit', 'screen--v2', 'screen--md', 'screen--1x']
450
+ */
451
+ get _screenClasses() {
452
+ const model = this._state.model;
453
+ const palette = this._state.palette;
454
+ if (!model) {
455
+ throw new Error("No model selected");
456
+ }
457
+ const classes = [];
458
+ classes.push("screen");
459
+ if (palette && palette.framework_class) {
460
+ classes.push(palette.framework_class);
461
+ }
462
+ if (model.css && model.css.classes && model.css.classes.device) {
463
+ classes.push(model.css.classes.device);
464
+ }
465
+ if (model.css && model.css.classes && model.css.classes.size) {
466
+ classes.push(model.css.classes.size);
467
+ }
468
+ if (this._state.isPortrait) {
469
+ classes.push("screen--portrait");
470
+ }
471
+ classes.push("screen--1x");
472
+ if (this._state.isDarkMode) {
473
+ classes.push("screen--dark-mode");
474
+ }
475
+ return classes;
476
+ }
477
+ /**
478
+ * Get current picker parameters (serializable state)
479
+ * @public
480
+ * @returns {Object} Current parameters for persistence or API calls
481
+ * @returns {string} return.modelName - Selected model name
482
+ * @returns {string} return.paletteId - Selected palette ID
483
+ * @returns {boolean} return.isPortrait - Portrait orientation flag
484
+ * @returns {boolean} return.isDarkMode - Dark mode flag
485
+ *
486
+ * @example
487
+ * const params = picker.params
488
+ * // { modelName: 'og_plus', paletteId: '123', isPortrait: false, isDarkMode: false }
489
+ *
490
+ * // Can be used to restore state later
491
+ * localStorage.setItem('picker-state', JSON.stringify(picker.params))
492
+ */
493
+ get params() {
494
+ return {
495
+ modelName: this._state.model?.name,
496
+ paletteId: this._state.palette?.id,
497
+ isPortrait: this._state.isPortrait,
498
+ isDarkMode: this._state.isDarkMode
499
+ };
500
+ }
501
+ /**
502
+ * Update picker configuration programmatically
503
+ * @public
504
+ * @param {Object} params - Configuration object (all fields optional)
505
+ * @param {string} [params.modelName] - Model name to select
506
+ * @param {string} [params.paletteId] - Palette ID to select
507
+ * @param {boolean} [params.isPortrait] - Portrait orientation
508
+ * @param {boolean} [params.isDarkMode] - Dark mode enabled
509
+ * @fires TRMNLPicker#trmnl:change
510
+ * @throws {Error} If params is not an object
511
+ *
512
+ * @example
513
+ * // Update single parameter
514
+ * picker.setParams({ isDarkMode: true })
515
+ *
516
+ * // Update multiple parameters
517
+ * picker.setParams({
518
+ * modelName: 'og_plus',
519
+ * paletteId: '123',
520
+ * isPortrait: true
521
+ * })
522
+ *
523
+ * // Note: Changing model resets palette to first valid palette of that model
524
+ */
525
+ setParams(params) {
526
+ this._setParams("setParams", params);
527
+ }
528
+ /**
529
+ * Internal method to update picker state with origin tracking
530
+ * @private
531
+ * @param {string} origin - Source of change ('constructor', 'form', 'setParams')
532
+ * @param {Object} params - Parameters to update
533
+ * @returns {boolean} True if any changes were made
534
+ */
535
+ _setParams(origin, params) {
536
+ if (!params || typeof params !== "object") {
537
+ throw new Error("params must be an object");
538
+ }
539
+ let changed = false;
540
+ if (params.modelName) {
541
+ const model = this.models.find((m) => m.name === params.modelName);
542
+ if (model) {
543
+ this.elements.modelSelect.value = model.name;
544
+ this._state.model = model;
545
+ this._populateModelPalettes();
546
+ const firstPaletteId = this._getFirstValidPaletteId(model);
547
+ this.elements.paletteSelect.value = firstPaletteId;
548
+ this._state.palette = this.palettes.find((p) => p.id === firstPaletteId);
549
+ changed = true;
550
+ }
551
+ }
552
+ if (params.paletteId) {
553
+ const palette = this.palettes.find((p) => p.id === params.paletteId);
554
+ if (palette) {
555
+ this.elements.paletteSelect.value = palette.id;
556
+ this._state.palette = palette;
557
+ changed = true;
558
+ }
559
+ }
560
+ if (typeof params.isPortrait === "boolean") {
561
+ this._state.isPortrait = params.isPortrait;
562
+ if (this.elements.orientationText) {
563
+ this.elements.orientationText.textContent = this._state.isPortrait ? "Portrait" : "Landscape";
564
+ }
565
+ changed = true;
566
+ }
567
+ if (typeof params.isDarkMode === "boolean") {
568
+ this._state.isDarkMode = params.isDarkMode;
569
+ if (this.elements.darkModeText) {
570
+ this.elements.darkModeText.textContent = this._state.isDarkMode ? "Dark Mode" : "Light Mode";
571
+ }
572
+ changed = true;
573
+ }
574
+ if (changed) {
575
+ this._updateResetButton();
576
+ }
577
+ if (changed && origin) {
578
+ this._emitChangeEvent(origin);
579
+ }
580
+ return changed;
581
+ }
582
+ /**
583
+ * Get complete picker state including full model and palette objects
584
+ * @public
585
+ * @returns {{
586
+ * model: Object,
587
+ * palette: Object,
588
+ * isPortrait: boolean,
589
+ * isDarkMode: boolean,
590
+ * screenClasses: Array<string>,
591
+ * width: number,
592
+ * height: number
593
+ * }} State object containing model (full model object from API), palette (full palette object from API), isPortrait flag, and isDarkMode flag
594
+ *
595
+ * @example
596
+ * const state = picker.state
597
+ * // {
598
+ * // model: { name: 'og_plus', label: 'OG+', width: 800, height: 480, ... },
599
+ * // palette: { id: '123', name: 'Black', framework_class: 'screen--1bit', ... },
600
+ * // isPortrait: false,
601
+ * // isDarkMode: false,
602
+ * // screenClasses: ['screen', 'screen--1bit', 'screen--v2', 'screen--md', 'screen--1x'],
603
+ * // width: 800,
604
+ * // height: 480
605
+ * // }
606
+ */
607
+ get state() {
608
+ return {
609
+ screenClasses: this._screenClasses,
610
+ ...this._dimensions,
611
+ ...this._state
612
+ };
613
+ }
614
+ /**
615
+ * Get current dimensions (width and height) of the screen in pixels
616
+ * @private
617
+ * @returns {{ width: number, height: number }} Object with width and height properties
618
+ */
619
+ get _dimensions() {
620
+ const model = this._state.model;
621
+ let width = model.width / model.scale_factor;
622
+ let height = model.height / model.scale_factor;
623
+ if (this._state.isPortrait) {
624
+ [width, height] = [height, width];
625
+ }
626
+ return { width, height };
627
+ }
628
+ /**
629
+ * Clean up event listeners and references
630
+ * @public
631
+ */
632
+ destroy() {
633
+ this.elements.modelSelect.removeEventListener("change", this.handlers.modelChange);
634
+ this.elements.paletteSelect.removeEventListener("change", this.handlers.paletteChange);
635
+ if (this.elements.orientationToggle) {
636
+ this.elements.orientationToggle.removeEventListener("click", this.handlers.orientationToggle);
637
+ }
638
+ if (this.elements.darkModeToggle) {
639
+ this.elements.darkModeToggle.removeEventListener("click", this.handlers.darkModeToggle);
640
+ }
641
+ if (this.elements.resetButton) {
642
+ this.elements.resetButton.removeEventListener("click", this.handlers.reset);
643
+ }
644
+ this.formElement = null;
645
+ this.elements = null;
646
+ this.handlers = null;
647
+ this.models = null;
648
+ this.palettes = null;
649
+ this._state = null;
650
+ }
651
+ };
652
+ var src_default = TRMNLPicker;
653
+ return __toCommonJS(src_exports);
654
+ })();
655
+ TRMNLPicker=TRMNLPicker.default;
656
+ //# sourceMappingURL=trmnl-picker.js.map