terrier-engine 4.51.1 → 4.51.3

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.
@@ -1,26 +1,25 @@
1
- import Schema, {SchemaDef} from "../../terrier/schema"
2
- import {PartTag} from "tuff-core/parts"
1
+ import Schema, { SchemaDef } from "../../terrier/schema"
2
+ import { PartTag } from "tuff-core/parts"
3
3
  import Dives from "./dives"
4
- import Queries, {Query, QueryModelPicker} from "../queries/queries"
4
+ import Queries, { Query, QueryModelPicker } from "../queries/queries"
5
5
  import QueryEditor from "../queries/query-editor"
6
- import {Logger} from "tuff-core/logging"
6
+ import { Logger } from "tuff-core/logging"
7
7
  import QueryForm from "../queries/query-form"
8
- import {TabContainerPart} from "../../terrier/tabs"
8
+ import { TabContainerPart } from "../../terrier/tabs"
9
9
  import ContentPart from "../../terrier/parts/content-part"
10
- import {ModalPart} from "../../terrier/modals"
11
- import {DdDive} from "../gen/models"
10
+ import { ModalPart } from "../../terrier/modals"
11
+ import { DdDive } from "../gen/models"
12
12
  import Ids from "../../terrier/ids"
13
13
  import Db from "../dd-db"
14
14
  import DdSession from "../dd-session"
15
- import {DiveRunModal} from "./dive-runs"
15
+ import { DiveRunModal } from "./dive-runs"
16
16
  import Nav from "tuff-core/nav"
17
17
  import Messages from "tuff-core/messages"
18
- import Arrays from "tuff-core/arrays"
19
- import {FormFields} from "tuff-core/forms"
18
+ import { FormFields } from "tuff-core/forms"
20
19
  import Fragments from "../../terrier/fragments"
21
- import {DiveDeliveryPanel} from "./dive-delivery"
20
+ import { DiveDeliveryPanel } from "./dive-delivery"
22
21
  import DivePlotList from "../plots/dive-plot-list"
23
- import {DivePage} from "./dive-page"
22
+ import { DivePage } from "./dive-page"
24
23
 
25
24
  const log = new Logger("DiveEditor")
26
25
 
@@ -49,11 +48,13 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
49
48
 
50
49
  static readonly diveChangedKey = Messages.untypedKey()
51
50
 
52
- queries = new Array<Query>()
51
+ queries = new Map<string, Query>()
52
+ // Array of map keys to give the queries a sort order.
53
+ queryOrder = new Array<string>()
53
54
 
