myio-js-library 0.1.558 → 0.1.559

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.
package/dist/index.cjs CHANGED
@@ -908,7 +908,7 @@ module.exports = __toCommonJS(src_exports);
908
908
  // package.json
909
909
  var package_default = {
910
910
  name: "myio-js-library",
911
- version: "0.1.558",
911
+ version: "0.1.559",
912
912
  description: "A clean, standalone JS SDK for MYIO projects",
913
913
  license: "MIT",
914
914
  repository: "github:gh-myio/myio-js-library",
@@ -73752,13 +73752,13 @@ function getStyles() {
73752
73752
  color: #ffffff;
73753
73753
  cursor: pointer;
73754
73754
  overflow: hidden;
73755
- box-shadow: 0 0 0 1px rgba(62, 26, 125, 0.5), 0 4px 16px rgba(62, 26, 125, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.2);
73755
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--fc-compare-btn) 50%, transparent), 0 4px 16px color-mix(in srgb, var(--fc-compare-btn) 40%, transparent), inset 0 1px 0 rgba(255, 255, 255, 0.2);
73756
73756
  transition: var(--fc-transition);
73757
73757
  }
73758
73758
 
73759
73759
  .myio-footer-compare:hover {
73760
73760
  background: linear-gradient(135deg, var(--fc-compare-btn-hover) 0%, var(--fc-compare-btn) 100%);
73761
- box-shadow: 0 0 0 1px rgba(62, 26, 125, 0.7), 0 6px 24px rgba(62, 26, 125, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.3);
73761
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--fc-compare-btn) 70%, transparent), 0 6px 24px color-mix(in srgb, var(--fc-compare-btn) 60%, transparent), inset 0 1px 0 rgba(255, 255, 255, 0.3);
73762
73762
  transform: translateY(-2px);
73763
73763
  }
73764
73764
 
@@ -73921,6 +73921,71 @@ function getStyles() {
73921
73921
  text-shadow: none;
73922
73922
  }
73923
73923
 
73924
+ /* ==========================================
73925
+ Brand (paleta do dashboard): fundo s\xF3lido na cor do tema, conte\xFAdo branco
73926
+ ========================================== */
73927
+
73928
+ .myio-footer-component--brand {
73929
+ box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);
73930
+ backdrop-filter: none;
73931
+ -webkit-backdrop-filter: none;
73932
+ }
73933
+ .myio-footer-component--brand .myio-footer-chip {
73934
+ background: var(--fc-chip-bg);
73935
+ box-shadow: none;
73936
+ }
73937
+ .myio-footer-component--brand .myio-footer-chip:hover {
73938
+ background: rgba(255, 255, 255, 0.2);
73939
+ border-color: rgba(255, 255, 255, 0.32);
73940
+ box-shadow: none;
73941
+ transform: translateY(-2px);
73942
+ }
73943
+ .myio-footer-component--brand .myio-footer-chip-remove {
73944
+ background: rgba(255, 255, 255, 0.1);
73945
+ border-color: rgba(255, 255, 255, 0.2);
73946
+ color: rgba(255, 255, 255, 0.85);
73947
+ }
73948
+ .myio-footer-component--brand .myio-footer-chip-remove:hover {
73949
+ background: rgba(255, 107, 107, 0.22);
73950
+ border-color: rgba(255, 107, 107, 0.5);
73951
+ color: #fff;
73952
+ }
73953
+ .myio-footer-component--brand .myio-footer-meta {
73954
+ background: var(--fc-meta-bg);
73955
+ box-shadow: none;
73956
+ cursor: default;
73957
+ }
73958
+ .myio-footer-component--brand .myio-footer-meta-title,
73959
+ .myio-footer-component--brand .myio-footer-totals,
73960
+ .myio-footer-component--brand .myio-footer-chip-name,
73961
+ .myio-footer-component--brand .myio-footer-chip-value {
73962
+ text-shadow: none;
73963
+ }
73964
+ .myio-footer-component--brand .myio-footer-empty {
73965
+ background: var(--fc-empty-bg);
73966
+ color: rgba(255, 255, 255, 0.85);
73967
+ text-shadow: none;
73968
+ animation: none;
73969
+ }
73970
+ .myio-footer-component--brand .myio-footer-clear-btn {
73971
+ background: var(--fc-clear-btn);
73972
+ border-color: rgba(255, 255, 255, 0.24);
73973
+ }
73974
+ .myio-footer-component--brand .myio-footer-clear-btn:hover {
73975
+ background: rgba(255, 255, 255, 0.2);
73976
+ box-shadow: none;
73977
+ }
73978
+ .myio-footer-component--brand .myio-footer-clear-btn:disabled {
73979
+ background: rgba(255, 255, 255, 0.08);
73980
+ border-color: rgba(255, 255, 255, 0.14);
73981
+ color: rgba(255, 255, 255, 0.45);
73982
+ }
73983
+ .myio-footer-component--brand .myio-footer-compare:disabled {
73984
+ background: rgba(255, 255, 255, 0.14);
73985
+ color: rgba(255, 255, 255, 0.55);
73986
+ opacity: 1;
73987
+ }
73988
+
73924
73989
  /* ==========================================
73925
73990
  Responsive
73926
73991
  ========================================== */
