From cfc2398409e82ab15e0f88a2aca0cbd2e5175c07 Mon Sep 17 00:00:00 2001 From: "ana.espejo" Date: Wed, 8 Jan 2025 12:01:27 -0300 Subject: [PATCH] feat(components): implementa help adicional ao p-help Implementa help adicional ao p-help, permitindo exibir um tooltip ou disparar um evento. fixes DTHFUI-10443 --- .../po-page-dynamic-edit-field.interface.ts | 2 + ...ple-po-page-dynamic-edit-user.component.ts | 9 +- .../po-dynamic-form-field.interface.ts | 37 ++- .../po-dynamic-form-fields.component.html | 49 +++- ...ple-po-dynamic-form-container.component.ts | 12 +- .../po-checkbox-group-base.component.ts | 38 +++ .../po-checkbox-group.component.html | 13 +- .../po-checkbox-group.component.spec.ts | 53 +++- .../po-checkbox-group.component.ts | 21 ++ ...mple-po-checkbox-group-labs.component.html | 92 +++---- ...sample-po-checkbox-group-labs.component.ts | 2 + .../po-checkbox/po-checkbox-base.component.ts | 45 ++++ .../po-checkbox/po-checkbox.component.html | 78 +++--- .../po-checkbox/po-checkbox.component.spec.ts | 70 ++++++ .../po-checkbox/po-checkbox.component.ts | 21 ++ .../po-checkbox/po-checkbox.module.ts | 3 +- .../sample-po-checkbox-labs.component.html | 13 + .../sample-po-checkbox-labs.component.ts | 4 + .../po-combo/po-combo-base.component.ts | 33 ++- .../po-field/po-combo/po-combo.component.html | 5 +- .../po-combo/po-combo.component.spec.ts | 49 ++++ .../po-field/po-combo/po-combo.component.ts | 21 ++ .../sample-po-combo-labs.component.html | 67 ++--- .../sample-po-combo-labs.component.ts | 2 + .../po-datepicker-range-base.component.ts | 38 +++ .../po-datepicker-range.component.html | 5 + .../po-datepicker-range.component.spec.ts | 69 ++++++ .../po-datepicker-range.component.ts | 21 ++ ...le-po-datepicker-range-labs.component.html | 102 ++++---- ...mple-po-datepicker-range-labs.component.ts | 2 + .../po-datepicker-base.component.ts | 37 ++- .../po-datepicker.component.html | 6 +- .../po-datepicker.component.spec.ts | 49 ++++ .../po-datepicker/po-datepicker.component.ts | 21 ++ .../sample-po-datepicker-labs.component.html | 149 ++++++----- .../sample-po-datepicker-labs.component.ts | 2 + .../po-decimal/po-decimal.component.html | 9 +- .../sample-po-decimal-labs.component.html | 122 +++++---- .../sample-po-decimal-labs.component.ts | 2 + .../sample-po-email-labs.component.html | 51 ++-- .../sample-po-email-labs.component.ts | 2 + .../po-field-container-bottom.component.html | 19 +- .../po-field-container-bottom.component.ts | 18 +- .../po-field/po-field-validate.model.ts | 2 - .../po-field/po-field.model.spec.ts | 49 ++++ .../lib/components/po-field/po-field.model.ts | 58 +++++ .../po-input/po-input-base.component.spec.ts | 49 ++++ .../po-input/po-input-base.component.ts | 61 ++++- .../po-field/po-input/po-input.component.html | 6 +- .../sample-po-input-labs.component.html | 97 ++++---- .../sample-po-input-labs.component.ts | 2 + .../po-field/po-login/po-login.component.html | 4 + .../sample-po-login-labs.component.html | 85 +++---- .../sample-po-login-labs.component.ts | 2 + .../po-lookup/po-lookup-base.component.ts | 51 +++- .../po-lookup/po-lookup.component.html | 5 +- .../po-lookup/po-lookup.component.spec.ts | 49 ++++ .../po-field/po-lookup/po-lookup.component.ts | 21 ++ .../sample-po-lookup-labs.component.html | 231 +++++++++--------- .../sample-po-lookup-labs.component.ts | 2 + .../po-multiselect-base.component.ts | 35 ++- .../po-multiselect.component.html | 5 +- .../po-multiselect.component.spec.ts | 49 ++++ .../po-multiselect.component.ts | 21 ++ .../sample-po-multiselect-labs.component.html | 146 ++++++----- .../sample-po-multiselect-labs.component.ts | 2 + .../po-number/po-number.component.html | 6 +- .../sample-po-number-labs.component.html | 80 +++--- .../sample-po-number-labs.component.ts | 2 + .../po-password/po-password.component.html | 6 +- .../sample-po-password-labs.component.html | 71 +++--- .../sample-po-password-labs.component.ts | 2 + .../po-radio-group-base.component.ts | 38 +++ .../po-radio-group.component.html | 16 +- .../po-radio-group.component.spec.ts | 49 ++++ .../po-radio-group.component.ts | 21 ++ .../sample-po-radio-group-labs.component.html | 90 ++++--- .../sample-po-radio-group-labs.component.ts | 4 +- .../po-rich-text-base.component.ts | 35 +++ .../po-rich-text/po-rich-text.component.html | 7 +- .../sample-po-rich-text-labs.component.html | 68 +++--- .../sample-po-rich-text-labs.component.ts | 3 +- .../po-select/po-select.component.html | 6 +- .../sample-po-select-labs.component.html | 73 +++--- .../sample-po-select-labs.component.ts | 2 + .../po-switch/po-switch.component.html | 9 +- .../sample-po-switch-labs.component.html | 91 +++---- .../sample-po-switch-labs.component.ts | 2 + .../po-textarea/po-textarea-base.component.ts | 38 +++ .../po-textarea/po-textarea.component.html | 5 + .../po-textarea/po-textarea.component.spec.ts | 70 ++++++ .../po-textarea/po-textarea.component.ts | 21 ++ .../sample-po-textarea-labs.component.html | 66 ++--- .../sample-po-textarea-labs.component.ts | 2 + .../po-upload/po-upload-base.component.ts | 38 +++ .../po-upload/po-upload.component.html | 9 +- .../po-upload/po-upload.component.spec.ts | 49 ++++ .../po-field/po-upload/po-upload.component.ts | 21 ++ .../sample-po-upload-labs.component.html | 86 +++---- .../sample-po-upload-labs.component.ts | 2 + .../sample-po-url-labs.component.html | 51 ++-- .../sample-po-url-labs.component.ts | 3 +- 102 files changed, 2629 insertions(+), 1008 deletions(-) diff --git a/projects/templates/src/lib/components/po-page-dynamic-edit/interfaces/po-page-dynamic-edit-field.interface.ts b/projects/templates/src/lib/components/po-page-dynamic-edit/interfaces/po-page-dynamic-edit-field.interface.ts index 5389e1fc6..89c1b9633 100644 --- a/projects/templates/src/lib/components/po-page-dynamic-edit/interfaces/po-page-dynamic-edit-field.interface.ts +++ b/projects/templates/src/lib/components/po-page-dynamic-edit/interfaces/po-page-dynamic-edit-field.interface.ts @@ -6,6 +6,8 @@ import { PoDynamicFormField } from '@po-ui/ng-components'; * @description * * Interface dos fields usados para compor o template `po-page-dynamic-edit`. + * Herda as definições da interface + * [PoDynamicFormField](https://po-ui.io/documentation/po-dynamic-form). */ export interface PoPageDynamicEditField extends PoDynamicFormField { /** Indica se o campo será duplicado caso seja executada a ação de duplicação. */ diff --git a/projects/templates/src/lib/components/po-page-dynamic-edit/samples/sample-po-page-dynamic-edit-user/sample-po-page-dynamic-edit-user.component.ts b/projects/templates/src/lib/components/po-page-dynamic-edit/samples/sample-po-page-dynamic-edit-user/sample-po-page-dynamic-edit-user.component.ts index 04c579c6f..015785562 100644 --- a/projects/templates/src/lib/components/po-page-dynamic-edit/samples/sample-po-page-dynamic-edit-user/sample-po-page-dynamic-edit-user.component.ts +++ b/projects/templates/src/lib/components/po-page-dynamic-edit/samples/sample-po-page-dynamic-edit-user/sample-po-page-dynamic-edit-user.component.ts @@ -36,7 +36,14 @@ export class SamplePoPageDynamicEditUserComponent { { property: 'name', divider: 'Personal data', required: true }, { property: 'nickname' }, { property: 'email', label: 'E-mail' }, - { property: 'birthdate', label: 'Birth date', type: 'date' }, + { + property: 'birthdate', + label: 'Birth date', + type: 'date', + errorMessage: 'Invalid date.', + help: 'Enter or select a valid date.', + additionalHelpTooltip: 'Please enter a valid date in the format MMDDYYYY.' + }, { property: 'genre', options: ['female', 'male', 'others'], gridLgColumns: 6 }, { property: 'nationality' }, { property: 'birthPlace', label: 'Place of birth' }, diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/po-dynamic-form-field.interface.ts b/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/po-dynamic-form-field.interface.ts index 68d391ca3..5cb2af4b4 100644 --- a/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/po-dynamic-form-field.interface.ts +++ b/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/po-dynamic-form-field.interface.ts @@ -36,6 +36,41 @@ import { PoDynamicField } from '../po-dynamic-field.interface'; * Interface para definição das propriedades dos campos de entrada que serão criados dinamicamente. */ export interface PoDynamicFormField extends PoDynamicField { + /** + * Evento disparado ao clicar no ícone de ajuda adicional. + * Este evento ativa automaticamente a exibição do ícone de ajuda adicional ao `p-help`. + * + * **Componentes compatíveis:** `po-checkbox`, `po-checkbox-group`, `po-combo`, `po-datepicker`, `po-datepicker-range`, + * `po-decimal`, `po-email`, `po-input`, `po-login`, `po-lookup`, `po-multiselect`, `po-number`, `po-password`, + * `po-radio-group`, `po-rich-text`, `po-select`, `po-switch`, `po-textarea`, `po-upload`, `po-url`. + */ + additionalHelp?: Function; + + /** + * Exibe um ícone de ajuda adicional ao `p-help`, com o texto desta propriedade no tooltip. + * Se o evento `p-additional-help` estiver definido, o tooltip não será exibido. + * **Como boa prática, indica-se utilizar um texto com até 140 caracteres.** + * + * **Componentes compatíveis:** `po-checkbox`, `po-checkbox-group`, `po-combo`, `po-datepicker`, `po-datepicker-range`, + * `po-decimal`, `po-email`, `po-input`, `po-login`, `po-lookup`, `po-multiselect`, `po-number`, `po-password`, + * `po-radio-group`, `po-rich-text`, `po-select`, `po-switch`, `po-textarea`, `po-upload`, `po-url`. + */ + additionalHelpTooltip?: string; + + /** + * Define que o `listbox` e/ou tooltip (`p-additional-help-tooltip` e/ou `p-error-limit`) serão incluídos no body da + * página e não dentro do componente. Essa opção é necessária para cenários com containers que possuem scroll ou + * overflow escondido, garantindo o posicionamento correto de ambos próximo ao elemento. + * + * > O uso dessa propriedade pode acarretar na perda sequencial da tabulação da página. + * Quando utilizado com `p-additional-help-tooltip`, leitores de tela como o NVDA podem não ler o conteúdo do tooltip. + * + * **Componentes compatíveis:** `po-checkbox`, `po-checkbox-group`, `po-combo`, `po-datepicker`, `po-datepicker-range`, + * `po-decimal`, `po-email`, `po-input`, `po-login`, `po-lookup`, `po-multiselect`, `po-number`, `po-password`, + * `po-radio-group`, `po-rich-text`, `po-select`, `po-switch`, `po-textarea`, `po-upload`, `po-url`. + */ + appendBox?: boolean; + /** * Define as colunas para utilização da busca avançada. Usada somente em conjunto com a propriedade `searchService`, * essa propriedade deve receber um array de objetos que implementam a interface [`PoLookupColumn`](/documentation/po-lookup). @@ -298,8 +333,6 @@ export interface PoDynamicFormField extends PoDynamicField { */ errorLimit?: boolean; - errorAppendBox?: boolean; - /** * Função executada para realizar a validação assíncrona personalizada. * Executada ao disparar o output `change` ou `change-model`, dependendo do valor da propriedade `triggerMode`. diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/po-dynamic-form-fields/po-dynamic-form-fields.component.html b/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/po-dynamic-form-fields/po-dynamic-form-fields.component.html index dae2c551b..375947af0 100644 --- a/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/po-dynamic-form-fields/po-dynamic-form-fields.component.html +++ b/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/po-dynamic-form-fields/po-dynamic-form-fields.component.html @@ -10,13 +10,14 @@ - @@ -47,6 +49,8 @@ [name]="field.property" [(ngModel)]="value[field.property]" [ngClass]="field.componentClass" + [additionalHelpEventTrigger]="field.additionalHelp ? 'event' : 'noEvent'" + [p-additional-help-tooltip]="field.additionalHelpTooltip" [p-clean]="field.clean" [p-disabled]="isDisabled(field)" [p-auto-focus]="field.focus" @@ -62,6 +66,7 @@ [p-field-error-message]="field.errorMessage" [p-error-limit]="field.errorLimit" [p-show-required]="field.showRequired" + (p-additional-help)="field.additionalHelp?.($event)" (p-change)="onChangeField(field)" > @@ -72,6 +77,8 @@ [name]="field.property" [(ngModel)]="value[field.property]" [ngClass]="field.componentClass" + [additionalHelpEventTrigger]="field.additionalHelp ? 'event' : 'noEvent'" + [p-additional-help-tooltip]="field.additionalHelpTooltip" [p-clean]="field.clean" [p-disabled]="isDisabled(field)" [p-error-pattern]="field.errorMessage" @@ -90,6 +97,7 @@ [p-required]="field.required" [p-required-field-error-message]="field.requiredFieldErrorMessage" [p-show-required]="field.showRequired" + (p-additional-help)="field.additionalHelp?.($event)" (p-change)="onChangeField(field)" (p-change-model)="onChangeFieldModel(field)" [p-icon]="field.icon" @@ -104,6 +112,8 @@ [name]="field.property" [(ngModel)]="value[field.property]" [ngClass]="field.componentClass" + [additionalHelpEventTrigger]="field.additionalHelp ? 'event' : 'noEvent'" + [p-additional-help-tooltip]="field.additionalHelpTooltip" [p-clean]="field.clean" [p-disabled]="isDisabled(field)" [p-error-pattern]="field.errorMessage" @@ -123,6 +133,7 @@ [p-required]="field.required" [p-required-field-error-message]="field.requiredFieldErrorMessage" [p-show-required]="field.showRequired" + (p-additional-help)="field.additionalHelp?.($event)" (p-change)="onChangeField(field)" (p-change-model)="onChangeFieldModel(field)" [p-icon]="field.icon" @@ -136,6 +147,8 @@ [name]="field.property" [(ngModel)]="value[field.property]" [ngClass]="field.componentClass" + [additionalHelpEventTrigger]="field.additionalHelp ? 'event' : 'noEvent'" + [p-additional-help-tooltip]="field.additionalHelpTooltip" [p-clean]="field.clean" [p-error-pattern]="field.errorMessage" [p-error-limit]="field.errorLimit" @@ -158,6 +171,7 @@ [p-required]="field.required" [p-required-field-error-message]="field.requiredFieldErrorMessage" [p-show-required]="field.showRequired" + (p-additional-help)="field.additionalHelp?.($event)" (p-change)="onChangeField(field)" (p-change-model)="onChangeFieldModel(field)" [p-placeholder]="field.placeholder" @@ -170,6 +184,8 @@ [name]="field.property" [(ngModel)]="value[field.property]" [ngClass]="field.componentClass" + [additionalHelpEventTrigger]="field.additionalHelp ? 'event' : 'noEvent'" + [p-additional-help-tooltip]="field.additionalHelpTooltip" [p-field-label]="field.fieldLabel" [p-field-value]="field.fieldValue" [p-disabled]="isDisabled(field)" @@ -181,6 +197,7 @@ [p-field-error-message]="field.errorMessage" [p-error-limit]="field.errorLimit" [p-show-required]="field.showRequired" + (p-additional-help)="field.additionalHelp?.($event)" (p-change)="onChangeField(field)" [p-placeholder]="field.placeholder" [p-readonly]="field.readonly" @@ -193,6 +210,8 @@ [name]="field.property" [(ngModel)]="value[field.property]" [ngClass]="field.componentClass" + [additionalHelpEventTrigger]="field.additionalHelp ? 'event' : 'noEvent'" + [p-additional-help-tooltip]="field.additionalHelpTooltip" [p-columns]="field.columns || 3" [p-auto-focus]="field.focus" [p-disabled]="isDisabled(field)" @@ -204,6 +223,7 @@ [p-field-error-message]="field.errorMessage" [p-error-limit]="field.errorLimit" [p-show-required]="field.showRequired" + (p-additional-help)="field.additionalHelp?.($event)" (p-change)="onChangeField(field)" > @@ -214,6 +234,8 @@ [name]="field.property" [(ngModel)]="value[field.property]" [ngClass]="field.componentClass" + [additionalHelpEventTrigger]="field.additionalHelp ? 'event' : 'noEvent'" + [p-additional-help-tooltip]="field.additionalHelpTooltip" [p-disabled]="isDisabled(field)" [p-format-model]="field.formatModel" [p-help]="field.help" @@ -222,6 +244,7 @@ [p-label-on]="field.booleanTrue" [p-label-position]="field.labelPosition" [p-hide-label-status]="field.hideLabelStatus" + (p-additional-help)="field.additionalHelp?.($event)" (p-change)="onChangeField(field)" > @@ -232,10 +255,13 @@ [name]="field.property" [(ngModel)]="value[field.property]" [ngClass]="field.componentClass" + [additionalHelpEventTrigger]="field.additionalHelp ? 'event' : 'noEvent'" + [p-additional-help-tooltip]="field.additionalHelpTooltip" [p-auto-focus]="field.focus" [p-disabled]="isDisabled(field)" [p-label]="field.label" [p-size]="field.size" + (p-additional-help)="field.additionalHelp?.($event)" (p-change)="onChangeField(field)" > @@ -247,6 +273,8 @@ [(ngModel)]="value[field.property]" [ngClass]="field.componentClass" p-emit-object-value + [additionalHelpEventTrigger]="field.additionalHelp ? 'event' : 'noEvent'" + [p-additional-help-tooltip]="field.additionalHelpTooltip" [p-auto-focus]="field.focus" [p-clean]="field.clean" [p-disabled]="isDisabled(field)" @@ -275,6 +303,7 @@ [p-disabled-tab-filter]="field.disabledTabFilter" [p-debounce-time]="field.debounceTime" [p-change-on-enter]="field.changeOnEnter" + (p-additional-help)="field.additionalHelp?.($event)" > @@ -286,6 +315,8 @@ p-field-label="label" p-field-value="value" [ngClass]="field.componentClass" + [additionalHelpEventTrigger]="field.additionalHelp ? 'event' : 'noEvent'" + [p-additional-help-tooltip]="field.additionalHelpTooltip" [p-clean]="field.clean" [p-columns]="field.columns" [p-disabled]="isDisabled(field)" @@ -310,6 +341,7 @@ (p-change)="onChangeField(field)" [p-placeholder]="field.placeholder" [p-advanced-filters]="field.advancedFilters" + (p-additional-help)="field.additionalHelp?.($event)" (p-change-visible-columns)="field.changeVisibleColumns?.($event)" (p-restore-column-manager)="field.columnRestoreManager?.($event)" > @@ -321,6 +353,8 @@ [name]="field.property" [(ngModel)]="value[field.property]" [ngClass]="field.componentClass" + [additionalHelpEventTrigger]="field.additionalHelp ? 'event' : 'noEvent'" + [p-additional-help-tooltip]="field.additionalHelpTooltip" [p-columns]="field.columns || 3" [p-auto-focus]="field.focus" [p-disabled]="isDisabled(field)" @@ -332,6 +366,7 @@ [p-show-required]="field.showRequired" [p-field-error-message]="field.errorMessage" [p-error-limit]="field.errorLimit" + (p-additional-help)="field.additionalHelp?.($event)" (p-change)="onChangeField(field)" > @@ -342,6 +377,8 @@ [name]="field.property" [(ngModel)]="value[field.property]" [ngClass]="field.componentClass" + [additionalHelpEventTrigger]="field.additionalHelp ? 'event' : 'noEvent'" + [p-additional-help-tooltip]="field.additionalHelpTooltip" [p-disabled]="isDisabled(field)" [p-auto-focus]="field.focus" [p-auto-height]="field.autoHeight" @@ -365,6 +402,7 @@ [p-placeholder-search]="field.placeholderSearch" [p-hide-search]="field.hideSearch" [p-hide-select-all]="field.hideSelectAll" + (p-additional-help)="field.additionalHelp?.($event)" > @@ -374,6 +412,8 @@ [name]="field.property" [(ngModel)]="value[field.property]" [ngClass]="field.componentClass" + [additionalHelpEventTrigger]="field.additionalHelp ? 'event' : 'noEvent'" + [p-additional-help-tooltip]="field.additionalHelpTooltip" [p-disabled]="isDisabled(field)" [p-auto-focus]="field.focus" [p-help]="field.help" @@ -387,6 +427,7 @@ [p-error-limit]="field.errorLimit" [p-show-required]="field.showRequired" [p-rows]="field.rows" + (p-additional-help)="field.additionalHelp?.($event)" (p-change)="onChangeField(field)" (p-change-model)="onChangeFieldModel(field)" [p-placeholder]="field.placeholder" @@ -399,6 +440,8 @@ [name]="field.property" [(ngModel)]="value[field.property]" [ngClass]="field.componentClass" + [additionalHelpEventTrigger]="field.additionalHelp ? 'event' : 'noEvent'" + [p-additional-help-tooltip]="field.additionalHelpTooltip" [p-clean]="field.clean" [p-disabled]="isDisabled(field)" [p-error-pattern]="field.errorMessage" @@ -418,6 +461,7 @@ [p-required]="field.required" [p-required-field-error-message]="field.requiredFieldErrorMessage" [p-show-required]="field.showRequired" + (p-additional-help)="field.additionalHelp?.($event)" (p-change)="onChangeField(field)" (p-change-model)="onChangeFieldModel(field)" [p-placeholder]="field.placeholder" @@ -429,6 +473,8 @@ *ngIf="compareTo(field.control, 'upload')" [(ngModel)]="value[field.property]" [ngClass]="field.componentClass" + [additionalHelpEventTrigger]="field.additionalHelp ? 'event' : 'noEvent'" + [p-additional-help-tooltip]="field.additionalHelpTooltip" [p-auto-upload]="field.autoUpload" [p-directory]="field.directory" [p-disabled]="isDisabled(field)" @@ -453,6 +499,7 @@ [p-optional]="field.optional" [p-required]="field.required" [p-show-required]="field.showRequired" + (p-additional-help)="field.additionalHelp?.($event)" (p-change)="onChangeField(field)" (p-change-model)="onChangeFieldModel(field)" [p-url]="field.url" diff --git a/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/samples/sample-po-dynamic-form-container/sample-po-dynamic-form-container.component.ts b/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/samples/sample-po-dynamic-form-container/sample-po-dynamic-form-container.component.ts index 62b6c2bf1..24c2ccd8b 100644 --- a/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/samples/sample-po-dynamic-form-container/sample-po-dynamic-form-container.component.ts +++ b/projects/ui/src/lib/components/po-dynamic/po-dynamic-form/samples/sample-po-dynamic-form-container/sample-po-dynamic-form-container.component.ts @@ -40,7 +40,9 @@ export class SamplePoDynamicFormContainerComponent implements OnInit { gridSmColumns: 12, maxValue: '2010-01-01', errorMessage: 'The date must be before the year 2010.', - order: -1 + order: -1, + help: 'Enter or select a valid date.', + additionalHelpTooltip: 'Please enter a valid date in the format MMDDYYYY.' }, { property: 'cpf', label: 'CPF', mask: '999.999.999-99', gridColumns: 6, gridSmColumns: 12, visible: false }, { property: 'cnpj', label: 'CNPJ', mask: '99.999.999/9999-99', gridColumns: 6, gridSmColumns: 12, visible: false }, @@ -60,7 +62,9 @@ export class SamplePoDynamicFormContainerComponent implements OnInit { secret: true, pattern: '[a-zA]{5}[Z0-9]{3}', errorMessage: 'At least 5 alphabetic and 3 numeric characters are required.', - placeholder: 'Type your password' + placeholder: 'Type your password', + help: 'Password must include a combination of letters and numbers.', + additionalHelpTooltip: 'At least 5 alphabetic and 3 numeric characters are required.' }, { property: 'rememberSecretKey', @@ -110,7 +114,9 @@ export class SamplePoDynamicFormContainerComponent implements OnInit { container: 'Work data', range: true, gridColumns: 5, - gridSmColumns: 12 + gridSmColumns: 12, + help: 'Enter or select a valid date range.', + additionalHelpTooltip: 'Ensure the start date is earlier than or equal to the end date.' }, { property: 'entryTime', diff --git a/projects/ui/src/lib/components/po-field/po-checkbox-group/po-checkbox-group-base.component.ts b/projects/ui/src/lib/components/po-field/po-checkbox-group/po-checkbox-group-base.component.ts index 7d76f6e76..857ca7b6e 100644 --- a/projects/ui/src/lib/components/po-field/po-checkbox-group/po-checkbox-group-base.component.ts +++ b/projects/ui/src/lib/components/po-field/po-checkbox-group/po-checkbox-group-base.component.ts @@ -41,6 +41,35 @@ const poCheckboxGroupColumnsTotalLength: number = 12; */ @Directive() export class PoCheckboxGroupBaseComponent implements ControlValueAccessor, Validator { + // Propriedade interna que define se o ícone de ajuda adicional terá cursor clicável (evento) ou padrão (tooltip). + @Input() additionalHelpEventTrigger: string | undefined; + + /** + * @optional + * + * @description + * Exibe um ícone de ajuda adicional ao `p-help`, com o texto desta propriedade no tooltip. + * Se o evento `p-additional-help` estiver definido, o tooltip não será exibido. + * **Como boa prática, indica-se utilizar um texto com até 140 caracteres.** + * > Requer um recuo mínimo de 8px se o componente estiver próximo à lateral da tela. + */ + @Input('p-additional-help-tooltip') additionalHelpTooltip?: string; + + /** + * @optional + * + * @description + * + * Define que o tooltip (`p-additional-help-tooltip` e/ou `p-error-limit`) será incluído no body da página e não + * dentro do componente. Essa opção pode ser necessária em cenários com containers que possuem scroll ou overflow + * escondido, garantindo o posicionamento correto do tooltip próximo ao elemento. + * + * > Quando utilizado com `p-additional-help-tooltip`, leitores de tela como o NVDA podem não ler o conteúdo do tooltip. + * + * @default `false` + */ + @Input({ alias: 'p-append-in-body', transform: convertToBoolean }) appendBox?: boolean = false; + /** * @optional * @@ -90,6 +119,15 @@ export class PoCheckboxGroupBaseComponent implements ControlValueAccessor, Valid */ @Input('p-field-error-message') fieldErrorMessage: string; + /** + * @optional + * + * @description + * Evento disparado ao clicar no ícone de ajuda adicional. + * Este evento ativa automaticamente a exibição do ícone de ajuda adicional ao `p-help`. + */ + @Output('p-additional-help') additionalHelp = new EventEmitter(); + /** * @optional * diff --git a/projects/ui/src/lib/components/po-field/po-checkbox-group/po-checkbox-group.component.html b/projects/ui/src/lib/components/po-field/po-checkbox-group/po-checkbox-group.component.html index 5ed223bed..0e42d1219 100644 --- a/projects/ui/src/lib/components/po-field/po-checkbox-group/po-checkbox-group.component.html +++ b/projects/ui/src/lib/components/po-field/po-checkbox-group/po-checkbox-group.component.html @@ -1,13 +1,12 @@ -
-
    +
    +
    • diff --git a/projects/ui/src/lib/components/po-field/po-checkbox-group/po-checkbox-group.component.spec.ts b/projects/ui/src/lib/components/po-field/po-checkbox-group/po-checkbox-group.component.spec.ts index 9817f8e2f..e178405f9 100644 --- a/projects/ui/src/lib/components/po-field/po-checkbox-group/po-checkbox-group.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-checkbox-group/po-checkbox-group.component.spec.ts @@ -50,10 +50,6 @@ describe('PoCheckboxGroupComponent:', () => { expect(component).toBeTruthy(); }); - it('should have help', () => { - expect(nativeElement.querySelector('.po-field-help').innerHTML).toContain('Help'); - }); - it('should create 2 checkbox options', () => { expect(nativeElement.querySelectorAll('po-checkbox').length).toBe(2); }); @@ -85,6 +81,26 @@ describe('PoCheckboxGroupComponent:', () => { expect(spyFocus).not.toHaveBeenCalled(); }); + describe('emitAdditionalHelp:', () => { + it('should emit additionalHelp when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).toHaveBeenCalled(); + }); + + it('should not emit additionalHelp when isAdditionalHelpEventTriggered returns false', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).not.toHaveBeenCalled(); + }); + }); + describe('focus:', () => { const checkbox1 = document.createElement('po-checkbox'); const checkbox2 = document.createElement('po-checkbox'); @@ -173,6 +189,35 @@ describe('PoCheckboxGroupComponent:', () => { }); }); + describe('getAdditionalHelpTooltip:', () => { + it('should return null when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeNull(); + }); + + it('should return additionalHelpTooltip when isAdditionalHelpEventTriggered returns false', () => { + const tooltip = 'Test Tooltip'; + component.additionalHelpTooltip = tooltip; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBe(tooltip); + }); + + it('should return undefined when additionalHelpTooltip is undefined and isAdditionalHelpEventTriggered returns false', () => { + component.additionalHelpTooltip = undefined; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeUndefined(); + }); + }); + describe('getErrorPattern:', () => { it('should return true in hasInvalidClass if fieldErrorMessage', () => { component['el'].nativeElement.classList.add('ng-invalid'); diff --git a/projects/ui/src/lib/components/po-field/po-checkbox-group/po-checkbox-group.component.ts b/projects/ui/src/lib/components/po-field/po-checkbox-group/po-checkbox-group.component.ts index 1f4b560a5..c5c40e6c7 100644 --- a/projects/ui/src/lib/components/po-field/po-checkbox-group/po-checkbox-group.component.ts +++ b/projects/ui/src/lib/components/po-field/po-checkbox-group/po-checkbox-group.component.ts @@ -71,6 +71,12 @@ export class PoCheckboxGroupComponent extends PoCheckboxGroupBaseComponent imple } } + emitAdditionalHelp() { + if (this.isAdditionalHelpEventTriggered()) { + this.additionalHelp.emit(); + } + } + /** * Função que atribui foco ao componente. * @@ -98,6 +104,10 @@ export class PoCheckboxGroupComponent extends PoCheckboxGroupBaseComponent imple } } + getAdditionalHelpTooltip() { + return this.isAdditionalHelpEventTriggered() ? null : this.additionalHelpTooltip; + } + getErrorPattern() { return this.fieldErrorMessage && this.hasInvalidClass() ? this.fieldErrorMessage : ''; } @@ -118,7 +128,18 @@ export class PoCheckboxGroupComponent extends PoCheckboxGroupBaseComponent imple } } + showAdditionalHelpIcon() { + return !!this.additionalHelpTooltip || this.isAdditionalHelpEventTriggered(); + } + trackByFn(index) { return index; } + + private isAdditionalHelpEventTriggered(): boolean { + return ( + this.additionalHelpEventTrigger === 'event' || + (this.additionalHelpEventTrigger === undefined && this.additionalHelp.observed) + ); + } } diff --git a/projects/ui/src/lib/components/po-field/po-checkbox-group/samples/sample-po-checkbox-group-labs/sample-po-checkbox-group-labs.component.html b/projects/ui/src/lib/components/po-field/po-checkbox-group/samples/sample-po-checkbox-group-labs/sample-po-checkbox-group-labs.component.html index 3520c7bfb..e07358277 100644 --- a/projects/ui/src/lib/components/po-field/po-checkbox-group/samples/sample-po-checkbox-group-labs/sample-po-checkbox-group-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-checkbox-group/samples/sample-po-checkbox-group-labs/sample-po-checkbox-group-labs.component.html @@ -1,6 +1,7 @@
      -
      - - + + - - + + - + - +
      + +

      -
      - + - -
      -
      - - -
      + -
      - - - - - -
      + + + + + + + + + + +
      - +
      diff --git a/projects/ui/src/lib/components/po-field/po-checkbox-group/samples/sample-po-checkbox-group-labs/sample-po-checkbox-group-labs.component.ts b/projects/ui/src/lib/components/po-field/po-checkbox-group/samples/sample-po-checkbox-group-labs/sample-po-checkbox-group-labs.component.ts index 1816600d1..546828768 100644 --- a/projects/ui/src/lib/components/po-field/po-checkbox-group/samples/sample-po-checkbox-group-labs/sample-po-checkbox-group-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-checkbox-group/samples/sample-po-checkbox-group-labs/sample-po-checkbox-group-labs.component.ts @@ -7,6 +7,7 @@ import { PoCheckboxGroupOption, PoRadioGroupOption } from '@po-ui/ng-components' templateUrl: './sample-po-checkbox-group-labs.component.html' }) export class SamplePoCheckboxGroupLabsComponent implements OnInit { + additionalHelpTooltip: string; checkboxGroup: object; columns: number; disabled: boolean; @@ -49,6 +50,7 @@ export class SamplePoCheckboxGroupLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.checkboxGroup = undefined; this.columns = undefined; this.disabled = false; diff --git a/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox-base.component.ts b/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox-base.component.ts index 437270785..4bb635c4d 100644 --- a/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox-base.component.ts +++ b/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox-base.component.ts @@ -47,6 +47,42 @@ import { PoCheckboxSize } from './po-checkbox-size.enum'; */ @Directive() export abstract class PoCheckboxBaseComponent implements ControlValueAccessor { + // Propriedade interna que define se o ícone de ajuda adicional terá cursor clicável (evento) ou padrão (tooltip). + @Input() additionalHelpEventTrigger: string | undefined; + + /** + * @optional + * + * @description + * Exibe um ícone de ajuda adicional ao `p-help`, com o texto desta propriedade no tooltip. + * Se o evento `p-additional-help` estiver definido, o tooltip não será exibido. + * **Como boa prática, indica-se utilizar um texto com até 140 caracteres.** + * > Requer um recuo mínimo de 8px se o componente estiver próximo à lateral da tela. + */ + @Input('p-additional-help-tooltip') additionalHelpTooltip?: string; + + /** + * @optional + * + * @description + * + * Define que o tooltip (`p-additional-help-tooltip`) será incluído no body da página e não dentro do componente. Essa + * opção pode ser necessária em cenários com containers que possuem scroll ou overflow escondido, garantindo o + * posicionamento correto do tooltip próximo ao elemento. + * + * > Quando utilizado com `p-additional-help-tooltip`, leitores de tela como o NVDA podem não ler o conteúdo do tooltip. + * + * @default `false` + */ + @Input({ alias: 'p-append-in-body', transform: convertToBoolean }) appendBox?: boolean = false; + + /** + * @optional + * + * @description + * Texto de apoio do campo */ + @Input('p-help') help?: string; + /** Define o nome do *checkbox*. */ @Input('name') name: string; @@ -66,6 +102,15 @@ export abstract class PoCheckboxBaseComponent implements ControlValueAccessor { /** Texto de exibição do *checkbox*. */ @Input('p-label') label?: string; + /** + * @optional + * + * @description + * Evento disparado ao clicar no ícone de ajuda adicional. + * Este evento ativa automaticamente a exibição do ícone de ajuda adicional ao `p-help`. + */ + @Output('p-additional-help') additionalHelp = new EventEmitter(); + /** * @optional * diff --git a/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox.component.html b/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox.component.html index 0d8ab133f..c653c8177 100644 --- a/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox.component.html +++ b/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox.component.html @@ -1,36 +1,48 @@ -
      - + + diff --git a/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox.component.spec.ts b/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox.component.spec.ts index 10a002a03..6c210d609 100644 --- a/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox.component.spec.ts @@ -78,6 +78,55 @@ describe('PoCheckboxComponent:', () => { }); }); + describe('emitAdditionalHelp:', () => { + it('should emit additionalHelp when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).toHaveBeenCalled(); + }); + + it('should not emit additionalHelp when isAdditionalHelpEventTriggered returns false', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).not.toHaveBeenCalled(); + }); + }); + + describe('getAdditionalHelpTooltip:', () => { + it('should return null when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeNull(); + }); + + it('should return additionalHelpTooltip when isAdditionalHelpEventTriggered returns false', () => { + const tooltip = 'Test Tooltip'; + component.additionalHelpTooltip = tooltip; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBe(tooltip); + }); + + it('should return undefined when additionalHelpTooltip is undefined and isAdditionalHelpEventTriggered returns false', () => { + component.additionalHelpTooltip = undefined; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeUndefined(); + }); + }); + describe('onKeyDown:', () => { let fakeEvent: any; @@ -149,6 +198,27 @@ describe('PoCheckboxComponent:', () => { expect(component['changeDetector'].detectChanges).toHaveBeenCalled(); }); + describe('isAdditionalHelpEventTriggered:', () => { + it('should return true when additionalHelpEventTrigger is "event"', () => { + component.additionalHelpEventTrigger = 'event'; + expect((component as any).isAdditionalHelpEventTriggered()).toBeTrue(); + }); + + it('should return true when additionalHelpEventTrigger is undefined and additionalHelp is observed', () => { + component.additionalHelpEventTrigger = undefined; + component.additionalHelp = { + observed: true + } as any; + + expect((component as any).isAdditionalHelpEventTriggered()).toBeTrue(); + }); + + it('should return false when additionalHelpEventTrigger is not "event" and additionalHelp is not observed', () => { + component.additionalHelpEventTrigger = 'noEvent'; + expect((component as any).isAdditionalHelpEventTriggered()).toBeFalse(); + }); + }); + it('onBlur: should call `onTouched` on blur', () => { component.onTouched = value => {}; diff --git a/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox.component.ts b/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox.component.ts index dad77ad81..7ee2e17ba 100644 --- a/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox.component.ts +++ b/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox.component.ts @@ -97,6 +97,16 @@ export class PoCheckboxComponent extends PoCheckboxBaseComponent implements Afte } } + emitAdditionalHelp() { + if (this.isAdditionalHelpEventTriggered()) { + this.additionalHelp.emit(); + } + } + + getAdditionalHelpTooltip() { + return this.isAdditionalHelpEventTriggered() ? null : this.additionalHelpTooltip; + } + onKeyDown(event: KeyboardEvent, value: boolean | string) { if (event.which === PoKeyCodeEnum.space || event.keyCode === PoKeyCodeEnum.space) { this.checkOption(value); @@ -105,6 +115,10 @@ export class PoCheckboxComponent extends PoCheckboxBaseComponent implements Afte } } + showAdditionalHelpIcon() { + return !!this.additionalHelpTooltip || this.isAdditionalHelpEventTriggered(); + } + protected changeModelValue(value: boolean | null | string) { if (value === null) { this.checkboxValue = 'mixed'; @@ -114,6 +128,13 @@ export class PoCheckboxComponent extends PoCheckboxBaseComponent implements Afte this.changeDetector.detectChanges(); } + private isAdditionalHelpEventTriggered(): boolean { + return ( + this.additionalHelpEventTrigger === 'event' || + (this.additionalHelpEventTrigger === undefined && this.additionalHelp.observed) + ); + } + get iconNameLib() { return this._iconToken.NAME_LIB; } diff --git a/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox.module.ts b/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox.module.ts index 87adefe1b..8e49766d1 100644 --- a/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox.module.ts +++ b/projects/ui/src/lib/components/po-field/po-checkbox/po-checkbox.module.ts @@ -4,10 +4,11 @@ import { CommonModule } from '@angular/common'; import { PoCheckboxComponent } from './po-checkbox.component'; import { PoLabelModule } from '../../po-label/po-label.module'; +import { PoFieldContainerModule } from '../po-field-container/po-field-container.module'; @NgModule({ declarations: [PoCheckboxComponent], exports: [PoCheckboxComponent], - imports: [CommonModule, FormsModule, PoLabelModule] + imports: [CommonModule, FormsModule, PoLabelModule, PoFieldContainerModule] }) export class PoCheckboxModule {} diff --git a/projects/ui/src/lib/components/po-field/po-checkbox/samples/sample-po-checkbox-labs/sample-po-checkbox-labs.component.html b/projects/ui/src/lib/components/po-field/po-checkbox/samples/sample-po-checkbox-labs/sample-po-checkbox-labs.component.html index e8e40c0c6..70394b91e 100644 --- a/projects/ui/src/lib/components/po-field/po-checkbox/samples/sample-po-checkbox-labs/sample-po-checkbox-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-checkbox/samples/sample-po-checkbox-labs/sample-po-checkbox-labs.component.html @@ -1,7 +1,9 @@ + + + + + diff --git a/projects/ui/src/lib/components/po-field/po-checkbox/samples/sample-po-checkbox-labs/sample-po-checkbox-labs.component.ts b/projects/ui/src/lib/components/po-field/po-checkbox/samples/sample-po-checkbox-labs/sample-po-checkbox-labs.component.ts index 8bfd1c9f2..2f9d05d22 100644 --- a/projects/ui/src/lib/components/po-field/po-checkbox/samples/sample-po-checkbox-labs/sample-po-checkbox-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-checkbox/samples/sample-po-checkbox-labs/sample-po-checkbox-labs.component.ts @@ -5,8 +5,10 @@ import { Component, OnInit } from '@angular/core'; templateUrl: './sample-po-checkbox-labs.component.html' }) export class SamplePoCheckboxLabsComponent implements OnInit { + additionalHelpTooltip: string; checkbox: boolean | null; disabled: boolean; + help: string; size: boolean; event: string; label: string; @@ -20,9 +22,11 @@ export class SamplePoCheckboxLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.checkbox = undefined; this.disabled = false; this.event = undefined; + this.help = ''; this.label = undefined; } } diff --git a/projects/ui/src/lib/components/po-field/po-combo/po-combo-base.component.ts b/projects/ui/src/lib/components/po-field/po-combo/po-combo-base.component.ts index b9401e2bb..b742e8dbc 100644 --- a/projects/ui/src/lib/components/po-field/po-combo/po-combo-base.component.ts +++ b/projects/ui/src/lib/components/po-field/po-combo/po-combo-base.component.ts @@ -14,7 +14,6 @@ import { PoComboOptionGroup } from './interfaces/po-combo-option-group.interface import { PoComboOption } from './interfaces/po-combo-option.interface'; import { PoComboFilterMode } from './po-combo-filter-mode.enum'; import { PoComboFilterService } from './po-combo-filter.service'; -import { Subscription } from 'rxjs'; const PO_COMBO_DEBOUNCE_TIME_DEFAULT = 400; const PO_COMBO_FIELD_LABEL_DEFAULT = 'label'; @@ -76,6 +75,20 @@ const PO_COMBO_FIELD_VALUE_DEFAULT = 'value'; */ @Directive() export abstract class PoComboBaseComponent implements ControlValueAccessor, OnInit, Validator { + // Propriedade interna que define se o ícone de ajuda adicional terá cursor clicável (evento) ou padrão (tooltip). + @Input() additionalHelpEventTrigger: string | undefined; + + /** + * @optional + * + * @description + * Exibe um ícone de ajuda adicional ao `p-help`, com o texto desta propriedade no tooltip. + * Se o evento `p-additional-help` estiver definido, o tooltip não será exibido. + * **Como boa prática, indica-se utilizar um texto com até 140 caracteres.** + * > Requer um recuo mínimo de 8px se o componente estiver próximo à lateral da tela. + */ + @Input('p-additional-help-tooltip') additionalHelpTooltip?: string; + /** * @optional * @@ -251,6 +264,15 @@ export abstract class PoComboBaseComponent implements ControlValueAccessor, OnIn */ @Input('p-field-error-message') fieldErrorMessage: string; + /** + * @optional + * + * @description + * Evento disparado ao clicar no ícone de ajuda adicional. + * Este evento ativa automaticamente a exibição do ícone de ajuda adicional ao `p-help`. + */ + @Output('p-additional-help') additionalHelp = new EventEmitter(); + /** * @optional * @@ -265,7 +287,6 @@ export abstract class PoComboBaseComponent implements ControlValueAccessor, OnIn */ @Input('p-error-limit') errorLimit: boolean = false; - @Input({ alias: 'p-error-append-in-body', transform: convertToBoolean }) errorAppendBox?: boolean = false; /** * @optional @@ -675,10 +696,12 @@ export abstract class PoComboBaseComponent implements ControlValueAccessor, OnIn * * @description * - * Define que o dropdown do combo será incluido no body da página e não suspenso com a caixa de texto do componente. - * Opção necessária para o caso de uso do componente em páginas que necessitam renderizar o combo fora do conteúdo principal. + * Define que o `listbox` e/ou tooltip (`p-additional-help-tooltip` e/ou `p-error-limit`) serão incluídos no body da + * página e não dentro do componente. Essa opção pode ser necessária em cenários com containers que possuem scroll ou + * overflow escondido,garantindo o posicionamento correto de ambos próximo ao elemento. * - * > Obs: O uso dessa propriedade pode acarretar na perda sequencial da tabulação da página + * > O uso dessa propriedade pode acarretar na perda sequencial da tabulação da página. Quando utilizado com + * `p-additional-help-tooltip`, leitores de tela como o NVDA podem não ler o conteúdo do tooltip. * * @default `false` */ diff --git a/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.html b/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.html index 4d0ae58a3..4532450ac 100644 --- a/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.html +++ b/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.html @@ -85,11 +85,14 @@ diff --git a/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.spec.ts b/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.spec.ts index c8d4953cf..42d1083fb 100644 --- a/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.spec.ts @@ -371,6 +371,35 @@ describe('PoComboComponent:', () => { expect(component.inputEl.nativeElement.focus).toHaveBeenCalled(); }); + describe('getAdditionalHelpTooltip:', () => { + it('should return null when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeNull(); + }); + + it('should return additionalHelpTooltip when isAdditionalHelpEventTriggered returns false', () => { + const tooltip = 'Test Tooltip'; + component.additionalHelpTooltip = tooltip; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBe(tooltip); + }); + + it('should return undefined when additionalHelpTooltip is undefined and isAdditionalHelpEventTriggered returns false', () => { + component.additionalHelpTooltip = undefined; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeUndefined(); + }); + }); + it('onBlur: should be called when blur event', () => { component['onModelTouched'] = () => {}; spyOn(component, 'onModelTouched'); @@ -1700,6 +1729,26 @@ describe('PoComboComponent - with service:', () => { expect(component['initInputObservable']).not.toHaveBeenCalled(); }); + describe('emitAdditionalHelp:', () => { + it('should emit additionalHelp when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).toHaveBeenCalled(); + }); + + it('should not emit additionalHelp when isAdditionalHelpEventTriggered returns false', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).not.toHaveBeenCalled(); + }); + }); + it(`searchOnEnterOrArrow: should call 'controlApplyFilter' if has a service, not has selectedView and value.length is greater than 'filterMinlength'`, () => { const value = 'newValue'; diff --git a/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.ts b/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.ts index 49ff698f5..a6d6b6682 100644 --- a/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.ts +++ b/projects/ui/src/lib/components/po-field/po-combo/po-combo.component.ts @@ -195,6 +195,12 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI } } + emitAdditionalHelp() { + if (this.isAdditionalHelpEventTriggered()) { + this.additionalHelp.emit(); + } + } + /** * Função que atribui foco ao componente. * @@ -218,6 +224,10 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI } } + getAdditionalHelpTooltip() { + return this.isAdditionalHelpEventTriggered() ? null : this.additionalHelpTooltip; + } + onBlur() { this.onModelTouched?.(); } @@ -570,6 +580,10 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI } } + showAdditionalHelpIcon() { + return !!this.additionalHelpTooltip || this.isAdditionalHelpEventTriggered(); + } + showMoreInfiniteScroll(): void { if (this.defaultService.hasNext) { this.infiniteLoading = true; @@ -626,6 +640,13 @@ export class PoComboComponent extends PoComboBaseComponent implements AfterViewI window.addEventListener('scroll', this.onScroll, true); } + private isAdditionalHelpEventTriggered(): boolean { + return ( + this.additionalHelpEventTrigger === 'event' || + (this.additionalHelpEventTrigger === undefined && this.additionalHelp.observed) + ); + } + private onErrorGetObjectByValue() { this.updateOptionByFilteredValue(null); } diff --git a/projects/ui/src/lib/components/po-field/po-combo/samples/sample-po-combo-labs/sample-po-combo-labs.component.html b/projects/ui/src/lib/components/po-field/po-combo/samples/sample-po-combo-labs/sample-po-combo-labs.component.html index 626a916c8..c6d8c7522 100644 --- a/projects/ui/src/lib/components/po-field/po-combo/samples/sample-po-combo-labs/sample-po-combo-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-combo/samples/sample-po-combo-labs/sample-po-combo-labs.component.html @@ -2,6 +2,7 @@ class="po-md-12" name="combo" [(ngModel)]="combo" + [p-additional-help-tooltip]="additionalHelpTooltip" [p-change-on-enter]="properties.includes('changeOnEnter')" [p-clean]="properties.includes('clean')" [p-debounce-time]="debounceTime" @@ -44,7 +45,7 @@
      - +
      @@ -92,50 +93,56 @@
      -
      - - - -
      -
      - - - - -
      + + + + + + + + + + +
      @@ -144,7 +151,7 @@
      - +
      diff --git a/projects/ui/src/lib/components/po-field/po-combo/samples/sample-po-combo-labs/sample-po-combo-labs.component.ts b/projects/ui/src/lib/components/po-field/po-combo/samples/sample-po-combo-labs/sample-po-combo-labs.component.ts index 340ce5b71..929b68e6b 100644 --- a/projects/ui/src/lib/components/po-field/po-combo/samples/sample-po-combo-labs/sample-po-combo-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-combo/samples/sample-po-combo-labs/sample-po-combo-labs.component.ts @@ -14,6 +14,7 @@ import { templateUrl: './sample-po-combo-labs.component.html' }) export class SamplePoComboLabsComponent implements OnInit { + additionalHelpTooltip: string; combo: string; comboOptionGroupSwitch: boolean; customLiterals: PoComboLiterals; @@ -91,6 +92,7 @@ export class SamplePoComboLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.combo = undefined; this.comboOptionGroupSwitch = false; this.customLiterals = undefined; diff --git a/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range-base.component.ts b/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range-base.component.ts index 0b04425ac..cbc552094 100644 --- a/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range-base.component.ts +++ b/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range-base.component.ts @@ -60,6 +60,35 @@ import { Subscription, switchMap } from 'rxjs'; */ @Directive() export abstract class PoDatepickerRangeBaseComponent implements ControlValueAccessor, Validator, OnDestroy { + // Propriedade interna que define se o ícone de ajuda adicional terá cursor clicável (evento) ou padrão (tooltip). + @Input() additionalHelpEventTrigger: string | undefined; + + /** + * @optional + * + * @description + * Exibe um ícone de ajuda adicional ao `p-help`, com o texto desta propriedade no tooltip. + * Se o evento `p-additional-help` estiver definido, o tooltip não será exibido. + * **Como boa prática, indica-se utilizar um texto com até 140 caracteres.** + * > Requer um recuo mínimo de 8px se o componente estiver próximo à lateral da tela. + */ + @Input('p-additional-help-tooltip') additionalHelpTooltip?: string; + + /** + * @optional + * + * @description + * + * Define que o `calendar` e/ou tooltip (`p-additional-help-tooltip` e/ou `p-error-limit`) serão incluídos no body da + * página e não dentro do componente. Essa opção pode ser necessária em cenários com containers que possuem scroll ou + * overflow escondido, garantindo o posicionamento correto de ambos próximo ao elemento. + * + * > Quando utilizado com `p-additional-help-tooltip`, leitores de tela como o NVDA podem não ler o conteúdo do tooltip. + * + * @default `false` + */ + @Input({ alias: 'p-append-in-body', transform: convertToBoolean }) appendBox?: boolean = false; + /** * @optional * @@ -118,6 +147,15 @@ export abstract class PoDatepickerRangeBaseComponent implements ControlValueAcce */ @Input('p-field-error-message') fieldErrorMessage: string; + /** + * @optional + * + * @description + * Evento disparado ao clicar no ícone de ajuda adicional. + * Este evento ativa automaticamente a exibição do ícone de ajuda adicional ao `p-help`. + */ + @Output('p-additional-help') additionalHelp = new EventEmitter(); + /** * @optional * diff --git a/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.html b/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.html index d23fdb9cc..872330202 100644 --- a/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.html +++ b/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.html @@ -72,10 +72,15 @@
      diff --git a/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.spec.ts b/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.spec.ts index 27ed8ed68..e135054af 100644 --- a/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.spec.ts @@ -211,6 +211,26 @@ describe('PoDatepickerRangeComponent:', () => { expect(removeListener).toHaveBeenCalled(); }); + describe('emitAdditionalHelp:', () => { + it('should emit additionalHelp when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).toHaveBeenCalled(); + }); + + it('should not emit additionalHelp when isAdditionalHelpEventTriggered returns false', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).not.toHaveBeenCalled(); + }); + }); + it('ngOnInit: should set `poMaskObject` with `buildMask` return', () => { component['poMaskObject'] = undefined; const buildMaskReturn = new PoMask(undefined, false); @@ -300,6 +320,26 @@ describe('PoDatepickerRangeComponent:', () => { expect(component.endDateInputValue).toBe(''); }); + describe('emitAdditionalHelp:', () => { + it('should emit additionalHelp when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).toHaveBeenCalled(); + }); + + it('should not emit additionalHelp when isAdditionalHelpEventTriggered returns false', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).not.toHaveBeenCalled(); + }); + }); + it('eventOnClick: should click when select text and edit model', () => { fixture.detectChanges(); spyOn(component['poMaskObject'], 'click'); @@ -339,6 +379,35 @@ describe('PoDatepickerRangeComponent:', () => { expect(component.startDateInput.nativeElement.focus).not.toHaveBeenCalled(); }); + describe('getAdditionalHelpTooltip:', () => { + it('should return null when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeNull(); + }); + + it('should return additionalHelpTooltip when isAdditionalHelpEventTriggered returns false', () => { + const tooltip = 'Test Tooltip'; + component.additionalHelpTooltip = tooltip; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBe(tooltip); + }); + + it('should return undefined when additionalHelpTooltip is undefined and isAdditionalHelpEventTriggered returns false', () => { + component.additionalHelpTooltip = undefined; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeUndefined(); + }); + }); + it('onBlur: should call `removeFocusFromDatePickerRangeField` and `updateModelByScreen` with `true`', () => { component['onTouchedModel'] = () => {}; spyOn(component, 'removeFocusFromDatePickerRangeField'); diff --git a/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.ts b/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.ts index d5f050375..9c40160e6 100644 --- a/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.ts +++ b/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.ts @@ -204,6 +204,12 @@ export class PoDatepickerRangeComponent this.updateModel(this.dateRange); } + emitAdditionalHelp() { + if (this.isAdditionalHelpEventTriggered()) { + this.additionalHelp.emit(); + } + } + eventOnClick($event: any) { this.poMaskObject.click($event); } @@ -231,6 +237,10 @@ export class PoDatepickerRangeComponent } } + getAdditionalHelpTooltip() { + return this.isAdditionalHelpEventTriggered() ? null : this.additionalHelpTooltip; + } + onBlur(event: any) { this.onTouchedModel?.(); const isStartDateTargetEvent = event.target.name === this.startDateInputName; @@ -289,6 +299,10 @@ export class PoDatepickerRangeComponent this.isDateRangeInputFormatValid = true; } + showAdditionalHelpIcon() { + return !!this.additionalHelpTooltip || this.isAdditionalHelpEventTriggered(); + } + toggleCalendar() { if (this.disabled || this.readonly) { return; @@ -401,6 +415,13 @@ export class PoDatepickerRangeComponent window.addEventListener('scroll', this.onScroll, true); } + private isAdditionalHelpEventTriggered(): boolean { + return ( + this.additionalHelpEventTrigger === 'event' || + (this.additionalHelpEventTrigger === undefined && this.additionalHelp.observed) + ); + } + private isEqualBeforeValue(startDate: string, endDate: string): boolean { return this.isDateRangeInputFormatValid && endDate === this.dateRange.end && startDate === this.dateRange.start; } diff --git a/projects/ui/src/lib/components/po-field/po-datepicker-range/samples/sample-po-datepicker-range-labs/sample-po-datepicker-range-labs.component.html b/projects/ui/src/lib/components/po-field/po-datepicker-range/samples/sample-po-datepicker-range-labs/sample-po-datepicker-range-labs.component.html index 4224584d2..aa26e7b5e 100644 --- a/projects/ui/src/lib/components/po-field/po-datepicker-range/samples/sample-po-datepicker-range-labs/sample-po-datepicker-range-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-datepicker-range/samples/sample-po-datepicker-range-labs/sample-po-datepicker-range-labs.component.html @@ -2,6 +2,7 @@ class="po-sm-12" name="datepickerRange" [(ngModel)]="datepickerRange" + [p-additional-help-tooltip]="additionalHelpTooltip" [p-clean]="properties.includes('clean')" [p-disabled]="properties.includes('disabled')" [p-end-date]="endDate" @@ -34,71 +35,58 @@
      -
      - + - -
      + -
      - - + + - - -
      + + -
      - - + + - - -
      + + -
      - - + + - - -
      + + + + +
      - +
      diff --git a/projects/ui/src/lib/components/po-field/po-datepicker-range/samples/sample-po-datepicker-range-labs/sample-po-datepicker-range-labs.component.ts b/projects/ui/src/lib/components/po-field/po-datepicker-range/samples/sample-po-datepicker-range-labs/sample-po-datepicker-range-labs.component.ts index bb69eecdd..7e1814972 100644 --- a/projects/ui/src/lib/components/po-field/po-datepicker-range/samples/sample-po-datepicker-range-labs/sample-po-datepicker-range-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-datepicker-range/samples/sample-po-datepicker-range-labs/sample-po-datepicker-range-labs.component.ts @@ -12,6 +12,7 @@ import { templateUrl: './sample-po-datepicker-range-labs.component.html' }) export class SamplePoDatepickerRangeLabsComponent implements OnInit { + additionalHelpTooltip: string; clean: boolean; customLiterals: PoDatepickerRangeLiterals; datepickerRange: PoDatepickerRange; @@ -66,6 +67,7 @@ export class SamplePoDatepickerRangeLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.clean = undefined; this.customLiterals = undefined; this.endDate = undefined; diff --git a/projects/ui/src/lib/components/po-field/po-datepicker/po-datepicker-base.component.ts b/projects/ui/src/lib/components/po-field/po-datepicker/po-datepicker-base.component.ts index b2629c06d..bc8443621 100644 --- a/projects/ui/src/lib/components/po-field/po-datepicker/po-datepicker-base.component.ts +++ b/projects/ui/src/lib/components/po-field/po-datepicker/po-datepicker-base.component.ts @@ -95,6 +95,20 @@ const poDatepickerFormatDefault: string = 'dd/mm/yyyy'; */ @Directive() export abstract class PoDatepickerBaseComponent implements ControlValueAccessor, OnInit, OnDestroy, Validator { + // Propriedade interna que define se o ícone de ajuda adicional terá cursor clicável (evento) ou padrão (tooltip). + @Input() additionalHelpEventTrigger: string | undefined; + + /** + * @optional + * + * @description + * Exibe um ícone de ajuda adicional ao `p-help`, com o texto desta propriedade no tooltip. + * Se o evento `p-additional-help` estiver definido, o tooltip não será exibido. + * **Como boa prática, indica-se utilizar um texto com até 140 caracteres.** + * > Requer um recuo mínimo de 8px se o componente estiver próximo à lateral da tela. + */ + @Input('p-additional-help-tooltip') additionalHelpTooltip?: string; + /** * @optional * @@ -162,8 +176,6 @@ export abstract class PoDatepickerBaseComponent implements ControlValueAccessor, */ @Input('p-error-limit') errorLimit: boolean = false; - @Input({ alias: 'p-error-append-in-body', transform: convertToBoolean }) errorAppendBox?: boolean = false; - /** * @optional * @@ -177,6 +189,15 @@ export abstract class PoDatepickerBaseComponent implements ControlValueAccessor, */ @Input('p-required-field-error-message') showErrorMessageRequired: boolean = false; + /** + * @optional + * + * @description + * Evento disparado ao clicar no ícone de ajuda adicional. + * Este evento ativa automaticamente a exibição do ícone de ajuda adicional ao `p-help`. + */ + @Output('p-additional-help') additionalHelp: EventEmitter = new EventEmitter(); + /** * @optional * @@ -439,12 +460,12 @@ export abstract class PoDatepickerBaseComponent implements ControlValueAccessor, * * @description * - * Define que o calendário do DatePicker será incluído no body da página, em vez de suspenso junto ao campo de entrada do componente. - * Essa opção é útil em cenários onde o DatePicker precisa ser renderizado fora do conteúdo principal da página, - * como em formulários que utilizam scroll ou containers com overflow escondido. - * - * > Obs: O uso dessa propriedade pode interferir na sequência de tabulação da página, especialmente em formulários longos. - * + * Define que o `calendar` e/ou tooltip (`p-additional-help-tooltip` e/ou `p-error-limit`) serão incluídos no body da + * página e não dentro do componente. Essa opção pode ser necessária em cenários com containers que possuem scroll ou + * overflow escondido, garantindo o posicionamento correto de ambos próximo ao elemento. + * + * > Quando utilizado com `p-additional-help-tooltip`, leitores de tela como o NVDA podem não ler o conteúdo do tooltip. + * * @default `false` */ @Input({ alias: 'p-append-in-body', transform: convertToBoolean }) appendBox: boolean = false; diff --git a/projects/ui/src/lib/components/po-field/po-datepicker/po-datepicker.component.html b/projects/ui/src/lib/components/po-field/po-datepicker/po-datepicker.component.html index 574d6c970..0668f8ca3 100644 --- a/projects/ui/src/lib/components/po-field/po-datepicker/po-datepicker.component.html +++ b/projects/ui/src/lib/components/po-field/po-datepicker/po-datepicker.component.html @@ -79,10 +79,14 @@
      diff --git a/projects/ui/src/lib/components/po-field/po-datepicker/po-datepicker.component.spec.ts b/projects/ui/src/lib/components/po-field/po-datepicker/po-datepicker.component.spec.ts index 1f17ca7c7..48d07651a 100644 --- a/projects/ui/src/lib/components/po-field/po-datepicker/po-datepicker.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-datepicker/po-datepicker.component.spec.ts @@ -491,6 +491,26 @@ describe('PoDatepickerComponent:', () => { expect(component['subscriptionValidator'].unsubscribe).toHaveBeenCalled(); }); + describe('emitAdditionalHelp:', () => { + it('should emit additionalHelp when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).toHaveBeenCalled(); + }); + + it('should not emit additionalHelp when isAdditionalHelpEventTriggered returns false', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).not.toHaveBeenCalled(); + }); + }); + it('focus: should call `focus` of datepicker', () => { component.inputEl = { nativeElement: { @@ -520,6 +540,35 @@ describe('PoDatepickerComponent:', () => { expect(component.inputEl.nativeElement.focus).not.toHaveBeenCalled(); }); + describe('getAdditionalHelpTooltip:', () => { + it('should return null when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeNull(); + }); + + it('should return additionalHelpTooltip when isAdditionalHelpEventTriggered returns false', () => { + const tooltip = 'Test Tooltip'; + component.additionalHelpTooltip = tooltip; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBe(tooltip); + }); + + it('should return undefined when additionalHelpTooltip is undefined and isAdditionalHelpEventTriggered returns false', () => { + component.additionalHelpTooltip = undefined; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeUndefined(); + }); + }); + it('addListener: should call wasClickedOnPicker when click in document', () => { component.visible = false; component.togglePicker(); diff --git a/projects/ui/src/lib/components/po-field/po-datepicker/po-datepicker.component.ts b/projects/ui/src/lib/components/po-field/po-datepicker/po-datepicker.component.ts index 0f7057c57..81c5bd526 100644 --- a/projects/ui/src/lib/components/po-field/po-datepicker/po-datepicker.component.ts +++ b/projects/ui/src/lib/components/po-field/po-datepicker/po-datepicker.component.ts @@ -179,6 +179,12 @@ export class PoDatepickerComponent extends PoDatepickerBaseComponent implements this.removeListeners(); } + emitAdditionalHelp() { + if (this.isAdditionalHelpEventTriggered()) { + this.additionalHelp.emit(); + } + } + /** * Função que atribui foco ao componente. * @@ -202,6 +208,10 @@ export class PoDatepickerComponent extends PoDatepickerBaseComponent implements } } + getAdditionalHelpTooltip() { + return this.isAdditionalHelpEventTriggered() ? null : this.additionalHelpTooltip; + } + togglePicker() { if (this.disabled || this.readonly) { return; @@ -382,6 +392,10 @@ export class PoDatepickerComponent extends PoDatepickerBaseComponent implements return element.classList.contains('po-datepicker-calendar-overlay'); } + showAdditionalHelpIcon() { + return !!this.additionalHelpTooltip || this.isAdditionalHelpEventTriggered(); + } + verifyErrorAsync(value) { if (this.errorPattern !== '' && this.errorAsync) { const errorAsync = this.errorAsync(value); @@ -457,6 +471,13 @@ export class PoDatepickerComponent extends PoDatepickerBaseComponent implements window.addEventListener('scroll', this.onScroll, true); } + private isAdditionalHelpEventTriggered(): boolean { + return ( + this.additionalHelpEventTrigger === 'event' || + (this.additionalHelpEventTrigger === undefined && this.additionalHelp.observed) + ); + } + private onScroll = (): void => { this.controlPosition.adjustPosition(poCalendarPositionDefault); }; diff --git a/projects/ui/src/lib/components/po-field/po-datepicker/samples/sample-po-datepicker-labs/sample-po-datepicker-labs.component.html b/projects/ui/src/lib/components/po-field/po-datepicker/samples/sample-po-datepicker-labs/sample-po-datepicker-labs.component.html index 31ebfaad9..d01973bbe 100644 --- a/projects/ui/src/lib/components/po-field/po-datepicker/samples/sample-po-datepicker-labs/sample-po-datepicker-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-datepicker/samples/sample-po-datepicker-labs/sample-po-datepicker-labs.component.html @@ -2,6 +2,7 @@ class="po-sm-12" name="datepicker" [(ngModel)]="datepicker" + [p-additional-help-tooltip]="additionalHelpTooltip" [p-clean]="properties.includes('clean')" [p-disabled]="properties.includes('disabled')" [p-max-date]="maxDate" @@ -36,88 +37,86 @@
      -
      - + - -
      + -
      - + + - - -
      + -
      - - - - - -
      + -
      - - - - - -
      + + -
      - - - - - -
      + + + + + + + + + + + + + +
      - +
      diff --git a/projects/ui/src/lib/components/po-field/po-datepicker/samples/sample-po-datepicker-labs/sample-po-datepicker-labs.component.ts b/projects/ui/src/lib/components/po-field/po-datepicker/samples/sample-po-datepicker-labs/sample-po-datepicker-labs.component.ts index 1f4eaf575..3dd16f476 100644 --- a/projects/ui/src/lib/components/po-field/po-datepicker/samples/sample-po-datepicker-labs/sample-po-datepicker-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-datepicker/samples/sample-po-datepicker-labs/sample-po-datepicker-labs.component.ts @@ -7,6 +7,7 @@ import { PoCheckboxGroupOption, PoDatepickerIsoFormat, PoRadioGroupOption } from templateUrl: './sample-po-datepicker-labs.component.html' }) export class SamplePoDatepickerLabsComponent implements OnInit { + additionalHelpTooltip: string; datepicker: string | Date; maxDate: string | Date; errorPattern: string; @@ -59,6 +60,7 @@ export class SamplePoDatepickerLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.datepicker = undefined; this.maxDate = undefined; this.event = undefined; diff --git a/projects/ui/src/lib/components/po-field/po-decimal/po-decimal.component.html b/projects/ui/src/lib/components/po-field/po-decimal/po-decimal.component.html index 31a61ecfb..cd7b7ed9d 100644 --- a/projects/ui/src/lib/components/po-field/po-decimal/po-decimal.component.html +++ b/projects/ui/src/lib/components/po-field/po-decimal/po-decimal.component.html @@ -45,11 +45,14 @@
      - + [p-show-additional-help-icon]="showAdditionalHelpIcon()" + (p-additional-help)="emitAdditionalHelp()" + > diff --git a/projects/ui/src/lib/components/po-field/po-decimal/samples/sample-po-decimal-labs/sample-po-decimal-labs.component.html b/projects/ui/src/lib/components/po-field/po-decimal/samples/sample-po-decimal-labs/sample-po-decimal-labs.component.html index 8f1645172..7dfff7beb 100644 --- a/projects/ui/src/lib/components/po-field/po-decimal/samples/sample-po-decimal-labs/sample-po-decimal-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-decimal/samples/sample-po-decimal-labs/sample-po-decimal-labs.component.html @@ -2,6 +2,7 @@ class="po-md-12" name="decimal" [(ngModel)]="decimal" + [p-additional-help-tooltip]="additionalHelpTooltip" [p-clean]="properties.includes('clean')" [p-decimals-length]="decimalsLength" [p-disabled]="properties.includes('disabled')" @@ -37,77 +38,74 @@
      -
      - + - -
      + -
      - - - - - - - -
      + + -
      - + - - -
      + -
      - + + - + - - -
      + -
      - - -
      + + + + + + + + + +
      - +
      diff --git a/projects/ui/src/lib/components/po-field/po-decimal/samples/sample-po-decimal-labs/sample-po-decimal-labs.component.ts b/projects/ui/src/lib/components/po-field/po-decimal/samples/sample-po-decimal-labs/sample-po-decimal-labs.component.ts index 46a8274ce..c81e0239b 100644 --- a/projects/ui/src/lib/components/po-field/po-decimal/samples/sample-po-decimal-labs/sample-po-decimal-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-decimal/samples/sample-po-decimal-labs/sample-po-decimal-labs.component.ts @@ -7,6 +7,7 @@ import { PoCheckboxGroupOption, PoSelectOption } from '@po-ui/ng-components'; templateUrl: './sample-po-decimal-labs.component.html' }) export class SamplePoDecimalLabsComponent implements OnInit { + additionalHelpTooltip: string; decimal: number; decimalsLength: number; event: string; @@ -63,6 +64,7 @@ export class SamplePoDecimalLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.decimal = undefined; this.decimalsLength = undefined; this.event = ''; diff --git a/projects/ui/src/lib/components/po-field/po-email/samples/sample-po-email-labs/sample-po-email-labs.component.html b/projects/ui/src/lib/components/po-field/po-email/samples/sample-po-email-labs/sample-po-email-labs.component.html index f199e1534..086fb8cc7 100644 --- a/projects/ui/src/lib/components/po-field/po-email/samples/sample-po-email-labs/sample-po-email-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-email/samples/sample-po-email-labs/sample-po-email-labs.component.html @@ -1,6 +1,7 @@
      -
      - + - -
      + -
      - + + - - -
      + -
      - + - -
      + -
      - - -
      + + + +
      - +
      diff --git a/projects/ui/src/lib/components/po-field/po-email/samples/sample-po-email-labs/sample-po-email-labs.component.ts b/projects/ui/src/lib/components/po-field/po-email/samples/sample-po-email-labs/sample-po-email-labs.component.ts index 4739f03c9..35b9cb36a 100644 --- a/projects/ui/src/lib/components/po-field/po-email/samples/sample-po-email-labs/sample-po-email-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-email/samples/sample-po-email-labs/sample-po-email-labs.component.ts @@ -7,6 +7,7 @@ import { PoCheckboxGroupOption } from '@po-ui/ng-components'; templateUrl: './sample-po-email-labs.component.html' }) export class SamplePoEmailLabsComponent implements OnInit { + additionalHelpTooltip: string; email: string; errorPattern: string; event: string; @@ -33,6 +34,7 @@ export class SamplePoEmailLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.properties = []; this.label = undefined; diff --git a/projects/ui/src/lib/components/po-field/po-field-container/po-field-container-bottom/po-field-container-bottom.component.html b/projects/ui/src/lib/components/po-field/po-field-container/po-field-container-bottom/po-field-container-bottom.component.html index bf9be14f1..135d01f79 100644 --- a/projects/ui/src/lib/components/po-field/po-field-container/po-field-container-bottom/po-field-container-bottom.component.html +++ b/projects/ui/src/lib/components/po-field/po-field-container/po-field-container-bottom/po-field-container-bottom.component.html @@ -18,8 +18,19 @@ {{ errorPattern }}
      - - - {{ help }} - +
      + + {{ help }} + +
      + +
      +
      diff --git a/projects/ui/src/lib/components/po-field/po-field-container/po-field-container-bottom/po-field-container-bottom.component.ts b/projects/ui/src/lib/components/po-field/po-field-container/po-field-container-bottom/po-field-container-bottom.component.ts index aca94096d..af73be11d 100644 --- a/projects/ui/src/lib/components/po-field/po-field-container/po-field-container-bottom/po-field-container-bottom.component.ts +++ b/projects/ui/src/lib/components/po-field/po-field-container/po-field-container-bottom/po-field-container-bottom.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { convertToBoolean } from '../../../../utils/util'; /** @@ -15,6 +15,14 @@ import { convertToBoolean } from '../../../../utils/util'; changeDetection: ChangeDetectionStrategy.OnPush }) export class PoFieldContainerBottomComponent { + /** Texto exibido no tooltip do ícone de ajuda adicional. */ + @Input('p-additional-help-tooltip') additionalHelpTooltip?: string = ''; + + /** Define se o tooltip será inserido no `body` em vez do componente. */ + @Input({ alias: 'p-append-in-body', transform: convertToBoolean }) appendBox: boolean = false; + + @Input('p-disabled') disabled: boolean = false; + /** * Mensagem que será apresentada quando o pattern ou a máscara não for satisfeita. * Obs: Esta mensagem não é apresentada quando o campo estiver vazio, mesmo que ele seja requerido. @@ -26,9 +34,11 @@ export class PoFieldContainerBottomComponent { */ @Input('p-error-limit') errorLimit: boolean = false; - @Input('p-disabled') disabled: boolean = false; - @Input('p-help') help?: string; - @Input({ alias: 'p-append-in-body', transform: convertToBoolean }) appendBox?: boolean = false; + /** Exibe o ícone de ajuda adicional. */ + @Input('p-show-additional-help-icon') showAdditionalHelpIcon: boolean = false; + + /** Evento disparado ao clicar no ícone de ajuda adicional. */ + @Output('p-additional-help') additionalHelp = new EventEmitter(); } diff --git a/projects/ui/src/lib/components/po-field/po-field-validate.model.ts b/projects/ui/src/lib/components/po-field/po-field-validate.model.ts index 7afae7467..058c3e067 100644 --- a/projects/ui/src/lib/components/po-field/po-field-validate.model.ts +++ b/projects/ui/src/lib/components/po-field/po-field-validate.model.ts @@ -49,8 +49,6 @@ export abstract class PoFieldValidateModel extends PoFieldModel implements */ @Input('p-field-error-message') fieldErrorMessage: string; - @Input({ alias: 'p-error-append-in-body', transform: convertToBoolean }) errorAppendBox?: boolean = false; - /** * @optional * diff --git a/projects/ui/src/lib/components/po-field/po-field.model.spec.ts b/projects/ui/src/lib/components/po-field/po-field.model.spec.ts index b2d8fdb04..f78e9def6 100644 --- a/projects/ui/src/lib/components/po-field/po-field.model.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-field.model.spec.ts @@ -70,10 +70,59 @@ describe('PoFieldModel', () => { expect(spyOnWriteValue).toHaveBeenCalledWith(expectedValue); }); + describe('emitAdditionalHelp:', () => { + it('should emit additionalHelp when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).toHaveBeenCalled(); + }); + + it('should not emit additionalHelp when isAdditionalHelpEventTriggered returns false', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).not.toHaveBeenCalled(); + }); + }); + it('should emit change', () => { spyOn(component.change, 'emit'); component.emitChange('test'); expect(component.change.emit).toHaveBeenCalled(); }); + + describe('getAdditionalHelpTooltip:', () => { + it('should return null when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeNull(); + }); + + it('should return additionalHelpTooltip when isAdditionalHelpEventTriggered returns false', () => { + const tooltip = 'Test Tooltip'; + component.additionalHelpTooltip = tooltip; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBe(tooltip); + }); + + it('should return undefined when additionalHelpTooltip is undefined and isAdditionalHelpEventTriggered returns false', () => { + component.additionalHelpTooltip = undefined; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeUndefined(); + }); + }); }); diff --git a/projects/ui/src/lib/components/po-field/po-field.model.ts b/projects/ui/src/lib/components/po-field/po-field.model.ts index 1abc59709..9c94976ef 100644 --- a/projects/ui/src/lib/components/po-field/po-field.model.ts +++ b/projects/ui/src/lib/components/po-field/po-field.model.ts @@ -5,6 +5,35 @@ import { convertToBoolean } from '../../utils/util'; @Directive() export abstract class PoFieldModel implements ControlValueAccessor { + // Propriedade interna que define se o ícone de ajuda adicional terá cursor clicável (evento) ou padrão (tooltip). + @Input() additionalHelpEventTrigger: string | undefined; + + /** + * @optional + * + * @description + * Exibe um ícone de ajuda adicional ao `p-help`, com o texto desta propriedade no tooltip. + * Se o evento `p-additional-help` estiver definido, o tooltip não será exibido. + * **Como boa prática, indica-se utilizar um texto com até 140 caracteres.** + * > Requer um recuo mínimo de 8px se o componente estiver próximo à lateral da tela. + */ + @Input('p-additional-help-tooltip') additionalHelpTooltip?: string; + + /** + * @optional + * + * @description + * + * Define que o tooltip (`p-additional-help-tooltip` e/ou `p-error-limit`) será incluído no body da página e não + * dentro do componente. Essa opção pode ser necessária em cenários com containers que possuem scroll ou overflow + * escondido, garantindo o posicionamento correto do tooltip próximo ao elemento. + * + * > Quando utilizado com `p-additional-help-tooltip`, leitores de tela como o NVDA podem não ler o conteúdo do tooltip. + * + * @default `false` + */ + @Input({ alias: 'p-append-in-body', transform: convertToBoolean }) appendBox: boolean = false; + /** Rótulo exibido pelo componente. */ @Input('p-label') label: string; @@ -25,6 +54,15 @@ export abstract class PoFieldModel implements ControlValueAccessor { */ @Input({ alias: 'p-disabled', transform: convertToBoolean }) disabled: boolean = false; + /** + * @optional + * + * @description + * Evento disparado ao clicar no ícone de ajuda adicional. + * Este evento ativa automaticamente a exibição do ícone de ajuda adicional ao `p-help`. + */ + @Output('p-additional-help') additionalHelp = new EventEmitter(); + /** * @optional * @@ -60,10 +98,30 @@ export abstract class PoFieldModel implements ControlValueAccessor { this.onWriteValue(value); } + emitAdditionalHelp() { + if (this.isAdditionalHelpEventTriggered()) { + this.additionalHelp.emit(); + } + } emitChange(value) { this.change.emit(value); } + getAdditionalHelpTooltip() { + return this.isAdditionalHelpEventTriggered() ? null : this.additionalHelpTooltip; + } + + showAdditionalHelpIcon() { + return !!this.additionalHelpTooltip || this.isAdditionalHelpEventTriggered(); + } + + private isAdditionalHelpEventTriggered(): boolean { + return ( + this.additionalHelpEventTrigger === 'event' || + (this.additionalHelpEventTrigger === undefined && this.additionalHelp.observed) + ); + } + protected updateModel(value: T) { if (this.propagateChange) { this.propagateChange(value); diff --git a/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.spec.ts b/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.spec.ts index f3e8d0ba7..ebd2cbcb5 100644 --- a/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.spec.ts @@ -437,6 +437,55 @@ describe('PoInputBase:', () => { expect(component.changeModel.emit).toHaveBeenCalledWith(newModelValue); }); + describe('emitAdditionalHelp:', () => { + it('should emit additionalHelp when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).toHaveBeenCalled(); + }); + + it('should not emit additionalHelp when isAdditionalHelpEventTriggered returns false', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).not.toHaveBeenCalled(); + }); + }); + + describe('getAdditionalHelpTooltip:', () => { + it('should return null when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeNull(); + }); + + it('should return additionalHelpTooltip when isAdditionalHelpEventTriggered returns false', () => { + const tooltip = 'Test Tooltip'; + component.additionalHelpTooltip = tooltip; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBe(tooltip); + }); + + it('should return undefined when additionalHelpTooltip is undefined and isAdditionalHelpEventTriggered returns false', () => { + component.additionalHelpTooltip = undefined; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeUndefined(); + }); + }); + it('validateModel: shouldn`t call `validatorChange` when it is falsy', () => { component['validateModel'](); diff --git a/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.ts b/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.ts index 5f7f9e634..d02e11c71 100644 --- a/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.ts +++ b/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.ts @@ -56,6 +56,35 @@ import { PoMask } from './po-mask'; */ @Directive() export abstract class PoInputBaseComponent implements ControlValueAccessor, Validator, OnDestroy { + // Propriedade interna que define se o ícone de ajuda adicional terá cursor clicável (evento) ou padrão (tooltip). + @Input() additionalHelpEventTrigger: string | undefined; + + /** + * @optional + * + * @description + * Exibe um ícone de ajuda adicional ao `p-help`, com o texto desta propriedade no tooltip. + * Se o evento `p-additional-help` estiver definido, o tooltip não será exibido. + * **Como boa prática, indica-se utilizar um texto com até 140 caracteres.** + * > Requer um recuo mínimo de 8px se o componente estiver próximo à lateral da tela. + */ + @Input('p-additional-help-tooltip') additionalHelpTooltip?: string; + + /** + * @optional + * + * @description + * + * Define que o tooltip (`p-additional-help-tooltip` e/ou `p-error-limit`) será incluído no body da página e não + * dentro do componente. Essa opção pode ser necessária em cenários com containers que possuem scroll ou overflow + * escondido, garantindo o posicionamento correto do tooltip próximo ao elemento. + * + * > Quando utilizado com `p-additional-help-tooltip`, leitores de tela como o NVDA podem não ler o conteúdo do tooltip. + * + * @default `false` + */ + @Input({ alias: 'p-append-in-body', transform: convertToBoolean }) appendBox: boolean = false; + /** * @optional * @@ -151,8 +180,6 @@ export abstract class PoInputBaseComponent implements ControlValueAccessor, Vali */ @Input('p-error-limit') errorLimit: boolean = false; - @Input({ alias: 'p-error-append-in-body', transform: convertToBoolean }) errorAppendBox?: boolean = false; - /** * @optional * @@ -223,6 +250,15 @@ export abstract class PoInputBaseComponent implements ControlValueAccessor, Vali return this._maskNoLengthValidation; } + /** + * @optional + * + * @description + * Evento disparado ao clicar no ícone de ajuda adicional. + * Este evento ativa automaticamente a exibição do ícone de ajuda adicional ao `p-help`. + */ + @Output('p-additional-help') additionalHelp = new EventEmitter(); + /** * @optional * @@ -492,6 +528,16 @@ export abstract class PoInputBaseComponent implements ControlValueAccessor, Vali } } + emitAdditionalHelp() { + if (this.isAdditionalHelpEventTriggered()) { + this.additionalHelp.emit(); + } + } + + getAdditionalHelpTooltip() { + return this.isAdditionalHelpEventTriggered() ? null : this.additionalHelpTooltip; + } + // Função implementada do ControlValueAccessor // Usada para interceptar os estados de habilitado via forms api setDisabledState(isDisabled: boolean) { @@ -515,6 +561,10 @@ export abstract class PoInputBaseComponent implements ControlValueAccessor, Vali this.validatorChange = fn; } + showAdditionalHelpIcon() { + return !!this.additionalHelpTooltip || this.isAdditionalHelpEventTriggered(); + } + updateModel(value: any) { // Quando o input não possui um formulário, então esta função não é registrada if (this.onChangePropagate) { @@ -599,6 +649,13 @@ export abstract class PoInputBaseComponent implements ControlValueAccessor, Vali } } + private isAdditionalHelpEventTriggered(): boolean { + return ( + this.additionalHelpEventTrigger === 'event' || + (this.additionalHelpEventTrigger === undefined && this.additionalHelp.observed) + ); + } + // utilizado para validar o pattern na inicializacao, fazendo dessa forma o campo fica sujo (dirty). private validatePatternOnWriteValue(value: string) { if (value && this.passedWriteValue) { diff --git a/projects/ui/src/lib/components/po-field/po-input/po-input.component.html b/projects/ui/src/lib/components/po-field/po-input/po-input.component.html index e42a3844f..fd87f358c 100644 --- a/projects/ui/src/lib/components/po-field/po-input/po-input.component.html +++ b/projects/ui/src/lib/components/po-field/po-input/po-input.component.html @@ -46,10 +46,14 @@
    diff --git a/projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-labs/sample-po-input-labs.component.html b/projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-labs/sample-po-input-labs.component.html index e255e5d85..82a8496c8 100644 --- a/projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-labs/sample-po-input-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-labs/sample-po-input-labs.component.html @@ -1,6 +1,7 @@
    -
    - + - -
    + -
    - + + - - -
    + -
    - - - - - -
    + -
    - + + - -
    + + -
    - - - - - -
    + + + + + + + + +
    - +
    diff --git a/projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-labs/sample-po-input-labs.component.ts b/projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-labs/sample-po-input-labs.component.ts index 9ae35e070..93b4f6eab 100644 --- a/projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-labs/sample-po-input-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-input/samples/sample-po-input-labs/sample-po-input-labs.component.ts @@ -7,6 +7,7 @@ import { PoCheckboxGroupOption, PoSelectOption } from '@po-ui/ng-components'; templateUrl: './sample-po-input-labs.component.html' }) export class SamplePoInputLabsComponent implements OnInit { + additionalHelpTooltip: string; input: string; errorPattern: string; event: string; @@ -50,6 +51,7 @@ export class SamplePoInputLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.input = undefined; } } diff --git a/projects/ui/src/lib/components/po-field/po-login/po-login.component.html b/projects/ui/src/lib/components/po-field/po-login/po-login.component.html index 2f30650a4..e564593c5 100644 --- a/projects/ui/src/lib/components/po-field/po-login/po-login.component.html +++ b/projects/ui/src/lib/components/po-field/po-login/po-login.component.html @@ -45,10 +45,14 @@
diff --git a/projects/ui/src/lib/components/po-field/po-login/samples/sample-po-login-labs/sample-po-login-labs.component.html b/projects/ui/src/lib/components/po-field/po-login/samples/sample-po-login-labs/sample-po-login-labs.component.html index 266da3509..de5d6cf10 100644 --- a/projects/ui/src/lib/components/po-field/po-login/samples/sample-po-login-labs/sample-po-login-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-login/samples/sample-po-login-labs/sample-po-login-labs.component.html @@ -1,6 +1,7 @@
-
- + - + - -
+ + + + + -
- - + + - - -
+ + -
- + - -
+ -
- - -
+ +
- +
diff --git a/projects/ui/src/lib/components/po-field/po-login/samples/sample-po-login-labs/sample-po-login-labs.component.ts b/projects/ui/src/lib/components/po-field/po-login/samples/sample-po-login-labs/sample-po-login-labs.component.ts index 8e92dfe8b..581ee5292 100644 --- a/projects/ui/src/lib/components/po-field/po-login/samples/sample-po-login-labs/sample-po-login-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-login/samples/sample-po-login-labs/sample-po-login-labs.component.ts @@ -7,6 +7,7 @@ import { PoCheckboxGroupOption } from '@po-ui/ng-components'; templateUrl: './sample-po-login-labs.component.html' }) export class SamplePoLoginLabsComponent implements OnInit { + additionalHelpTooltip: string; errorPattern: string; event: string; help: string; @@ -39,6 +40,7 @@ export class SamplePoLoginLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.errorPattern = ''; this.event = ''; diff --git a/projects/ui/src/lib/components/po-field/po-lookup/po-lookup-base.component.ts b/projects/ui/src/lib/components/po-field/po-lookup/po-lookup-base.component.ts index 850643900..4d312120c 100644 --- a/projects/ui/src/lib/components/po-field/po-lookup/po-lookup-base.component.ts +++ b/projects/ui/src/lib/components/po-field/po-lookup/po-lookup-base.component.ts @@ -14,18 +14,10 @@ import { Output, SimpleChanges } from '@angular/core'; -import { - AbstractControl, - ControlValueAccessor, - NgControl, - UntypedFormControl, - Validator, - Validators -} from '@angular/forms'; +import { AbstractControl, ControlValueAccessor, NgControl, UntypedFormControl, Validator } from '@angular/forms'; import { Subscription } from 'rxjs'; import { finalize } from 'rxjs/operators'; -import { InputBoolean } from '../../../decorators'; import { convertToBoolean, isTypeof } from '../../../utils/util'; import { requiredFailed } from '../validators'; import { PoLookupAdvancedFilter } from './interfaces/po-lookup-advanced-filter.interface'; @@ -77,6 +69,36 @@ export abstract class PoLookupBaseComponent { private _literals?: PoLookupLiterals; private language: string; + + // Propriedade interna que define se o ícone de ajuda adicional terá cursor clicável (evento) ou padrão (tooltip). + @Input() additionalHelpEventTrigger: string | undefined; + + /** + * @optional + * + * @description + * Exibe um ícone de ajuda adicional ao `p-help`, com o texto desta propriedade no tooltip. + * Se o evento `p-additional-help` estiver definido, o tooltip não será exibido. + * **Como boa prática, indica-se utilizar um texto com até 140 caracteres.** + * > Requer um recuo mínimo de 8px se o componente estiver próximo à lateral da tela. + */ + @Input('p-additional-help-tooltip') additionalHelpTooltip?: string; + + /** + * @optional + * + * @description + * + * Define que o tooltip (`p-additional-help-tooltip` e/ou `p-error-limit`) será incluído no body da página e não + * dentro do componente. Essa opção pode ser necessária em cenários com containers que possuem scroll ou overflow + * escondido, garantindo o posicionamento correto do tooltip próximo ao elemento. + * + * > Quando utilizado com `p-additional-help-tooltip`, leitores de tela como o NVDA podem não ler o conteúdo do tooltip. + * + * @default `false` + */ + @Input({ alias: 'p-append-in-body', transform: convertToBoolean }) appendBox?: boolean = false; + /** * @optional * @@ -356,8 +378,6 @@ export abstract class PoLookupBaseComponent */ @Input('p-error-limit') errorLimit: boolean = false; - @Input({ alias: 'p-error-append-in-body', transform: convertToBoolean }) errorAppendBox?: boolean = false; - /** * @optional * @@ -402,6 +422,15 @@ export abstract class PoLookupBaseComponent */ @Input({ alias: 'p-virtual-scroll', transform: convertToBoolean }) virtualScroll?: boolean = true; + /** + * @optional + * + * @description + * Evento disparado ao clicar no ícone de ajuda adicional. + * Este evento ativa automaticamente a exibição do ícone de ajuda adicional ao `p-help`. + */ + @Output('p-additional-help') additionalHelp = new EventEmitter(); + /** * Evento será disparado quando ocorrer algum erro na requisição de busca do item. * Será passado por parâmetro o objeto de erro retornado. diff --git a/projects/ui/src/lib/components/po-field/po-lookup/po-lookup.component.html b/projects/ui/src/lib/components/po-field/po-lookup/po-lookup.component.html index a0664a50c..0289caba6 100644 --- a/projects/ui/src/lib/components/po-field/po-lookup/po-lookup.component.html +++ b/projects/ui/src/lib/components/po-field/po-lookup/po-lookup.component.html @@ -56,11 +56,14 @@ diff --git a/projects/ui/src/lib/components/po-field/po-lookup/po-lookup.component.spec.ts b/projects/ui/src/lib/components/po-field/po-lookup/po-lookup.component.spec.ts index 86d6d7a44..333c99346 100644 --- a/projects/ui/src/lib/components/po-field/po-lookup/po-lookup.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-lookup/po-lookup.component.spec.ts @@ -277,6 +277,55 @@ describe('PoLookupComponent:', () => { expect(component.inputEl.nativeElement.focus).not.toHaveBeenCalled(); }); + describe('emitAdditionalHelp:', () => { + it('should emit additionalHelp when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).toHaveBeenCalled(); + }); + + it('should not emit additionalHelp when isAdditionalHelpEventTriggered returns false', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).not.toHaveBeenCalled(); + }); + }); + + describe('getAdditionalHelpTooltip:', () => { + it('should return null when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeNull(); + }); + + it('should return additionalHelpTooltip when isAdditionalHelpEventTriggered returns false', () => { + const tooltip = 'Test Tooltip'; + component.additionalHelpTooltip = tooltip; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBe(tooltip); + }); + + it('should return undefined when additionalHelpTooltip is undefined and isAdditionalHelpEventTriggered returns false', () => { + component.additionalHelpTooltip = undefined; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeUndefined(); + }); + }); + it('openLookup: should call `openModal` if `isAllowedOpenModal` return true', inject( [LookupFilterService], (lookupFilterService: LookupFilterService) => { diff --git a/projects/ui/src/lib/components/po-field/po-lookup/po-lookup.component.ts b/projects/ui/src/lib/components/po-field/po-lookup/po-lookup.component.ts index 62bd55181..b6f1339e6 100644 --- a/projects/ui/src/lib/components/po-field/po-lookup/po-lookup.component.ts +++ b/projects/ui/src/lib/components/po-field/po-lookup/po-lookup.component.ts @@ -227,6 +227,16 @@ export class PoLookupComponent extends PoLookupBaseComponent implements AfterVie } } + emitAdditionalHelp() { + if (this.isAdditionalHelpEventTriggered()) { + this.additionalHelp.emit(); + } + } + + getAdditionalHelpTooltip() { + return this.isAdditionalHelpEventTriggered() ? null : this.additionalHelpTooltip; + } + openLookup(): void { if (this.isAllowedOpenModal()) { const { @@ -421,6 +431,17 @@ export class PoLookupComponent extends PoLookupBaseComponent implements AfterVie this.visibleDisclaimers = [...newDisclaimers]; } + showAdditionalHelpIcon() { + return !!this.additionalHelpTooltip || this.isAdditionalHelpEventTriggered(); + } + + private isAdditionalHelpEventTriggered(): boolean { + return ( + this.additionalHelpEventTrigger === 'event' || + (this.additionalHelpEventTrigger === undefined && this.additionalHelp.observed) + ); + } + private isAllowedOpenModal(): boolean { if (!this.service) { console.warn('No service informed'); diff --git a/projects/ui/src/lib/components/po-field/po-lookup/samples/sample-po-lookup-labs/sample-po-lookup-labs.component.html b/projects/ui/src/lib/components/po-field/po-lookup/samples/sample-po-lookup-labs/sample-po-lookup-labs.component.html index 10fa622aa..2b4e64a52 100644 --- a/projects/ui/src/lib/components/po-field/po-lookup/samples/sample-po-lookup-labs/sample-po-lookup-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-lookup/samples/sample-po-lookup-labs/sample-po-lookup-labs.component.html @@ -1,6 +1,7 @@
-
- - - - -
- -
- - - - - -
- -
- - - - - -
- -
- - - -
- -
- - - - - -
- -
- - - - - -
- -
- - -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
- +
diff --git a/projects/ui/src/lib/components/po-field/po-lookup/samples/sample-po-lookup-labs/sample-po-lookup-labs.component.ts b/projects/ui/src/lib/components/po-field/po-lookup/samples/sample-po-lookup-labs/sample-po-lookup-labs.component.ts index 25e73a66b..c30143dd7 100644 --- a/projects/ui/src/lib/components/po-field/po-lookup/samples/sample-po-lookup-labs/sample-po-lookup-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-lookup/samples/sample-po-lookup-labs/sample-po-lookup-labs.component.ts @@ -19,6 +19,7 @@ import { SamplePoLookupService } from '../sample-po-lookup.service'; providers: [SamplePoLookupService] }) export class SamplePoLookupLabsComponent implements OnInit { + additionalHelpTooltip: string; columns: Array; columnsName: Array; customLiterals: PoLookupLiterals; @@ -118,6 +119,7 @@ export class SamplePoLookupLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.columnsName = ['id', 'name']; this.customLiterals = undefined; this.updateColumns(); diff --git a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-base.component.ts b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-base.component.ts index fec39b0c6..92262e8c7 100644 --- a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-base.component.ts +++ b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect-base.component.ts @@ -1,5 +1,5 @@ import { Directive, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { AbstractControl, ControlValueAccessor, Validator, Validators } from '@angular/forms'; +import { AbstractControl, ControlValueAccessor, Validator } from '@angular/forms'; import { Observable, Subject, Subscription } from 'rxjs'; import { debounceTime, distinctUntilChanged, switchMap, tap } from 'rxjs/operators'; @@ -112,6 +112,20 @@ export const poMultiselectLiteralsDefault = { */ @Directive() export abstract class PoMultiselectBaseComponent implements ControlValueAccessor, OnInit, Validator { + // Propriedade interna que define se o ícone de ajuda adicional terá cursor clicável (evento) ou padrão (tooltip). + @Input() additionalHelpEventTrigger: string | undefined; + + /** + * @optional + * + * @description + * Exibe um ícone de ajuda adicional ao `p-help`, com o texto desta propriedade no tooltip. + * Se o evento `p-additional-help` estiver definido, o tooltip não será exibido. + * **Como boa prática, indica-se utilizar um texto com até 140 caracteres.** + * > Requer um recuo mínimo de 8px se o componente estiver próximo à lateral da tela. + */ + @Input('p-additional-help-tooltip') additionalHelpTooltip?: string; + /** * @optional * @@ -186,6 +200,15 @@ export abstract class PoMultiselectBaseComponent implements ControlValueAccessor */ @Input('p-field-error-message') fieldErrorMessage: string; + /** + * @optional + * + * @description + * Evento disparado ao clicar no ícone de ajuda adicional. + * Este evento ativa automaticamente a exibição do ícone de ajuda adicional ao `p-help`. + */ + @Output('p-additional-help') additionalHelp = new EventEmitter(); + /** * @optional * @@ -214,17 +237,17 @@ export abstract class PoMultiselectBaseComponent implements ControlValueAccessor * * @description * - * Define que o dropdown do multiselect será incluido no body da página e não suspenso com a caixa de texto do componente. - * Opção necessária para o caso de uso do componente em páginas que necessitam renderizar o multiselect fora do conteúdo principal. + * Define que o `listbox` e/ou tooltip (`p-additional-help-tooltip` e/ou `p-error-limit`) serão incluídos no body da + * página e não dentro do componente. Essa opção pode ser necessária em cenários com containers que possuem scroll ou + * overflow escondido, garantindo o posicionamento correto de ambos próximo ao elemento. * - * > Obs: O uso dessa propriedade pode acarretar na perda sequencial da tabulação da página + * > O uso dessa propriedade pode interferir na sequência de tabulação da página. Quando utilizado com + * `p-additional-help-tooltip`, leitores de tela como o NVDA podem não ler o conteúdo do tooltip. * * @default `false` */ @Input({ alias: 'p-append-in-body', transform: convertToBoolean }) appendBox?: boolean = false; - @Input({ alias: 'p-error-append-in-body', transform: convertToBoolean }) errorAppendBox?: boolean = false; - selectedOptions: Array = []; visibleOptionsDropdown: Array = []; visibleTags = []; diff --git a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.html b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.html index 71ad74c4b..00919fd35 100644 --- a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.html +++ b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.html @@ -65,11 +65,14 @@
diff --git a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.spec.ts b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.spec.ts index 1711655b2..470aeb1b3 100644 --- a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.spec.ts @@ -514,6 +514,26 @@ describe('PoMultiselectComponent:', () => { expect(fnDestroy).not.toThrow(); }); + describe('emitAdditionalHelp:', () => { + it('should emit additionalHelp when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).toHaveBeenCalled(); + }); + + it('should not emit additionalHelp when isAdditionalHelpEventTriggered returns false', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).not.toHaveBeenCalled(); + }); + }); + it('Should call `setService` if a change occurs in `filterService` and contain `filterService`', () => { const changes = { filterService: 'filterServiceURL' }; component.filterService = 'http://localhost:4200/test'; @@ -583,6 +603,35 @@ describe('PoMultiselectComponent:', () => { expect(component.inputElement.nativeElement.focus).not.toHaveBeenCalled(); }); + describe('getAdditionalHelpTooltip:', () => { + it('should return null when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeNull(); + }); + + it('should return additionalHelpTooltip when isAdditionalHelpEventTriggered returns false', () => { + const tooltip = 'Test Tooltip'; + component.additionalHelpTooltip = tooltip; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBe(tooltip); + }); + + it('should return undefined when additionalHelpTooltip is undefined and isAdditionalHelpEventTriggered returns false', () => { + component.additionalHelpTooltip = undefined; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeUndefined(); + }); + }); + it(`calculateVisibleItems: should calc visible items and not set 'isCalculateVisibleItems' to false when tags width is 0`, () => { const selectedOptions = [ diff --git a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.ts b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.ts index 298cf3c96..566870bf2 100644 --- a/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.ts +++ b/projects/ui/src/lib/components/po-field/po-multiselect/po-multiselect.component.ts @@ -190,6 +190,12 @@ export class PoMultiselectComponent this.subscription.unsubscribe(); } + emitAdditionalHelp() { + if (this.isAdditionalHelpEventTriggered()) { + this.additionalHelp.emit(); + } + } + /** * Função que atribui foco ao componente. * @@ -213,6 +219,10 @@ export class PoMultiselectComponent } } + getAdditionalHelpTooltip() { + return this.isAdditionalHelpEventTriggered() ? null : this.additionalHelpTooltip; + } + getInputWidth() { return this.el.nativeElement.querySelector('.po-multiselect-input').offsetWidth - poMultiselectInputPaddingRight; } @@ -454,6 +464,10 @@ export class PoMultiselectComponent }, 300); } + showAdditionalHelpIcon() { + return !!this.additionalHelpTooltip || this.isAdditionalHelpEventTriggered(); + } + wasClickedOnToggle(event: MouseEvent): void { if ( this.dropdownOpen && @@ -616,6 +630,13 @@ export class PoMultiselectComponent window.addEventListener('scroll', this.onScroll, true); } + private isAdditionalHelpEventTriggered(): boolean { + return ( + this.additionalHelpEventTrigger === 'event' || + (this.additionalHelpEventTrigger === undefined && this.additionalHelp.observed) + ); + } + private onScroll = (): void => { this.adjustContainerPosition(); }; diff --git a/projects/ui/src/lib/components/po-field/po-multiselect/samples/sample-po-multiselect-labs/sample-po-multiselect-labs.component.html b/projects/ui/src/lib/components/po-field/po-multiselect/samples/sample-po-multiselect-labs/sample-po-multiselect-labs.component.html index 6600bfafc..3cbf7839d 100644 --- a/projects/ui/src/lib/components/po-field/po-multiselect/samples/sample-po-multiselect-labs/sample-po-multiselect-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-multiselect/samples/sample-po-multiselect-labs/sample-po-multiselect-labs.component.html @@ -3,6 +3,7 @@ class="po-md-12" name="PO Multiselect" [(ngModel)]="multiselect" + [p-additional-help-tooltip]="additionalHelpTooltip" [p-auto-height]="properties.includes('autoHeight')" [p-disabled]="properties.includes('disabled')" [p-field-label]="fieldLabel" @@ -48,7 +49,7 @@
- +
@@ -56,88 +57,85 @@
-
- + - -
+ -
- - - - -
+ + -
- - + - - -
+ + + + + + + + -
- - -
+ + -
- + - -
+ -
- - - - - -
+ + + + +
- +
diff --git a/projects/ui/src/lib/components/po-field/po-multiselect/samples/sample-po-multiselect-labs/sample-po-multiselect-labs.component.ts b/projects/ui/src/lib/components/po-field/po-multiselect/samples/sample-po-multiselect-labs/sample-po-multiselect-labs.component.ts index 8851932f7..6f298178d 100644 --- a/projects/ui/src/lib/components/po-field/po-multiselect/samples/sample-po-multiselect-labs/sample-po-multiselect-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-multiselect/samples/sample-po-multiselect-labs/sample-po-multiselect-labs.component.ts @@ -12,6 +12,7 @@ import { templateUrl: './sample-po-multiselect-labs.component.html' }) export class SamplePoMultiselectLabsComponent implements OnInit { + additionalHelpTooltip: string; customLiterals: PoMultiselectLiterals; event: string; filterMode: string; @@ -69,6 +70,7 @@ export class SamplePoMultiselectLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.customLiterals = undefined; this.help = ''; this.filterMode = undefined; diff --git a/projects/ui/src/lib/components/po-field/po-number/po-number.component.html b/projects/ui/src/lib/components/po-field/po-number/po-number.component.html index 8f280a931..684145b6b 100644 --- a/projects/ui/src/lib/components/po-field/po-number/po-number.component.html +++ b/projects/ui/src/lib/components/po-field/po-number/po-number.component.html @@ -46,11 +46,15 @@ diff --git a/projects/ui/src/lib/components/po-field/po-number/samples/sample-po-number-labs/sample-po-number-labs.component.html b/projects/ui/src/lib/components/po-field/po-number/samples/sample-po-number-labs/sample-po-number-labs.component.html index 58b953213..ec0883f7e 100644 --- a/projects/ui/src/lib/components/po-field/po-number/samples/sample-po-number-labs/sample-po-number-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-number/samples/sample-po-number-labs/sample-po-number-labs.component.html @@ -2,6 +2,7 @@ class="po-md-12" name="PO number" [(ngModel)]="number" + [p-additional-help-tooltip]="additionalHelpTooltip" [p-clean]="properties.includes('clean')" [p-disabled]="properties.includes('disabled')" [p-error-pattern]="messageErrorPattern" @@ -37,57 +38,54 @@
-
- + - -
+ -
- - - - -
+ + -
- + - - + + - + - - -
+ -
- + - - -
+ -
- - -
+ + + + + + +
- +
diff --git a/projects/ui/src/lib/components/po-field/po-number/samples/sample-po-number-labs/sample-po-number-labs.component.ts b/projects/ui/src/lib/components/po-field/po-number/samples/sample-po-number-labs/sample-po-number-labs.component.ts index 3cd92c434..19438f53f 100644 --- a/projects/ui/src/lib/components/po-field/po-number/samples/sample-po-number-labs/sample-po-number-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-number/samples/sample-po-number-labs/sample-po-number-labs.component.ts @@ -7,6 +7,7 @@ import { PoCheckboxGroupOption, PoSelectOption } from '@po-ui/ng-components'; templateUrl: './sample-po-number-labs.component.html' }) export class SamplePoNumberLabsComponent implements OnInit { + additionalHelpTooltip: string; event: string; messageErrorPattern: string; help: string; @@ -48,6 +49,7 @@ export class SamplePoNumberLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.number = undefined; this.max = undefined; this.maxlength = undefined; diff --git a/projects/ui/src/lib/components/po-field/po-password/po-password.component.html b/projects/ui/src/lib/components/po-field/po-password/po-password.component.html index f81dc1633..c4156ba4c 100644 --- a/projects/ui/src/lib/components/po-field/po-password/po-password.component.html +++ b/projects/ui/src/lib/components/po-field/po-password/po-password.component.html @@ -56,10 +56,14 @@ diff --git a/projects/ui/src/lib/components/po-field/po-password/samples/sample-po-password-labs/sample-po-password-labs.component.html b/projects/ui/src/lib/components/po-field/po-password/samples/sample-po-password-labs/sample-po-password-labs.component.html index be5c621ae..ad6c42cd3 100644 --- a/projects/ui/src/lib/components/po-field/po-password/samples/sample-po-password-labs/sample-po-password-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-password/samples/sample-po-password-labs/sample-po-password-labs.component.html @@ -1,6 +1,7 @@
-
- + - -
+ -
- + + - - -
+ -
- - -
+ -
- + + - -
+ -
- - -
+ + + +
- +
diff --git a/projects/ui/src/lib/components/po-field/po-password/samples/sample-po-password-labs/sample-po-password-labs.component.ts b/projects/ui/src/lib/components/po-field/po-password/samples/sample-po-password-labs/sample-po-password-labs.component.ts index c9b583744..d04bcd830 100644 --- a/projects/ui/src/lib/components/po-field/po-password/samples/sample-po-password-labs/sample-po-password-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-password/samples/sample-po-password-labs/sample-po-password-labs.component.ts @@ -7,6 +7,7 @@ import { PoCheckboxGroupOption } from '@po-ui/ng-components'; templateUrl: './sample-po-password-labs.component.html' }) export class SamplePoPasswordLabsComponent implements OnInit { + additionalHelpTooltip: string; errorPattern: string; event: string; help: string; @@ -41,6 +42,7 @@ export class SamplePoPasswordLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.errorPattern = undefined; this.event = undefined; this.help = undefined; diff --git a/projects/ui/src/lib/components/po-field/po-radio-group/po-radio-group-base.component.ts b/projects/ui/src/lib/components/po-field/po-radio-group/po-radio-group-base.component.ts index b607507d1..f27782aaa 100644 --- a/projects/ui/src/lib/components/po-field/po-radio-group/po-radio-group-base.component.ts +++ b/projects/ui/src/lib/components/po-field/po-radio-group/po-radio-group-base.component.ts @@ -60,6 +60,35 @@ const poRadioGroupColumnsTotalLength: number = 12; @Directive() export abstract class PoRadioGroupBaseComponent implements ControlValueAccessor, Validator { + // Propriedade interna que define se o ícone de ajuda adicional terá cursor clicável (evento) ou padrão (tooltip). + @Input() additionalHelpEventTrigger: string | undefined; + + /** + * @optional + * + * @description + * Exibe um ícone de ajuda adicional ao `p-help`, com o texto desta propriedade no tooltip. + * Se o evento `p-additional-help` estiver definido, o tooltip não será exibido. + * **Como boa prática, indica-se utilizar um texto com até 140 caracteres.** + * > Requer um recuo mínimo de 8px se o componente estiver próximo à lateral da tela. + */ + @Input('p-additional-help-tooltip') additionalHelpTooltip?: string; + + /** + * @optional + * + * @description + * + * Define que o tooltip (`p-additional-help-tooltip` e/ou `p-error-limit`) será incluído no body da página e não + * dentro do componente. Essa opção pode ser necessária em cenários com containers que possuem scroll ou overflow + * escondido, garantindo o posicionamento correto do tooltip próximo ao elemento. + * + * > Quando utilizado com `p-additional-help-tooltip`, leitores de tela como o NVDA podem não ler o conteúdo do tooltip. + * + * @default `false` + */ + @Input({ alias: 'p-append-in-body', transform: convertToBoolean }) appendBox?: boolean = false; + /** * @optional * @@ -103,6 +132,15 @@ export abstract class PoRadioGroupBaseComponent implements ControlValueAccessor, */ @Input('p-field-error-message') fieldErrorMessage: string; + /** + * @optional + * + * @description + * Evento disparado ao clicar no ícone de ajuda adicional. + * Este evento ativa automaticamente a exibição do ícone de ajuda adicional ao `p-help`. + */ + @Output('p-additional-help') additionalHelp = new EventEmitter(); + /** * @optional * diff --git a/projects/ui/src/lib/components/po-field/po-radio-group/po-radio-group.component.html b/projects/ui/src/lib/components/po-field/po-radio-group/po-radio-group.component.html index 66656896b..b09c36caa 100644 --- a/projects/ui/src/lib/components/po-field/po-radio-group/po-radio-group.component.html +++ b/projects/ui/src/lib/components/po-field/po-radio-group/po-radio-group.component.html @@ -1,6 +1,5 @@ + + - diff --git a/projects/ui/src/lib/components/po-field/po-radio-group/po-radio-group.component.spec.ts b/projects/ui/src/lib/components/po-field/po-radio-group/po-radio-group.component.spec.ts index 306cc07cf..282f9658b 100644 --- a/projects/ui/src/lib/components/po-field/po-radio-group/po-radio-group.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-radio-group/po-radio-group.component.spec.ts @@ -145,6 +145,26 @@ describe('PoRadioGroupComponent:', () => { }); }); + describe('emitAdditionalHelp:', () => { + it('should emit additionalHelp when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).toHaveBeenCalled(); + }); + + it('should not emit additionalHelp when isAdditionalHelpEventTriggered returns false', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).not.toHaveBeenCalled(); + }); + }); + describe('focus:', () => { it('should call `focus` of radio', () => { component.options = [ @@ -211,6 +231,35 @@ describe('PoRadioGroupComponent:', () => { }); }); + describe('getAdditionalHelpTooltip:', () => { + it('should return null when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeNull(); + }); + + it('should return additionalHelpTooltip when isAdditionalHelpEventTriggered returns false', () => { + const tooltip = 'Test Tooltip'; + component.additionalHelpTooltip = tooltip; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBe(tooltip); + }); + + it('should return undefined when additionalHelpTooltip is undefined and isAdditionalHelpEventTriggered returns false', () => { + component.additionalHelpTooltip = undefined; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeUndefined(); + }); + }); + describe('onKeyUp:', () => { it('should call `changeValue` when `isArrowKey` is true.', () => { spyOn(component, 'changeValue'); diff --git a/projects/ui/src/lib/components/po-field/po-radio-group/po-radio-group.component.ts b/projects/ui/src/lib/components/po-field/po-radio-group/po-radio-group.component.ts index 914bc6f0e..cd8cc469f 100644 --- a/projects/ui/src/lib/components/po-field/po-radio-group/po-radio-group.component.ts +++ b/projects/ui/src/lib/components/po-field/po-radio-group/po-radio-group.component.ts @@ -99,6 +99,12 @@ export class PoRadioGroupComponent extends PoRadioGroupBaseComponent implements this.cd.markForCheck(); } + emitAdditionalHelp() { + if (this.isAdditionalHelpEventTriggered()) { + this.additionalHelp.emit(); + } + } + eventClick(value: any, disabled: any) { if (!disabled) { this.onTouched?.(); @@ -133,6 +139,10 @@ export class PoRadioGroupComponent extends PoRadioGroupBaseComponent implements } } + getAdditionalHelpTooltip() { + return this.isAdditionalHelpEventTriggered() ? null : this.additionalHelpTooltip; + } + getElementByValue(value) { return this.inputEl.nativeElement.querySelector(`input[value='${value}']`); } @@ -155,6 +165,17 @@ export class PoRadioGroupComponent extends PoRadioGroupBaseComponent implements } } + showAdditionalHelpIcon() { + return !!this.additionalHelpTooltip || this.isAdditionalHelpEventTriggered(); + } + + private isAdditionalHelpEventTriggered(): boolean { + return ( + this.additionalHelpEventTrigger === 'event' || + (this.additionalHelpEventTrigger === undefined && this.additionalHelp.observed) + ); + } + private isArrowKey(key: number) { return key >= 37 && key <= 40; } diff --git a/projects/ui/src/lib/components/po-field/po-radio-group/samples/sample-po-radio-group-labs/sample-po-radio-group-labs.component.html b/projects/ui/src/lib/components/po-field/po-radio-group/samples/sample-po-radio-group-labs/sample-po-radio-group-labs.component.html index df5733622..6461d2cd1 100644 --- a/projects/ui/src/lib/components/po-field/po-radio-group/samples/sample-po-radio-group-labs/sample-po-radio-group-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-radio-group/samples/sample-po-radio-group-labs/sample-po-radio-group-labs.component.html @@ -1,6 +1,7 @@
-
- + - -
+ -
- - - - - -
-
- - - - - -
+ + + + + + + + + + + + +
- + +
diff --git a/projects/ui/src/lib/components/po-field/po-radio-group/samples/sample-po-radio-group-labs/sample-po-radio-group-labs.component.ts b/projects/ui/src/lib/components/po-field/po-radio-group/samples/sample-po-radio-group-labs/sample-po-radio-group-labs.component.ts index b65c9fc27..2e1e652de 100644 --- a/projects/ui/src/lib/components/po-field/po-radio-group/samples/sample-po-radio-group-labs/sample-po-radio-group-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-radio-group/samples/sample-po-radio-group-labs/sample-po-radio-group-labs.component.ts @@ -7,6 +7,7 @@ import { PoCheckboxGroupOption, PoRadioGroupOption } from '@po-ui/ng-components' templateUrl: './sample-po-radio-group-labs.component.html' }) export class SamplePoRadioGroupLabsComponent implements OnInit { + additionalHelpTooltip: string; columns: number; event: string; help: string; @@ -52,11 +53,12 @@ export class SamplePoRadioGroupLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.event = ''; this.radioGroup = undefined; this.properties = []; this.fieldErrorMessage = ''; - + this.size = 'medium'; this.option = this.getNewOption(); this.options = []; } diff --git a/projects/ui/src/lib/components/po-field/po-rich-text/po-rich-text-base.component.ts b/projects/ui/src/lib/components/po-field/po-rich-text/po-rich-text-base.component.ts index 8c2a03432..b8c61926b 100644 --- a/projects/ui/src/lib/components/po-field/po-rich-text/po-rich-text-base.component.ts +++ b/projects/ui/src/lib/components/po-field/po-rich-text/po-rich-text-base.component.ts @@ -17,6 +17,32 @@ import { PoRichTextService } from './po-rich-text.service'; */ @Directive() export abstract class PoRichTextBaseComponent implements ControlValueAccessor, Validator { + /** + * @optional + * + * @description + * Exibe um ícone de ajuda adicional ao `p-help`, com o texto desta propriedade no tooltip. + * Se o evento `p-additional-help` estiver definido, o tooltip não será exibido. + * **Como boa prática, indica-se utilizar um texto com até 140 caracteres.** + * > Requer um recuo mínimo de 8px se o componente estiver próximo à lateral da tela. + */ + @Input('p-additional-help-tooltip') additionalHelpTooltip?: string; + + /** + * @optional + * + * @description + * + * Define que o tooltip (`p-additional-help-tooltip` e/ou `p-error-limit`) será incluído no body da página e não + * dentro do componente. Essa opção pode ser necessária em cenários com containers que possuem scroll ou overflow + * escondido, garantindo o posicionamento correto do tooltip próximo ao elemento. + * + * > Quando utilizado com `p-additional-help-tooltip`, leitores de tela como o NVDA podem não ler o conteúdo do tooltip. + * + * @default `false` + */ + @Input({ alias: 'p-append-in-body', transform: convertToBoolean }) appendBox?: boolean = false; + /** * @optional * @@ -131,6 +157,15 @@ export abstract class PoRichTextBaseComponent implements ControlValueAccessor, V return this._hideToolbarActions; } + /** + * @optional + * + * @description + * Evento disparado ao clicar no ícone de ajuda adicional. + * Este evento ativa automaticamente a exibição do ícone de ajuda adicional ao `p-help`. + */ + @Output('p-additional-help') additionalHelp = new EventEmitter(); + /** * @optional * diff --git a/projects/ui/src/lib/components/po-field/po-rich-text/po-rich-text.component.html b/projects/ui/src/lib/components/po-field/po-rich-text/po-rich-text.component.html index 1e7229c48..3206f9efb 100644 --- a/projects/ui/src/lib/components/po-field/po-rich-text/po-rich-text.component.html +++ b/projects/ui/src/lib/components/po-field/po-rich-text/po-rich-text.component.html @@ -28,8 +28,13 @@ diff --git a/projects/ui/src/lib/components/po-field/po-rich-text/samples/sample-po-rich-text-labs/sample-po-rich-text-labs.component.html b/projects/ui/src/lib/components/po-field/po-rich-text/samples/sample-po-rich-text-labs/sample-po-rich-text-labs.component.html index 6fc045bec..48f3b57a9 100644 --- a/projects/ui/src/lib/components/po-field/po-rich-text/samples/sample-po-rich-text-labs/sample-po-rich-text-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-rich-text/samples/sample-po-rich-text-labs/sample-po-rich-text-labs.component.html @@ -1,6 +1,7 @@
- + +
@@ -28,44 +30,46 @@
-
- + - -
+ -
- + + - - -
+ -
- + - - + - - -
+ + + + +
- +
diff --git a/projects/ui/src/lib/components/po-field/po-rich-text/samples/sample-po-rich-text-labs/sample-po-rich-text-labs.component.ts b/projects/ui/src/lib/components/po-field/po-rich-text/samples/sample-po-rich-text-labs/sample-po-rich-text-labs.component.ts index e8b1cb03f..6dd9f0c2c 100644 --- a/projects/ui/src/lib/components/po-field/po-rich-text/samples/sample-po-rich-text-labs/sample-po-rich-text-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-rich-text/samples/sample-po-rich-text-labs/sample-po-rich-text-labs.component.ts @@ -7,6 +7,7 @@ import { PoCheckboxGroupOption, PoMultiselectOption, PoRichTextToolbarActions } templateUrl: './sample-po-rich-text-labs.component.html' }) export class SamplePoRichTextLabsComponent implements OnInit { + additionalHelpTooltip: string; errorMessage: string; event: string; help: string; @@ -15,7 +16,6 @@ export class SamplePoRichTextLabsComponent implements OnInit { placeholder: string; properties: Array; richText: string; - toolbarHideActions = [PoRichTextToolbarActions.Link]; public readonly toolbarHideActionsOptions: Array = [ @@ -44,6 +44,7 @@ export class SamplePoRichTextLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.errorMessage = ''; this.help = ''; this.label = ''; diff --git a/projects/ui/src/lib/components/po-field/po-select/po-select.component.html b/projects/ui/src/lib/components/po-field/po-select/po-select.component.html index 7171f36cd..85b6cb3b1 100644 --- a/projects/ui/src/lib/components/po-field/po-select/po-select.component.html +++ b/projects/ui/src/lib/components/po-field/po-select/po-select.component.html @@ -53,10 +53,14 @@ diff --git a/projects/ui/src/lib/components/po-field/po-select/samples/sample-po-select-labs/sample-po-select-labs.component.html b/projects/ui/src/lib/components/po-field/po-select/samples/sample-po-select-labs/sample-po-select-labs.component.html index fd7578349..11de50ee4 100644 --- a/projects/ui/src/lib/components/po-field/po-select/samples/sample-po-select-labs/sample-po-select-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-select/samples/sample-po-select-labs/sample-po-select-labs.component.html @@ -2,6 +2,7 @@ class="po-md-12" name="select" [(ngModel)]="select" + [p-additional-help-tooltip]="additionalHelpTooltip" [p-disabled]="properties.includes('disabled')" [p-help]="help" [p-label]="label" @@ -35,7 +36,7 @@
- +
@@ -83,38 +84,42 @@
-
- - - -
-
- - - - -
- -
- - -
+ + + + + + + + + + + + + +
-
+
diff --git a/projects/ui/src/lib/components/po-field/po-select/samples/sample-po-select-labs/sample-po-select-labs.component.ts b/projects/ui/src/lib/components/po-field/po-select/samples/sample-po-select-labs/sample-po-select-labs.component.ts index 3e846cdfc..fa2323cbe 100644 --- a/projects/ui/src/lib/components/po-field/po-select/samples/sample-po-select-labs/sample-po-select-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-select/samples/sample-po-select-labs/sample-po-select-labs.component.ts @@ -7,6 +7,7 @@ import { PoCheckboxGroupOption, PoSelectOption, PoSelectOptionGroup } from '@po- templateUrl: './sample-po-select-labs.component.html' }) export class SamplePoSelectLabsComponent implements OnInit { + additionalHelpTooltip: string; event: string; help: string; label: string; @@ -51,6 +52,7 @@ export class SamplePoSelectLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.event = ''; this.help = undefined; this.label = undefined; diff --git a/projects/ui/src/lib/components/po-field/po-switch/po-switch.component.html b/projects/ui/src/lib/components/po-field/po-switch/po-switch.component.html index 1eb84c593..f1d207a66 100644 --- a/projects/ui/src/lib/components/po-field/po-switch/po-switch.component.html +++ b/projects/ui/src/lib/components/po-field/po-switch/po-switch.component.html @@ -31,5 +31,12 @@
- + diff --git a/projects/ui/src/lib/components/po-field/po-switch/samples/sample-po-switch-labs/sample-po-switch-labs.component.html b/projects/ui/src/lib/components/po-field/po-switch/samples/sample-po-switch-labs/sample-po-switch-labs.component.html index 6c6d59816..d3aadf38f 100644 --- a/projects/ui/src/lib/components/po-field/po-switch/samples/sample-po-switch-labs/sample-po-switch-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-switch/samples/sample-po-switch-labs/sample-po-switch-labs.component.html @@ -1,6 +1,7 @@
- + - +

-
- + - -
+ -
- - + + - - -
+ + -
- - + + - - -
+ + + + +
- +
diff --git a/projects/ui/src/lib/components/po-field/po-switch/samples/sample-po-switch-labs/sample-po-switch-labs.component.ts b/projects/ui/src/lib/components/po-field/po-switch/samples/sample-po-switch-labs/sample-po-switch-labs.component.ts index 800eef9d4..f19466fe4 100644 --- a/projects/ui/src/lib/components/po-field/po-switch/samples/sample-po-switch-labs/sample-po-switch-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-switch/samples/sample-po-switch-labs/sample-po-switch-labs.component.ts @@ -7,6 +7,7 @@ import { PoCheckboxGroupOption, PoRadioGroupOption, PoSwitchLabelPosition } from templateUrl: './sample-po-switch-labs.component.html' }) export class SamplePoSwitchLabsComponent implements OnInit { + additionalHelpTooltip: string; event: string; help: string; label: string; @@ -36,6 +37,7 @@ export class SamplePoSwitchLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.event = ''; this.help = undefined; this.label = undefined; diff --git a/projects/ui/src/lib/components/po-field/po-textarea/po-textarea-base.component.ts b/projects/ui/src/lib/components/po-field/po-textarea/po-textarea-base.component.ts index cf5d46e58..8befc376d 100644 --- a/projects/ui/src/lib/components/po-field/po-textarea/po-textarea-base.component.ts +++ b/projects/ui/src/lib/components/po-field/po-textarea/po-textarea-base.component.ts @@ -34,6 +34,35 @@ import { maxlengpoailed, minlengpoailed, requiredFailed } from '../validators'; */ @Directive() export abstract class PoTextareaBaseComponent implements ControlValueAccessor, Validator { + // Propriedade interna que define se o ícone de ajuda adicional terá cursor clicável (evento) ou padrão (tooltip). + @Input() additionalHelpEventTrigger: string | undefined; + + /** + * @optional + * + * @description + * Exibe um ícone de ajuda adicional ao `p-help`, com o texto desta propriedade no tooltip. + * Se o evento `p-additional-help` estiver definido, o tooltip não será exibido. + * **Como boa prática, indica-se utilizar um texto com até 140 caracteres.** + * > Requer um recuo mínimo de 8px se o componente estiver próximo à lateral da tela. + */ + @Input('p-additional-help-tooltip') additionalHelpTooltip?: string; + + /** + * @optional + * + * @description + * + * Define que o tooltip (`p-additional-help-tooltip` e/ou `p-error-limit`) será incluído no body da página e não + * dentro do componente. Essa opção pode ser necessária em cenários com containers que possuem scroll ou overflow + * escondido, garantindo o posicionamento correto do tooltip próximo ao elemento. + * + * > Quando utilizado com `p-additional-help-tooltip`, leitores de tela como o NVDA podem não ler o conteúdo do tooltip. + * + * @default `false` + */ + @Input({ alias: 'p-append-in-body', transform: convertToBoolean }) appendBox?: boolean = false; + /** * @optional * @@ -83,6 +112,15 @@ export abstract class PoTextareaBaseComponent implements ControlValueAccessor, V */ @Input('p-field-error-message') fieldErrorMessage: string; + /** + * @optional + * + * @description + * Evento disparado ao clicar no ícone de ajuda adicional. + * Este evento ativa automaticamente a exibição do ícone de ajuda adicional ao `p-help`. + */ + @Output('p-additional-help') additionalHelp = new EventEmitter(); + /** * @optional * diff --git a/projects/ui/src/lib/components/po-field/po-textarea/po-textarea.component.html b/projects/ui/src/lib/components/po-field/po-textarea/po-textarea.component.html index 49630ac61..62191c105 100644 --- a/projects/ui/src/lib/components/po-field/po-textarea/po-textarea.component.html +++ b/projects/ui/src/lib/components/po-field/po-textarea/po-textarea.component.html @@ -24,9 +24,14 @@ diff --git a/projects/ui/src/lib/components/po-field/po-textarea/po-textarea.component.spec.ts b/projects/ui/src/lib/components/po-field/po-textarea/po-textarea.component.spec.ts index 273bd4564..a6ea05e58 100644 --- a/projects/ui/src/lib/components/po-field/po-textarea/po-textarea.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-textarea/po-textarea.component.spec.ts @@ -157,6 +157,26 @@ describe('PoTextareaComponent:', () => { }); }); + describe('emitAdditionalHelp:', () => { + it('should emit additionalHelp when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).toHaveBeenCalled(); + }); + + it('should not emit additionalHelp when isAdditionalHelpEventTriggered returns false', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).not.toHaveBeenCalled(); + }); + }); + it('focus: should call `focus` of textarea', () => { component.inputEl = { nativeElement: { @@ -186,6 +206,35 @@ describe('PoTextareaComponent:', () => { expect(component.inputEl.nativeElement.focus).not.toHaveBeenCalled(); }); + describe('getAdditionalHelpTooltip:', () => { + it('should return null when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeNull(); + }); + + it('should return additionalHelpTooltip when isAdditionalHelpEventTriggered returns false', () => { + const tooltip = 'Test Tooltip'; + component.additionalHelpTooltip = tooltip; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBe(tooltip); + }); + + it('should return undefined when additionalHelpTooltip is undefined and isAdditionalHelpEventTriggered returns false', () => { + component.additionalHelpTooltip = undefined; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeUndefined(); + }); + }); + it('writeValueModel: should call change if value exists', () => { const value = 'value'; const fakeThis = { @@ -250,4 +299,25 @@ describe('PoTextareaComponent:', () => { }); }); }); + + describe('isAdditionalHelpEventTriggered:', () => { + it('should return true when additionalHelpEventTrigger is "event"', () => { + component.additionalHelpEventTrigger = 'event'; + expect((component as any).isAdditionalHelpEventTriggered()).toBeTrue(); + }); + + it('should return true when additionalHelpEventTrigger is undefined and additionalHelp is observed', () => { + component.additionalHelpEventTrigger = undefined; + component.additionalHelp = { + observed: true + } as any; + + expect((component as any).isAdditionalHelpEventTriggered()).toBeTrue(); + }); + + it('should return false when additionalHelpEventTrigger is not "event" and additionalHelp is not observed', () => { + component.additionalHelpEventTrigger = 'noEvent'; + expect((component as any).isAdditionalHelpEventTriggered()).toBeFalse(); + }); + }); }); diff --git a/projects/ui/src/lib/components/po-field/po-textarea/po-textarea.component.ts b/projects/ui/src/lib/components/po-field/po-textarea/po-textarea.component.ts index aaa898af2..dec67ec6b 100644 --- a/projects/ui/src/lib/components/po-field/po-textarea/po-textarea.component.ts +++ b/projects/ui/src/lib/components/po-field/po-textarea/po-textarea.component.ts @@ -70,6 +70,12 @@ export class PoTextareaComponent extends PoTextareaBaseComponent implements Afte super(cd); } + emitAdditionalHelp() { + if (this.isAdditionalHelpEventTriggered()) { + this.additionalHelp.emit(); + } + } + /** * Função que atribui foco ao componente. * @@ -99,6 +105,10 @@ export class PoTextareaComponent extends PoTextareaBaseComponent implements Afte } } + getAdditionalHelpTooltip() { + return this.isAdditionalHelpEventTriggered() ? null : this.additionalHelpTooltip; + } + getErrorPattern() { return this.fieldErrorMessage && this.hasInvalidClass() ? this.fieldErrorMessage : ''; } @@ -160,4 +170,15 @@ export class PoTextareaComponent extends PoTextareaBaseComponent implements Afte this.change.emit(elementValue); } } + + showAdditionalHelpIcon() { + return !!this.additionalHelpTooltip || this.isAdditionalHelpEventTriggered(); + } + + private isAdditionalHelpEventTriggered(): boolean { + return ( + this.additionalHelpEventTrigger === 'event' || + (this.additionalHelpEventTrigger === undefined && this.additionalHelp.observed) + ); + } } diff --git a/projects/ui/src/lib/components/po-field/po-textarea/samples/sample-po-textarea-labs/sample-po-textarea-labs.component.html b/projects/ui/src/lib/components/po-field/po-textarea/samples/sample-po-textarea-labs/sample-po-textarea-labs.component.html index 0bc3f6140..20adc5902 100644 --- a/projects/ui/src/lib/components/po-field/po-textarea/samples/sample-po-textarea-labs/sample-po-textarea-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-textarea/samples/sample-po-textarea-labs/sample-po-textarea-labs.component.html @@ -1,6 +1,7 @@
-
- + - -
+ -
- + + - -
+ -
- + + - -
+ -
- - + - - -
+ + + +
- +
diff --git a/projects/ui/src/lib/components/po-field/po-textarea/samples/sample-po-textarea-labs/sample-po-textarea-labs.component.ts b/projects/ui/src/lib/components/po-field/po-textarea/samples/sample-po-textarea-labs/sample-po-textarea-labs.component.ts index 1fd67fcac..45f64692e 100644 --- a/projects/ui/src/lib/components/po-field/po-textarea/samples/sample-po-textarea-labs/sample-po-textarea-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-textarea/samples/sample-po-textarea-labs/sample-po-textarea-labs.component.ts @@ -7,6 +7,7 @@ import { PoCheckboxGroupOption } from '@po-ui/ng-components'; templateUrl: './sample-po-textarea-labs.component.html' }) export class SamplePoTextareaLabsComponent implements OnInit { + additionalHelpTooltip: string; event: string; help: string; label: string; @@ -36,6 +37,7 @@ export class SamplePoTextareaLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.textarea = undefined; this.label = undefined; this.help = undefined; diff --git a/projects/ui/src/lib/components/po-field/po-upload/po-upload-base.component.ts b/projects/ui/src/lib/components/po-field/po-upload/po-upload-base.component.ts index 73e5343f9..177ae5df7 100644 --- a/projects/ui/src/lib/components/po-field/po-upload/po-upload-base.component.ts +++ b/projects/ui/src/lib/components/po-field/po-upload/po-upload-base.component.ts @@ -141,6 +141,35 @@ const poUploadMinFileSize = 0; */ @Directive() export abstract class PoUploadBaseComponent implements ControlValueAccessor, Validator { + // Propriedade interna que define se o ícone de ajuda adicional terá cursor clicável (evento) ou padrão (tooltip). + @Input() additionalHelpEventTrigger: string | undefined; + + /** + * @optional + * + * @description + * Exibe um ícone de ajuda adicional ao `p-help`, com o texto desta propriedade no tooltip. + * Se o evento `p-additional-help` estiver definido, o tooltip não será exibido. + * **Como boa prática, indica-se utilizar um texto com até 140 caracteres.** + * > Requer um recuo mínimo de 8px se o componente estiver próximo à lateral da tela. + */ + @Input('p-additional-help-tooltip') additionalHelpTooltip?: string; + + /** + * @optional + * + * @description + * + * Define que o tooltip (`p-additional-help-tooltip`) será incluído no body da página e não dentro do componente. Essa + * opção pode ser necessária em cenários com containers que possuem scroll ou overflow escondido, garantindo o + * posicionamento correto do tooltip próximo ao elemento. + * + * > Quando utilizado com `p-additional-help-tooltip`, leitores de tela como o NVDA podem não ler o conteúdo do tooltip. + * + * @default `false` + */ + @Input({ alias: 'p-append-in-body', transform: convertToBoolean }) appendBox?: boolean = false; + /** * @optional * @@ -240,6 +269,15 @@ export abstract class PoUploadBaseComponent implements ControlValueAccessor, Val @Input({ alias: 'p-required-url', transform: convertToBoolean }) requiredUrl: boolean = true; + /** + * @optional + * + * @description + * Evento disparado ao clicar no ícone de ajuda adicional. + * Este evento ativa automaticamente a exibição do ícone de ajuda adicional ao `p-help`. + */ + @Output('p-additional-help') additionalHelp = new EventEmitter(); + /** * @optional * diff --git a/projects/ui/src/lib/components/po-field/po-upload/po-upload.component.html b/projects/ui/src/lib/components/po-field/po-upload/po-upload.component.html index 9962b138b..b447ec696 100644 --- a/projects/ui/src/lib/components/po-field/po-upload/po-upload.component.html +++ b/projects/ui/src/lib/components/po-field/po-upload/po-upload.component.html @@ -90,5 +90,12 @@ > - + diff --git a/projects/ui/src/lib/components/po-field/po-upload/po-upload.component.spec.ts b/projects/ui/src/lib/components/po-field/po-upload/po-upload.component.spec.ts index df6dc40af..1e8ebbaab 100644 --- a/projects/ui/src/lib/components/po-field/po-upload/po-upload.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-upload/po-upload.component.spec.ts @@ -431,6 +431,26 @@ describe('PoUploadComponent:', () => { expect(component['cleanInputValue']).toHaveBeenCalled(); }); + describe('emitAdditionalHelp:', () => { + it('should emit additionalHelp when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).toHaveBeenCalled(); + }); + + it('should not emit additionalHelp when isAdditionalHelpEventTriggered returns false', () => { + spyOn(component.additionalHelp, 'emit'); + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + component.emitAdditionalHelp(); + + expect(component.additionalHelp.emit).not.toHaveBeenCalled(); + }); + }); + describe('focus:', () => { it('should call `uploadButton.focus` if `uploadButton` is defined', () => { component.hideSelectButton = false; @@ -499,6 +519,35 @@ describe('PoUploadComponent:', () => { }); }); + describe('getAdditionalHelpTooltip:', () => { + it('should return null when isAdditionalHelpEventTriggered returns true', () => { + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(true); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeNull(); + }); + + it('should return additionalHelpTooltip when isAdditionalHelpEventTriggered returns false', () => { + const tooltip = 'Test Tooltip'; + component.additionalHelpTooltip = tooltip; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBe(tooltip); + }); + + it('should return undefined when additionalHelpTooltip is undefined and isAdditionalHelpEventTriggered returns false', () => { + component.additionalHelpTooltip = undefined; + spyOn(component as any, 'isAdditionalHelpEventTriggered').and.returnValue(false); + + const result = component.getAdditionalHelpTooltip(); + + expect(result).toBeUndefined(); + }); + }); + it('onFileChangeDragDrop: should call `updateFiles` with files.', () => { const files = 'teste'; diff --git a/projects/ui/src/lib/components/po-field/po-upload/po-upload.component.ts b/projects/ui/src/lib/components/po-field/po-upload/po-upload.component.ts index 1c8be3bf5..33ed23b92 100644 --- a/projects/ui/src/lib/components/po-field/po-upload/po-upload.component.ts +++ b/projects/ui/src/lib/components/po-field/po-upload/po-upload.component.ts @@ -190,6 +190,12 @@ export class PoUploadComponent extends PoUploadBaseComponent implements AfterVie this.cleanInputValue(); } + emitAdditionalHelp() { + if (this.isAdditionalHelpEventTriggered()) { + this.additionalHelp.emit(); + } + } + /** * Função que atribui foco ao componente. * @@ -220,6 +226,10 @@ export class PoUploadComponent extends PoUploadBaseComponent implements AfterVie } } + getAdditionalHelpTooltip() { + return this.isAdditionalHelpEventTriggered() ? null : this.additionalHelpTooltip; + } + // Verifica se existe algum arquivo sendo enviado ao serviço. hasAnyFileUploading(files: Array) { if (files && files.length) { @@ -305,6 +315,10 @@ export class PoUploadComponent extends PoUploadBaseComponent implements AfterVie } } + showAdditionalHelpIcon() { + return !!this.additionalHelpTooltip || this.isAdditionalHelpEventTriggered(); + } + // Caso o componente estiver no modo AutoUpload, o arquivo também será removido da lista. stopUpload(file: PoUploadFile) { this.uploadService.stopRequestByFile(file, () => { @@ -358,6 +372,13 @@ export class PoUploadComponent extends PoUploadBaseComponent implements AfterVie this.cd.detectChanges(); } + private isAdditionalHelpEventTriggered(): boolean { + return ( + this.additionalHelpEventTrigger === 'event' || + (this.additionalHelpEventTrigger === undefined && this.additionalHelp.observed) + ); + } + // função disparada na resposta do sucesso ou error private responseHandler(file: PoUploadFile, status: PoUploadStatus) { file.status = status; diff --git a/projects/ui/src/lib/components/po-field/po-upload/samples/sample-po-upload-labs/sample-po-upload-labs.component.html b/projects/ui/src/lib/components/po-field/po-upload/samples/sample-po-upload-labs/sample-po-upload-labs.component.html index c17cbe8c2..c5b266f24 100644 --- a/projects/ui/src/lib/components/po-field/po-upload/samples/sample-po-upload-labs/sample-po-upload-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-upload/samples/sample-po-upload-labs/sample-po-upload-labs.component.html @@ -1,6 +1,7 @@ -
-
+
+ -
-
-
- -
+
-
-
-
-
+ + + - -
+ -
-
- -
- - - - - - - - - -
- -
- -
- + +
+ +
+ + + + + + +
+
+ +
+ +
+ +
diff --git a/projects/ui/src/lib/components/po-field/po-upload/samples/sample-po-upload-labs/sample-po-upload-labs.component.ts b/projects/ui/src/lib/components/po-field/po-upload/samples/sample-po-upload-labs/sample-po-upload-labs.component.ts index 603b1cb54..73d58f1f0 100644 --- a/projects/ui/src/lib/components/po-field/po-upload/samples/sample-po-upload-labs/sample-po-upload-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-upload/samples/sample-po-upload-labs/sample-po-upload-labs.component.ts @@ -14,6 +14,7 @@ import { templateUrl: './sample-po-upload-labs.component.html' }) export class SamplePoUploadLabsComponent implements OnInit { + additionalHelpTooltip: string; allowedExtensions: string; customLiterals: PoUploadLiterals; dragDropHeight: number; @@ -125,6 +126,7 @@ export class SamplePoUploadLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.allowedExtensions = undefined; this.customLiterals = undefined; this.dragDropHeight = undefined; diff --git a/projects/ui/src/lib/components/po-field/po-url/samples/sample-po-url-labs/sample-po-url-labs.component.html b/projects/ui/src/lib/components/po-field/po-url/samples/sample-po-url-labs/sample-po-url-labs.component.html index 604d7ea07..ec3fcc295 100644 --- a/projects/ui/src/lib/components/po-field/po-url/samples/sample-po-url-labs/sample-po-url-labs.component.html +++ b/projects/ui/src/lib/components/po-field/po-url/samples/sample-po-url-labs/sample-po-url-labs.component.html @@ -1,6 +1,7 @@
-
- + - -
+ -
- + + - - -
+ -
- + - -
+ -
- - -
+ + + +
- +
diff --git a/projects/ui/src/lib/components/po-field/po-url/samples/sample-po-url-labs/sample-po-url-labs.component.ts b/projects/ui/src/lib/components/po-field/po-url/samples/sample-po-url-labs/sample-po-url-labs.component.ts index e26b6b414..81d126402 100644 --- a/projects/ui/src/lib/components/po-field/po-url/samples/sample-po-url-labs/sample-po-url-labs.component.ts +++ b/projects/ui/src/lib/components/po-field/po-url/samples/sample-po-url-labs/sample-po-url-labs.component.ts @@ -7,6 +7,7 @@ import { PoCheckboxGroupOption } from '@po-ui/ng-components'; templateUrl: './sample-po-url-labs.component.html' }) export class SamplePoUrlLabsComponent implements OnInit { + additionalHelpTooltip: string; errorPattern: string; event: string; help: string; @@ -38,6 +39,7 @@ export class SamplePoUrlLabsComponent implements OnInit { } restore() { + this.additionalHelpTooltip = ''; this.properties = []; this.label = undefined; @@ -47,7 +49,6 @@ export class SamplePoUrlLabsComponent implements OnInit { this.minlength = undefined; this.maxlength = undefined; - this.url = ''; this.event = ''; }