54
55
  async init() {
55
- this.queryTabs = this.makePart(TabContainerPart, {side: 'top'})
56
- this.settingsTabs = this.makePart(TabContainerPart, {side: 'top'})
56
+ this.queryTabs = this.makePart(TabContainerPart, { side: 'top', reorderable: true })
57
+ this.settingsTabs = this.makePart(TabContainerPart, { side: 'top' })
57
58
 
58
59
  this.queryTabs.addBeforeAction({
59
60
  title: 'Queries:',
@@ -63,40 +64,48 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
63
64
  title: "Add Another Query",
64
65
  classes: ['dd-hint', 'arrow-right', 'glyp-hint'],
65
66
  tooltip: "Each query represents a separate tab in the resulting spreadsheet",
66
- click: {key: this.newQueryKey}
67
+ click: { key: this.newQueryKey }
67
68
  })
68
69
  this.queryTabs.addAfterAction({
69
70
  icon: 'glyp-copy',
70
71
  classes: ['duplicate-query'],
71
72
  tooltip: "Duplicate this query",
72
- click: {key: this.duplicateQueryKey}
73
+ click: { key: this.duplicateQueryKey }
73
74
  })
74
75
  this.queryTabs.addAfterAction({
75
76
  icon: 'glyp-plus_outline',
76
77
  classes: ['new-query'],
77
78
  tooltip: "Add a new query to this Dive",
78
- click: {key: this.newQueryKey}
79
+ click: { key: this.newQueryKey }
79
80
  })
80
81
 
81
- this.queries = this.state.dive.query_data?.queries || []
82
- for (const query of this.queries) {
82
+ this.queries = new Map()
83
+ for (const query of this.state.dive.query_data?.queries || []) {
84
+ this.queries.set(query.id, query)
85
+ this.queryOrder.push(query.id)
83
86
  this.addQueryTab(query)
84
87
  }
85
88
 
86
89
  this.listenMessage(QueryForm.settingsChangedKey, m => {
87
90
  const query = m.data
88
91
  log.info(`Query settings changed`, query)
89
- this.queryTabs.updateTab({key: query.id, title: query.name})
92
+ this.queryTabs.updateTab({ key: query.id, title: query.name })
93
+ })
94
+
95
+ // Reorder queries in the list when the tab sort order is updated.
96
+ this.listenMessage(this.queryTabs.tabsModifiedKey, m => {
97
+ const { newOrder } = m.data
98
+ this.queryOrder = newOrder
90
99
  })
91
100
 
92
101
  this.onClick(this.newQueryKey, _ => {
93
- this.app.showModal(NewQueryModal, {editor: this as DiveEditor, schema: this.state.schema})
102
+ this.app.showModal(NewQueryModal, { editor: this as DiveEditor, schema: this.state.schema })
94
103
  })
95
104
 
96
105
  this.onClick(this.duplicateQueryKey, _ => {
97
106
  const id = this.queryTabs.currentTagKey
98
107
  if (id?.length) {
99
- const query = this.queries.find(q => q.id == id)
108
+ const query = this.queries.get(id)
100
109
  if (query) {
101
110
  this.app.showModal(DuplicateQueryModal, {
102
111
  editor: this as DiveEditor,
@@ -114,7 +123,7 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
114
123
  })
115
124
 
116
125
  this.onClick(DiveEditor.deleteQueryKey, m => {
117
- this.app.confirm({title: "Delete Query", icon: 'glyp-delete', body: "Are you sure you want to delete this query?"}, () => {
126
+ this.app.confirm({ title: "Delete Query", icon: 'glyp-delete', body: "Are you sure you want to delete this query?" }, () => {
118
127
  this.deleteQuery(m.data.id)
119
128
  })
120
129
  })
@@ -137,7 +146,7 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
137
146
  * @param query
138
147
  */
139
148
  addQuery(query: Query) {
140
- this.queries.push(query)
149
+ this.queries.set(query.id, query)
141
150
  this.addQueryTab(query)
142
151
  }
143
152
 
@@ -146,16 +155,17 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
146
155
  * @param query
147
156
  */
148
157
  private addQueryTab(query: Query) {
149
- const state = {...this.state, query}
150
- this.queryTabs.upsertTab({key: query.id, title: query.name}, QueryEditor, state)
158
+ const state = { ...this.state, query }
159
+ this.queryTabs.upsertTab({ key: query.id, title: query.name }, QueryEditor, state)
151
160
  }
152
161
 
153
162
  deleteQuery(id: string) {
154
163
  log.info(`Deleting query ${id}`)
155
- if (Arrays.deleteIf(this.queries, q => q.id == id) > 0) {
156
- this.queryTabs.removeTab(id)
157
- this.dirty()
158
- }
164
+ if (!this.queries.has(id)) return
165
+
166
+ this.queries.delete(id)
167
+ this.queryTabs.removeTab(id)
168
+ this.dirty()
159
169
  }
160
170
 
161
171
  get parentClasses(): Array<string> {
@@ -178,7 +188,7 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
178
188
 
179
189
  return {
180
190
  ...this.state.dive,
181
- query_data: {queries}
191
+ query_data: { queries: this.queryOrder.map(id => queries.get(id)!) }
182
192
  }
183
193
  }
184
194
 
@@ -189,7 +199,7 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
189
199
  // Editor Page
190
200
  ////////////////////////////////////////////////////////////////////////////////
191
201
 
192
- export class DiveEditorPage extends DivePage<{id: string}> {
202
+ export class DiveEditorPage extends DivePage<{ id: string }> {
193
203
 
194
204
  editor!: DiveEditor
195
205
  session!: DdSession
@@ -205,7 +215,7 @@ export class DiveEditorPage extends DivePage<{id: string}> {
205
215
  const schema = await Schema.get()
206
216
  this.session = await DdSession.get()
207
217
  const dive = await Dives.get(this.state.id)
208
- this.editor = this.makePart(DiveEditor, {schema, dive, session: this.session})
218
+ this.editor = this.makePart(DiveEditor, { schema, dive, session: this.session })
209
219
 
210
220
  this.mainContentWidth = 'wide'
211
221
 
@@ -240,20 +250,20 @@ export class DiveEditorPage extends DivePage<{id: string}> {
240
250
  title: 'Discard',
241
251
  icon: 'glyp-cancelled',
242
252
  classes: ['discard-dive-action'],
243
- click: {key: this.discardKey}
253
+ click: { key: this.discardKey }
244
254
  }, 'tertiary')
245
255
 
246
256
  this.addAction({
247
257
  title: 'Save',
248
258
  icon: 'glyp-complete',
249
259
  classes: ['save-dive-action'],
250
- click: {key: this.saveKey}
260
+ click: { key: this.saveKey }
251
261
  }, 'tertiary')
252
262
 
253
263
  this.addAction({
254
264
  title: 'Run',
255
265
  icon: 'glyp-play',
256
- click: {key: this.runKey}
266
+ click: { key: this.runKey }
257
267
  }, 'tertiary')
258
268
 
259
269
  this.onClick(this.discardKey, _ => {
@@ -275,7 +285,7 @@ export class DiveEditorPage extends DivePage<{id: string}> {
275
285
  this.listenMessage(DiveEditor.diveChangedKey, _ => {
276
286
  log.info("Dive changed")
277
287
  this.element?.classList.add('changed')
278
- }, {attach: 'passive'})
288
+ }, { attach: 'passive' })
279
289
 
280
290
  this.dirty()
281
291
  }
@@ -303,7 +313,7 @@ export class DiveEditorPage extends DivePage<{id: string}> {
303
313
 
304
314
  async run() {
305
315
  const dive = await this.editor.serialize()
306
- this.app.showModal(DiveRunModal, {dive})
316
+ this.app.showModal(DiveRunModal, { dive })
307
317
  }
308
318
  }
309
319
 
@@ -324,8 +334,8 @@ class NewQueryModal extends ModalPart<NewQueryState> {
324
334
  modelPicker!: QueryModelPicker
325
335
 
326
336
  async init() {
327
- this.settingsForm = this.makePart(QueryForm, {query: {id: 'new', name: '', notes: ''}})
328
- this.modelPicker = this.makePart(QueryModelPicker, {schema: this.state.schema})
337
+ this.settingsForm = this.makePart(QueryForm, { query: { id: 'new', name: '', notes: '' } })
338
+ this.modelPicker = this.makePart(QueryModelPicker, { schema: this.state.schema })
329
339
 
330
340
  this.setIcon('glyp-data_dive_query')
331
341
  this.setTitle("New Query")
@@ -333,7 +343,7 @@ class NewQueryModal extends ModalPart<NewQueryState> {
333
343
  this.addAction({
334
344
  title: "Add",
335
345
  icon: 'glyp-plus',
336
- click: {key: this.addKey}
346
+ click: { key: this.addKey }
337
347
  })
338
348
 
339
349
  this.onClick(this.addKey, async _ => {
@@ -361,16 +371,16 @@ class NewQueryModal extends ModalPart<NewQueryState> {
361
371
  log.info(`Saving new query`)
362
372
  const settings = await this.settingsForm.fields.serialize()
363
373
  if (!settings.name?.length) {
364
- this.showToast("Please enter a query name", {color: 'alert'})
374
+ this.showToast("Please enter a query name", { color: 'alert' })
365
375
  this.dirty()
366
376
  return
367
377
  }
368
378
  const model = this.modelPicker.model
369
379
  if (!model) {
370
- this.showToast("Please select a model", {color: 'alert'})
380
+ this.showToast("Please select a model", { color: 'alert' })
371
381
  return
372
382
  }
373
- const query = {...settings, id: Ids.makeUuid(), from: {model: model.name}}
383
+ const query = { ...settings, id: Ids.makeUuid(), from: { model: model.name } }
374
384
  this.state.editor.addQuery(query)
375
385
  this.state.editor.queryTabs.showTab(query.id)
376
386
  this.pop()
@@ -398,13 +408,13 @@ class DuplicateQueryModal extends ModalPart<DuplicateQueryState> {
398
408
  this.setIcon('glyp-data_dive_query')
399
409
  this.setTitle("Duplicate Query")
400
410
 
401
- const newQuery = {...this.state.query, name: `${this.state.query.name} Copy`}
411
+ const newQuery = { ...this.state.query, name: `${this.state.query.name} Copy` }
402
412
  this.fields = new FormFields<Query>(this, newQuery)
403
413
 
404
414
  this.addAction({
405
415
  title: "Duplicate",
406
416
  icon: 'glyp-checkmark',
407
- click: {key: this.dupKey}
417
+ click: { key: this.dupKey }
408
418
  })
409
419
 
410
420
  this.onClick(this.dupKey, async _ => {
@@ -414,7 +424,7 @@ class DuplicateQueryModal extends ModalPart<DuplicateQueryState> {
414
424
 
415
425
  async save() {
416
426
  const newName = this.fields.data.name
417
- const query = {...Queries.duplicate(this.state.query), name: newName}
427
+ const query = { ...Queries.duplicate(this.state.query), name: newName }
418
428
  this.state.editor.addQuery(query)
419
429
  this.state.editor.queryTabs.showTab(query.id)
420
430
  this.pop()
@@ -428,5 +438,4 @@ class DuplicateQueryModal extends ModalPart<DuplicateQueryState> {
428
438
  this.fields.textInput(col, 'name')
429
439
  })
430
440
  }
431
- }
432
-
441
+ }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "files": [
5
5
  "*"
6
6
  ],
7
- "version": "4.51.1",
7
+ "version": "4.51.3",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Terrier-Tech/terrier-engine"
@@ -35,4 +35,4 @@
35
35
  "vite-plugin-ruby": "^5.1.0",
36
36
  "vitest": "^2.1.5"
37
37
  }
38
- }
38
+ }
package/terrier/glyps.ts CHANGED
@@ -1,8 +1,8 @@
1
- // This file was automatically generated by glyps:compile on 04/09/25 10:46 AM, DO NOT MANUALLY EDIT!
1
+ // This file was automatically generated by glyps:compile on 05/19/25 1:37 PM, DO NOT MANUALLY EDIT!
2
2
 
3
3
  import Strings from "tuff-core/strings"
4
4
 
5
- const names = ['glyp-abacus', 'glyp-account', 'glyp-accounts_payable', 'glyp-accounts_receivable', 'glyp-action_log', 'glyp-activate', 'glyp-activate_user', 'glyp-active', 'glyp-activity_high', 'glyp-activity_low', 'glyp-activity_medium', 'glyp-activity_none', 'glyp-address1', 'glyp-adjustment', 'glyp-admin', 'glyp-administration', 'glyp-affinity', 'glyp-agency', 'glyp-air_freshener', 'glyp-alert', 'glyp-align_horizontal', 'glyp-align_vertical', 'glyp-anchor', 'glyp-android_phone', 'glyp-android_tablet', 'glyp-annotation', 'glyp-announcement', 'glyp-app_update', 'glyp-append_selection', 'glyp-appointment', 'glyp-appointment_setup', 'glyp-archive', 'glyp-arrow_down', 'glyp-arrow_left', 'glyp-arrow_right', 'glyp-arrow_transfer', 'glyp-arrow_up', 'glyp-ascending', 'glyp-assign', 'glyp-assign_all', 'glyp-attachment', 'glyp-audit', 'glyp-audit_analyzer', 'glyp-audits', 'glyp-autopay', 'glyp-availability', 'glyp-background_batch', 'glyp-background_job', 'glyp-background_style', 'glyp-bait_for_demolition', 'glyp-bar_chart', 'glyp-barcode', 'glyp-bat_exclusion', 'glyp-bed_bug_fumigation', 'glyp-begin_service', 'glyp-belongs_to', 'glyp-billing', 'glyp-billing_office', 'glyp-billing_terms', 'glyp-billto', 'glyp-bioremediation', 'glyp-bird_exclusion', 'glyp-black_light', 'glyp-bookmark', 'glyp-branch', 'glyp-branch_bats', 'glyp-branch_birds', 'glyp-branch_general', 'glyp-branch_irrigation', 'glyp-branch_landscape', 'glyp-branch_multi_housing', 'glyp-branch_office', 'glyp-branch_orders', 'glyp-branch_residential', 'glyp-branch_sales', 'glyp-branch_termites', 'glyp-branch_weeds', 'glyp-branch_wildlife', 'glyp-build', 'glyp-business', 'glyp-business_cert', 'glyp-business_cert_type', 'glyp-calculate', 'glyp-calendar', 'glyp-calendar_branch', 'glyp-calendar_map', 'glyp-calendar_rolling', 'glyp-calendar_share', 'glyp-calendar_snap', 'glyp-calendar_style', 'glyp-california', 'glyp-call', 'glyp-callback', 'glyp-camera', 'glyp-canada', 'glyp-cancellation', 'glyp-cancelled', 'glyp-card_american_express', 'glyp-card_discover', 'glyp-card_mastercard', 'glyp-card_visa', 'glyp-catalog', 'glyp-category', 'glyp-caught', 'glyp-cellulose_debris', 'glyp-cert', 'glyp-cert_timeline', 'glyp-cert_type', 'glyp-certificate', 'glyp-certified_summary', 'glyp-certs', 'glyp-ceu', 'glyp-check_all', 'glyp-check_in', 'glyp-check_in_service', 'glyp-checked', 'glyp-checked_check_all', 'glyp-checked_empty', 'glyp-checkmark', 'glyp-checkmark_filled', 'glyp-checkout', 'glyp-chemical', 'glyp-chemical_authorization', 'glyp-chemical_backpack_sprayer', 'glyp-chemical_biological_controls', 'glyp-chemical_disinfectant', 'glyp-chemical_fertilizer', 'glyp-chemical_flying_insect_bait', 'glyp-chemical_fumigants', 'glyp-chemical_insect_aerosol', 'glyp-chemical_insect_bait', 'glyp-chemical_insect_dust', 'glyp-chemical_insect_granules', 'glyp-chemical_insect_spray', 'glyp-chemical_label', 'glyp-chemical_mole_bait', 'glyp-chemical_pest_bird_control', 'glyp-chemical_pheromone_products', 'glyp-chemical_power_sprayer', 'glyp-chemical_rodent_attractant', 'glyp-chemical_rodenticide_block', 'glyp-chemical_rodenticide_non_block', 'glyp-chemical_sds', 'glyp-chemical_tree_products', 'glyp-chemical_weed_control', 'glyp-chemical_wildlife_repellents', 'glyp-chemical_wildlife_toxicants', 'glyp-chevron_down', 'glyp-chevron_left', 'glyp-chevron_right', 'glyp-chevron_up', 'glyp-circle', 'glyp-city', 'glyp-click', 'glyp-client_actions', 'glyp-client_pest_sightings', 'glyp-client_required', 'glyp-clipboard', 'glyp-close', 'glyp-close_empty', 'glyp-cloud', 'glyp-clyp_environment', 'glyp-clyp_settings', 'glyp-clypboard', 'glyp-clypedia', 'glyp-clypmart', 'glyp-code', 'glyp-code_details', 'glyp-collections', 'glyp-color', 'glyp-columns', 'glyp-comment', 'glyp-comment_filled', 'glyp-comments', 'glyp-commission_origin', 'glyp-commissions', 'glyp-compact', 'glyp-company_setup', 'glyp-compass', 'glyp-complete', 'glyp-complete_certificate', 'glyp-complete_enrollment', 'glyp-compressed', 'glyp-condition_appliance_machinery', 'glyp-condition_customer', 'glyp-condition_device', 'glyp-condition_door_window', 'glyp-condition_dumpster', 'glyp-condition_entry_point', 'glyp-condition_evidence', 'glyp-condition_exclusion', 'glyp-condition_exterior', 'glyp-condition_interior', 'glyp-condition_plumbing', 'glyp-condition_prep_storage', 'glyp-condition_rodent_evidence', 'glyp-condition_rodent_exclusion', 'glyp-condition_roof_ceiling', 'glyp-condition_structure', 'glyp-condition_supplies', 'glyp-condition_termites', 'glyp-condition_ventilation', 'glyp-condition_wall_floor', 'glyp-condition_wildlife', 'glyp-conditions', 'glyp-consolidate', 'glyp-construction', 'glyp-continuing_ed', 'glyp-contract', 'glyp-contract_cancellation', 'glyp-contract_overview', 'glyp-conversation', 'glyp-copesan', 'glyp-copy', 'glyp-county', 'glyp-credit_memo', 'glyp-credit_non_revenue', 'glyp-credit_production', 'glyp-credit_prospect', 'glyp-credit_sales_bonus', 'glyp-crew', 'glyp-cumulative', 'glyp-curriculum', 'glyp-cursor', 'glyp-custom_form', 'glyp-customer', 'glyp-customer_service', 'glyp-damage', 'glyp-dashboard', 'glyp-data_dive', 'glyp-data_dive_query', 'glyp-data_dives', 'glyp-database', 'glyp-deactivate_user', 'glyp-dead_animal_removal', 'glyp-default', 'glyp-delete', 'glyp-demo_mode', 'glyp-descending', 'glyp-design', 'glyp-desktop', 'glyp-detail_report', 'glyp-developer', 'glyp-device', 'glyp-device_sync', 'glyp-differential', 'glyp-disable', 'glyp-disabled_filled', 'glyp-distribute_horizontal', 'glyp-distribute_vertical', 'glyp-division', 'glyp-document', 'glyp-documentation', 'glyp-documents', 'glyp-download', 'glyp-driving', 'glyp-duplicate', 'glyp-duplicate_location', 'glyp-duration', 'glyp-earth_to_wood', 'glyp-edit', 'glyp-email', 'glyp-employment', 'glyp-entertainment_public_venues', 'glyp-equipment_bear_trap', 'glyp-equipment_flying_insect_bait_station', 'glyp-equipment_flying_insect_light_trap', 'glyp-equipment_insect_monitor', 'glyp-equipment_lethal_animal_trap', 'glyp-equipment_live_animal_trap', 'glyp-equipment_live_rodent_trap', 'glyp-equipment_maintenance', 'glyp-equipment_map_viewer', 'glyp-equipment_maps', 'glyp-equipment_mosquito_trap', 'glyp-equipment_other', 'glyp-equipment_pheromone_trap', 'glyp-equipment_pick_up', 'glyp-equipment_rodent_bait', 'glyp-equipment_rodent_trap', 'glyp-equipment_termite_bait_station', 'glyp-equipment_termite_monitor', 'glyp-escape', 'glyp-exam', 'glyp-exams', 'glyp-exclusion', 'glyp-existing_location', 'glyp-expand', 'glyp-expired', 'glyp-expiring', 'glyp-exterior', 'glyp-extras', 'glyp-facebook', 'glyp-family', 'glyp-farm', 'glyp-farm_grain_seed', 'glyp-fast_and_frequent', 'glyp-favorite', 'glyp-features', 'glyp-feedback', 'glyp-field_guide', 'glyp-field_training', 'glyp-file_blank', 'glyp-file_html', 'glyp-file_image', 'glyp-file_pdf', 'glyp-file_printable', 'glyp-file_spreadsheet', 'glyp-file_text', 'glyp-filter', 'glyp-finance', 'glyp-finding', 'glyp-fogging', 'glyp-folder', 'glyp-followup', 'glyp-food_bev_pharma', 'glyp-formula', 'glyp-fuel', 'glyp-fumigation', 'glyp-function', 'glyp-function_aggregate', 'glyp-function_time', 'glyp-gain_loss', 'glyp-generate_invoices', 'glyp-generate_orders', 'glyp-geo', 'glyp-geo_heat_map', 'glyp-geoboard', 'glyp-google', 'glyp-google_calendar', 'glyp-government', 'glyp-grain_seed', 'glyp-grid', 'glyp-grouped', 'glyp-grouping_combine', 'glyp-grouping_separate', 'glyp-guide', 'glyp-has_many', 'glyp-health_care', 'glyp-heat_map', 'glyp-heat_treatment', 'glyp-help', 'glyp-hidden', 'glyp-hint', 'glyp-home', 'glyp-honeybee_removal', 'glyp-housing', 'glyp-icon', 'glyp-identifier', 'glyp-image', 'glyp-images', 'glyp-import', 'glyp-import_certs', 'glyp-import_data', 'glyp-import_users', 'glyp-in_progress', 'glyp-inbox', 'glyp-incomplete_certificate', 'glyp-industrial', 'glyp-info', 'glyp-information_technology', 'glyp-inspect_map', 'glyp-inspections', 'glyp-insulation', 'glyp-interior', 'glyp-invoice', 'glyp-invoice_charge', 'glyp-invoice_credit', 'glyp-invoice_paid', 'glyp-invoice_review', 'glyp-ipad', 'glyp-iphone', 'glyp-irrigation_install', 'glyp-irrigation_maintenance', 'glyp-items', 'glyp-job', 'glyp-job_plan', 'glyp-job_plan_history', 'glyp-job_plan_run', 'glyp-job_plan_scheduled', 'glyp-job_plan_single_click', 'glyp-job_plan_step_by_step', 'glyp-join', 'glyp-join_inner', 'glyp-join_left', 'glyp-jump', 'glyp-justice', 'glyp-k9_inspection', 'glyp-key', 'glyp-label_bottom', 'glyp-label_left', 'glyp-label_right', 'glyp-label_top', 'glyp-labor', 'glyp-landscape', 'glyp-landscape_maintenance', 'glyp-laptop', 'glyp-lead', 'glyp-lead_listing', 'glyp-lead_source', 'glyp-leads_report', 'glyp-learning_hub', 'glyp-ledger', 'glyp-legal', 'glyp-license', 'glyp-lifetime', 'glyp-limit', 'glyp-line_cap_arrow', 'glyp-line_cap_bar', 'glyp-line_cap_circle', 'glyp-line_cap_none', 'glyp-line_caps', 'glyp-line_style', 'glyp-link', 'glyp-list', 'glyp-location', 'glyp-location_charge', 'glyp-location_credit', 'glyp-location_import', 'glyp-location_kind', 'glyp-location_message', 'glyp-location_origin', 'glyp-location_seed', 'glyp-location_tags', 'glyp-locations', 'glyp-locked', 'glyp-lodging', 'glyp-log_in', 'glyp-log_out', 'glyp-log_report', 'glyp-logbook', 'glyp-logbook_documents', 'glyp-lot_number', 'glyp-lycensed_simulator', 'glyp-manager', 'glyp-manual', 'glyp-map', 'glyp-markdown', 'glyp-market', 'glyp-materials', 'glyp-mattress_encasement', 'glyp-merge', 'glyp-message_billing', 'glyp-message_collections', 'glyp-message_complaint', 'glyp-message_misc', 'glyp-message_technician', 'glyp-messages', 'glyp-minus_outline', 'glyp-misc', 'glyp-miscellaneous', 'glyp-missing', 'glyp-moisture', 'glyp-move_order', 'glyp-mowing', 'glyp-multi_housing', 'glyp-multi_tech_order', 'glyp-mute', 'glyp-navicon', 'glyp-neighborhood', 'glyp-new_location', 'glyp-new_ticket', 'glyp-no_charge', 'glyp-no_service', 'glyp-note', 'glyp-note_access', 'glyp-note_billing', 'glyp-note_collection', 'glyp-note_complaint', 'glyp-note_directions', 'glyp-note_focused', 'glyp-note_office', 'glyp-note_preservice', 'glyp-note_sales', 'glyp-note_service', 'glyp-notification', 'glyp-nuke', 'glyp-number', 'glyp-occupant', 'glyp-office', 'glyp-office_government', 'glyp-office_training', 'glyp-offline_mode', 'glyp-on_demand', 'glyp-on_demand_order', 'glyp-online_mode', 'glyp-open', 'glyp-open_invoice', 'glyp-operations', 'glyp-options', 'glyp-order_actions', 'glyp-order_approved', 'glyp-order_batch', 'glyp-order_editor', 'glyp-order_pending', 'glyp-order_series', 'glyp-order_unapproved', 'glyp-org', 'glyp-org_feature', 'glyp-org_settings', 'glyp-org_structure', 'glyp-org_unit', 'glyp-org_unit_settings', 'glyp-orgs', 'glyp-origin', 'glyp-origin_client', 'glyp-origin_tech', 'glyp-outbox', 'glyp-outlier', 'glyp-outlook', 'glyp-overlap', 'glyp-password', 'glyp-past_due', 'glyp-pause', 'glyp-paused', 'glyp-pay_brackets', 'glyp-payment', 'glyp-payment_ach', 'glyp-payment_application_apply', 'glyp-payment_application_correction', 'glyp-payment_application_initial', 'glyp-payment_application_refund', 'glyp-payment_application_transfer', 'glyp-payment_application_unapply', 'glyp-payment_application_unapply_all', 'glyp-payment_applications', 'glyp-payment_batch', 'glyp-payment_cash', 'glyp-payment_check', 'glyp-payment_coupon', 'glyp-payment_credit', 'glyp-payment_discount', 'glyp-payment_eft', 'glyp-payment_finance_charge', 'glyp-payments', 'glyp-payroll', 'glyp-pdf', 'glyp-pending', 'glyp-periodic', 'glyp-periodic_assessment', 'glyp-permission', 'glyp-pest', 'glyp-pest_ants', 'glyp-pest_bats', 'glyp-pest_bed_bugs', 'glyp-pest_birds', 'glyp-pest_crawling_insects', 'glyp-pest_dermestids', 'glyp-pest_fall_invaders', 'glyp-pest_flying_insects', 'glyp-pest_moles', 'glyp-pest_mosquitoes', 'glyp-pest_nuisance_animals', 'glyp-pest_occasional_invaders', 'glyp-pest_ornamental', 'glyp-pest_ornamental_diseases', 'glyp-pest_roaches', 'glyp-pest_rodents', 'glyp-pest_scorpions', 'glyp-pest_snakes', 'glyp-pest_spiders', 'glyp-pest_stinging_insects', 'glyp-pest_stored_product_pests', 'glyp-pest_ticks_and_fleas', 'glyp-pest_viruses_and_bacteria', 'glyp-pest_weeds', 'glyp-pest_wood_destroyers', 'glyp-phone', 'glyp-pick_date', 'glyp-pick_list', 'glyp-platform_android', 'glyp-platform_ios', 'glyp-platform_macos', 'glyp-platform_windows', 'glyp-play', 'glyp-plus', 'glyp-plus_filled', 'glyp-plus_outline', 'glyp-portal', 'glyp-price_increase', 'glyp-price_increase_alt', 'glyp-pricing', 'glyp-pricing_table', 'glyp-print', 'glyp-privacy', 'glyp-product_sale', 'glyp-production', 'glyp-professional_consultation', 'glyp-profile', 'glyp-program', 'glyp-program_elements', 'glyp-program_initiation', 'glyp-program_order', 'glyp-program_review', 'glyp-promo', 'glyp-proposal', 'glyp-protection', 'glyp-purchase_order', 'glyp-quality', 'glyp-query', 'glyp-radio_checked', 'glyp-radio_empty', 'glyp-reassign', 'glyp-receipt', 'glyp-recent', 'glyp-reciprocity', 'glyp-recommendation', 'glyp-record_details', 'glyp-recurring_revenue', 'glyp-redo', 'glyp-refresh', 'glyp-refund', 'glyp-region', 'glyp-released', 'glyp-remove', 'glyp-renew', 'glyp-renewal', 'glyp-report', 'glyp-required_input', 'glyp-reschedule', 'glyp-restaurant_bar', 'glyp-revenue', 'glyp-review', 'glyp-rfid', 'glyp-ride_along', 'glyp-rodent_exclusion', 'glyp-role_admin', 'glyp-role_customer', 'glyp-role_super', 'glyp-role_tech', 'glyp-roster', 'glyp-route_change', 'glyp-route_optimization', 'glyp-rows', 'glyp-ruby', 'glyp-ruler', 'glyp-sales', 'glyp-sales_contest', 'glyp-sales_pipeline', 'glyp-sales_tax', 'glyp-sales_tax_exemption', 'glyp-schedule_lock', 'glyp-schedule_lock_afternoon', 'glyp-schedule_lock_day', 'glyp-schedule_lock_exact', 'glyp-schedule_lock_four_hour', 'glyp-schedule_lock_morning', 'glyp-schedule_lock_none', 'glyp-schedule_lock_two_day', 'glyp-schedule_lock_two_hour', 'glyp-schedule_lock_week', 'glyp-schedule_tyoe_every', 'glyp-schedule_type_every', 'glyp-schedule_type_none', 'glyp-schedule_type_on_demand', 'glyp-schedule_type_schedule', 'glyp-scheduled', 'glyp-scheduling', 'glyp-school_church', 'glyp-scope', 'glyp-scoreboard', 'glyp-scrape', 'glyp-script', 'glyp-search', 'glyp-seeding', 'glyp-segment', 'glyp-send', 'glyp-sent', 'glyp-sentricon', 'glyp-service_agreement', 'glyp-service_digest', 'glyp-service_form', 'glyp-service_form_new', 'glyp-service_form_settings', 'glyp-service_form_template', 'glyp-service_miscellaneous', 'glyp-service_reminder', 'glyp-service_report', 'glyp-service_request', 'glyp-services', 'glyp-settings', 'glyp-setup', 'glyp-signature', 'glyp-simulator', 'glyp-site_map', 'glyp-site_map_annotation', 'glyp-site_map_chemical', 'glyp-site_map_edit_details', 'glyp-site_map_edit_geo', 'glyp-site_map_equipment', 'glyp-site_map_exterior', 'glyp-site_map_foundation', 'glyp-site_map_icon', 'glyp-site_map_interior', 'glyp-site_map_label', 'glyp-site_map_perimeter', 'glyp-site_map_settings_left', 'glyp-site_map_settings_right', 'glyp-site_map_template', 'glyp-skill', 'glyp-skip', 'glyp-slope', 'glyp-smart_traps', 'glyp-sms', 'glyp-snow_removal', 'glyp-sort', 'glyp-special', 'glyp-split', 'glyp-spp_scorecard', 'glyp-square_edge', 'glyp-start_sheet', 'glyp-start_time', 'glyp-state_ak', 'glyp-state_al', 'glyp-state_ar', 'glyp-state_az', 'glyp-state_ca', 'glyp-state_co', 'glyp-state_ct', 'glyp-state_dc', 'glyp-state_de', 'glyp-state_fl', 'glyp-state_ga', 'glyp-state_hi', 'glyp-state_ia', 'glyp-state_id', 'glyp-state_il', 'glyp-state_in', 'glyp-state_ks', 'glyp-state_ky', 'glyp-state_la', 'glyp-state_ma', 'glyp-state_mb', 'glyp-state_md', 'glyp-state_me', 'glyp-state_mi', 'glyp-state_mn', 'glyp-state_mo', 'glyp-state_ms', 'glyp-state_mt', 'glyp-state_nc', 'glyp-state_nd', 'glyp-state_ne', 'glyp-state_nh', 'glyp-state_nj', 'glyp-state_nm', 'glyp-state_nv', 'glyp-state_ny', 'glyp-state_oh', 'glyp-state_ok', 'glyp-state_or', 'glyp-state_pa', 'glyp-state_ri', 'glyp-state_sc', 'glyp-state_sd', 'glyp-state_tn', 'glyp-state_tx', 'glyp-state_ut', 'glyp-state_va', 'glyp-state_vt', 'glyp-state_wa', 'glyp-state_wi', 'glyp-state_wv', 'glyp-state_wy', 'glyp-statement', 'glyp-states', 'glyp-steps', 'glyp-sticky', 'glyp-stop_time', 'glyp-store_material', 'glyp-store_order', 'glyp-store_order_back_order', 'glyp-store_order_cancelled', 'glyp-store_order_complete', 'glyp-store_order_pending', 'glyp-store_order_pending_return', 'glyp-store_order_pre_order', 'glyp-store_order_returned', 'glyp-stores', 'glyp-styling', 'glyp-subscribe', 'glyp-subscription', 'glyp-supplemental', 'glyp-support', 'glyp-sync', 'glyp-sync_down', 'glyp-sync_up', 'glyp-table', 'glyp-tablet', 'glyp-tablets', 'glyp-tag', 'glyp-tag_manager', 'glyp-tags', 'glyp-task_runs', 'glyp-taxability', 'glyp-tech_cert', 'glyp-tech_cert_type', 'glyp-tech_pest_sightings', 'glyp-technician', 'glyp-technician_approach', 'glyp-template', 'glyp-termite_fumigation', 'glyp-terrier', 'glyp-terrier_hub', 'glyp-terrier_sync', 'glyp-territory', 'glyp-text', 'glyp-thermometer', 'glyp-third_party', 'glyp-third_party_billing', 'glyp-threshold', 'glyp-ticket', 'glyp-ticket_type', 'glyp-tickets', 'glyp-tidy', 'glyp-time', 'glyp-time_entry', 'glyp-timeline', 'glyp-timer', 'glyp-toolbox', 'glyp-tqi', 'glyp-tracker', 'glyp-tracking', 'glyp-training_course', 'glyp-treatment_split', 'glyp-trends', 'glyp-trial', 'glyp-trip_charge', 'glyp-ui_type', 'glyp-unarchive', 'glyp-unassign', 'glyp-unassign_all', 'glyp-unchecked', 'glyp-undo', 'glyp-unfavorite', 'glyp-unit_sweep', 'glyp-unit_tag', 'glyp-university', 'glyp-unlocked', 'glyp-unmute', 'glyp-unscheduled', 'glyp-update_check_positions', 'glyp-upload', 'glyp-user', 'glyp-user_credit', 'glyp-user_tags', 'glyp-users', 'glyp-utility', 'glyp-vacation', 'glyp-vacuum', 'glyp-variant', 'glyp-vendor', 'glyp-versions', 'glyp-video', 'glyp-visible', 'glyp-warehouse', 'glyp-wdi', 'glyp-wdi_az', 'glyp-wdi_ca', 'glyp-wdi_npma', 'glyp-wdi_nv', 'glyp-wdo', 'glyp-web_dusting', 'glyp-website', 'glyp-wildlife', 'glyp-wildlife_exclusion', 'glyp-winter_check', 'glyp-wizard', 'glyp-work', 'glyp-work_class', 'glyp-work_code', 'glyp-work_order', 'glyp-work_production', 'glyp-work_progress', 'glyp-work_yield', 'glyp-year', 'glyp-yield', 'glyp-zip_code', 'glyp-zone_adjacent', 'glyp-zone_check', 'glyp-zone_production', 'glyp-zone_remote', 'glyp-zoom', 'glyp-zoom_fit'] as const
5
+ const names = ['glyp-abacus', 'glyp-account', 'glyp-accounts_payable', 'glyp-accounts_receivable', 'glyp-action_log', 'glyp-activate', 'glyp-activate_user', 'glyp-active', 'glyp-activity_high', 'glyp-activity_low', 'glyp-activity_medium', 'glyp-activity_none', 'glyp-address1', 'glyp-adjustment', 'glyp-admin', 'glyp-administration', 'glyp-affinity', 'glyp-agency', 'glyp-air_freshener', 'glyp-alert', 'glyp-align_horizontal', 'glyp-align_vertical', 'glyp-anchor', 'glyp-android_phone', 'glyp-android_tablet', 'glyp-annotation', 'glyp-announcement', 'glyp-app_update', 'glyp-append_selection', 'glyp-appointment', 'glyp-appointment_setup', 'glyp-archive', 'glyp-arrow_down', 'glyp-arrow_left', 'glyp-arrow_right', 'glyp-arrow_transfer', 'glyp-arrow_up', 'glyp-ascending', 'glyp-assign', 'glyp-assign_all', 'glyp-attachment', 'glyp-audit', 'glyp-audit_analyzer', 'glyp-audits', 'glyp-autopay', 'glyp-autopay_bank_account', 'glyp-availability', 'glyp-background_batch', 'glyp-background_job', 'glyp-background_style', 'glyp-bait_for_demolition', 'glyp-bar_chart', 'glyp-barcode', 'glyp-bat_exclusion', 'glyp-bed_bug_fumigation', 'glyp-begin_service', 'glyp-belongs_to', 'glyp-billing', 'glyp-billing_office', 'glyp-billing_terms', 'glyp-billto', 'glyp-bioremediation', 'glyp-bird_exclusion', 'glyp-black_light', 'glyp-bookmark', 'glyp-branch', 'glyp-branch_bats', 'glyp-branch_birds', 'glyp-branch_general', 'glyp-branch_irrigation', 'glyp-branch_landscape', 'glyp-branch_multi_housing', 'glyp-branch_office', 'glyp-branch_orders', 'glyp-branch_residential', 'glyp-branch_sales', 'glyp-branch_termites', 'glyp-branch_weeds', 'glyp-branch_wildlife', 'glyp-build', 'glyp-business', 'glyp-business_cert', 'glyp-business_cert_type', 'glyp-calculate', 'glyp-calendar', 'glyp-calendar_branch', 'glyp-calendar_map', 'glyp-calendar_rolling', 'glyp-calendar_share', 'glyp-calendar_snap', 'glyp-calendar_style', 'glyp-california', 'glyp-call', 'glyp-callback', 'glyp-camera', 'glyp-canada', 'glyp-cancellation', 'glyp-cancelled', 'glyp-card_american_express', 'glyp-card_discover', 'glyp-card_mastercard', 'glyp-card_visa', 'glyp-catalog', 'glyp-category', 'glyp-caught', 'glyp-cellulose_debris', 'glyp-cert', 'glyp-cert_timeline', 'glyp-cert_type', 'glyp-certificate', 'glyp-certified_summary', 'glyp-certs', 'glyp-ceu', 'glyp-check_all', 'glyp-check_in', 'glyp-check_in_service', 'glyp-checked', 'glyp-checked_check_all', 'glyp-checked_empty', 'glyp-checkmark', 'glyp-checkmark_filled', 'glyp-checkout', 'glyp-chemical', 'glyp-chemical_authorization', 'glyp-chemical_backpack_sprayer', 'glyp-chemical_biological_controls', 'glyp-chemical_disinfectant', 'glyp-chemical_fertilizer', 'glyp-chemical_flying_insect_bait', 'glyp-chemical_fumigants', 'glyp-chemical_insect_aerosol', 'glyp-chemical_insect_bait', 'glyp-chemical_insect_dust', 'glyp-chemical_insect_granules', 'glyp-chemical_insect_spray', 'glyp-chemical_label', 'glyp-chemical_mole_bait', 'glyp-chemical_pest_bird_control', 'glyp-chemical_pheromone_products', 'glyp-chemical_power_sprayer', 'glyp-chemical_rodent_attractant', 'glyp-chemical_rodenticide_block', 'glyp-chemical_rodenticide_non_block', 'glyp-chemical_sds', 'glyp-chemical_tree_products', 'glyp-chemical_weed_control', 'glyp-chemical_wildlife_repellents', 'glyp-chemical_wildlife_toxicants', 'glyp-chevron_down', 'glyp-chevron_left', 'glyp-chevron_right', 'glyp-chevron_up', 'glyp-circle', 'glyp-city', 'glyp-click', 'glyp-client_actions', 'glyp-client_pest_sightings', 'glyp-client_required', 'glyp-clipboard', 'glyp-close', 'glyp-close_empty', 'glyp-cloud', 'glyp-clyp_environment', 'glyp-clyp_settings', 'glyp-clypboard', 'glyp-clypedia', 'glyp-clypmart', 'glyp-code', 'glyp-code_details', 'glyp-collections', 'glyp-color', 'glyp-columns', 'glyp-comment', 'glyp-comment_filled', 'glyp-comments', 'glyp-commission_origin', 'glyp-commissions', 'glyp-compact', 'glyp-company_setup', 'glyp-compass', 'glyp-complete', 'glyp-complete_certificate', 'glyp-complete_enrollment', 'glyp-compressed', 'glyp-condition_appliance_machinery', 'glyp-condition_customer', 'glyp-condition_device', 'glyp-condition_door_window', 'glyp-condition_dumpster', 'glyp-condition_entry_point', 'glyp-condition_evidence', 'glyp-condition_exclusion', 'glyp-condition_exterior', 'glyp-condition_interior', 'glyp-condition_plumbing', 'glyp-condition_prep_storage', 'glyp-condition_rodent_evidence', 'glyp-condition_rodent_exclusion', 'glyp-condition_roof_ceiling', 'glyp-condition_structure', 'glyp-condition_supplies', 'glyp-condition_termites', 'glyp-condition_ventilation', 'glyp-condition_wall_floor', 'glyp-condition_wildlife', 'glyp-conditions', 'glyp-consolidate', 'glyp-construction', 'glyp-continuing_ed', 'glyp-contract', 'glyp-contract_cancellation', 'glyp-contract_overview', 'glyp-conversation', 'glyp-copesan', 'glyp-copy', 'glyp-county', 'glyp-credit_memo', 'glyp-credit_non_revenue', 'glyp-credit_production', 'glyp-credit_prospect', 'glyp-credit_sales_bonus', 'glyp-crew', 'glyp-cumulative', 'glyp-curriculum', 'glyp-cursor', 'glyp-custom_form', 'glyp-customer', 'glyp-customer_service', 'glyp-damage', 'glyp-dashboard', 'glyp-data_dive', 'glyp-data_dive_query', 'glyp-data_dives', 'glyp-database', 'glyp-deactivate_user', 'glyp-dead_animal_removal', 'glyp-default', 'glyp-delete', 'glyp-demo_mode', 'glyp-descending', 'glyp-design', 'glyp-desktop', 'glyp-detail_report', 'glyp-developer', 'glyp-device', 'glyp-device_sync', 'glyp-differential', 'glyp-disable', 'glyp-disabled_filled', 'glyp-distribute_horizontal', 'glyp-distribute_vertical', 'glyp-division', 'glyp-document', 'glyp-documentation', 'glyp-documents', 'glyp-download', 'glyp-driving', 'glyp-duplicate', 'glyp-duplicate_location', 'glyp-duration', 'glyp-earth_to_wood', 'glyp-edit', 'glyp-email', 'glyp-employment', 'glyp-entertainment_public_venues', 'glyp-equipment_bear_trap', 'glyp-equipment_flying_insect_bait_station', 'glyp-equipment_flying_insect_light_trap', 'glyp-equipment_insect_monitor', 'glyp-equipment_lethal_animal_trap', 'glyp-equipment_live_animal_trap', 'glyp-equipment_live_rodent_trap', 'glyp-equipment_maintenance', 'glyp-equipment_map_viewer', 'glyp-equipment_maps', 'glyp-equipment_mosquito_trap', 'glyp-equipment_other', 'glyp-equipment_pheromone_trap', 'glyp-equipment_pick_up', 'glyp-equipment_rodent_bait', 'glyp-equipment_rodent_trap', 'glyp-equipment_termite_bait_station', 'glyp-equipment_termite_monitor', 'glyp-escape', 'glyp-exam', 'glyp-exams', 'glyp-exclusion', 'glyp-existing_location', 'glyp-expand', 'glyp-expired', 'glyp-expiring', 'glyp-exterior', 'glyp-extras', 'glyp-facebook', 'glyp-family', 'glyp-farm', 'glyp-farm_grain_seed', 'glyp-fast_and_frequent', 'glyp-favorite', 'glyp-features', 'glyp-feedback', 'glyp-field_guide', 'glyp-field_training', 'glyp-file_blank', 'glyp-file_html', 'glyp-file_image', 'glyp-file_pdf', 'glyp-file_printable', 'glyp-file_spreadsheet', 'glyp-file_text', 'glyp-filter', 'glyp-finance', 'glyp-finding', 'glyp-fogging', 'glyp-folder', 'glyp-followup', 'glyp-food_bev_pharma', 'glyp-formula', 'glyp-fuel', 'glyp-fumigation', 'glyp-function', 'glyp-function_aggregate', 'glyp-function_time', 'glyp-gain_loss', 'glyp-generate_invoices', 'glyp-generate_orders', 'glyp-geo', 'glyp-geo_heat_map', 'glyp-geoboard', 'glyp-google', 'glyp-google_calendar', 'glyp-government', 'glyp-grain_seed', 'glyp-grid', 'glyp-grouped', 'glyp-grouping_combine', 'glyp-grouping_separate', 'glyp-guide', 'glyp-has_many', 'glyp-health_care', 'glyp-heat_map', 'glyp-heat_treatment', 'glyp-help', 'glyp-hidden', 'glyp-hint', 'glyp-home', 'glyp-honeybee_removal', 'glyp-housing', 'glyp-icon', 'glyp-identifier', 'glyp-image', 'glyp-images', 'glyp-import', 'glyp-import_certs', 'glyp-import_data', 'glyp-import_users', 'glyp-in_progress', 'glyp-inbox', 'glyp-incomplete_certificate', 'glyp-industrial', 'glyp-info', 'glyp-information_technology', 'glyp-inspect_map', 'glyp-inspections', 'glyp-insulation', 'glyp-integrations', 'glyp-interior', 'glyp-invoice', 'glyp-invoice_charge', 'glyp-invoice_credit', 'glyp-invoice_paid', 'glyp-invoice_review', 'glyp-ipad', 'glyp-iphone', 'glyp-irrigation_install', 'glyp-irrigation_maintenance', 'glyp-items', 'glyp-job', 'glyp-job_plan', 'glyp-job_plan_history', 'glyp-job_plan_run', 'glyp-job_plan_scheduled', 'glyp-job_plan_single_click', 'glyp-job_plan_step_by_step', 'glyp-join', 'glyp-join_inner', 'glyp-join_left', 'glyp-jump', 'glyp-justice', 'glyp-k9_inspection', 'glyp-key', 'glyp-label_bottom', 'glyp-label_left', 'glyp-label_right', 'glyp-label_top', 'glyp-labor', 'glyp-landscape', 'glyp-landscape_maintenance', 'glyp-laptop', 'glyp-lead', 'glyp-lead_listing', 'glyp-lead_source', 'glyp-leads_report', 'glyp-learning_hub', 'glyp-ledger', 'glyp-legal', 'glyp-license', 'glyp-lifetime', 'glyp-limit', 'glyp-line_cap_arrow', 'glyp-line_cap_bar', 'glyp-line_cap_circle', 'glyp-line_cap_none', 'glyp-line_caps', 'glyp-line_style', 'glyp-link', 'glyp-list', 'glyp-location', 'glyp-location_charge', 'glyp-location_credit', 'glyp-location_import', 'glyp-location_integration', 'glyp-location_kind', 'glyp-location_message', 'glyp-location_origin', 'glyp-location_seed', 'glyp-location_tags', 'glyp-locations', 'glyp-locked', 'glyp-lodging', 'glyp-log_in', 'glyp-log_out', 'glyp-log_report', 'glyp-logbook', 'glyp-logbook_documents', 'glyp-lot_number', 'glyp-lycensed_simulator', 'glyp-manager', 'glyp-manual', 'glyp-map', 'glyp-markdown', 'glyp-market', 'glyp-materials', 'glyp-mattress_encasement', 'glyp-merge', 'glyp-message_billing', 'glyp-message_collections', 'glyp-message_complaint', 'glyp-message_misc', 'glyp-message_technician', 'glyp-messages', 'glyp-minus_outline', 'glyp-misc', 'glyp-miscellaneous', 'glyp-missing', 'glyp-moisture', 'glyp-move_order', 'glyp-mowing', 'glyp-multi_housing', 'glyp-multi_tech_order', 'glyp-mute', 'glyp-navicon', 'glyp-neighborhood', 'glyp-new_location', 'glyp-new_ticket', 'glyp-no_charge', 'glyp-no_service', 'glyp-note', 'glyp-note_access', 'glyp-note_billing', 'glyp-note_collection', 'glyp-note_complaint', 'glyp-note_directions', 'glyp-note_focused', 'glyp-note_office', 'glyp-note_preservice', 'glyp-note_sales', 'glyp-note_service', 'glyp-notification', 'glyp-nuke', 'glyp-number', 'glyp-occupant', 'glyp-office', 'glyp-office_government', 'glyp-office_training', 'glyp-offline_mode', 'glyp-on_demand', 'glyp-on_demand_order', 'glyp-online_mode', 'glyp-open', 'glyp-open_invoice', 'glyp-operations', 'glyp-options', 'glyp-order_actions', 'glyp-order_approved', 'glyp-order_batch', 'glyp-order_editor', 'glyp-order_pending', 'glyp-order_series', 'glyp-order_unapproved', 'glyp-org', 'glyp-org_feature', 'glyp-org_settings', 'glyp-org_structure', 'glyp-org_unit', 'glyp-org_unit_settings', 'glyp-orgs', 'glyp-origin', 'glyp-origin_client', 'glyp-origin_tech', 'glyp-outbox', 'glyp-outlier', 'glyp-outlook', 'glyp-overlap', 'glyp-password', 'glyp-past_due', 'glyp-pause', 'glyp-paused', 'glyp-pay_brackets', 'glyp-payment', 'glyp-payment_ach', 'glyp-payment_application_apply', 'glyp-payment_application_correction', 'glyp-payment_application_initial', 'glyp-payment_application_refund', 'glyp-payment_application_transfer', 'glyp-payment_application_unapply', 'glyp-payment_application_unapply_all', 'glyp-payment_applications', 'glyp-payment_batch', 'glyp-payment_cash', 'glyp-payment_check', 'glyp-payment_coupon', 'glyp-payment_credit', 'glyp-payment_discount', 'glyp-payment_eft', 'glyp-payment_finance_charge', 'glyp-payments', 'glyp-payroll', 'glyp-pdf', 'glyp-pending', 'glyp-periodic', 'glyp-periodic_assessment', 'glyp-permission', 'glyp-pest', 'glyp-pest_ants', 'glyp-pest_bats', 'glyp-pest_bed_bugs', 'glyp-pest_birds', 'glyp-pest_crawling_insects', 'glyp-pest_dermestids', 'glyp-pest_fall_invaders', 'glyp-pest_flying_insects', 'glyp-pest_moles', 'glyp-pest_mosquitoes', 'glyp-pest_nuisance_animals', 'glyp-pest_occasional_invaders', 'glyp-pest_ornamental', 'glyp-pest_ornamental_diseases', 'glyp-pest_roaches', 'glyp-pest_rodents', 'glyp-pest_scorpions', 'glyp-pest_snakes', 'glyp-pest_spiders', 'glyp-pest_stinging_insects', 'glyp-pest_stored_product_pests', 'glyp-pest_ticks_and_fleas', 'glyp-pest_viruses_and_bacteria', 'glyp-pest_weeds', 'glyp-pest_wood_destroyers', 'glyp-phone', 'glyp-pick_date', 'glyp-pick_list', 'glyp-platform_android', 'glyp-platform_ios', 'glyp-platform_macos', 'glyp-platform_windows', 'glyp-play', 'glyp-plus', 'glyp-plus_filled', 'glyp-plus_outline', 'glyp-portal', 'glyp-price_increase', 'glyp-price_increase_alt', 'glyp-pricing', 'glyp-pricing_table', 'glyp-print', 'glyp-privacy', 'glyp-product_sale', 'glyp-production', 'glyp-professional_consultation', 'glyp-profile', 'glyp-program', 'glyp-program_elements', 'glyp-program_initiation', 'glyp-program_order', 'glyp-program_review', 'glyp-promo', 'glyp-proposal', 'glyp-protection', 'glyp-purchase_order', 'glyp-quality', 'glyp-query', 'glyp-radio_checked', 'glyp-radio_empty', 'glyp-reassign', 'glyp-receipt', 'glyp-recent', 'glyp-reciprocity', 'glyp-recommendation', 'glyp-record_details', 'glyp-recurring_revenue', 'glyp-redo', 'glyp-refresh', 'glyp-refund', 'glyp-region', 'glyp-released', 'glyp-remove', 'glyp-renew', 'glyp-renewal', 'glyp-report', 'glyp-required_input', 'glyp-reschedule', 'glyp-restaurant_bar', 'glyp-revenue', 'glyp-review', 'glyp-rfid', 'glyp-ride_along', 'glyp-rodent_exclusion', 'glyp-role_admin', 'glyp-role_customer', 'glyp-role_super', 'glyp-role_tech', 'glyp-roster', 'glyp-route_change', 'glyp-route_optimization', 'glyp-rows', 'glyp-ruby', 'glyp-ruler', 'glyp-sales', 'glyp-sales_contest', 'glyp-sales_pipeline', 'glyp-sales_tax', 'glyp-sales_tax_exemption', 'glyp-schedule_lock', 'glyp-schedule_lock_afternoon', 'glyp-schedule_lock_day', 'glyp-schedule_lock_exact', 'glyp-schedule_lock_four_hour', 'glyp-schedule_lock_morning', 'glyp-schedule_lock_none', 'glyp-schedule_lock_two_day', 'glyp-schedule_lock_two_hour', 'glyp-schedule_lock_week', 'glyp-schedule_tyoe_every', 'glyp-schedule_type_every', 'glyp-schedule_type_none', 'glyp-schedule_type_on_demand', 'glyp-schedule_type_schedule', 'glyp-scheduled', 'glyp-scheduling', 'glyp-scheduling_comment', 'glyp-school_church', 'glyp-scope', 'glyp-scoreboard', 'glyp-scrape', 'glyp-script', 'glyp-search', 'glyp-seeding', 'glyp-segment', 'glyp-send', 'glyp-sent', 'glyp-sentricon', 'glyp-service_agreement', 'glyp-service_digest', 'glyp-service_form', 'glyp-service_form_new', 'glyp-service_form_settings', 'glyp-service_form_template', 'glyp-service_miscellaneous', 'glyp-service_reminder', 'glyp-service_report', 'glyp-service_request', 'glyp-services', 'glyp-settings', 'glyp-setup', 'glyp-signature', 'glyp-simulator', 'glyp-site_map', 'glyp-site_map_annotation', 'glyp-site_map_chemical', 'glyp-site_map_edit_details', 'glyp-site_map_edit_geo', 'glyp-site_map_equipment', 'glyp-site_map_exterior', 'glyp-site_map_foundation', 'glyp-site_map_icon', 'glyp-site_map_interior', 'glyp-site_map_label', 'glyp-site_map_perimeter', 'glyp-site_map_settings_left', 'glyp-site_map_settings_right', 'glyp-site_map_template', 'glyp-skill', 'glyp-skip', 'glyp-slope', 'glyp-smart_traps', 'glyp-sms', 'glyp-snow_removal', 'glyp-sort', 'glyp-special', 'glyp-split', 'glyp-spp_scorecard', 'glyp-square_edge', 'glyp-start_sheet', 'glyp-start_time', 'glyp-state_ak', 'glyp-state_al', 'glyp-state_ar', 'glyp-state_az', 'glyp-state_ca', 'glyp-state_co', 'glyp-state_ct', 'glyp-state_dc', 'glyp-state_de', 'glyp-state_fl', 'glyp-state_ga', 'glyp-state_hi', 'glyp-state_ia', 'glyp-state_id', 'glyp-state_il', 'glyp-state_in', 'glyp-state_ks', 'glyp-state_ky', 'glyp-state_la', 'glyp-state_ma', 'glyp-state_mb', 'glyp-state_md', 'glyp-state_me', 'glyp-state_mi', 'glyp-state_mn', 'glyp-state_mo', 'glyp-state_ms', 'glyp-state_mt', 'glyp-state_nc', 'glyp-state_nd', 'glyp-state_ne', 'glyp-state_nh', 'glyp-state_nj', 'glyp-state_nm', 'glyp-state_nv', 'glyp-state_ny', 'glyp-state_oh', 'glyp-state_ok', 'glyp-state_or', 'glyp-state_pa', 'glyp-state_ri', 'glyp-state_sc', 'glyp-state_sd', 'glyp-state_tn', 'glyp-state_tx', 'glyp-state_ut', 'glyp-state_va', 'glyp-state_vt', 'glyp-state_wa', 'glyp-state_wi', 'glyp-state_wv', 'glyp-state_wy', 'glyp-statement', 'glyp-states', 'glyp-steps', 'glyp-sticky', 'glyp-stop_time', 'glyp-store_material', 'glyp-store_order', 'glyp-store_order_back_order', 'glyp-store_order_cancelled', 'glyp-store_order_complete', 'glyp-store_order_pending', 'glyp-store_order_pending_return', 'glyp-store_order_pre_order', 'glyp-store_order_returned', 'glyp-stores', 'glyp-styling', 'glyp-subscribe', 'glyp-subscription', 'glyp-supplemental', 'glyp-support', 'glyp-sync', 'glyp-sync_down', 'glyp-sync_up', 'glyp-table', 'glyp-tablet', 'glyp-tablets', 'glyp-tactacam', 'glyp-tag', 'glyp-tag_manager', 'glyp-tags', 'glyp-task_runs', 'glyp-taxability', 'glyp-tech_cert', 'glyp-tech_cert_type', 'glyp-tech_pest_sightings', 'glyp-technician', 'glyp-technician_approach', 'glyp-template', 'glyp-termite_fumigation', 'glyp-terrier', 'glyp-terrier_hub', 'glyp-terrier_sync', 'glyp-territory', 'glyp-text', 'glyp-thermometer', 'glyp-third_party', 'glyp-third_party_billing', 'glyp-threshold', 'glyp-ticket', 'glyp-ticket_type', 'glyp-tickets', 'glyp-tidy', 'glyp-time', 'glyp-time_entry', 'glyp-timeline', 'glyp-timer', 'glyp-toolbox', 'glyp-tqi', 'glyp-tracker', 'glyp-tracking', 'glyp-training_course', 'glyp-treatment_split', 'glyp-trends', 'glyp-trial', 'glyp-trip_charge', 'glyp-ui_type', 'glyp-unarchive', 'glyp-unassign', 'glyp-unassign_all', 'glyp-unchecked', 'glyp-undo', 'glyp-unfavorite', 'glyp-unit_sweep', 'glyp-unit_tag', 'glyp-university', 'glyp-unlocked', 'glyp-unmute', 'glyp-unscheduled', 'glyp-update_check_positions', 'glyp-upload', 'glyp-user', 'glyp-user_credit', 'glyp-user_tags', 'glyp-users', 'glyp-utility', 'glyp-vacation', 'glyp-vacuum', 'glyp-variant', 'glyp-vendor', 'glyp-versions', 'glyp-video', 'glyp-visible', 'glyp-warehouse', 'glyp-wdi', 'glyp-wdi_az', 'glyp-wdi_ca', 'glyp-wdi_npma', 'glyp-wdi_nv', 'glyp-wdo', 'glyp-web_dusting', 'glyp-website', 'glyp-wildlife', 'glyp-wildlife_exclusion', 'glyp-winter_check', 'glyp-wizard', 'glyp-work', 'glyp-work_class', 'glyp-work_code', 'glyp-work_order', 'glyp-work_production', 'glyp-work_progress', 'glyp-work_yield', 'glyp-year', 'glyp-yield', 'glyp-zip_code', 'glyp-zone_adjacent', 'glyp-zone_check', 'glyp-zone_production', 'glyp-zone_remote', 'glyp-zoom', 'glyp-zoom_fit'] as const
6
6
 
7
7
  /**
8
8
  * Format a glyp name so that it's suitable to show to the user.
@@ -6,7 +6,12 @@ const log = new Logger('LoadOnScrollPlugin')
6
6
 
7
7
  type MarginUnit = '%' | 'px'
8
8
  type MarginLength = `${number}${MarginUnit}`
9
- type MarginString = `${MarginLength} ${MarginLength} ${MarginLength} ${MarginLength}`
9
+ type MarginString = MarginLength | `${MarginLength} ${MarginLength}` | `${MarginLength} ${MarginLength} ${MarginLength} ${MarginLength}`
10
+
11
+ /**
12
+ * Define the behavior of the load on scroll functionality.
13
+ * For information about the intersect parameters, see https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
14
+ */
10
15
  export type LoadOnScrollOptions<TState> = {
11
16
  // The name of the collection to load on scroll
12
17
  collectionName: string
@@ -14,15 +19,14 @@ export type LoadOnScrollOptions<TState> = {
14
19
  collectionPartType: PartConstructor<Part<TState>, TState>
15
20
  // Called to load the next state. If undefined is returned, no more states will be loaded
16
21
  loadNextStates: (existingStates: TState[]) => Promise<TState[] | undefined>
22
+ // The root element whose viewport is scrolling. If not provided, the document viewport is used.
23
+ // Usually not required, but if using intersectThreshold on a scrollable element, might be necessary.
24
+ intersectRootSelector: string
17
25
  // Percentage of the last item in the collection that must be visible before the next state is loaded.
18
- // If not provided, a default value of 25% is used. Must be between 0 and 1
19
- // See: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#threshold
26
+ // If not provided, a default value of 25% is used. Must be between 0 and 1.
20
27
  intersectThreshold?: number
21
- // Defines a margin on the document that shifts when the last element is intersecting.
22
- // See: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin
28
+ // Expands the intersection region by the specified margin.
23
29
  intersectRootMargin?: MarginString
24
- // Configure the intersection observer
25
- configureIntersect?: (builder: IntersectionObserverBuilder, partElem: HTMLElement) => any
26
30
  }
27
31
 
28
32
  /**
@@ -50,17 +54,20 @@ export default class LoadOnScrollPlugin<TState> extends PartPlugin<LoadOnScrollO
50
54
  return
51
55
  }
52
56
 
53
- this.observer?.disconnect()
54
- if (this.observer) {
55
- this.observer.disconnect()
56
- } else {
57
- const builder = new IntersectionObserverBuilder(this.onIntersect.bind(this))
58
- builder.rootMargin(this.state.intersectRootMargin ?? '0px 0px 0px 0px')
59
- builder.threshold(this.state.intersectThreshold ?? 0.25)
60
- if (this.state.configureIntersect) {
61
- this.state.configureIntersect(builder, elem)
57
+ const intersectRoot = (this.state.intersectRootSelector) ? elem.querySelector(this.state.intersectRootSelector) : null
58
+
59
+ // We may have re-rendered since the last update, in which case the intersectionRoot will be a new element.
60
+ // In that case, recreate observer with the new root.
61
+ if (!this.observer || this.observer.root !== intersectRoot) {
62
+ const config: IntersectionObserverInit = {
63
+ root: intersectRoot,
64
+ rootMargin: this.state.intersectRootMargin ?? '0px 0px 0px 0px',
65
+ threshold: this.state.intersectThreshold ?? 0.25,
62
66
  }
63
- this.observer = builder.build()
67
+ this.observer?.disconnect()
68
+ this.observer = new IntersectionObserver(this.onIntersect.bind(this), config)
69
+ } else {
70
+ this.observer.disconnect()
64
71
  }
65
72
  this.observer.observe(lastElement)
66
73
  }
@@ -88,33 +95,4 @@ export default class LoadOnScrollPlugin<TState> extends PartPlugin<LoadOnScrollO
88
95
  this.isLoading = false
89
96
  this.part.stale()
90
97
  }
91
- }
92
-
93
- export class IntersectionObserverBuilder {
94
- private readonly callback: IntersectionObserverCallback
95
- private readonly config: IntersectionObserverInit
96
-
97
- constructor(callback: IntersectionObserverCallback) {
98
- this.callback = callback
99
- this.config = {}
100
- }
101
-
102
- root(root: Element | Document | undefined): IntersectionObserverBuilder {
103
- this.config.root = root
104
- return this
105
- }
106
-
107
- rootMargin(margin: MarginString): IntersectionObserverBuilder {
108
- this.config.rootMargin = margin
109
- return this
110
- }
111
-
112
- threshold(threshold: number): IntersectionObserverBuilder {
113
- this.config.threshold = threshold
114
- return this
115
- }
116
-
117
- build() : IntersectionObserver {
118
- return new IntersectionObserver(this.callback, this.config)
119
- }
120
98
  }
package/terrier/tabs.ts CHANGED
@@ -1,8 +1,9 @@
1
- import {Logger} from "tuff-core/logging"
1
+ import { Logger } from "tuff-core/logging"
2
2
  import Messages from "tuff-core/messages"
3
- import {Part, PartParent, PartTag, StatelessPart} from "tuff-core/parts"
3
+ import { Part, PartParent, PartTag, StatelessPart } from "tuff-core/parts"
4
4
  import TerrierPart from "./parts/terrier-part"
5
- import {Action, IconName, Packet} from "./theme"
5
+ import { Action, IconName, Packet } from "./theme"
6
+ import SortablePlugin from "tuff-sortable/sortable-plugin"
6
7
 
7
8
  const log = new Logger("Tabs")
8
9
 
@@ -31,7 +32,8 @@ export type TabSide = typeof Sides[number]
31
32
 
32
33
  export type TabContainerState = {
33
34
  side: TabSide
34
- currentTab? : string
35
+ reorderable?: boolean
36
+ currentTab?: string
35
37
  }
36
38
 
37
39
  export class TabContainerPart extends TerrierPart<TabContainerState> {
@@ -39,8 +41,11 @@ export class TabContainerPart extends TerrierPart<TabContainerState> {
39
41
  private tabs = {} as Record<string, TabDefinition>
40
42
  changeTabKey = Messages.typedKey<{ tabKey: string }>()
41
43
  changeSideKey = Messages.typedKey<{ side: TabSide }>()
44
+ tabsModifiedKey = Messages.typedKey<{ newOrder: string[] }>()
42
45
 
43
46
  async init() {
47
+ this.state = Object.assign({ reorderable: false }, this.state)
48
+
44
49
  this.onClick(this.changeTabKey, m => {
45
50
  log.info(`Clicked on tab ${m.data.tabKey}`)
46
51
  this.showTab(m.data.tabKey)
@@ -51,6 +56,23 @@ export class TabContainerPart extends TerrierPart<TabContainerState> {
51
56
  this.state.side = m.data.side
52
57
  this.dirty()
53
58
  })
59
+
60
+ if (this.state.reorderable) {
61
+ this.makePlugin(SortablePlugin, {
62
+ zoneClass: 'tt-tab-list',
63
+ targetClass: 'tab',
64
+ onSorted: (_, evt) => {
65
+ this.onTabsModified(evt.toChildren)
66
+ }
67
+ })
68
+ }
69
+ }
70
+
71
+ onTabsModified(tabElementsMaybe?: HTMLElement[]) {
72
+ const tabElements = tabElementsMaybe ??
73
+ Array.from(this.element?.querySelectorAll('.tt-tab-list') ?? [])
74
+ const newOrder = tabElements.map(tabElement => tabElement.dataset.key)
75
+ this.emitMessage(this.tabsModifiedKey, { newOrder })
54
76
  }
55
77
 
56
78
  /**
@@ -65,7 +87,10 @@ export class TabContainerPart extends TerrierPart<TabContainerState> {
65
87
  state: InferredPartStateType
66
88
  ): PartType {
67
89
  const existingTab = this.tabs[tab.key] ?? {}
68
- this.tabs[tab.key] = Object.assign(existingTab, {state: 'enabled'}, tab)
90
+ this.tabs[tab.key] = Object.assign(existingTab, {
91
+ state: 'enabled',
92
+ }, tab)
93
+ this.onTabsModified()
69
94
  const part = this.makePart(constructor, state)
70
95
  existingTab.part = part
71
96
  this.dirty()
@@ -89,16 +114,14 @@ export class TabContainerPart extends TerrierPart<TabContainerState> {
89
114
  */
90
115
  removeTab(key: string) {
91
116
  const tab = this.tabs[key]
92
- if (tab) {
93
- log.info(`Removing tab ${key}`, tab)
94
- delete this.tabs[key]
95
- this.removeChild(tab.part)
96
- this.state.currentTab = undefined
97
- this.dirty()
98
- }
99
- else {
100
- log.warn(`No tab ${key} to remove!`)
101
- }
117
+ if (!tab) return log.warn(`No tab ${key} to remove!`)
118
+
119
+ log.info(`Removing tab ${key}`, tab)
120
+ delete this.tabs[key]
121
+ this.removeChild(tab.part)
122
+ this.onTabsModified()
123
+ this.state.currentTab = undefined
124
+ this.dirty()
102
125
  }
103
126
 
104
127
  /**
@@ -149,38 +172,32 @@ export class TabContainerPart extends TerrierPart<TabContainerState> {
149
172
  parent.div('tt-tab-container', this.state.side, container => {
150
173
  container.div('.tt-flex.tt-tab-list', tabList => {
151
174
  if (this._beforeActions.length) {
152
- this.theme.renderActions(tabList, this._beforeActions, {defaultClass: 'action'})
175
+ this.theme.renderActions(tabList, this._beforeActions, { defaultClass: 'action' })
153
176
  }
154
177
  for (const tab of Object.values(this.tabs)) {
155
178
  if (tab.state == 'hidden') continue
156
179
 
157
180
  tabList.a('.tab', a => {
181
+ a.attrs({ draggable: this.state.reorderable })
182
+ a.data({ key: tab.key })
158
183
  a.class(tab.state || 'enabled')
159
- if (tab.key === currentTabKey) {
160
- a.class('active')
161
- }
162
- if (tab.icon) {
163
- this.theme.renderIcon(a, tab.icon)
164
- }
165
- a.span({text: tab.title})
166
- a.emitClick(this.changeTabKey, {tabKey: tab.key})
167
- if (tab.click) {
168
- a.emitClick(tab.click.key, tab.click.data || {})
169
- }
184
+ if (tab.key === currentTabKey) a.class('active')
185
+ if (tab.icon) this.theme.renderIcon(a, tab.icon)
186
+ a.span({ text: tab.title })
187
+ a.emitClick(this.changeTabKey, { tabKey: tab.key })
188
+ if (tab.click) a.emitClick(tab.click.key, tab.click.data || {})
170
189
  })
171
190
  }
172
191
  if (this._afterActions.length) {
173
192
  tabList.div('.spacer')
174
- this.theme.renderActions(tabList, this._afterActions, {defaultClass: 'action'})
193
+ this.theme.renderActions(tabList, this._afterActions, { defaultClass: 'action' })
175
194
  }
176
195
  })
177
196
 
178
197
  if (currentTabKey) {
179
198
  const currentTab = this.tabs[currentTabKey]
180
199
  container.div('.tt-tab-content', panel => {
181
- if (currentTab.classes?.length) {
182
- panel.class(...currentTab.classes)
183
- }
200
+ if (currentTab.classes?.length) panel.class(...currentTab.classes)
184
201
  panel.part(currentTab.part as StatelessPart)
185
202
  })
186
203
  }