@@ -74001,6 +74066,7 @@ var ChipRenderer = class {
74001
74066
  const chip = document.createElement("div");
74002
74067
  chip.className = "myio-footer-chip";
74003
74068
  chip.dataset.entityId = entity.id;
74069
+ chip.dataset.fullName = entity.name || "";
74004
74070
  const content = document.createElement("div");
74005
74071
  content.className = "myio-footer-chip-content";
74006
74072
  const name = document.createElement("span");
@@ -74111,7 +74177,43 @@ var FooterView = class {
74111
74177
  getThemeConfig() {
74112
74178
  const themeConfig = this.theme === "dark" ? this.configTemplate.darkMode : this.configTemplate.lightMode;
74113
74179
  const defaults = this.theme === "dark" ? DEFAULT_DARK_THEME2 : DEFAULT_LIGHT_THEME2;
74114
- return { ...defaults, ...themeConfig };
74180
+ return { ...defaults, ...themeConfig, ...this.getBrandOverrides() };
74181
+ }
74182
+ /** Accent do tema do dashboard (createMyIOTheme → window.MyIOUtils.theme), se houver. */
74183
+ getDashboardAccent() {
74184
+ const theme = window.MyIOUtils?.theme;
74185
+ if (!theme?.accent) return null;
74186
+ return { accent: theme.accent, accentText: theme.accentText || "#ffffff" };
74187
+ }
74188
+ /**
74189
+ * Com paleta do dashboard o footer segue o padrão do dashboard de shopping: fundo sólido na cor
74190
+ * do tema e conteúdo branco (mesma identidade do header/tabs).
74191
+ */
74192
+ getBrandOverrides() {
74193
+ const brand = this.getDashboardAccent();
74194
+ if (!brand) return {};
74195
+ const a = brand.accent;
74196
+ return {
74197
+ primaryColor: a,
74198
+ primaryHoverColor: `color-mix(in srgb, ${a} 85%, #fff)`,
74199
+ primaryDarkColor: `color-mix(in srgb, ${a} 70%, #000)`,
74200
+ backgroundGradientStart: a,
74201
+ backgroundGradientEnd: a,
74202
+ borderTopColor: "rgba(255, 255, 255, 0.18)",
74203
+ textColor: brand.accentText,
74204
+ mutedTextColor: "rgba(255, 255, 255, 0.75)",
74205
+ chipBackgroundColor: "rgba(255, 255, 255, 0.12)",
74206
+ chipBorderColor: "rgba(255, 255, 255, 0.24)",
74207
+ chipTextColor: brand.accentText,
74208
+ compareButtonColor: `color-mix(in srgb, ${a} 55%, #0f172a)`,
74209
+ compareButtonHoverColor: `color-mix(in srgb, ${a} 70%, #0f172a)`,
74210
+ clearButtonColor: "rgba(255, 255, 255, 0.12)",
74211
+ clearButtonTextColor: brand.accentText,
74212
+ metaBackgroundColor: "rgba(255, 255, 255, 0.10)",
74213
+ metaBorderColor: "rgba(255, 255, 255, 0.24)",
74214
+ emptyBackgroundColor: "rgba(255, 255, 255, 0.06)",
74215
+ emptyBorderColor: "rgba(255, 255, 255, 0.35)"
74216
+ };
74115
74217
  }
74116
74218
  /**
74117
74219
  * Apply theme colors as CSS custom properties
@@ -74144,7 +74246,8 @@ var FooterView = class {
74144
74246
  */
74145
74247
  getClassName() {
74146
74248
  const base = "myio-footer-component";
74147
- return this.theme === "light" ? `${base} ${base}--light` : base;
74249
+ const brand = this.getDashboardAccent() ? ` ${base}--brand` : "";
74250
+ return (this.theme === "light" ? `${base} ${base}--light` : base) + brand;
74148
74251
  }
74149
74252
  /**
74150
74253
  * Render the footer component
@@ -74202,6 +74305,27 @@ var FooterView = class {
74202
74305
  * Bind event listeners
74203
74306
  */
74204
74307
  bindEvents() {
74308
+ this.footerEl.addEventListener("mouseover", (e) => {
74309
+ const target = e.target;
74310
+ if (target.closest(".myio-footer-chip-remove")) return hideFooterZoom();
74311
+ const chip = target.closest(".myio-footer-chip");
74312
+ if (chip) {
74313
+ const name = chip.dataset.fullName || chip.querySelector(".myio-footer-chip-name")?.textContent || "";
74314
+ const value = chip.querySelector(".myio-footer-chip-value")?.textContent || "";
74315
+ showFooterZoom(chip, name, value);
74316
+ return;
74317
+ }
74318
+ const meta = target.closest(".myio-footer-meta");
74319
+ if (meta) {
74320
+ showFooterZoom(meta, "Sele\xE7\xE3o", this.totalsEl?.textContent || "");
74321
+ }
74322
+ });
74323
+ this.footerEl.addEventListener("mouseout", (e) => {
74324
+ const to = e.relatedTarget;
74325
+ const from = e.target.closest(".myio-footer-chip, .myio-footer-meta");
74326
+ if (from && to && from.contains(to)) return;
74327
+ hideFooterZoom();
74328
+ });
74205
74329
  window.addEventListener("myio:theme-change", ((e) => {
74206
74330
  this.setTheme(e.detail.mode);
74207
74331
  }));
@@ -74283,6 +74407,7 @@ var FooterView = class {
74283
74407
  */
74284
74408
  renderDock(entities) {
74285
74409
  if (!this.dockEl) return;
74410
+ hideFooterZoom();
74286
74411
  if (entities.length === 0) {
74287
74412
  const emptyEl = this.chipRenderer.createEmptyState(this.emptyMessage);
74288
74413
  this.dockEl.replaceChildren(emptyEl);
@@ -74353,6 +74478,7 @@ var FooterView = class {
74353
74478
  */
74354
74479
  destroy() {
74355
74480
  this.eventHandlers.clear();
74481
+ hideFooterZoom();
74356
74482
  if (this.footerEl.parentNode) {
74357
74483
  this.footerEl.parentNode.removeChild(this.footerEl);
74358
74484
  }
@@ -74362,6 +74488,58 @@ var FooterView = class {
74362
74488
  this.clearBtnEl = null;
74363
74489
  }
74364
74490
  };
74491
+ var FOOTER_ZOOM_ID = "myio-footer-chip-zoom";
74492
+ function ensureFooterZoomEl() {
74493
+ if (!document.getElementById("myio-footer-chip-zoom-styles")) {
74494
+ const st = document.createElement("style");
74495
+ st.id = "myio-footer-chip-zoom-styles";
74496
+ st.textContent = `
74497
+ #${FOOTER_ZOOM_ID} {
74498
+ position: fixed; z-index: 2147483000; pointer-events: none;
74499
+ display: flex; flex-direction: column; gap: 2px;
74500
+ min-width: 140px; max-width: 360px; padding: 10px 14px;
74501
+ background: var(--myio-brand-700, #3e1a7d); color: #fff;
74502
+ border: 1px solid rgba(255, 255, 255, 0.28); border-radius: 10px;
74503
+ box-shadow: 0 10px 28px rgba(15, 23, 42, 0.35);
74504
+ font-family: 'Nunito', system-ui, sans-serif;
74505
+ transform: translate(-50%, 6px) scale(0.92); transform-origin: 50% 100%;
74506
+ opacity: 0; transition: opacity 0.12s ease, transform 0.12s ease;
74507
+ }
74508
+ #${FOOTER_ZOOM_ID}.show { opacity: 1; transform: translate(-50%, 0) scale(1); }
74509
+ #${FOOTER_ZOOM_ID} .zoom-name {
74510
+ font-size: 14px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.01em;
74511
+ white-space: normal; word-break: break-word; line-height: 1.2;
74512
+ }
74513
+ #${FOOTER_ZOOM_ID} .zoom-value {
74514
+ font-size: 22px; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.15;
74515
+ }
74516
+ `;
74517
+ document.head.appendChild(st);
74518
+ }
74519
+ let el2 = document.getElementById(FOOTER_ZOOM_ID);
74520
+ if (!el2) {
74521
+ el2 = document.createElement("div");
74522
+ el2.id = FOOTER_ZOOM_ID;
74523
+ el2.setAttribute("role", "tooltip");
74524
+ el2.innerHTML = '<span class="zoom-name"></span><span class="zoom-value"></span>';
74525
+ document.body.appendChild(el2);
74526
+ }
74527
+ return el2;
74528
+ }
74529
+ function showFooterZoom(anchor, name, value) {
74530
+ const el2 = ensureFooterZoomEl();
74531
+ el2.querySelector(".zoom-name").textContent = name || "";
74532
+ el2.querySelector(".zoom-value").textContent = value || "";
74533
+ const r = anchor.getBoundingClientRect();
74534
+ const half = 75;
74535
+ const left = Math.min(Math.max(r.left + r.width / 2, half + 8), window.innerWidth - half - 8);
74536
+ el2.style.left = `${left}px`;
74537
+ el2.style.bottom = `${window.innerHeight - r.top + 8}px`;
74538
+ el2.classList.add("show");
74539
+ }
74540
+ function hideFooterZoom() {
74541
+ document.getElementById(FOOTER_ZOOM_ID)?.classList.remove("show");
74542
+ }
74365
74543
 
74366
74544
  // src/components/footer/AlertDialogs.ts
74367
74545
  var currentOverlay = null;
@@ -118327,14 +118505,14 @@ var MenuView = class {
118327
118505
  ========================================== */
118328
118506
 
118329
118507
  .myio-toolbar-item--nav {
118330
- background: var(--menu-nav-bg, #3e1a7d);
118508
+ background: var(--menu-nav-bg, var(--myio-brand-700, #3e1a7d));
118331
118509
  color: var(--menu-nav-color, #ffffff);
118332
118510
  font-weight: 600;
118333
118511
  padding: 8px 16px;
118334
118512
  }
118335
118513
 
118336
118514
  .myio-toolbar-item--nav:hover {
118337
- background: var(--menu-nav-hover-bg, #4c2391);
118515
+ background: var(--menu-nav-hover-bg, color-mix(in srgb, var(--myio-brand-700, #3e1a7d) 85%, #fff));
118338
118516
  }
118339
118517
 
118340
118518
  .myio-toolbar-item--nav .nav-label {
@@ -118348,7 +118526,7 @@ var MenuView = class {
118348
118526
  ========================================== */
118349
118527
 
118350
118528
  .myio-toolbar-item--filter {
118351
- color: var(--menu-filter-color, #6366f1);
118529
+ color: var(--menu-filter-color, var(--myio-brand-700, #6366f1));
118352
118530
  }
118353
118531
 
118354
118532
  .myio-toolbar-item--filter:hover {
@@ -118371,7 +118549,7 @@ var MenuView = class {
118371
118549
  min-width: 340px;
118372
118550
  padding: 0;
118373
118551
  background: transparent;
118374
- border: 2px solid var(--menu-date-border, #3e1a7d);
118552
+ border: 2px solid var(--menu-date-border, var(--myio-brand-700, #3e1a7d));
118375
118553
  border-radius: 8px;
118376
118554
  overflow: hidden;
118377
118555
  transition: all 0.2s ease;
@@ -118379,17 +118557,17 @@ var MenuView = class {
118379
118557
  }
118380
118558
 
118381
118559
  .myio-toolbar-item--date:hover {
118382
- border-color: var(--menu-date-hover-border, #5a2d9e);
118383
- box-shadow: 0 0 0 3px rgba(62, 26, 125, 0.15);
118560
+ border-color: var(--menu-date-hover-border, color-mix(in srgb, var(--myio-brand-700, #3e1a7d) 80%, #fff));
118561
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--myio-brand-700, #3e1a7d) 15%, transparent);
118384
118562
  }
118385
118563
 
118386
118564
  .myio-toolbar-item--date:focus-within {
118387
- border-color: var(--menu-date-focus-border, #3e1a7d);
118388
- box-shadow: 0 0 0 3px rgba(62, 26, 125, 0.25);
118565
+ border-color: var(--menu-date-focus-border, var(--myio-brand-700, #3e1a7d));
118566
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--myio-brand-700, #3e1a7d) 25%, transparent);
118389
118567
  }
118390
118568
 
118391
118569
  .myio-toolbar-item--date .ico {
118392
- background: var(--menu-date-icon-bg, #3e1a7d);
118570
+ background: var(--menu-date-icon-bg, var(--myio-brand-700, #3e1a7d));
118393
118571
  color: white;
118394
118572
  padding: 0 12px;
118395
118573
  margin: 0;
@@ -124412,6 +124590,16 @@ function generateFilterModalStyles(containerId, modalClass, primaryColor) {
124412
124590
  opacity: 0.5;
124413
124591
  }
124414
124592
  #${containerId} .filter-tab:hover { opacity: 0.8; transform: translateY(-1px); }
124593
+ /* Categorias (deviceProfile): r\xF3tulos longos s\xE3o encurtados (nome completo no title) */
124594
+ #${containerId} .filter-tab--category {
124595
+ max-width: 100%;
124596
+ background: color-mix(in srgb, var(--myio-brand-700, #3e1a7d) 8%, #fff);
124597
+ color: var(--myio-brand-700, #3e1a7d);
124598
+ }
124599
+ #${containerId}[data-theme="dark"] .filter-tab--category {
124600
+ background: color-mix(in srgb, var(--myio-brand-700, #3e1a7d) 30%, #1e293b);
124601
+ color: #e2e8f0;
124602
+ }
124415
124603
  #${containerId} .filter-tab.active { opacity: 1; box-shadow: 0 1px 3px rgba(0,0,0,0.15); }
124416
124604
  #${containerId} .filter-tab[data-filter="all"] { background: #e2e8f0; color: #475569; }
124417
124605
  #${containerId} .filter-tab[data-filter="online"],
@@ -124695,6 +124883,28 @@ function generateFilterModalStyles(containerId, modalClass, primaryColor) {
124695
124883
  }
124696
124884
 
124697
124885
  // src/components/filter-modal/FilterModalView.ts
124886
+ var CATEGORY_META = {
124887
+ ELEVADOR: { label: "Elevadores", icon: "\u{1F6D7}" },
124888
+ ESCADA_ROLANTE: { label: "Escadas Rolantes", icon: "\u{1F4F6}" },
124889
+ CHILLER: { label: "Chillers", icon: "\u2744\uFE0F" },
124890
+ FANCOIL: { label: "Fancoils", icon: "\u{1F300}" },
124891
+ BOMBA_CAG: { label: "Bombas CAG", icon: "\u{1F4A7}" },
124892
+ BOMBA_HIDRAULICA: { label: "Bombas Hidr\xE1ulicas", icon: "\u{1F4A7}" },
124893
+ BOMBA_INCENDIO: { label: "Bombas de Inc\xEAndio", icon: "\u{1F9EF}" },
124894
+ MOTOR: { label: "Motores", icon: "\u2699\uFE0F" },
124895
+ "3F_MEDIDOR": { label: "Lojas", icon: "\u{1F3EA}" },
124896
+ ENTRADA: { label: "Entrada", icon: "\u{1F50C}" },
124897
+ HIDROMETRO: { label: "Hidr\xF4metros", icon: "\u{1F4A7}" },
124898
+ HIDROMETRO_AREA_COMUM: { label: "Hidr\xF4metros \xC1rea Comum", icon: "\u{1F4A7}" },
124899
+ HIDROMETRO_SHOPPING: { label: "Hidr\xF4metros Entrada", icon: "\u{1F4A7}" },
124900
+ TERMOSTATO: { label: "Termostatos", icon: "\u{1F321}\uFE0F" }
124901
+ };
124902
+ function categoryMeta(profile) {
124903
+ const key = String(profile || "").toUpperCase();
124904
+ if (CATEGORY_META[key]) return CATEGORY_META[key];
124905
+ const label = key.split(/[_\s]+/).filter(Boolean).map((w) => w.charAt(0) + w.slice(1).toLowerCase()).join(" ");
124906
+ return { label: label || key, icon: "\u{1F3F7}\uFE0F" };
124907
+ }
124698
124908
  var FilterModalView = class {
124699
124909
  containerId;
124700
124910
  widgetName;
@@ -124711,6 +124921,13 @@ var FilterModalView = class {
124711
124921
  onApply;
124712
124922
  onClose;
124713
124923
  globalContainer = null;
124924
+ /** Abas de categoria (uma por deviceProfile presente nos itens) — recriadas a cada open() */
124925
+ dynamicTabs = [];
124926
+ dynamicIcons = {};
124927
+ dynamicTitles = {};
124928
+ groups = FILTER_GROUPS;
124929
+ /** Moldura padrão (header no acento do tema + maximizar/fechar + footer premium) */
124930
+ chrome = null;
124714
124931
  escHandler = null;
124715
124932
  currentItems = [];
124716
124933
  constructor(params) {
@@ -124732,7 +124949,7 @@ var FilterModalView = class {
124732
124949
  });
124733
124950
  }
124734
124951
  generateFilterTabsHTML(counts) {
124735
- const allTab = this.filterTabs.find((t) => t.id === "all");
124952
+ const allTab = this.allTabs().find((t) => t.id === "all");
124736
124953
  let html = "";
124737
124954
  if (allTab) {
124738
124955
  const icon2 = FILTER_TAB_ICONS["all"] || "";
@@ -124744,19 +124961,19 @@ var FilterModalView = class {
124744
124961
  </div>
124745
124962
  `;
124746
124963
  }
124747
- FILTER_GROUPS.forEach((group) => {
124748
- const groupTabs = this.filterTabs.filter((t) => group.filters.includes(t.id));
124964
+ this.groups.forEach((group) => {
124965
+ const groupTabs = this.allTabs().filter((t) => group.filters.includes(t.id));
124749
124966
  if (groupTabs.length === 0) return;
124750
124967
  html += `
124751
124968
  <div class="filter-group">
124752
124969
  <span class="filter-group-label">${group.label}</span>
124753
124970
  <div class="filter-group-tabs">
124754
124971
  ${groupTabs.map((tab) => {
124755
- const icon2 = FILTER_TAB_ICONS[tab.id] || "";
124972
+ const icon2 = FILTER_TAB_ICONS[tab.id] || this.dynamicIcons[tab.id] || "";
124756
124973
  const count = counts[tab.id] || 0;
124757
124974
  const expandBtn = count > 0 ? `<button class="filter-tab-expand" data-expand-filter="${tab.id}" title="Ver dispositivos">+</button>` : "";
124758
124975
  return `
124759
- <button class="filter-tab active" data-filter="${tab.id}">
124976
+ <button class="filter-tab active${tab.id.startsWith("cat_") ? " filter-tab--category" : ""}" data-filter="${tab.id}" title="${this.dynamicTitles[tab.id] || tab.label}">
124760
124977
  ${icon2} ${tab.label} (<span id="count${tab.id.charAt(0).toUpperCase() + tab.id.slice(1)}">${count}</span>)${expandBtn}
124761
124978
  </button>
124762
124979
  `;
@@ -124826,9 +125043,29 @@ var FilterModalView = class {
124826
125043
  </div>
124827
125044
  `;
124828
125045
  }
125046
+ allTabs() {
125047
+ return [...this.filterTabs, ...this.dynamicTabs];
125048
+ }
125049
+ /** Grupo "Categoria": uma aba por deviceProfile presente nos itens (ex.: Elevadores, Escadas Rolantes). */
125050
+ buildCategoryTabs(items) {
125051
+ const profileOf3 = (item) => String(item.deviceProfile || "").trim().toUpperCase();
125052
+ const profiles = Array.from(new Set(items.map(profileOf3).filter(Boolean)));
125053
+ profiles.sort((a, b) => categoryMeta(a).label.localeCompare(categoryMeta(b).label, "pt-BR"));
125054
+ this.dynamicIcons = {};
125055
+ this.dynamicTitles = {};
125056
+ this.dynamicTabs = profiles.map((profile) => {
125057
+ const id = `cat_${profile.replace(/[^A-Za-z0-9]/g, "_")}`;
125058
+ const meta = categoryMeta(profile);
125059
+ this.dynamicIcons[id] = meta.icon;
125060
+ this.dynamicTitles[id] = meta.label;
125061
+ const label = meta.label.length > 22 ? `${meta.label.slice(0, 21).trimEnd()}\u2026` : meta.label;
125062
+ return { id, label, filter: (item) => profileOf3(item) === profile };
125063
+ });
125064
+ this.groups = this.dynamicTabs.length ? [...FILTER_GROUPS, { id: "category", label: "Categoria", filters: this.dynamicTabs.map((t) => t.id) }] : FILTER_GROUPS;
125065
+ }
124829
125066
  calculateCounts(items) {
124830
125067
  const counts = {};
124831
- this.filterTabs.forEach((tab) => {
125068
+ this.allTabs().forEach((tab) => {
124832
125069
  counts[tab.id] = items.filter(tab.filter).length;
124833
125070
  });
124834
125071
  return counts;
@@ -124969,7 +125206,7 @@ var FilterModalView = class {
124969
125206
  bindFilterTabHandlers(modal, items) {
124970
125207
  const filterTabsEl = modal.querySelectorAll(".filter-tab");
124971
125208
  const getFilterFn = (filterId) => {
124972
- const tabConfig = this.filterTabs.find((t) => t.id === filterId);
125209
+ const tabConfig = this.allTabs().find((t) => t.id === filterId);
124973
125210
  return tabConfig ? tabConfig.filter : () => false;
124974
125211
  };
124975
125212
  const applyActiveFilters = () => {
@@ -124978,16 +125215,16 @@ var FilterModalView = class {
124978
125215
  return groupActiveFilters.some((filterId) => getFilterFn(filterId)(item));
124979
125216
  };
124980
125217
  let filteredItems = [...items];
124981
- for (let i = 0; i < FILTER_GROUPS.length; i++) {
124982
- const group = FILTER_GROUPS[i];
125218
+ for (let i = 0; i < this.groups.length; i++) {
125219
+ const group = this.groups[i];
124983
125220
  const activeInGroup = Array.from(filterTabsEl).filter(
124984
125221
  (t) => group.filters.includes(t.getAttribute("data-filter") || "") && t.classList.contains("active")
124985
125222
  ).map((t) => t.getAttribute("data-filter") || "");
124986
125223
  if (activeInGroup.length > 0) {
124987
125224
  filteredItems = filteredItems.filter((item) => itemPassesGroup(item, activeInGroup));
124988
125225
  }
124989
- for (let j = i + 1; j < FILTER_GROUPS.length; j++) {
124990
- const nextGroup = FILTER_GROUPS[j];
125226
+ for (let j = i + 1; j < this.groups.length; j++) {
125227
+ const nextGroup = this.groups[j];
124991
125228
  nextGroup.filters.forEach((filterId) => {
124992
125229
  const tab = Array.from(filterTabsEl).find((t) => t.getAttribute("data-filter") === filterId);
124993
125230
  if (!tab) return;
@@ -124999,7 +125236,7 @@ var FilterModalView = class {
124999
125236
  }
125000
125237
  const activeFilters = Array.from(filterTabsEl).filter((t) => t.classList.contains("active") && t.getAttribute("data-filter") !== "all").map((t) => t.getAttribute("data-filter") || "");
125001
125238
  const activeByGroup = {};
125002
- FILTER_GROUPS.forEach((group) => {
125239
+ this.groups.forEach((group) => {
125003
125240
  const activeInGroup = activeFilters.filter((id) => group.filters.includes(id));
125004
125241
  if (activeInGroup.length > 0) activeByGroup[group.id] = activeInGroup;
125005
125242
  });
@@ -125019,7 +125256,7 @@ var FilterModalView = class {
125019
125256
  };
125020
125257
  const getFilteredItems = () => {
125021
125258
  let filteredItems = [...items];
125022
- FILTER_GROUPS.forEach((group) => {
125259
+ this.groups.forEach((group) => {
125023
125260
  const activeInGroup = Array.from(filterTabsEl).filter(
125024
125261
  (t) => group.filters.includes(t.getAttribute("data-filter") || "") && t.classList.contains("active")
125025
125262
  ).map((t) => t.getAttribute("data-filter") || "");
@@ -125032,7 +125269,7 @@ var FilterModalView = class {
125032
125269
  return filteredItems;
125033
125270
  };
125034
125271
  const updateFilterCounts = (filteredItems) => {
125035
- this.filterTabs.forEach((tabConfig) => {
125272
+ this.allTabs().forEach((tabConfig) => {
125036
125273
  if (tabConfig.id === "all") {
125037
125274
  const countEl = modal.querySelector("#countAll");
125038
125275
  if (countEl) countEl.textContent = String(filteredItems.length);
@@ -125081,7 +125318,7 @@ var FilterModalView = class {
125081
125318
  connectivityTab.classList.add("active");
125082
125319
  }
125083
125320
  const filteredItems2 = getFilteredItems();
125084
- const typeIds = ["elevators", "escalators", "hvac", "others", "commonArea", "stores"];
125321
+ const typeIds = ["elevators", "escalators", "hvac", "others", "commonArea", "stores", ...this.dynamicTabs.map((t) => t.id)];
125085
125322
  typeIds.forEach((typeId) => {
125086
125323
  const typeTab = Array.from(filterTabsEl).find((t) => t.getAttribute("data-filter") === typeId);
125087
125324
  if (!typeTab) return;
@@ -125111,7 +125348,7 @@ var FilterModalView = class {
125111
125348
  expandBtns.forEach((btn) => {
125112
125349
  const filterId = btn.getAttribute("data-expand-filter");
125113
125350
  if (!filterId) return;
125114
- const filterTabConfig = this.filterTabs.find((t) => t.id === filterId);
125351
+ const filterTabConfig = this.allTabs().find((t) => t.id === filterId);
125115
125352
  const filterFn = filterTabConfig?.filter || (() => false);
125116
125353
  btn.addEventListener("mouseenter", (e) => {
125117
125354
  e.stopPropagation();
@@ -125132,9 +125369,9 @@ var FilterModalView = class {
125132
125369
  showDeviceTooltip(triggerEl, filterId, devices) {
125133
125370
  const InfoTooltip2 = window.MyIOLibrary;
125134
125371
  if (!InfoTooltip2?.InfoTooltip) return;
125135
- const filterTabConfig = this.filterTabs.find((t) => t.id === filterId);
125372
+ const filterTabConfig = this.allTabs().find((t) => t.id === filterId);
125136
125373
  const label = filterTabConfig?.label || filterId;
125137
- const icon2 = FILTER_TAB_ICONS[filterId] || "\u{1F4CB}";
125374
+ const icon2 = FILTER_TAB_ICONS[filterId] || this.dynamicIcons[filterId] || "\u{1F4CB}";
125138
125375
  let devicesHtml = "";
125139
125376
  if (devices.length === 0) {
125140
125377
  devicesHtml = `
@@ -125211,12 +125448,17 @@ var FilterModalView = class {
125211
125448
  this.globalContainer.innerHTML = `<style>${generateFilterModalStyles(this.containerId, this.modalClass, this.primaryColor)}</style>${this.generateModalHTML()}`;
125212
125449
  document.body.appendChild(this.globalContainer);
125213
125450
  const modal2 = this.globalContainer.querySelector("#filterModal");
125214
- if (modal2) this.setupHandlers(modal2, items);
125451
+ if (modal2) {
125452
+ this.setupHandlers(modal2, items);
125453
+ this.mountChrome(modal2);
125454
+ }
125215
125455
  }
125216
125456
  }
125217
125457
  this.globalContainer.setAttribute("data-theme", this.themeMode);
125458
+ if (this.chrome && this.chrome.getTheme() !== this.themeMode) this.chrome.setTheme(this.themeMode);
125218
125459
  const modal = this.globalContainer.querySelector("#filterModal");
125219
125460
  if (!modal) return;
125461
+ this.buildCategoryTabs(items);
125220
125462
  const counts = this.calculateCounts(items);
125221
125463
  const tabsContainer = modal.querySelector("#filterTabsContainer");
125222
125464
  if (tabsContainer) {
@@ -125231,6 +125473,28 @@ var FilterModalView = class {
125231
125473
  modal.classList.remove("hidden");
125232
125474
  document.body.classList.add("filter-modal-open");
125233
125475
  }
125476
+ /** Troca o header próprio pela moldura padrão das modais premium e adiciona o footer premium. */
125477
+ mountChrome(modal) {
125478
+ const card = modal.querySelector(`.${this.modalClass}-card`);
125479
+ if (!card || !this.globalContainer) return;
125480
+ this.chrome = createPremiumModalChrome({
125481
+ modalId: `${this.containerId}Chrome`,
125482
+ icon: "\u{1F50D}",
125483
+ title: "Filtrar e Ordenar",
125484
+ modalEl: card,
125485
+ headerSlot: modal.querySelector(`#${this.containerId}Header`),
125486
+ themeTarget: this.globalContainer,
125487
+ // estilos escuros da modal são por #container[data-theme]
125488
+ theme: this.themeMode === "dark" ? "dark" : "light",
125489
+ showThemeToggle: true,
125490
+ maximizedClass: "maximized",
125491
+ headerRadius: "16px 16px 0 0",
125492
+ onClose: () => this.close(),
125493
+ onThemeChange: (t) => {
125494
+ this.themeMode = t;
125495
+ }
125496
+ });
125497
+ }
125234
125498
  close() {
125235
125499
  if (this.globalContainer) {
125236
125500
  const modal = this.globalContainer.querySelector("#filterModal");
@@ -125245,12 +125509,15 @@ var FilterModalView = class {
125245
125509
  if (this.globalContainer) {
125246
125510
  this.globalContainer.setAttribute("data-theme", mode);
125247
125511
  }
125512
+ this.chrome?.setTheme(mode === "dark" ? "dark" : "light");
125248
125513
  }
125249
125514
  destroy() {
125250
125515
  if (this.escHandler) {
125251
125516
  document.removeEventListener("keydown", this.escHandler);
125252
125517
  this.escHandler = null;
125253
125518
  }
125519
+ this.chrome?.destroy();
125520
+ this.chrome = null;
125254
125521
  if (this.globalContainer) {
125255
125522
  this.globalContainer.remove();
125256
125523
  this.globalContainer = null;
package/dist/index.d.cts CHANGED
@@ -17706,11 +17706,21 @@ declare class FilterModalView {
17706
17706
  private onApply;
17707
17707
  private onClose;
17708
17708
  private globalContainer;
17709
+ /** Abas de categoria (uma por deviceProfile presente nos itens) — recriadas a cada open() */
17710
+ private dynamicTabs;
17711
+ private dynamicIcons;
17712
+ private dynamicTitles;
17713
+ private groups;
17714
+ /** Moldura padrão (header no acento do tema + maximizar/fechar + footer premium) */
17715
+ private chrome;
17709
17716
  private escHandler;
17710
17717
  private currentItems;
17711
17718
  constructor(params: FilterModalParams);
17712
17719
  private generateFilterTabsHTML;
17713
17720
  private generateModalHTML;
17721
+ private allTabs;
17722
+ /** Grupo "Categoria": uma aba por deviceProfile presente nos itens (ex.: Elevadores, Escadas Rolantes). */
17723
+ private buildCategoryTabs;
17714
17724
  private calculateCounts;
17715
17725
  private populateChecklist;
17716
17726
  private setupHandlers;
@@ -17719,6 +17729,8 @@ declare class FilterModalView {
17719
17729
  private showDeviceTooltip;
17720
17730
  private hideDeviceTooltip;
17721
17731
  open(items: FilterableDevice[], state?: FilterModalState): void;
17732
+ /** Troca o header próprio pela moldura padrão das modais premium e adiciona o footer premium. */
17733
+ private mountChrome;
17722
17734
  close(): void;
17723
17735
  setThemeMode(mode: FilterModalThemeMode): void;
17724
17736
  destroy(): void;
package/dist/index.d.ts CHANGED
@@ -17706,11 +17706,21 @@ declare class FilterModalView {
17706
17706
  private onApply;
17707
17707
  private onClose;
17708
17708
  private globalContainer;
17709
+ /** Abas de categoria (uma por deviceProfile presente nos itens) — recriadas a cada open() */
17710
+ private dynamicTabs;
17711
+ private dynamicIcons;
17712
+ private dynamicTitles;
17713
+ private groups;
17714
+ /** Moldura padrão (header no acento do tema + maximizar/fechar + footer premium) */
17715
+ private chrome;
17709
17716
  private escHandler;
17710
17717
  private currentItems;
17711
17718
  constructor(params: FilterModalParams);
17712
17719
  private generateFilterTabsHTML;
17713
17720
  private generateModalHTML;
17721
+ private allTabs;
17722
+ /** Grupo "Categoria": uma aba por deviceProfile presente nos itens (ex.: Elevadores, Escadas Rolantes). */
17723
+ private buildCategoryTabs;
17714
17724
  private calculateCounts;
17715
17725
  private populateChecklist;
17716
17726
  private setupHandlers;
@@ -17719,6 +17729,8 @@ declare class FilterModalView {
17719
17729
  private showDeviceTooltip;
17720
17730
  private hideDeviceTooltip;
17721
17731
  open(items: FilterableDevice[], state?: FilterModalState): void;
17732
+ /** Troca o header próprio pela moldura padrão das modais premium e adiciona o footer premium. */
17733
+ private mountChrome;
17722
17734
  close(): void;
17723
17735
  setThemeMode(mode: FilterModalThemeMode): void;
17724
17736
  destroy(): void;