structra-ui 0.2.54 → 0.2.56

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "structra-ui",
3
- "version": "0.2.54",
3
+ "version": "0.2.56",
4
4
  "description": "Biblioteca de componentes Angular da StructraLab (UI reutilizável).",
5
5
  "homepage": "https://structralab.com/",
6
6
  "license": "MIT",
@@ -1040,6 +1040,7 @@ declare class SwitchFieldComponent extends BooleanFieldDirective implements Afte
1040
1040
  declare class DropdownBaseComponent {
1041
1041
  private readonly appTheme;
1042
1042
  private readonly overlay;
1043
+ private connectedOverlay?;
1043
1044
  /**
1044
1045
  * O overlay do CDK fica sob `body`; sem estas classes, `var(--app-color-*)` cai no `:root`.
1045
1046
  * Tem de ser **array**: uma única string com espaço quebra `classList.add` e as classes não aplicam.
@@ -1068,11 +1069,18 @@ declare class DropdownBaseComponent {
1068
1069
  /** Empurra o overlay para caber no viewport quando há espaço insuficiente. */
1069
1070
  pushConnectedOverlay: boolean;
1070
1071
  /**
1071
- * Por omissão **reposiciona** o painel quando o viewport ou contentores com scroll se movem
1072
- * (mesma ideia que submenus em `app-menu-list`). Sobrescrever em casos especiais (ex.: fechar ao scroll).
1072
+ * `true`: mantém a última posição escolhida e reancora ao rect do gatilho — evita flip/push bruscos
1073
+ * quando o conteúdo do painel muda (ex.: multiselect que reordena opções ao seleccionar).
1074
+ */
1075
+ lockConnectedOverlayPosition: boolean;
1076
+ /**
1077
+ * Sobrescrever a estratégia CDK (ex.: `noop()` em cenários de teste).
1078
+ * Por omissão: **reposiciona** em desktop; em ambientes sem `(hover: hover)` usa **fecho ao scroll**
1079
+ * da página (lista não fica desalinhada ao rolar o formulário no telemóvel).
1073
1080
  */
1074
1081
  scrollStrategy: ScrollStrategy | null;
1075
- readonly defaultScrollStrategy: _angular_cdk_overlay.RepositionScrollStrategy;
1082
+ readonly repositionScrollStrategy: _angular_cdk_overlay.RepositionScrollStrategy;
1083
+ readonly closeOnOuterScrollStrategy: _angular_cdk_overlay.CloseScrollStrategy;
1076
1084
  get effectiveScrollStrategy(): ScrollStrategy;
1077
1085
  /**
1078
1086
  * `true`: painel sem fundo/borda/sombra próprios — o conteúdo (ex.: `app-menu-list`) define o chrome.
@@ -1090,12 +1098,17 @@ declare class DropdownBaseComponent {
1090
1098
  onOverlayKeydown(ev: KeyboardEvent): void;
1091
1099
  setOpen(next: boolean): void;
1092
1100
  toggle(): void;
1101
+ /**
1102
+ * Após o gatilho ou o painel mudarem de layout com o overlay aberto (ex.: teclado / multiselect).
1103
+ */
1104
+ scheduleOverlayPositionSync(): void;
1093
1105
  static ɵfac: i0.ɵɵFactoryDeclaration<DropdownBaseComponent, never>;
1094
- static ɵcmp: i0.ɵɵComponentDeclaration<DropdownBaseComponent, "app-dropdown-base", never, { "open": { "alias": "open"; "required": false; }; "panelTemplate": { "alias": "panelTemplate"; "required": true; }; "panelWidthPx": { "alias": "panelWidthPx"; "required": false; }; "closeOnEscape": { "alias": "closeOnEscape"; "required": false; }; "hasBackdrop": { "alias": "hasBackdrop"; "required": false; }; "connectedPositions": { "alias": "connectedPositions"; "required": false; }; "pushConnectedOverlay": { "alias": "pushConnectedOverlay"; "required": false; }; "scrollStrategy": { "alias": "scrollStrategy"; "required": false; }; "bareOverlayPanel": { "alias": "bareOverlayPanel"; "required": false; }; }, { "openChange": "openChange"; }, never, ["[appDropdownTrigger]"], true, never>;
1106
+ static ɵcmp: i0.ɵɵComponentDeclaration<DropdownBaseComponent, "app-dropdown-base", never, { "open": { "alias": "open"; "required": false; }; "panelTemplate": { "alias": "panelTemplate"; "required": true; }; "panelWidthPx": { "alias": "panelWidthPx"; "required": false; }; "closeOnEscape": { "alias": "closeOnEscape"; "required": false; }; "hasBackdrop": { "alias": "hasBackdrop"; "required": false; }; "connectedPositions": { "alias": "connectedPositions"; "required": false; }; "pushConnectedOverlay": { "alias": "pushConnectedOverlay"; "required": false; }; "lockConnectedOverlayPosition": { "alias": "lockConnectedOverlayPosition"; "required": false; }; "scrollStrategy": { "alias": "scrollStrategy"; "required": false; }; "bareOverlayPanel": { "alias": "bareOverlayPanel"; "required": false; }; }, { "openChange": "openChange"; }, never, ["[appDropdownTrigger]"], true, never>;
1095
1107
  static ngAcceptInputType_open: unknown;
1096
1108
  static ngAcceptInputType_closeOnEscape: unknown;
1097
1109
  static ngAcceptInputType_hasBackdrop: unknown;
1098
1110
  static ngAcceptInputType_pushConnectedOverlay: unknown;
1111
+ static ngAcceptInputType_lockConnectedOverlayPosition: unknown;
1099
1112
  static ngAcceptInputType_bareOverlayPanel: unknown;
1100
1113
  }
1101
1114
 
@@ -1255,9 +1268,15 @@ declare class MultiselectFieldComponent extends DropdownMultiOptionFieldBase imp
1255
1268
  optionsPanelTpl: TemplateRef<unknown>;
1256
1269
  private readonly dropdownTriggerRootRef;
1257
1270
  private readonly nativeInputRef?;
1271
+ private readonly dropdownBase?;
1258
1272
  panelOpen: boolean;
1259
1273
  panelOverlayWidth: number;
1260
1274
  private scrollLoadTimer;
1275
+ /**
1276
+ * Sem hover primário (telemóvel): o CDK volta a avaliar encaixe quando a lista reordena e o painel
1277
+ * pode «saltar» por cima do campo — `lockPosition` mantém ancoragem ao fundo do gatilho.
1278
+ */
1279
+ get lockOverlayWhileOpen(): boolean;
1261
1280
  ngAfterViewInit(): void;
1262
1281
  writeValue(value: unknown[] | null): void;
1263
1282
  protected afterWriteValue(_value: unknown[]): void;
@@ -1332,6 +1351,11 @@ declare class DropdownSearchFieldComponent extends DropdownOptionFieldBase imple
1332
1351
  get isLockedSelectionDisplay(): boolean;
1333
1352
  /** `readonly` nativo: prop do campo ou bloqueio de exibição da seleção. */
1334
1353
  get isNativeInputReadonly(): boolean;
1354
+ /**
1355
+ * Paginação / API pode devolver o mesmo `value` em mais do que uma linha — `track` só por valor
1356
+ * duplica chaves (aviso NG0955). O índice na vista torna a chave única sem alterar select/multiselect.
1357
+ */
1358
+ trackOption(index: number, opt: UiSelectOption<unknown>): unknown;
1335
1359
  ngOnChanges(changes: SimpleChanges): void;
1336
1360
  ngOnDestroy(): void;
1337
1361
  onDropdownOpenChange(next: boolean, openMode?: DropdownSearchOpenMode): void;
@@ -3090,9 +3114,12 @@ declare class DrawerComponent implements OnChanges, OnDestroy {
3090
3114
  static ngAcceptInputType_showCloseButton: unknown;
3091
3115
  }
3092
3116
 
3093
- declare class MenuListComponent<T = string> implements OnChanges {
3117
+ declare class MenuListComponent<T = string> implements OnChanges, AfterViewInit {
3094
3118
  private readonly cdr;
3119
+ private readonly injector;
3120
+ private readonly destroyRef;
3095
3121
  readonly appTheme: i0.InputSignal<"blue" | "green" | "purple" | "orange" | "white" | "gray" | "sepia" | "black" | AppTheme | undefined>;
3122
+ private bodyRouteSyncGen;
3096
3123
  items: readonly MenuNodeItem<T>[];
3097
3124
  menuId: string;
3098
3125
  dense: boolean;
@@ -3109,30 +3136,52 @@ declare class MenuListComponent<T = string> implements OnChanges {
3109
3136
  */
3110
3137
  persistGroupExpansion: boolean;
3111
3138
  searchable: boolean;
3139
+ /**
3140
+ * Com `searchable`, se `true` limpa o campo e repõe a lista completa após {@link itemSelect}.
3141
+ */
3142
+ limparAposPesquisar: boolean;
3112
3143
  searchPlaceholder: string;
3113
3144
  readonly itemSelect: EventEmitter<T>;
3114
3145
  readonly closeRequest: EventEmitter<void>;
3115
3146
  readonly submenuBack: EventEmitter<void>;
3116
3147
  readonly groupToggled: i0.OutputEmitterRef<number>;
3148
+ /** Ver {@link MenuListBodyComponent.selectionScrollHint}. */
3149
+ readonly selectionScrollHint: i0.OutputEmitterRef<void>;
3117
3150
  private body?;
3118
3151
  searchQuery: string;
3152
+ constructor();
3153
+ ngAfterViewInit(): void;
3119
3154
  displayItems: MenuNodeItem<T>[];
3120
3155
  get showSearchField(): boolean;
3121
3156
  get expandAllGroupsWhenSearching(): boolean;
3122
3157
  get showSearchNoResults(): boolean;
3123
3158
  ngOnChanges(changes: SimpleChanges): void;
3159
+ /** `false` quando o corpo já tratou a selecção no mesmo ciclo — não repetir após `afterNextRender`. */
3160
+ private needsDeferredBodyRouteSync;
3161
+ /**
3162
+ * O corpo do menu só existe após o primeiro render; no drawer mobile isso atrasa `ensureExpanded`
3163
+ * relativamente ao `ngOnChanges` do corpo — repetimos após `afterNextRender`.
3164
+ */
3165
+ private scheduleBodyRouteSelectionSync;
3124
3166
  onSearchQueryInput(value: string | null): void;
3167
+ onMenuItemSelect(id: T): void;
3125
3168
  resetHighlight(): void;
3126
3169
  focusFirstOption(): void;
3170
+ /**
3171
+ * Usado pela `app-sidebar` ao abrir o drawer móvel: garante {@link MenuListBodyComponent.syncRouteSelectionExpansion}
3172
+ * antes do scroll (grupos fechados em `sessionStorage` passam a abrir no caminho do item activo).
3173
+ */
3174
+ syncSelectionForMobileDrawerOpen(): void;
3127
3175
  private syncDisplayItems;
3128
3176
  static ɵfac: i0.ɵɵFactoryDeclaration<MenuListComponent<any>, never>;
3129
- static ɵcmp: i0.ɵɵComponentDeclaration<MenuListComponent<any>, "app-menu-list", never, { "appTheme": { "alias": "appTheme"; "required": false; "isSignal": true; }; "items": { "alias": "items"; "required": true; }; "menuId": { "alias": "menuId"; "required": false; }; "dense": { "alias": "dense"; "required": false; }; "nested": { "alias": "nested"; "required": false; }; "selectedItemId": { "alias": "selectedItemId"; "required": false; }; "compressText": { "alias": "compressText"; "required": false; }; "clickToOpen": { "alias": "clickToOpen"; "required": false; }; "groupCollapsible": { "alias": "groupCollapsible"; "required": false; }; "groupDefaultExpansion": { "alias": "groupDefaultExpansion"; "required": false; }; "persistGroupExpansion": { "alias": "persistGroupExpansion"; "required": false; }; "searchable": { "alias": "searchable"; "required": false; }; "searchPlaceholder": { "alias": "searchPlaceholder"; "required": false; }; }, { "itemSelect": "itemSelect"; "closeRequest": "closeRequest"; "submenuBack": "submenuBack"; "groupToggled": "groupToggled"; }, never, never, true, never>;
3177
+ static ɵcmp: i0.ɵɵComponentDeclaration<MenuListComponent<any>, "app-menu-list", never, { "appTheme": { "alias": "appTheme"; "required": false; "isSignal": true; }; "items": { "alias": "items"; "required": true; }; "menuId": { "alias": "menuId"; "required": false; }; "dense": { "alias": "dense"; "required": false; }; "nested": { "alias": "nested"; "required": false; }; "selectedItemId": { "alias": "selectedItemId"; "required": false; }; "compressText": { "alias": "compressText"; "required": false; }; "clickToOpen": { "alias": "clickToOpen"; "required": false; }; "groupCollapsible": { "alias": "groupCollapsible"; "required": false; }; "groupDefaultExpansion": { "alias": "groupDefaultExpansion"; "required": false; }; "persistGroupExpansion": { "alias": "persistGroupExpansion"; "required": false; }; "searchable": { "alias": "searchable"; "required": false; }; "limparAposPesquisar": { "alias": "limparAposPesquisar"; "required": false; }; "searchPlaceholder": { "alias": "searchPlaceholder"; "required": false; }; }, { "itemSelect": "itemSelect"; "closeRequest": "closeRequest"; "submenuBack": "submenuBack"; "groupToggled": "groupToggled"; "selectionScrollHint": "selectionScrollHint"; }, never, never, true, never>;
3130
3178
  static ngAcceptInputType_dense: unknown;
3131
3179
  static ngAcceptInputType_nested: unknown;
3132
3180
  static ngAcceptInputType_compressText: unknown;
3133
3181
  static ngAcceptInputType_groupCollapsible: unknown;
3134
3182
  static ngAcceptInputType_persistGroupExpansion: unknown;
3135
3183
  static ngAcceptInputType_searchable: unknown;
3184
+ static ngAcceptInputType_limparAposPesquisar: unknown;
3136
3185
  }
3137
3186
 
3138
3187
  /**
@@ -3429,6 +3478,7 @@ declare class AppSidebarComponent<T extends string = string> implements OnChange
3429
3478
  private readonly cdr;
3430
3479
  private readonly destroyRef;
3431
3480
  private readonly hostEl;
3481
+ private readonly injector;
3432
3482
  private readonly renderer;
3433
3483
  /** Igual a `[appTheme]` / {@link ThemeDirective}: paleta explícita no host; omitir = herdar / serviço. */
3434
3484
  readonly appTheme: i0.InputSignal<"blue" | "green" | "purple" | "orange" | "white" | "gray" | "sepia" | "black" | AppTheme | undefined>;
@@ -3438,8 +3488,16 @@ declare class AppSidebarComponent<T extends string = string> implements OnChange
3438
3488
  selectedItemId: T | null;
3439
3489
  /** Modo estreito: só ícones + `compressText` na lista (rótulos via `title` / `aria-label`). */
3440
3490
  collapsed: boolean;
3491
+ /**
3492
+ * Ligado ao estado do drawer no telemóvel (ex.: `mobileNavOpen` do `app-shell`).
3493
+ * Em cada transição para `true`, volta a agendar o scroll até ao item activo — útil porque `selectedItemId`
3494
+ * pode não mudar entre aberturas e o menu pode ser recriado dentro do `@if (open)` do drawer.
3495
+ */
3496
+ mobileNavOpenPulse: boolean;
3441
3497
  /** Repassado a {@link MenuListComponent.searchable}: campo de pesquisa na lista da lateral. */
3442
3498
  searchable: boolean;
3499
+ /** Repassado a {@link MenuListComponent.limparAposPesquisar}. */
3500
+ limparAposPesquisar: boolean;
3443
3501
  /**
3444
3502
  * Repassado a {@link MenuListComponent.clickToOpen}: submenus na lateral abrem por **clique** (`true`, omissão)
3445
3503
  * ou por **hover** (`false`).
@@ -3455,6 +3513,7 @@ declare class AppSidebarComponent<T extends string = string> implements OnChange
3455
3513
  */
3456
3514
  groupDefaultExpansion: 'all' | 'first';
3457
3515
  readonly itemSelect: EventEmitter<T>;
3516
+ private menuList?;
3458
3517
  /**
3459
3518
  * Valor efectivo de `compressText` em `app-menu-list`: ao expandir a partir do modo rail,
3460
3519
  * mantém-se comprimido até a animação de largura terminar (evita flicker de texto).
@@ -3464,14 +3523,29 @@ declare class AppSidebarComponent<T extends string = string> implements OnChange
3464
3523
  readonly listCompressText: i0.WritableSignal<boolean>;
3465
3524
  private listRevealTimeoutId;
3466
3525
  private scrollRestoreTimeoutId;
3526
+ /** Invalida tentativas de scroll pendentes (ex.: novo agendamento ou destroy). */
3527
+ private scrollToSelectedGeneration;
3528
+ /** `setTimeout` extra ao abrir o drawer — animação (~240ms) e flex podem atrasar métricas do scroll. */
3529
+ private drawerScrollAssistTimeouts;
3467
3530
  constructor();
3468
3531
  ngAfterViewInit(): void;
3469
3532
  ngOnChanges(changes: SimpleChanges): void;
3470
3533
  /**
3471
- * Garante que o item `--selected` fica inteiro na área útil (margem + cabeçalho sticky),
3472
- * com da opção vizinha visível quando scroll, e movimento suave.
3534
+ * Agenda scroll até ao `--selected` depois do próximo render (filhos OnPush) e reintenta em `rAF`
3535
+ * até o elemento existir necessário no drawer mobile, onde o menu acaba de montar.
3536
+ */
3537
+ /**
3538
+ * Repete o scroll quando o utilizador clica num item já activo mas «cortado» (`selectedItemId` não muda).
3473
3539
  */
3474
- private scrollSidebarToSelectedItem;
3540
+ onSidebarItemSelect(id: T): void;
3541
+ /** Também ligado a `(selectionScrollHint)` em `app-menu-list` para o drawer mobile. */
3542
+ scheduleScrollToSelected(): void;
3543
+ private static readonly SCROLL_TO_SELECTED_MAX_ATTEMPTS;
3544
+ private static readonly SCROLL_TO_SELECTED_MAX_ATTEMPTS_DRAWER;
3545
+ /** Sidebar projectada em `.app-drawer__body` — animação do painel e `overflow` do shell afectam o layout. */
3546
+ private isInsideMobileDrawer;
3547
+ private clearDrawerScrollAssistTimeouts;
3548
+ private runScrollToSelectedRetry;
3475
3549
  /** Após clicar num grupo colapsável: leva o cabeçalho do grupo para a zona visível (como o item seleccionado). */
3476
3550
  onMenuGroupToggled(groupId: number): void;
3477
3551
  /** Área com scroll dos itens (`cdkScrollable`), não o cabeçalho nem a pesquisa. */
@@ -3485,9 +3559,11 @@ declare class AppSidebarComponent<T extends string = string> implements OnChange
3485
3559
  private scheduleSidebarHeadLayoutGuards;
3486
3560
  private applySidebarHeadLayoutGuards;
3487
3561
  static ɵfac: i0.ɵɵFactoryDeclaration<AppSidebarComponent<any>, never>;
3488
- static ɵcmp: i0.ɵɵComponentDeclaration<AppSidebarComponent<any>, "app-sidebar", never, { "appTheme": { "alias": "appTheme"; "required": false; "isSignal": true; }; "items": { "alias": "items"; "required": true; }; "menuId": { "alias": "menuId"; "required": false; }; "selectedItemId": { "alias": "selectedItemId"; "required": false; }; "collapsed": { "alias": "collapsed"; "required": false; }; "searchable": { "alias": "searchable"; "required": false; }; "clickToOpen": { "alias": "clickToOpen"; "required": false; }; "groupCollapsible": { "alias": "groupCollapsible"; "required": false; }; "groupDefaultExpansion": { "alias": "groupDefaultExpansion"; "required": false; }; }, { "itemSelect": "itemSelect"; }, never, ["[appSidebarBrand]", "[appSidebarToolbar]"], true, never>;
3562
+ static ɵcmp: i0.ɵɵComponentDeclaration<AppSidebarComponent<any>, "app-sidebar", never, { "appTheme": { "alias": "appTheme"; "required": false; "isSignal": true; }; "items": { "alias": "items"; "required": true; }; "menuId": { "alias": "menuId"; "required": false; }; "selectedItemId": { "alias": "selectedItemId"; "required": false; }; "collapsed": { "alias": "collapsed"; "required": false; }; "mobileNavOpenPulse": { "alias": "mobileNavOpenPulse"; "required": false; }; "searchable": { "alias": "searchable"; "required": false; }; "limparAposPesquisar": { "alias": "limparAposPesquisar"; "required": false; }; "clickToOpen": { "alias": "clickToOpen"; "required": false; }; "groupCollapsible": { "alias": "groupCollapsible"; "required": false; }; "groupDefaultExpansion": { "alias": "groupDefaultExpansion"; "required": false; }; }, { "itemSelect": "itemSelect"; }, never, ["[appSidebarBrand]", "[appSidebarToolbar]"], true, never>;
3489
3563
  static ngAcceptInputType_collapsed: unknown;
3564
+ static ngAcceptInputType_mobileNavOpenPulse: unknown;
3490
3565
  static ngAcceptInputType_searchable: unknown;
3566
+ static ngAcceptInputType_limparAposPesquisar: unknown;
3491
3567
  static ngAcceptInputType_groupCollapsible: unknown;
3492
3568
  }
3493
3569