diff --git a/Documents/GeneratedContext/xDashboard-xFrameworkComponentsHolder-20260821_041126.txt b/Documents/GeneratedContext/xDashboard-xFrameworkComponentsHolder-20260821_041126.txt new file mode 100644 index 0000000..d6f7a5c --- /dev/null +++ b/Documents/GeneratedContext/xDashboard-xFrameworkComponentsHolder-20260821_041126.txt @@ -0,0 +1,99898 @@ + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\public-api.ts + +/* + * Public API Surface of x-framework-components + */ + +// +//#region Export Components ... +// +//#region Component Modules ... +export * from './lib/x-ide/x-ide.module'; +export * from './lib/x-fab/x-fab.module'; +export * from './lib/x-map/x-map.module'; +export * from './lib/x-icon/x-icon.module'; +export * from './lib/x-card/x-card.module'; +export * from './lib/x-list/x-list.module'; +export * from './lib/x-form/x-form.module'; +export * from './lib/x-grid/x-grid.module'; +export * from './lib/x-page/x-page.module'; +export * from './lib/x-tabs/x-tabs.module'; +export * from './lib/x-tree/x-tree.module'; +export * from './lib/x-chart/x-chart.module'; +export * from './lib/x-theme/x-theme.module'; +export * from './lib/x-table/x-table.module'; +export * from './lib/x-frame/x-frame.module'; +export * from './lib/x-empty/x-empty.module'; +export * from './lib/x-color/x-color.module'; +export * from './lib/dialogs/x-dialog.module'; +export * from './lib/x-button/x-button.module'; +export * from './lib/x-slider/x-slider.module'; +export * from './lib/x-slotter/x-slotter.module'; +export * from './lib/x-spinner/x-spinner.module'; +export * from './lib/x-stepper/x-stepper.module'; +export * from './lib/x-counter/x-counter.module'; +export * from './lib/x-markdown/x-markdown.module'; +export * from './lib/x-thumbnail/x-thumbnail.module'; +export * from './lib/x-data-pager/x-data-pager.module'; +export * from './lib/x-search-bar/x-search-bar.module'; +export * from './lib/x-action-bar/x-action-bar.module'; +export * from './lib/x-file-upload/x-file-upload.module'; +export * from './lib/x-progress-bar/x-progress-bar.module'; +export * from './lib/x-color-picker/x-color-picker.module'; +export * from './lib/x-data-scroller/x-data-scroller.module'; +export * from './lib/x-image-cropper/x-image-cropper.module'; +export * from './lib/x-tab-navigator/x-tab-navigator.module'; +export * from './lib/x-avatar-upload/x-avatar-upload.module'; +export * from './lib/x-file-drop-area/x-file-drop-area.module'; +export * from './lib/x-screen-capture/x-screen-capture.module'; +export * from './lib/x-model-presenter/x-model-presenter.module'; +export * from './lib/x-expandable-list/x-expandable-list.module'; +export * from './lib/x-expandable-panel/x-expandable-panel.module'; +export * from './lib/x-description-list/x-description-list.module'; +//#endregion + +// +//#region Component ItSelfd ... +export * from './lib/x-ide/x-ide.component'; +export * from './lib/x-fab/x-fab.component'; +export * from './lib/x-map/x-map.component'; +export * from './lib/x-form/x-form.component'; +export * from './lib/x-icon/x-icon.component'; +export * from './lib/x-card/x-card.component'; +export * from './lib/x-list/x-list.component'; +export * from './lib/x-grid/x-grid.component'; +export * from './lib/x-page/x-page.component'; +export * from './lib/x-tabs/x-tabs.component'; +export * from './lib/x-tree/x-tree.component'; +export * from './lib/x-chart/x-chart.component'; +export * from './lib/x-color/x-color.component'; +export * from './lib/x-empty/x-empty.component'; +export * from './lib/x-frame/x-frame.component'; +export * from './lib/x-table/x-table.component'; +export * from './lib/x-button/x-button.component'; +export * from './lib/x-slider/x-slider.component'; +export * from './lib/x-tabs/x-tab/x-tab.component'; +export * from './lib/x-spinner/x-spinner.component'; +export * from './lib/x-slotter/x-slotter.component'; +export * from './lib/x-stepper/x-stepper.component'; +export * from './lib/x-counter/x-counter.component'; +export * from './lib/x-markdown/x-markdown.component'; +export * from './lib/x-stepper/x-step/x-step.component'; +export * from './lib/x-slotter/x-slot/x-slot.component'; +export * from './lib/x-thumbnail/x-thumbnail.component'; +export * from './lib/x-slider/x-slide/x-slide.component'; +export * from './lib/x-page/x-footer/x-footer.component'; +export * from './lib/x-data-pager/x-data-pager.component'; +export * from './lib/x-search-bar/x-search-bar.component'; +export * from './lib/x-action-bar/x-action-bar.component'; +export * from './lib/x-page/x-toolbar/x-toolbar.component'; +export * from './lib/dialogs/x-popover/x-popover.component'; +export * from './lib/x-file-upload/x-file-upload.component'; +export * from './lib/x-progress-bar/x-progress-bar.component'; +export * from './lib/x-color-picker/x-color-picker.component'; +export * from './lib/x-page/x-navigator/x-navigator.component'; +export * from './lib/x-data-scroller/x-data-scroller.component'; +export * from './lib/x-map/x-map-dialog/x-map-dialog.component'; +export * from './lib/x-image-cropper/x-image-cropper.component'; +export * from './lib/x-tab-navigator/x-tab-navigator.component'; +export * from './lib/x-avatar-upload/x-avatar-upload.component'; +export * from './lib/x-file-drop-area/x-file-drop-area.component'; +export * from './lib/x-screen-capture/x-screen-capture.component'; +export * from './lib/x-expandable-panel/x-panel/x-panel.component'; +export * from './lib/x-expandable-list/x-expandable-list.component'; +export * from './lib/x-model-presenter/x-model-presenter.component'; +export * from './lib/x-expandable-panel/x-expandable-panel.component'; +export * from './lib/x-description-list/x-description-list.component'; +export * from './lib/x-theme/x-theme-manager/x-theme-manager.component'; +export * from './lib/dialogs/x-selector-dialog/x-selector-dialog.component'; +export * from './lib/x-color-picker/x-color-picker-dialog/x-color-picker-dialog.component'; +export * from './lib/x-model-presenter/x-model-presenter-item/x-model-presenter-item.component'; +export * from './lib/x-description-list/x-description-list-item/x-description-list-item.component'; +//#endregion + +// +//#region Component Typings ... +export * from './lib/x-ide/x-ide.typings'; +export * from './lib/x-fab/x-fab.typings'; +export * from './lib/x-map/x-map.typings'; +export * from './lib/x-grid/x-grid.typings'; +export * from './lib/x-form/x-form.typings'; +export * from './lib/x-icon/x-icon.typings'; +export * from './lib/x-card/x-card.typings'; +export * from './lib/x-list/x-list.typings'; +export * from './lib/x-page/x-page.typings'; +export * from './lib/x-tabs/x-tabs.typings'; +export * from './lib/x-tree/x-tree.typings'; +export * from './lib/x-empty/x-empty.typings'; +export * from './lib/x-frame/x-frame.typings'; +export * from './lib/x-page/x-page.container'; +export * from './lib/x-chart/x-chart.typings'; +export * from './lib/x-color/x-color.typings'; +export * from './lib/x-theme/x-theme-typings'; +export * from './lib/x-table/x-table.typings'; +export * from './lib/x-button/x-button.typings'; +export * from './lib/x-slider/x-slider.typings'; +export * from './lib/x-stepper/x-stepper.typings'; +export * from './lib/x-slotter/x-slotter.typings'; +export * from './lib/x-counter/x-counter.typings'; +export * from './lib/x-markdown/x-markdown.typings'; +export * from './lib/x-thumbnail/x-thumbnail.typings'; +export * from './lib/x-page/x-footer/x-footer.typings'; +export * from './lib/x-data-pager/x-data-pager.typings'; +export * from './lib/x-action-bar/x-action-bar.typings'; +export * from './lib/x-search-bar/x-search-bar.typings'; +export * from './lib/x-page/x-toolbar/x-toolbar.typings'; +export * from './lib/dialogs/x-popover/x-popover.typings'; +export * from './lib/x-file-upload/x-file-upload.typings'; +export * from './lib/x-progress-bar/x-progress-bar.typings'; +export * from './lib/x-color-picker/x-color-picker.typings'; +export * from './lib/x-page/x-navigator/x-navigator.typings'; +export * from './lib/x-data-scroller/x-data-scroller.typings'; +export * from './lib/x-image-cropper/x-image-cropper.typings'; +export * from './lib/x-tab-navigator/x-tab-navigator.typings'; +export * from './lib/x-avatar-upload/x-avatar-upload.typings'; +export * from './lib/x-map/x-map-dialog/x-map-dialog.typings'; +export * from './lib/x-file-drop-area/x-file-drop-area.typings'; +export * from './lib/x-screen-capture/x-screen-capture.typings'; +export * from './lib/x-screen-capture/x-screen-capture.container'; +export * from './lib/x-expandable-list/x-expandable-list.typings'; +export * from './lib/x-model-presenter/x-model-presenter.typings'; +export * from './lib/x-expandable-panel/x-expandable-panel.typings'; +export * from './lib/x-description-list/x-description-list.typings'; +export * from './lib/x-theme/x-theme-manager/x-theme-manager.typings'; +export * from './lib/dialogs/x-selector-dialog/x-selector-dialog.typings'; +export * from './lib/x-color-picker/x-color-picker-dialog/x-color-picker-dialog.typings'; +export * from './lib/x-model-presenter/x-model-presenter-item/x-model-presenter-item.typings'; +export * from './lib/x-description-list/x-description-list-item/x-description-list-item.typings'; +//#endregion + +// +//#region Component Tools ... +export * from './lib/x-map/x-map.tools'; +export * from './lib/x-form/x-form.tools'; +export * from './lib/x-theme/x-theme.tools'; +export * from './lib/dialogs/x-popover/x-popover.tools'; +export * from './lib/x-form/validators/x-form-validators'; +export * from './lib/dialogs/x-popover/x-popover.service'; +export * from './lib/x-slider/x-slide.animation.providers'; +export * from './lib/x-page/x-navigator/x-navigator.tools'; +export * from './lib/x-tab-navigator/x-tab-navigator.tools'; +export * from './lib/x-map/x-map-dialog/x-map-dialog.service'; +export * from './lib/x-image-cropper/x-image-cropper.service'; +export * from './lib/x-screen-capture/x-screen-capture.service'; +export * from './lib/x-expandable-list/x-expandable-list.tools'; +export * from './lib/x-color-picker/directives/x-color-text.directive'; +export * from './lib/dialogs/x-selector-dialog/x-selector-dialog.service'; +export * from './lib/x-color-picker/x-color-picker-dialog/x-color-picker-dialog.service'; +//#endregion +//#endregion + +// +//#region Export Classes ... +// +export * from './lib/classes/x-base-page'; +export * from './lib/classes/x-base-component'; +export * from './lib/classes/i-x-base.container'; +export * from './lib/classes/i-x-file.container'; +export * from './lib/classes/i-x-toolbar-footer'; +export * from './lib/classes/x-base-action.model'; +export * from './lib/classes/x-base-dialog.service'; +export * from './lib/classes/x-base.dialog.component'; +export * from './lib/classes/x-base.observable.tools'; +export * from './lib/classes/x-base.dialog.component'; +export * from './lib/classes/i-x-card-empty.container'; +export * from './lib/classes/i-x-base-dialog.container'; +export * from './lib/classes/i-x-next-prev-reset.container'; +// +export * from './lib/dialogs/x-dialog/x-dialog.typings'; +export * from './lib/dialogs/x-dialog/x-dialog.component'; +//#endregion + +// +//#region Tokens ... +export * from './lib/tokens/x-injectable-tokens'; +//#endregion + +// +//#region Export Config ... +export * from './lib/config/x-framework-components.config'; +//#endregion + +// +//#region Export Modules ... +export * from './lib/modules/x-material.module'; +export * from './lib/x-framework-components.module'; +export * from './lib/modules/x-base-component.module'; +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\test.ts + +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js'; +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: { + context(path: string, deep?: boolean, filter?: RegExp): { + keys(): string[]; + (id: string): T; + }; +}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-framework-components.module.ts + +import { NgModule } from '@angular/core'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { XIDEModule } from './x-ide/x-ide.module'; +import { XFabModule } from './x-fab/x-fab.module'; +import { XMapModule } from './x-map/x-map.module'; +import { XIconModule } from './x-icon/x-icon.module'; +import { XCardModule } from './x-card/x-card.module'; +import { XListModule } from './x-list/x-list.module'; +import { XFormModule } from './x-form/x-form.module'; +import { XGridModule } from './x-grid/x-grid.module'; +import { XPageModule } from './x-page/x-page.module'; +import { XTabsModule } from './x-tabs/x-tabs.module'; +import { XFrameModule } from './x-frame/x-frame.module'; +import { XEmptyModule } from './x-empty/x-empty.module'; +import { XTableModule } from './x-table/x-table.module'; +import { XFrameworkCoreModule } from 'x-framework-core'; +import { XThemeModule } from './x-theme/x-theme.module'; +import { XColorModule } from './x-color/x-color.module'; +import { XChartModule } from './x-chart/x-chart.module'; +import { XSliderModule } from './x-slider/x-slider.module'; +import { XButtonModule } from './x-button/x-button.module'; +import { XDialogsModule } from './dialogs/x-dialog.module'; +import { XMaterialModule } from './modules/x-material.module'; +import { XSlotterModule } from './x-slotter/x-slotter.module'; +import { XSpinnerModule } from './x-spinner/x-spinner.module'; +import { XStepperModule } from './x-stepper/x-stepper.module'; +import { XCounterModule } from './x-counter/x-counter.module'; +import { XFrameworkServicesModule } from 'x-framework-services'; +import { XMarkdownModule } from './x-markdown/x-markdown.module'; +import { XThumbnailModule } from './x-thumbnail/x-thumbnail.module'; +import { XActionBarModule } from './x-action-bar/x-action-bar.module'; +import { XSearchBarModule } from './x-search-bar/x-search-bar.module'; +import { XDataPagerModule } from './x-data-pager/x-data-pager.module'; +import { XBaseComponentModule } from './modules/x-base-component.module'; +import { XFileUploadModule } from './x-file-upload/x-file-upload.module'; +import { XColorPickerModule } from './x-color-picker/x-color-picker.module'; +import { XProgressBarModule } from './x-progress-bar/x-progress-bar.module'; +import { XImageCropperModule } from './x-image-cropper/x-image-cropper.module'; +import { XTabNavigatorModule } from './x-tab-navigator/x-tab-navigator.module'; +import { XAvatarUploadModule } from './x-avatar-upload/x-avatar-upload.module'; +import { XDataScrollerModule } from './x-data-scroller/x-data-scroller.module'; +import { XFileDropAreaModule } from './x-file-drop-area/x-file-drop-area.module'; +import { XScreenCaptureModule } from './x-screen-capture/x-screen-capture.module'; +import { XExpandableListModule } from './x-expandable-list/x-expandable-list.module'; +import { XModelPresenterModule } from './x-model-presenter/x-model-presenter.module'; +import { XExpandablePanelModule } from './x-expandable-panel/x-expandable-panel.module'; +import { XDescriptionListModule } from './x-description-list/x-description-list.module'; + +@NgModule({ + declarations: [], + imports: [ + // + IonicModule, + RouterModule, + CommonModule, + + // + XDialogsModule, + + // + XMaterialModule, + XBaseComponentModule, + + // + XFrameworkCoreModule, + XFrameworkServicesModule, + + // + XIDEModule, + XFabModule, + XMapModule, + XPageModule, + XIconModule, + XFormModule, + XCardModule, + XListModule, + XTabsModule, + XGridModule, + XChartModule, + XFrameModule, + XEmptyModule, + XColorModule, + XTableModule, + XThemeModule, + XButtonModule, + XSliderModule, + XSlotterModule, + XSpinnerModule, + XStepperModule, + XCounterModule, + XMarkdownModule, + XDataPagerModule, + XSearchBarModule, + XThumbnailModule, + XActionBarModule, + XFileUploadModule, + XProgressBarModule, + XColorPickerModule, + XDataScrollerModule, + XTabNavigatorModule, + XImageCropperModule, + XFileDropAreaModule, + XAvatarUploadModule, + XScreenCaptureModule, + XExpandableListModule, + XModelPresenterModule, + XExpandablePanelModule, + XDescriptionListModule, + ], + exports: [ + // + IonicModule, + RouterModule, + CommonModule, + + // + XDialogsModule, + + // + XMaterialModule, + XBaseComponentModule, + + // + XFrameworkCoreModule, + XFrameworkServicesModule, + + // + XIDEModule, + XFabModule, + XMapModule, + XPageModule, + XIconModule, + XFormModule, + XCardModule, + XListModule, + XTabsModule, + XGridModule, + XEmptyModule, + XChartModule, + XFrameModule, + XColorModule, + XTableModule, + XThemeModule, + XButtonModule, + XSliderModule, + XSlotterModule, + XSpinnerModule, + XStepperModule, + XCounterModule, + XMarkdownModule, + XDataPagerModule, + XSearchBarModule, + XActionBarModule, + XThumbnailModule, + XFileUploadModule, + XProgressBarModule, + XColorPickerModule, + XDataScrollerModule, + XTabNavigatorModule, + XImageCropperModule, + XFileDropAreaModule, + XAvatarUploadModule, + XScreenCaptureModule, + XExpandableListModule, + XModelPresenterModule, + XExpandablePanelModule, + XDescriptionListModule, + ], +}) +export class XFrameworkComponentsModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\i-x-base-dialog.container.ts + +import { XResourceIdentifier, XStandardType } from 'x-framework-core'; + +export interface XBaseDialogConfig { + title?: XStandardType; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\i-x-base.container.ts + +import { Observable } from 'rxjs'; +import { Component } from '@angular/core'; +import { XBaseComponent } from './x-base-component'; + +// +//#region Container ... +export interface IXBaseContainer {} + +export abstract class XBaseContainer implements IXBaseContainer {} + +@Component({ + template: '', +}) +export abstract class XBaseContainerComponent extends XBaseComponent { + // + //#region Abstracts ... + public abstract extractProps(container?: any): Observable; + //#endregion + + // + //#region Props Actions ... + public async extractPropsAsync(container?: any): Promise { + return await this.getValueAsync(this.extractProps(container)); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +export interface IXBaseComponentContainer {} + +export abstract class XBaseComponentContainer + implements IXBaseComponentContainer {} + +@Component({ + template: '', +}) +export abstract class XBaseComponentContainerComponent extends XBaseComponent { + // + //#region Abstracts ... + public abstract extractProps(container?: any): Observable; + //#endregion + + // + //#region Props Actions ... + public async extractPropsAsync(container?: any): Promise { + return await this.getValueAsync(this.extractProps(container)); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\i-x-card-empty.container.ts + +import { + Input, + Inject, + NgZone, + Output, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, +} from 'x-framework-core'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from './i-x-base.container'; +import { + XCardActionModel, + XComponentCardContainer, + IXComponentCardContainer, +} from '../x-card/x-card.typings'; +import { + XEmptyContainer, + XEmptyActionModel, + IXComponentEmptyContainer, +} from '../x-empty/x-empty.typings'; +import { + XActionBarItem, + XActionBarActionModel, + XActionBarActionFiredModel, + XComponentActionBarContainer, + IXComponentActionBarContainer, +} from '../x-action-bar/x-action-bar.typings'; +import { + XSearchBarActionModel, + XSearchBarTypeIdentifier, + XSearchBarEventIdentifier, + XComponentSearchBarContainer, + IXComponentSearchBarContainer, +} from '../x-search-bar/x-search-bar.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { Observable, Subject, forkJoin } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region XCardEmptyContainer ... +// +type TXCardEmptyContainer = IXComponentCardContainer & + IXComponentEmptyContainer; +export interface IXCardEmptyContainer extends TXCardEmptyContainer { } + +// +@Injectable({ + providedIn: 'root', +}) +export class XCardEmptyContainer + extends XBaseContainer + implements IXCardEmptyContainer { + // + //#region XComponentCard ... + readonly isInPage: XStandardType = + this.cardPropertyProvider.isInPage; + readonly wrapWithCard: XStandardType = + this.cardPropertyProvider.wrapWithCard; + readonly cardShowFooter: XStandardType = + this.cardPropertyProvider.cardShowFooter; + readonly cardShowActions: XStandardType = + this.cardPropertyProvider.cardShowActions; + readonly cardColor: XStandardType = + this.cardPropertyProvider.cardColor; + readonly cardForegroundColor: XStandardType = + this.cardPropertyProvider.cardForegroundColor; + readonly cardTitle: XStandardType = + this.cardPropertyProvider.cardTitle; + readonly cardActionProvider: Subject = + this.cardPropertyProvider.cardActionProvider; + readonly cardSubTitle: XStandardType = + this.cardPropertyProvider.cardSubTitle; + readonly cardCssClass: XStandardType> = + this.cardPropertyProvider.cardCssClass; + //#endregion + + // + //#region XComponentEmpty ... + // + readonly showEmpty: XStandardType = true; + readonly emptyHasRefresh: XStandardType = + this.emptyPropertyProvider.hasRefresh; + + // + readonly emptyActionProvider: Subject = + this.emptyPropertyProvider.actionProvider; + readonly emptyCssClass: XStandardType> = + this.emptyPropertyProvider.cssClass; + readonly emptyColor: XStandardType = + this.emptyPropertyProvider.color; + readonly emptyForegroundColor: XStandardType = + this.emptyPropertyProvider.foregroundColor; + readonly emptyMessageColor: XStandardType = + this.emptyPropertyProvider.messageColor; + readonly emptyForegroundMessageColor: XStandardType = + this.emptyPropertyProvider.foregroundMessageColor; + readonly emptyRefreshButtonColor: XStandardType = + this.emptyPropertyProvider.refreshButtonColor; + readonly emptyForegroundRefreshButtonColor: XStandardType = + this.emptyPropertyProvider.foregroundRefreshButtonColor; + readonly emptyMessage: XStandardType = + this.emptyPropertyProvider.message; + readonly emptyRefreshIcon: XStandardType = + this.emptyPropertyProvider.refreshIcon; + readonly emptyRefresh: EventEmitter = + this.emptyPropertyProvider.refresh; + //#endregion + + // + //#region Constructor ... + constructor( + protected cardPropertyProvider: XComponentCardContainer, + protected emptyPropertyProvider: XEmptyContainer + ) { + super(); + } + //#endregion +} + +// +@Injectable() +@Component({ + template: ``, +}) +export class XCardEmptyContainerComponent + extends XBaseContainerComponent + implements IXCardEmptyContainer { + // + //#region Props ... + // + //#region XComponentCard ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + //#endregion + + // + //#region XComponentEmpty ... + // + @Input() + showEmpty: XStandardType = this.propertyProvider.showEmpty; + + @Input() + emptyHasRefresh: XStandardType = + this.propertyProvider.emptyHasRefresh; + + @Input() + emptyActionProvider: Subject = + this.propertyProvider.emptyActionProvider; + + @Input() + emptyCssClass: XStandardType> = + this.propertyProvider.emptyCssClass; + + @Input() + emptyColor: XStandardType = + this.propertyProvider.emptyColor; + + @Input() + emptyForegroundColor: XStandardType = + this.propertyProvider.emptyForegroundColor; + + @Input() + emptyMessageColor: XStandardType = + this.propertyProvider.emptyMessageColor; + + @Input() + emptyForegroundMessageColor: XStandardType = + this.propertyProvider.emptyForegroundMessageColor; + + @Input() + emptyRefreshButtonColor: XStandardType = + this.propertyProvider.emptyRefreshButtonColor; + + @Input() + emptyForegroundRefreshButtonColor: XStandardType = + this.propertyProvider.emptyForegroundRefreshButtonColor; + + @Input() + emptyMessage: XStandardType = + this.propertyProvider.emptyMessage; + + @Input() + emptyRefreshIcon: XStandardType = + this.propertyProvider.emptyRefreshIcon; + + @Output() + emptyRefresh: EventEmitter = new EventEmitter(); + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XCardEmptyContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXCardEmptyContainer + ): Observable { + return forkJoin({ + // + //#region XComponentCard ... + cardCssClass: this.getArrayValue( + container ? container.cardCssClass : this.cardCssClass + ), + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + //#endregion + + // + //#region XComponentEmpty ... + // + showEmpty: this.getValue( + container ? container.showEmpty : this.showEmpty + ), + emptyHasRefresh: this.getValue( + container ? container.emptyHasRefresh : this.emptyHasRefresh + ), + + // + emptyCssClass: this.getValue( + container ? container.emptyCssClass : this.emptyCssClass + ), + emptyColor: this.getValue( + container ? container.emptyColor : this.emptyColor + ), + emptyForegroundColor: this.getValue( + container ? container.emptyForegroundColor : this.emptyForegroundColor + ), + emptyMessageColor: this.getValue( + container ? container.emptyMessageColor : this.emptyMessageColor + ), + emptyForegroundMessageColor: this.getValue( + container + ? container.emptyForegroundMessageColor + : this.emptyForegroundMessageColor + ), + emptyRefreshButtonColor: this.getValue( + container + ? container.emptyRefreshButtonColor + : this.emptyRefreshButtonColor + ), + emptyForegroundRefreshButtonColor: this.getValue( + container + ? container.emptyForegroundRefreshButtonColor + : this.emptyForegroundRefreshButtonColor + ), + emptyMessage: this.getValue( + container ? container.emptyMessage : this.emptyMessage + ), + emptyRefreshIcon: this.getValue( + container ? container.emptyRefreshIcon : this.emptyRefreshIcon + ), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + + // + emptyActionProvider: container + ? container.emptyActionProvider + : this.emptyActionProvider, + + // + emptyRefresh: this.emptyRefresh, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region XCardActionBarContainer ... +type TXCardActionBarContainer = IXComponentCardContainer & IXComponentActionBarContainer; +export interface IXCardActionBarContainer extends TXCardActionBarContainer { } + +@Injectable({ + providedIn: 'root', +}) +export class XCardActionBarContainer extends XBaseContainer implements IXCardActionBarContainer { + // + //#region Props ... + // + //#region ComponentCard Props ... + readonly isInPage: XStandardType = + this.cardPropertyProvider.isInPage; + readonly wrapWithCard: XStandardType = + this.cardPropertyProvider.wrapWithCard; + readonly cardActionProvider: Subject = + this.cardPropertyProvider.cardActionProvider; + readonly cardCssClass: XStandardType> = + this.cardPropertyProvider.cardCssClass; + readonly cardColor: XStandardType = + this.cardPropertyProvider.cardColor; + readonly cardForegroundColor: XStandardType = + this.cardPropertyProvider.cardForegroundColor; + readonly cardTitle: XStandardType = + this.cardPropertyProvider.cardTitle; + readonly cardSubTitle: XStandardType = + this.cardPropertyProvider.cardSubTitle; + readonly cardShowFooter: XStandardType = + this.cardPropertyProvider.cardShowFooter; + readonly cardShowActions: XStandardType = + this.cardPropertyProvider.cardShowActions; + //#endregion + + // + //#region ComponentActionBar Props ... + readonly showActionBar: XStandardType = + this.actionBarPropertyProvider.showActionBar; + readonly actionBarActionProvider: Subject = + this.actionBarPropertyProvider.actionBarActionProvider; + readonly actionBarCssClass: XStandardType> = + this.actionBarPropertyProvider.actionBarCssClass; + readonly actionBarColor: XStandardType = + this.actionBarPropertyProvider.actionBarColor; + readonly actionBarForegroundColor: XStandardType = + this.actionBarPropertyProvider.actionBarForegroundColor; + readonly actionBarActions: XStandardType> = + this.actionBarPropertyProvider.actionBarActions; + readonly actionBarActionFired: EventEmitter = + this.actionBarPropertyProvider.actionBarActionFired; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected cardPropertyProvider: XComponentCardContainer, + protected actionBarPropertyProvider: XComponentActionBarContainer + ) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export class XCardActionBarContainerComponent + extends XBaseContainerComponent + implements IXCardActionBarContainer { + // + //#region Props ... + // + //#region ComponentCard Props ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + //#endregion + + // + //#region ComponentActionBar Props ... + @Input() + showActionBar: XStandardType = this.propertyProvider.showActionBar; + + @Input() + actionBarCssClass: XStandardType> = + this.propertyProvider.actionBarCssClass; + + @Input() + actionBarColor: XStandardType = + this.propertyProvider.actionBarColor; + + @Input() + actionBarForegroundColor: XStandardType = + this.propertyProvider.actionBarForegroundColor; + + @Input() + actionBarActionProvider: Subject = + this.propertyProvider.actionBarActionProvider; + + @Output() + actionBarActionFired: EventEmitter = + this.propertyProvider.actionBarActionFired; + + actionBarActions: XStandardType> = + this.propertyProvider.actionBarActions; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XCardActionBarContainer, + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstracts ... + public extractProps(container?: IXCardActionBarContainer): Observable { + return forkJoin({ + // + //#region XComponentCard ... + cardCssClass: this.getArrayValue( + container ? container.cardCssClass : this.cardCssClass + ), + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + //#endregion + + // + //#region XComponentActionBar ... + showActionBar: this.getValue( + container ? container.showActionBar : this.showActionBar + ), + actionBarCssClass: this.getValue( + container ? container.actionBarCssClass : this.actionBarCssClass + ), + actionBarColor: this.getValue( + container ? container.actionBarColor : this.actionBarColor + ), + actionBarForegroundColor: this.getValue( + container + ? container.actionBarForegroundColor + : this.actionBarForegroundColor + ), + actionBarActions: this.getValue( + container ? container.actionBarActions : this.actionBarActions + ), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + + // + actionBarActionProvider: container + ? container.actionBarActionProvider + : this.actionBarActionProvider, + + // + actionBarActionFired: this.actionBarActionFired, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region XCardEmptyActionSearchBarContainer ... +// +type TXCardEmptyActionSearchBarContainer = IXCardEmptyContainer & + IXComponentActionBarContainer & + IXComponentSearchBarContainer; +export interface IXCardEmptyActionSearchBarContainer + extends IXBaseContainer, + TXCardEmptyActionSearchBarContainer { } + +// +@Injectable({ + providedIn: 'root', +}) +export class XCardEmptyActionSearchBarContainer + extends XCardEmptyContainer + implements IXCardEmptyActionSearchBarContainer { + // + //#region Props ... + // + //#region ActionBar ... + // + readonly showActionBar: XStandardType = + this.actionBarPropertyProvider.showActionBar; + + // + readonly actionBarActionProvider: Subject = + this.actionBarPropertyProvider.actionBarActionProvider; + readonly actionBarCssClass: XStandardType> = + this.actionBarPropertyProvider.actionBarCssClass; + + // + readonly actionBarColor: XStandardType = + this.actionBarPropertyProvider.actionBarColor; + readonly actionBarForegroundColor: XStandardType = + this.actionBarPropertyProvider.actionBarForegroundColor; + + // + readonly actionBarActions: XStandardType> = + this.actionBarPropertyProvider.actionBarActions; + + // + readonly actionBarActionFired: EventEmitter = + this.actionBarPropertyProvider.actionBarActionFired; + //#endregion + + // + //#region SearchBar ... + // + readonly showSearchBar: XStandardType = + this.searchBarPropertyProvider.showSearchBar; + + // + readonly searchBarActionProvider: Subject = + this.searchBarPropertyProvider.searchBarActionProvider; + readonly searchBarCssClass: XStandardType> = + this.searchBarPropertyProvider.searchBarCssClass; + + // + readonly searchBarColor: XStandardType = + this.searchBarPropertyProvider.searchBarColor; + readonly searchBarForegroundColor: XStandardType = + this.searchBarPropertyProvider.searchBarForegroundColor; + + // + readonly searchBarSearchInputColor: XStandardType = + this.searchBarPropertyProvider.searchBarSearchInputColor; + readonly searchBarForegroundSearchInputColor: XStandardType = + this.searchBarPropertyProvider.searchBarForegroundSearchInputColor; + + // + readonly searchBarType: XStandardType = + this.searchBarPropertyProvider.searchBarType; + readonly searchBarTitle: XStandardType = + this.searchBarPropertyProvider.searchBarTitle; + readonly searchBarValue: XStandardType = + this.searchBarPropertyProvider.searchBarValue; + readonly searchBarEnterKeyHint: XStandardType = + this.searchBarPropertyProvider.searchBarEnterKeyHint; + readonly searchBarKeyboardType: XStandardType = + this.searchBarPropertyProvider.searchBarKeyboardType; + readonly searchBarSpellcheck: XStandardType = + this.searchBarPropertyProvider.searchBarSpellcheck; + readonly searchBarDebounce: XStandardType = + this.searchBarPropertyProvider.searchBarDebounce; + + // + readonly searchBarShowCancelButton: XStandardType = + this.searchBarPropertyProvider.searchBarShowCancelButton; + readonly searchBarCancelButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonIcon; + readonly searchBarCancelButtonTitle: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonTitle; + readonly searchBarCancelButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonIconColor; + + // + readonly searchBarShowClearButton: XStandardType = + this.searchBarPropertyProvider.searchBarShowClearButton; + readonly searchBarClearButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarClearButtonIcon; + readonly searchBarClearButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarClearButtonIconColor; + + // + readonly searchBarSearchButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarSearchButtonIcon; + readonly searchBarSearchButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarSearchButtonIconColor; + + // + readonly searchBarQueryChange: EventEmitter = + this.searchBarPropertyProvider.searchBarQueryChange; + readonly searchBarOnEvent: EventEmitter = + this.searchBarPropertyProvider.searchBarOnEvent; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected cardPropertyProvider: XComponentCardContainer, + protected emptyPropertyProvider: XEmptyContainer, + protected actionBarPropertyProvider: XComponentActionBarContainer, + protected searchBarPropertyProvider: XComponentSearchBarContainer + ) { + super(cardPropertyProvider, emptyPropertyProvider); + } + //#endregion +} + +// +@Injectable() +@Component({ + template: ``, +}) +export class XCardEmptyActionSearchBarContainerComponent + extends XCardEmptyContainerComponent + implements IXCardEmptyActionSearchBarContainer { + // + //#region Props ... + // + //#region ActionBar ... + // + @Input() + showActionBar: XStandardType = this.propertyProvider.showActionBar; + + // + @Input() + actionBarActionProvider: Subject = + this.propertyProvider.actionBarActionProvider; + + @Input() + actionBarCssClass: XStandardType> = + this.propertyProvider.actionBarCssClass; + + // + @Input() + actionBarColor: XStandardType = + this.propertyProvider.actionBarColor; + + @Input() + actionBarForegroundColor: XStandardType = + this.propertyProvider.actionBarForegroundColor; + + // + @Input() + actionBarActions: XStandardType> = + this.propertyProvider.actionBarActions; + + // + @Output() + actionBarActionFired: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region SearchBar ... + // + @Input() + showSearchBar: XStandardType = this.propertyProvider.showSearchBar; + + // + @Input() + searchBarActionProvider: Subject = + this.propertyProvider.searchBarActionProvider; + + @Input() + searchBarCssClass: XStandardType> = + this.propertyProvider.searchBarCssClass; + + // + @Input() + searchBarColor: XStandardType = + this.propertyProvider.searchBarColor; + + @Input() + searchBarForegroundColor: XStandardType = + this.propertyProvider.searchBarForegroundColor; + + // + @Input() + searchBarSearchInputColor: XStandardType = + this.propertyProvider.searchBarSearchInputColor; + + @Input() + searchBarForegroundSearchInputColor: XStandardType = + this.propertyProvider.searchBarForegroundSearchInputColor; + + // + @Input() + searchBarType: XStandardType = + this.propertyProvider.searchBarType; + + @Input() + searchBarTitle: XStandardType = + this.propertyProvider.searchBarTitle; + + @Input() + searchBarValue: XStandardType = this.propertyProvider.searchBarValue; + + @Input() + searchBarEnterKeyHint: XStandardType = + this.propertyProvider.searchBarEnterKeyHint; + + @Input() + searchBarKeyboardType: XStandardType = + this.propertyProvider.searchBarKeyboardType; + + @Input() + searchBarSpellcheck: XStandardType = + this.propertyProvider.searchBarSpellcheck; + + @Input() + searchBarDebounce: XStandardType = + this.propertyProvider.searchBarDebounce; + + // + @Input() + searchBarShowCancelButton: XStandardType = + this.propertyProvider.searchBarShowCancelButton; + + @Input() + searchBarCancelButtonIcon: XStandardType = + this.propertyProvider.searchBarCancelButtonIcon; + + @Input() + searchBarCancelButtonTitle: XStandardType = + this.propertyProvider.searchBarCancelButtonTitle; + + @Input() + searchBarCancelButtonIconColor: XStandardType = + this.propertyProvider.searchBarCancelButtonIconColor; + + // + @Input() + searchBarClearButtonIcon: XStandardType = + this.propertyProvider.searchBarClearButtonIcon; + + @Input() + searchBarClearButtonIconColor: XStandardType = + this.propertyProvider.searchBarClearButtonIconColor; + + // + @Input() + searchBarShowClearButton: XStandardType = + this.propertyProvider.searchBarShowClearButton; + + @Input() + searchBarSearchButtonIcon: XStandardType = + this.propertyProvider.searchBarSearchButtonIcon; + + @Input() + searchBarSearchButtonIconColor: XStandardType = + this.propertyProvider.searchBarSearchButtonIconColor; + + // + @Output() + searchBarQueryChange: EventEmitter = new EventEmitter(); + + @Output() + searchBarOnEvent: EventEmitter = new EventEmitter(); + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XCardEmptyActionSearchBarContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXCardEmptyActionSearchBarContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + // + //#region ActionBar ... + // + showActionBar: this.getValue( + container ? container.showActionBar : this.showActionBar + ), + actionBarCssClass: this.getValue( + container ? container.actionBarCssClass : this.actionBarCssClass + ), + actionBarColor: this.getValue( + container ? container.actionBarColor : this.actionBarColor + ), + actionBarForegroundColor: this.getValue( + container + ? container.actionBarForegroundColor + : this.actionBarForegroundColor + ), + actionBarActions: this.getValue( + container ? container.actionBarActions : this.actionBarActions + ), + //#endregion + + // + //#region SearchBar ... + // + showSearchBar: this.getValue( + container ? container.showSearchBar : this.showSearchBar + ), + searchBarCssClass: this.getValue( + container ? container.searchBarCssClass : this.searchBarCssClass + ), + searchBarColor: this.getValue( + container ? container.searchBarColor : this.searchBarColor + ), + searchBarForegroundColor: this.getValue( + container + ? container.searchBarForegroundColor + : this.searchBarForegroundColor + ), + searchBarSearchInputColor: this.getValue( + container + ? container.searchBarSearchInputColor + : this.searchBarSearchInputColor + ), + searchBarForegroundSearchInputColor: this.getValue( + container + ? container.searchBarForegroundSearchInputColor + : this.searchBarForegroundSearchInputColor + ), + + // + searchBarType: this.getValue( + container ? container.searchBarType : this.searchBarType + ), + searchBarTitle: this.getValue( + container ? container.searchBarTitle : this.searchBarTitle + ), + searchBarValue: this.getValue( + container ? container.searchBarValue : this.searchBarValue + ), + searchBarEnterKeyHint: this.getValue( + container + ? container.searchBarEnterKeyHint + : this.searchBarEnterKeyHint + ), + searchBarKeyboardType: this.getValue( + container + ? container.searchBarKeyboardType + : this.searchBarKeyboardType + ), + searchBarSpellcheck: this.getValue( + container + ? container.searchBarSpellcheck + : this.searchBarSpellcheck + ), + searchBarDebounce: this.getValue( + container ? container.searchBarDebounce : this.searchBarDebounce + ), + + // + searchBarShowCancelButton: this.getValue( + container + ? container.searchBarShowCancelButton + : this.searchBarShowCancelButton + ), + searchBarCancelButtonIcon: this.getValue( + container + ? container.searchBarCancelButtonIcon + : this.searchBarCancelButtonIcon + ), + searchBarCancelButtonTitle: this.getValue( + container + ? container.searchBarCancelButtonTitle + : this.searchBarCancelButtonTitle + ), + searchBarCancelButtonIconColor: this.getValue( + container + ? container.searchBarCancelButtonIconColor + : this.searchBarCancelButtonIconColor + ), + + // + searchBarShowClearButton: this.getValue( + container + ? container.searchBarShowClearButton + : this.searchBarShowClearButton + ), + searchBarClearButtonIcon: this.getValue( + container + ? container.searchBarClearButtonIcon + : this.searchBarClearButtonIcon + ), + searchBarClearButtonIconColor: this.getValue( + container + ? container.searchBarClearButtonIconColor + : this.searchBarClearButtonIconColor + ), + + // + searchBarSearchButtonIcon: this.getValue( + container + ? container.searchBarSearchButtonIcon + : this.searchBarSearchButtonIcon + ), + searchBarSearchButtonIconColor: this.getValue( + container + ? container.searchBarSearchButtonIconColor + : this.searchBarSearchButtonIconColor + ), + //#endregion + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionBarActionProvider: container + ? container.actionBarActionProvider + : this.actionBarActionProvider, + actionBarActionFired: this.actionBarActionFired, + + // + searchBarActionProvider: container + ? container.searchBarActionProvider + : this.searchBarActionProvider, + + // + searchBarQueryChange: this.searchBarQueryChange, + searchBarOnEvent: this.searchBarOnEvent, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\i-x-file.container.ts + +import { + Input, + Inject, + NgZone, + Output, + Component, + Renderer2, + Injectable, + ElementRef, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XExceptionIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XExceptionIdentifier, +} from 'x-framework-core'; +import { + XCardActionModel, + XComponentCardContainer, + IXComponentCardContainer, +} from '../x-card/x-card.typings'; +import { map } from 'rxjs/operators'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { + XFileDropAreaActionModel, + XComponentFileDropAreaContainer, + IXComponentFileDropAreaContainer, +} from '../x-file-drop-area/x-file-drop-area.typings'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XBaseContainer, XBaseContainerComponent } from './i-x-base.container'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region Max Allowed Files ... +export interface IXMaxAllowedFilesContainer { + maxAllowedFiles: XStandardType; + maxAllowedFilesException: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XMaxAllowedFilesContainer + implements Readonly +{ + // + //#region Props ... + readonly maxAllowedFiles: XStandardType = this.config.maxUploadFiles; + + readonly maxAllowedFilesException: XStandardType = + XExceptionIDs.MaxAllowedFilesExceeded; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + private config: XFrameworkComponentsConfig + ) {} + //#endregion +} +//#endregion + +// +//#region Max Allowed File Size ... +export interface IXMaxAllowedSizeContainer { + maxAllowedSize: XStandardType; + maxAllowedSizeException: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XMaxAllowedSizeContainer + implements Readonly +{ + // + //#region Props ... + readonly maxAllowedSize: XStandardType = this.config.maxAllowedSize; + + readonly maxAllowedSizeException: XStandardType = + XExceptionIDs.MaxFilesSizeExceeded; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + private config: XFrameworkComponentsConfig + ) {} + //#endregion +} +//#endregion + +// +//#region Min Allowed File Size ... +export interface IXMinAllowedFileSizeContainer { + minAllowedFileSize: XStandardType; + minAllowedFileSizeException: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XMinAllowedFileSizeContainer + implements Readonly +{ + // + //#region Props ... + readonly minAllowedFileSize: XStandardType = + this.config.minAllowedFileSize; + + readonly minAllowedFileSizeException: XStandardType = + XExceptionIDs.MinFilesSizeExceeded; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + private config: XFrameworkComponentsConfig + ) {} + //#endregion +} +//#endregion + +// +//#region Max Allowed Size ... +export interface IXMaxAllowedFileSizeContainer { + maxAllowedFileSize: XStandardType; + maxAllowedFileSizeException: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XMaxAllowedFileSizeContainer + implements Readonly +{ + // + //#region Props ... + readonly maxAllowedFileSize: XStandardType = + this.config.maxAllowedFileSize; + + readonly maxAllowedFileSizeException: XStandardType = + XExceptionIDs.MaxFilesSizeExceeded; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + private config: XFrameworkComponentsConfig + ) {} + //#endregion +} +//#endregion + +// +//#region Regular File ... +export interface IXFileRegularContainer { + emptyFileException: XStandardType; + notAllowedFileException: XStandardType; + allowedExtensions: XStandardType>; +} + +@Injectable({ + providedIn: 'root', +}) +export class XFileRegularContainer implements Readonly { + // + //#region Props ... + readonly emptyFileException: XStandardType = + XExceptionIDs.EmptyFile; + + readonly notAllowedFileException: XStandardType = + XExceptionIDs.InavlidFile; + + readonly allowedExtensions: XStandardType> = []; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + private config: XFrameworkComponentsConfig + ) {} + //#endregion +} +//#endregion + +// +//#region Combine ... +type XCombinedType = IXFileRegularContainer & + IXMaxAllowedFilesContainer & + IXMaxAllowedSizeContainer & + IXMinAllowedFileSizeContainer & + IXMaxAllowedFileSizeContainer; + +// tslint:disable-next-line:no-empty-interface +export interface IXFileContainer extends XCombinedType {} + +@Injectable({ + providedIn: 'root', +}) +export class XFileContainer implements Readonly { + // + //#region Props ... + // + readonly emptyFileException: XStandardType = + XExceptionIDs.EmptyFile; + + readonly notAllowedFileException: XStandardType = + XExceptionIDs.InavlidFile; + + readonly allowedExtensions: XStandardType> = []; + + // + readonly maxAllowedFiles: XStandardType = this.config.maxUploadFiles; + + readonly maxAllowedFilesException: XStandardType = + XExceptionIDs.MaxAllowedFilesExceeded; + + // + readonly maxAllowedSize: XStandardType = this.config.maxAllowedSize; + + readonly maxAllowedSizeException: XStandardType = + XExceptionIDs.MaxFilesSizeExceeded; + + // + readonly minAllowedFileSize: XStandardType = + this.config.minAllowedFileSize; + + readonly minAllowedFileSizeException: XStandardType = + XExceptionIDs.MinFilesSizeExceeded; + + // + readonly maxAllowedFileSize: XStandardType = + this.config.maxAllowedFileSize; + + readonly maxAllowedFileSizeException: XStandardType = + XExceptionIDs.MaxFilesSizeExceeded; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + private config: XFrameworkComponentsConfig + ) {} + //#endregion +} +//#endregion + +// +//#region XFileContainerComponent ... +@Injectable({ + providedIn: 'root', +}) +@Component({ + template: ` + + `, +}) +export abstract class XFileContainerComponent + extends XBaseContainerComponent + implements IXFileContainer +{ + // + //#region Props ... + // + @Input() + emptyFileException: XStandardType = + this.propertyProvider.emptyFileException; + + @Input() + notAllowedFileException: XStandardType = + this.propertyProvider.notAllowedFileException; + + @Input() + allowedExtensions: XStandardType> = + this.propertyProvider.allowedExtensions; + + // + @Input() + maxAllowedFiles: XStandardType = + this.propertyProvider.maxAllowedFiles; + + @Input() + maxAllowedFilesException: XStandardType = + this.propertyProvider.maxAllowedFilesException; + + // + @Input() + maxAllowedSize: XStandardType = this.propertyProvider.maxAllowedSize; + + @Input() + maxAllowedSizeException: XStandardType = + this.propertyProvider.maxAllowedSizeException; + + // + @Input() + minAllowedFileSize: XStandardType = + this.propertyProvider.minAllowedFileSize; + + @Input() + minAllowedFileSizeException: XStandardType = + this.propertyProvider.minAllowedFileSizeException; + + // + @Input() + maxAllowedFileSize: XStandardType = + this.propertyProvider.maxAllowedFileSize; + + @Input() + maxAllowedFileSizeException: XStandardType = + this.propertyProvider.maxAllowedFileSizeException; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XFileContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXFileContainer + ): Observable { + return forkJoin({ + // + emptyFileException: this.getValue( + container ? container.emptyFileException : this.emptyFileException + ), + notAllowedFileException: this.getValue( + container + ? container.notAllowedFileException + : this.notAllowedFileException + ), + allowedExtensions: this.getValue( + container ? container.allowedExtensions : this.allowedExtensions + ), + maxAllowedFiles: this.getValue( + container ? container.maxAllowedFiles : this.maxAllowedFiles + ), + maxAllowedFilesException: this.getValue( + container + ? container.maxAllowedFilesException + : this.maxAllowedFilesException + ), + maxAllowedSize: this.getValue( + container ? container.maxAllowedSize : this.maxAllowedSize + ), + maxAllowedSizeException: this.getValue( + container + ? container.maxAllowedSizeException + : this.maxAllowedSizeException + ), + minAllowedFileSize: this.getValue( + container ? container.minAllowedFileSize : this.minAllowedFileSize + ), + minAllowedFileSizeException: this.getValue( + container + ? container.minAllowedFileSizeException + : this.minAllowedFileSizeException + ), + maxAllowedFileSize: this.getValue( + container ? container.maxAllowedFileSize : this.maxAllowedFileSize + ), + maxAllowedFileSizeException: this.getValue( + container + ? container.maxAllowedFileSizeException + : this.maxAllowedFileSizeException + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region XCardFileContainer ... +// +type TXCardFileContainer = IXComponentCardContainer & IXFileContainer; +export interface IXCardFileContainer extends TXCardFileContainer {} + +// +@Injectable({ + providedIn: 'root', +}) +export class XCardFileContainer + extends XBaseContainer + implements Readonly +{ + // + //#region XComponentCard ... + readonly isInPage: XStandardType = + this.cardPropertyProvider.isInPage; + readonly wrapWithCard: XStandardType = + this.cardPropertyProvider.wrapWithCard; + readonly cardShowFooter: XStandardType = + this.cardPropertyProvider.cardShowFooter; + readonly cardShowActions: XStandardType = + this.cardPropertyProvider.cardShowActions; + readonly cardColor: XStandardType = + this.cardPropertyProvider.cardColor; + readonly cardForegroundColor: XStandardType = + this.cardPropertyProvider.cardForegroundColor; + readonly cardTitle: XStandardType = + this.cardPropertyProvider.cardTitle; + readonly cardActionProvider: Subject = + this.cardPropertyProvider.cardActionProvider; + readonly cardSubTitle: XStandardType = + this.cardPropertyProvider.cardSubTitle; + readonly cardCssClass: XStandardType> = + this.cardPropertyProvider.cardCssClass; + //#endregion + + // + //#region XFile ... + // + readonly emptyFileException: XStandardType = + this.filePropertyProvider.emptyFileException; + + readonly notAllowedFileException: XStandardType = + this.filePropertyProvider.notAllowedFileException; + + readonly allowedExtensions: XStandardType> = + this.filePropertyProvider.allowedExtensions; + + // + readonly maxAllowedFiles: XStandardType = + this.filePropertyProvider.maxAllowedFiles; + + readonly maxAllowedFilesException: XStandardType = + this.filePropertyProvider.maxAllowedFilesException; + + // + readonly maxAllowedSize: XStandardType = + this.filePropertyProvider.maxAllowedSize; + + readonly maxAllowedSizeException: XStandardType = + this.filePropertyProvider.maxAllowedSizeException; + + // + readonly minAllowedFileSize: XStandardType = + this.filePropertyProvider.minAllowedFileSize; + + readonly minAllowedFileSizeException: XStandardType = + this.filePropertyProvider.minAllowedFileSizeException; + + // + readonly maxAllowedFileSize: XStandardType = + this.filePropertyProvider.maxAllowedFileSize; + + readonly maxAllowedFileSizeException: XStandardType = + this.filePropertyProvider.maxAllowedFileSizeException; + //#endregion + + // + //#region Constructor ... + constructor( + protected cardPropertyProvider: XComponentCardContainer, + protected filePropertyProvider: XFileContainer + ) { + super(); + } + //#endregion +} + +// +@Injectable() +@Component({ + template: ``, +}) +export class XCardFileContainerComponent + extends XBaseContainerComponent + implements IXCardFileContainer +{ + // + //#region Props ... + // + //#region XComponentCard ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + //#endregion + + // + //#region File ... + // + @Input() + emptyFileException: XStandardType = + this.propertyProvider.emptyFileException; + + @Input() + notAllowedFileException: XStandardType = + this.propertyProvider.notAllowedFileException; + + @Input() + allowedExtensions: XStandardType> = + this.propertyProvider.allowedExtensions; + + // + @Input() + maxAllowedFiles: XStandardType = + this.propertyProvider.maxAllowedFiles; + + @Input() + maxAllowedFilesException: XStandardType = + this.propertyProvider.maxAllowedFilesException; + + // + @Input() + maxAllowedSize: XStandardType = this.propertyProvider.maxAllowedSize; + + @Input() + maxAllowedSizeException: XStandardType = + this.propertyProvider.maxAllowedSizeException; + + // + @Input() + minAllowedFileSize: XStandardType = + this.propertyProvider.minAllowedFileSize; + + @Input() + minAllowedFileSizeException: XStandardType = + this.propertyProvider.minAllowedFileSizeException; + + // + @Input() + maxAllowedFileSize: XStandardType = + this.propertyProvider.maxAllowedFileSize; + + @Input() + maxAllowedFileSizeException: XStandardType = + this.propertyProvider.maxAllowedFileSizeException; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XCardFileContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXCardFileContainer + ): Observable { + return forkJoin({ + // + //#region XComponentCard ... + cardCssClass: this.getArrayValue( + container ? container.cardCssClass : this.cardCssClass + ), + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + //#endregion + + // + //#region File ... + emptyFileException: this.getValue( + container ? container.emptyFileException : this.emptyFileException + ), + notAllowedFileException: this.getValue( + container + ? container.notAllowedFileException + : this.notAllowedFileException + ), + allowedExtensions: this.getValue( + container ? container.allowedExtensions : this.allowedExtensions + ), + maxAllowedFiles: this.getValue( + container ? container.maxAllowedFiles : this.maxAllowedFiles + ), + maxAllowedFilesException: this.getValue( + container + ? container.maxAllowedFilesException + : this.maxAllowedFilesException + ), + maxAllowedSize: this.getValue( + container ? container.maxAllowedSize : this.maxAllowedSize + ), + maxAllowedSizeException: this.getValue( + container + ? container.maxAllowedSizeException + : this.maxAllowedSizeException + ), + minAllowedFileSize: this.getValue( + container ? container.minAllowedFileSize : this.minAllowedFileSize + ), + minAllowedFileSizeException: this.getValue( + container + ? container.minAllowedFileSizeException + : this.minAllowedFileSizeException + ), + maxAllowedFileSize: this.getValue( + container ? container.maxAllowedFileSize : this.maxAllowedFileSize + ), + maxAllowedFileSizeException: this.getValue( + container + ? container.maxAllowedFileSizeException + : this.maxAllowedFileSizeException + ), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region XCardFileFileDropAreaContainer ... +// +type TXCardFileFileDropContainer = IXComponentCardContainer & + IXFileContainer & + IXComponentFileDropAreaContainer; +export interface IXCardFileFileDropContainer + extends TXCardFileFileDropContainer {} + +// +@Injectable({ + providedIn: 'root', +}) +export class XCardFileFileDropContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + //#region ComponentCard ... + readonly isInPage: XStandardType = + this.cardPropertyProvider.isInPage; + readonly wrapWithCard: XStandardType = + this.cardPropertyProvider.wrapWithCard; + readonly cardShowFooter: XStandardType = + this.cardPropertyProvider.cardShowFooter; + readonly cardShowActions: XStandardType = + this.cardPropertyProvider.cardShowActions; + readonly cardColor: XStandardType = + this.cardPropertyProvider.cardColor; + readonly cardForegroundColor: XStandardType = + this.cardPropertyProvider.cardForegroundColor; + readonly cardTitle: XStandardType = + this.cardPropertyProvider.cardTitle; + readonly cardActionProvider: Subject = + this.cardPropertyProvider.cardActionProvider; + readonly cardSubTitle: XStandardType = + this.cardPropertyProvider.cardSubTitle; + readonly cardCssClass: XStandardType> = + this.cardPropertyProvider.cardCssClass; + //#endregion + + // + //#region File ... + // + readonly emptyFileException: XStandardType = + this.filePropertyProvider.emptyFileException; + + readonly notAllowedFileException: XStandardType = + this.filePropertyProvider.notAllowedFileException; + + readonly allowedExtensions: XStandardType> = + this.filePropertyProvider.allowedExtensions; + + // + readonly maxAllowedFiles: XStandardType = + this.filePropertyProvider.maxAllowedFiles; + + readonly maxAllowedFilesException: XStandardType = + this.filePropertyProvider.maxAllowedFilesException; + + // + readonly maxAllowedSize: XStandardType = + this.filePropertyProvider.maxAllowedSize; + + readonly maxAllowedSizeException: XStandardType = + this.filePropertyProvider.maxAllowedSizeException; + + // + readonly minAllowedFileSize: XStandardType = + this.filePropertyProvider.minAllowedFileSize; + + readonly minAllowedFileSizeException: XStandardType = + this.filePropertyProvider.minAllowedFileSizeException; + + // + readonly maxAllowedFileSize: XStandardType = + this.filePropertyProvider.maxAllowedFileSize; + + readonly maxAllowedFileSizeException: XStandardType = + this.filePropertyProvider.maxAllowedFileSizeException; + //#endregion + + // + //#region File Drop ... + // + readonly showFileDropArea: XStandardType = + this.fileDropPropertyProvider.showFileDropArea; + + // + readonly fileDropAreaActionProvider: Subject = + this.fileDropPropertyProvider.fileDropAreaActionProvider; + + readonly fileDropAreaCssClass: XStandardType> = + this.fileDropPropertyProvider.fileDropAreaCssClass; + + // + readonly fileDropAreaColor: XStandardType = + this.fileDropPropertyProvider.fileDropAreaColor; + + readonly fileDropAreaForegroundColor: XStandardType = + this.fileDropPropertyProvider.fileDropAreaForegroundColor; + + // + readonly fileDropAreaIcon: XStandardType = + this.fileDropPropertyProvider.fileDropAreaIcon; + + readonly fileDropAreaMessage: XStandardType = + this.fileDropPropertyProvider.fileDropAreaMessage; + + // + readonly fileDropAreaDisableFileCheck: XStandardType = + this.fileDropPropertyProvider.fileDropAreaDisableFileCheck; + + // + readonly fileDropAreaSupportsMultipleFiles: XStandardType = + this.fileDropPropertyProvider.fileDropAreaSupportsMultipleFiles; + + readonly fileDropAreaAllowedExtensions: XStandardType< + XOneOrManyType + > = this.fileDropPropertyProvider.fileDropAreaAllowedExtensions; + + // + readonly fileDropAreaDragDisabled: XStandardType = + this.fileDropPropertyProvider.fileDropAreaDragDisabled; + + readonly fileDropAreaContentTemplate: TemplateRef = + this.fileDropPropertyProvider.fileDropAreaContentTemplate; + + readonly fileDropAreaFilesChangeEmiter: EventEmitter> = + this.fileDropPropertyProvider.fileDropAreaFilesChangeEmiter; + + // + readonly fileDropAreaSuccessColor: XStandardType = + this.fileDropPropertyProvider.fileDropAreaSuccessColor; + + readonly fileDropAreaErrorColor: XStandardType = + this.fileDropPropertyProvider.fileDropAreaErrorColor; + + readonly fileDropAreaAccentColor: XStandardType = + this.fileDropPropertyProvider.fileDropAreaAccentColor; + + // + //#region ComponentsCard ... + readonly fileDropAreaIsInPage: XStandardType = + this.fileDropPropertyProvider.fileDropAreaIsInPage; + readonly fileDropAreaWrapWithCard: XStandardType = + this.fileDropPropertyProvider.fileDropAreaWrapWithCard; + readonly fileDropAreaCardShowFooter: XStandardType = + this.fileDropPropertyProvider.fileDropAreaCardShowFooter; + readonly fileDropAreaCardShowActions: XStandardType = + this.fileDropPropertyProvider.fileDropAreaCardShowActions; + readonly fileDropAreaCardColor: XStandardType = + this.fileDropPropertyProvider.fileDropAreaCardColor; + readonly fileDropAreaCardForegroundColor: XStandardType = + this.fileDropPropertyProvider.fileDropAreaCardForegroundColor; + readonly fileDropAreaCardTitle: XStandardType = + this.fileDropPropertyProvider.fileDropAreaCardTitle; + readonly fileDropAreaCardActionProvider: Subject = + this.fileDropPropertyProvider.fileDropAreaCardActionProvider; + readonly fileDropAreaCardSubTitle: XStandardType = + this.fileDropPropertyProvider.fileDropAreaCardSubTitle; + readonly fileDropAreaCardCssClass: XStandardType> = + this.fileDropPropertyProvider.fileDropAreaCardCssClass; + //#endregion + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected cardPropertyProvider: XComponentCardContainer, + protected fileDropPropertyProvider: XComponentFileDropAreaContainer, + protected filePropertyProvider: XFileContainer + ) { + super(); + } + //#endregion +} + +// +@Injectable() +@Component({ + template: ``, +}) +export class XCardFileFileDropContainerComponent + extends XBaseContainerComponent + implements IXCardFileContainer +{ + // + //#region Props ... + // + //#region XComponentCard ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + //#endregion + + // + //#region File ... + // + @Input() + emptyFileException: XStandardType = + this.propertyProvider.emptyFileException; + + @Input() + notAllowedFileException: XStandardType = + this.propertyProvider.notAllowedFileException; + + @Input() + allowedExtensions: XStandardType> = + this.propertyProvider.allowedExtensions; + + // + @Input() + maxAllowedFiles: XStandardType = + this.propertyProvider.maxAllowedFiles; + + @Input() + maxAllowedFilesException: XStandardType = + this.propertyProvider.maxAllowedFilesException; + + // + @Input() + maxAllowedSize: XStandardType = this.propertyProvider.maxAllowedSize; + + @Input() + maxAllowedSizeException: XStandardType = + this.propertyProvider.maxAllowedSizeException; + + // + @Input() + minAllowedFileSize: XStandardType = + this.propertyProvider.minAllowedFileSize; + + @Input() + minAllowedFileSizeException: XStandardType = + this.propertyProvider.minAllowedFileSizeException; + + // + @Input() + maxAllowedFileSize: XStandardType = + this.propertyProvider.maxAllowedFileSize; + + @Input() + maxAllowedFileSizeException: XStandardType = + this.propertyProvider.maxAllowedFileSizeException; + //#endregion + + // + //#region FileDrop ... + // + @Input() + fileDropAreaActionProvider: Subject = + this.propertyProvider.fileDropAreaActionProvider; + + @Input() + fileDropAreaCssClass: XStandardType> = + this.propertyProvider.fileDropAreaCssClass; + + // + @Input() + fileDropAreaColor: XStandardType = + this.propertyProvider.fileDropAreaColor; + + @Input() + fileDropAreaForegroundColor: XStandardType = + this.propertyProvider.fileDropAreaForegroundColor; + + // + @Input() + fileDropAreaIcon: XStandardType = + this.propertyProvider.fileDropAreaIcon; + + @Input() + fileDropAreaMessage: XStandardType = + this.propertyProvider.fileDropAreaMessage; + + // + @Input() + fileDropAreaDisableFileCheck: XStandardType = + this.propertyProvider.fileDropAreaDisableFileCheck; + + // + @Input() + fileDropAreaSupportsMultipleFiles: XStandardType = + this.propertyProvider.fileDropAreaSupportsMultipleFiles; + + @Input() + fileDropAreaAllowedExtensions: XStandardType> = + this.propertyProvider.fileDropAreaAllowedExtensions; + + // + @Input() + fileDropAreaDragDisabled: XStandardType = + this.propertyProvider.fileDropAreaDragDisabled; + + @Input() + fileDropAreaContentTemplate: TemplateRef = + this.propertyProvider.fileDropAreaContentTemplate; + + @Output() + fileDropAreaFilesChangeEmiter: EventEmitter> = + new EventEmitter(); + + // + @Input() + fileDropAreaSuccessColor: XStandardType = + this.propertyProvider.fileDropAreaSuccessColor; + + @Input() + fileDropAreaErrorColor: XStandardType = + this.propertyProvider.fileDropAreaErrorColor; + + @Input() + fileDropAreaAccentColor: XStandardType = + this.propertyProvider.fileDropAreaAccentColor; + + // + @Input() + showFileDropArea: XStandardType = + this.propertyProvider.showFileDropArea; + + // + //#region ComponentsCard ... + @Input() + fileDropAreaIsInPage: XStandardType = + this.propertyProvider.fileDropAreaIsInPage; + + @Input() + fileDropAreaWrapWithCard: XStandardType = + this.propertyProvider.fileDropAreaWrapWithCard; + + @Input() + fileDropAreaCardShowFooter: XStandardType = + this.propertyProvider.fileDropAreaCardShowFooter; + + @Input() + fileDropAreaCardShowActions: XStandardType = + this.propertyProvider.fileDropAreaCardShowActions; + + @Input() + fileDropAreaCardColor: XStandardType = + this.propertyProvider.fileDropAreaCardColor; + + @Input() + fileDropAreaCardForegroundColor: XStandardType = + this.propertyProvider.fileDropAreaCardForegroundColor; + + @Input() + fileDropAreaCardTitle: XStandardType = + this.propertyProvider.fileDropAreaCardTitle; + + @Input() + fileDropAreaCardActionProvider: Subject = + this.propertyProvider.fileDropAreaCardActionProvider; + + @Input() + fileDropAreaCardSubTitle: XStandardType = + this.propertyProvider.fileDropAreaCardSubTitle; + + @Input() + fileDropAreaCardCssClass: XStandardType> = + this.propertyProvider.fileDropAreaCardCssClass; + //#endregion + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XCardFileFileDropContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXCardFileFileDropContainer + ): Observable { + return forkJoin({ + // + //#region XComponentCard ... + cardCssClass: this.getArrayValue( + container ? container.cardCssClass : this.cardCssClass + ), + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + //#endregion + + // + //#region File ... + emptyFileException: this.getValue( + container ? container.emptyFileException : this.emptyFileException + ), + notAllowedFileException: this.getValue( + container + ? container.notAllowedFileException + : this.notAllowedFileException + ), + allowedExtensions: this.getValue( + container ? container.allowedExtensions : this.allowedExtensions + ), + maxAllowedFiles: this.getValue( + container ? container.maxAllowedFiles : this.maxAllowedFiles + ), + maxAllowedFilesException: this.getValue( + container + ? container.maxAllowedFilesException + : this.maxAllowedFilesException + ), + maxAllowedSize: this.getValue( + container ? container.maxAllowedSize : this.maxAllowedSize + ), + maxAllowedSizeException: this.getValue( + container + ? container.maxAllowedSizeException + : this.maxAllowedSizeException + ), + minAllowedFileSize: this.getValue( + container ? container.minAllowedFileSize : this.minAllowedFileSize + ), + minAllowedFileSizeException: this.getValue( + container + ? container.minAllowedFileSizeException + : this.minAllowedFileSizeException + ), + maxAllowedFileSize: this.getValue( + container ? container.maxAllowedFileSize : this.maxAllowedFileSize + ), + maxAllowedFileSizeException: this.getValue( + container + ? container.maxAllowedFileSizeException + : this.maxAllowedFileSizeException + ), + //#endregion + + // + //#region FileDrop ... + // + fileDropAreaCssClass: this.getValue( + container ? container.fileDropAreaCssClass : this.fileDropAreaCssClass + ), + fileDropAreaColor: this.getValue( + container ? container.fileDropAreaColor : this.fileDropAreaColor + ), + fileDropAreaForegroundColor: this.getValue( + container + ? container.fileDropAreaForegroundColor + : this.fileDropAreaForegroundColor + ), + fileDropAreaIcon: this.getValue( + container ? container.fileDropAreaIcon : this.fileDropAreaIcon + ), + fileDropAreaMessage: this.getValue( + container ? container.fileDropAreaMessage : this.fileDropAreaMessage + ), + fileDropAreaDisableFileCheck: this.getValue( + container + ? container.fileDropAreaDisableFileCheck + : this.fileDropAreaDisableFileCheck + ), + fileDropAreaSupportsMultipleFiles: this.getValue( + container + ? container.fileDropAreaSupportsMultipleFiles + : this.fileDropAreaSupportsMultipleFiles + ), + fileDropAreaAllowedExtensions: this.getValue( + container + ? container.fileDropAreaAllowedExtensions + : this.fileDropAreaAllowedExtensions + ), + fileDropAreaDragDisabled: this.getValue( + container + ? container.fileDropAreaDragDisabled + : this.fileDropAreaDragDisabled + ), + fileDropAreaContentTemplate: this.getValue( + container + ? container.fileDropAreaContentTemplate + : this.fileDropAreaContentTemplate + ), + fileDropAreaSuccessColor: this.getValue( + container + ? container.fileDropAreaSuccessColor + : this.fileDropAreaSuccessColor + ), + fileDropAreaErrorColor: this.getValue( + container + ? container.fileDropAreaErrorColor + : this.fileDropAreaErrorColor + ), + fileDropAreaAccentColor: this.getValue( + container + ? container.fileDropAreaAccentColor + : this.fileDropAreaAccentColor + ), + showFileDropArea: this.getValue( + container ? container.showFileDropArea : this.showFileDropArea + ), + fileDropAreaIsInPage: this.getValue( + container ? container.fileDropAreaIsInPage : this.fileDropAreaIsInPage + ), + fileDropAreaWrapWithCard: this.getValue( + container + ? container.fileDropAreaWrapWithCard + : this.fileDropAreaWrapWithCard + ), + fileDropAreaCardShowFooter: this.getValue( + container + ? container.fileDropAreaCardShowFooter + : this.fileDropAreaCardShowFooter + ), + fileDropAreaCardShowActions: this.getValue( + container + ? container.fileDropAreaCardShowActions + : this.fileDropAreaCardShowActions + ), + fileDropAreaCardColor: this.getValue( + container ? container.fileDropAreaCardColor : this.fileDropAreaCardColor + ), + fileDropAreaCardForegroundColor: this.getValue( + container + ? container.fileDropAreaCardForegroundColor + : this.fileDropAreaCardForegroundColor + ), + fileDropAreaCardTitle: this.getValue( + container ? container.fileDropAreaCardTitle : this.fileDropAreaCardTitle + ), + fileDropAreaCardSubTitle: this.getValue( + container + ? container.fileDropAreaCardSubTitle + : this.fileDropAreaCardSubTitle + ), + fileDropAreaCardCssClass: this.getValue( + container + ? container.fileDropAreaCardCssClass + : this.fileDropAreaCardCssClass + ), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + + // + fileDropAreaActionProvider: container + ? container.fileDropAreaActionProvider + : this.fileDropAreaActionProvider, + + // + fileDropAreaCardActionProvider: container + ? container.fileDropAreaCardActionProvider + : this.fileDropAreaCardActionProvider, + + // + fileDropAreaFilesChangeEmiter: this.fileDropAreaFilesChangeEmiter, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\i-x-next-prev-reset.container.ts + +import { + Input, + Inject, + Output, + NgZone, + Renderer2, + Component, + Injectable, + ElementRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from './i-x-base.container'; +import { + XCardActionModel, + XComponentCardContainer, + IXComponentCardContainer, +} from '../x-card/x-card.typings'; +import { map } from 'rxjs/operators'; +import { Observable, forkJoin, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { IXButtonContainer, XButtonType } from '../x-button/x-button.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XSlotNameIdentifier, XSlotName } from '../x-slotter/x-slotter.typings'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +export enum XNPRButtonType { + Next = 'next', + Prev = 'prev', + Reset = 'reset', +} + +// +export type XNPRButtonTypeIdentifier = XNPRButtonType | string; + +// +//#region Next ... +export interface IXNextHost { + canNext: XStandardType; + onNext: EventEmitter; +} + +export interface IXNextContainer extends IXBaseContainer { + // + //#region Next ... + hasNext: XStandardType; + disableNext: XStandardType; + nextConfirmer: () => XStandardType; + nextPosition: XStandardType; + nextCssClass: XStandardType>; + nextButton: XStandardType>; + //#endregion +} + +export const defaultNextButton: Partial = { + title: XResourceIDs.next, + type: XButtonType.Raised, + color: XColorWithBrightness.Primary, +}; + +@Injectable({ + providedIn: 'root', +}) +export abstract class XNextContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Next ... + readonly hasNext: XStandardType; + readonly disableNext: XStandardType; + readonly nextConfirmer: () => XStandardType; + readonly nextPosition: XStandardType = XSlotName.START; + readonly nextCssClass: XStandardType> = + 'ion-text-start'; + readonly nextButton: XStandardType> = { + ...defaultNextButton, + }; + //#endregion +} +//#endregion + +// +//#region Prev ... +export interface IXPrevHost { + canPrev: XStandardType; + onPrev: EventEmitter; +} + +export interface IXPrevContainer extends IXBaseContainer { + // + //#region Prev ... + hasPrev: XStandardType; + disablePrev: XStandardType; + prevConfirmer: () => XStandardType; + prevPosition: XStandardType; + prevCssClass: XStandardType>; + prevButton: XStandardType>; + //#endregion +} + +export const defaultPrevButton: Partial = { + type: XButtonType.Raised, + title: XResourceIDs.previous, + color: XColorWithBrightness.Primary, +}; + +@Injectable({ + providedIn: 'root', +}) +export abstract class XPrevContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Prev ... + readonly hasPrev: XStandardType; + readonly disablePrev: XStandardType; + readonly prevConfirmer: () => XStandardType; + readonly prevPosition: XStandardType = XSlotName.END; + readonly prevCssClass: XStandardType> = 'ion-text-end'; + readonly prevButton: XStandardType> = { + ...defaultPrevButton, + }; + //#endregion +} +//#endregion + +// +//#region Reset ... +export interface IXResetHost { + canReset: XStandardType; + onReset: EventEmitter; +} + +export interface IXResetContainer extends IXBaseContainer { + // + //#region Reset ... + hasReset: XStandardType; + disableReset: XStandardType; + resetConfirmer: () => XStandardType; + resetPosition: XStandardType; + resetCssClass: XStandardType>; + resetButton: XStandardType>; + //#endregion +} + +export const defaultResetButton: Partial = { + type: XButtonType.Raised, + title: XResourceIDs.reset, + color: XColorWithBrightness.Warning, +}; + +@Injectable({ + providedIn: 'root', +}) +export abstract class XResetContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Reset ... + readonly hasReset: XStandardType; + readonly disableReset: XStandardType; + readonly resetConfirmer: () => XStandardType; + readonly resetPosition: XStandardType = XSlotName.CENTER; + readonly resetCssClass: XStandardType> = + 'ion-text-center'; + readonly resetButton: XStandardType> = { + ...defaultResetButton, + }; + //#endregion +} +//#endregion + +// +//#region Combined ... +// +//#region Host ... +// +type XCompleteHostInterface = IXNextHost & IXPrevHost & IXResetHost; +// tslint:disable-next-line:no-empty-interface +export interface IXNextPrevResetHostContainer extends XCompleteHostInterface {} + +@Injectable({ + providedIn: 'root', +}) +export class XNextPrevResetHostContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + //#region Next ... + readonly canNext: XStandardType = true; + readonly onNext: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Prev ... + readonly canPrev: XStandardType = true; + readonly onPrev: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Reset ... + readonly canReset: XStandardType = true; + readonly onReset: EventEmitter = new EventEmitter(); + //#endregion + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XNextPrevResetHostContainerComponent + extends XBaseContainerComponent + implements IXNextPrevResetHostContainer +{ + // + //#region Props ... + // + //#region Next ... + @Input() + canNext: XStandardType = this.propertyProvider.canNext; + + @Output() + onNext: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Prev ... + @Input() + canPrev: XStandardType = this.propertyProvider.canPrev; + + @Output() + onPrev: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Reset ... + @Input() + canReset: XStandardType = this.propertyProvider.canReset; + + @Output() + onReset: EventEmitter = new EventEmitter(); + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XNextPrevResetHostContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXNextPrevResetHostContainer + ): Observable { + return forkJoin({ + // + canNext: this.getValue(container ? container.canNext : this.canNext), + canPrev: this.getValue(container ? container.canPrev : this.canPrev), + canReset: this.getValue(container ? container.canReset : this.canReset), + }).pipe( + map((data) => { + return { + // + ...data, + + // + onNext: this.onNext, + onPrev: this.onPrev, + onReset: this.onReset, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region XCardHost +// +type TXCardEmptyContainer = IXComponentCardContainer & + IXNextPrevResetHostContainer; +export interface IXCardNextPrevResetHostContainer + extends TXCardEmptyContainer {} + +// +@Injectable({ + providedIn: 'root', +}) +export class XCardNextPrevResetHostContainer + extends XBaseContainer + implements IXCardNextPrevResetHostContainer +{ + // + //#region Props ... + // + //#region XComponentCard ... + readonly isInPage: XStandardType = + this.cardPropertyProvider.isInPage; + readonly wrapWithCard: XStandardType = + this.cardPropertyProvider.wrapWithCard; + readonly cardShowFooter: XStandardType = + this.cardPropertyProvider.cardShowFooter; + readonly cardShowActions: XStandardType = + this.cardPropertyProvider.cardShowActions; + readonly cardColor: XStandardType = + this.cardPropertyProvider.cardColor; + readonly cardForegroundColor: XStandardType = + this.cardPropertyProvider.cardForegroundColor; + readonly cardTitle: XStandardType = + this.cardPropertyProvider.cardTitle; + readonly cardActionProvider: Subject = + this.cardPropertyProvider.cardActionProvider; + readonly cardSubTitle: XStandardType = + this.cardPropertyProvider.cardSubTitle; + readonly cardCssClass: XStandardType> = + this.cardPropertyProvider.cardCssClass; + //#endregion + + // + //#region XNextPrevResetHost ... + // + //#region Next ... + readonly canNext: XStandardType = + this.nextPrevResetHostPropertyProvider.canNext; + readonly onNext: EventEmitter = + this.nextPrevResetHostPropertyProvider.onNext; + //#endregion + + // + //#region Prev ... + readonly canPrev: XStandardType = + this.nextPrevResetHostPropertyProvider.canPrev; + readonly onPrev: EventEmitter = + this.nextPrevResetHostPropertyProvider.onPrev; + //#endregion + + // + //#region Reset ... + readonly canReset: XStandardType = + this.nextPrevResetHostPropertyProvider.canReset; + readonly onReset: EventEmitter = + this.nextPrevResetHostPropertyProvider.onReset; + //#endregion + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected cardPropertyProvider: XComponentCardContainer, + protected nextPrevResetHostPropertyProvider: XNextPrevResetHostContainer + ) { + super(); + } + //#endregion +} + +// +@Injectable() +@Component({ + template: ``, +}) +export class XCardNextPrevResetHostContainerComponent + extends XBaseContainerComponent + implements IXCardNextPrevResetHostContainer +{ + // + //#region Props ... + // + //#region XComponentCard ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + //#endregion + + // + //#region XNextPrevResetHost ... + // + //#region Next ... + @Input() + canNext: XStandardType = this.propertyProvider.canNext; + + @Output() + onNext: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Prev ... + @Input() + canPrev: XStandardType = this.propertyProvider.canPrev; + + @Output() + onPrev: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Reset ... + @Input() + canReset: XStandardType = this.propertyProvider.canReset; + + @Output() + onReset: EventEmitter = new EventEmitter(); + //#endregion + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XCardNextPrevResetHostContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXCardNextPrevResetHostContainer + ): Observable { + return forkJoin({ + // + //#region XComponentCard ... + cardCssClass: this.getArrayValue( + container ? container.cardCssClass : this.cardCssClass + ), + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + //#endregion + + // + //#region XNextPrevResetHost ... + // + canNext: this.getValue(container ? container.canNext : this.canNext), + canPrev: this.getValue(container ? container.canPrev : this.canPrev), + canReset: this.getValue(container ? container.canReset : this.canReset), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + + // + onNext: this.onNext, + onPrev: this.onPrev, + onReset: this.onReset, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region Interface ... +// +type TXNextPrevResetContainer = IXNextContainer & + IXPrevContainer & + IXResetContainer; +// tslint:disable-next-line:no-empty-interface +export interface IXNextPrevResetContainer + extends IXBaseContainer, + TXNextPrevResetContainer {} + +@Injectable({ + providedIn: 'root', +}) +export class XNextPrevResetContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + //#region Next ... + hasNext: XStandardType = this.nextPropertyProvider.hasNext; + disableNext: XStandardType = this.nextPropertyProvider.disableNext; + nextConfirmer: () => XStandardType = + this.nextPropertyProvider.nextConfirmer; + nextPosition: XStandardType = + this.nextPropertyProvider.nextPosition; + nextCssClass: XStandardType> = + this.nextPropertyProvider.nextCssClass; + nextButton: XStandardType> = + this.nextPropertyProvider.nextButton; + //#endregion + + // + //#region Prev ... + hasPrev: XStandardType = this.prevPropertyProvider.hasPrev; + disablePrev: XStandardType = this.prevPropertyProvider.disablePrev; + prevConfirmer: () => XStandardType = + this.prevPropertyProvider.prevConfirmer; + prevPosition: XStandardType = + this.prevPropertyProvider.prevPosition; + prevCssClass: XStandardType> = + this.prevPropertyProvider.prevCssClass; + prevButton: XStandardType> = + this.prevPropertyProvider.prevButton; + //#endregion + + // + //#region Reset ... + hasReset: XStandardType = this.resetpropertyProvider.hasReset; + disableReset: XStandardType = + this.resetpropertyProvider.disableReset; + resetConfirmer: () => XStandardType = + this.resetpropertyProvider.resetConfirmer; + resetPosition: XStandardType = + this.resetpropertyProvider.resetPosition; + resetCssClass: XStandardType> = + this.resetpropertyProvider.resetCssClass; + resetButton: XStandardType> = + this.resetpropertyProvider.resetButton; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected prevPropertyProvider: XPrevContainer, + protected nextPropertyProvider: XNextContainer, + protected resetpropertyProvider: XResetContainer + ) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XNextPrevResetContainerComponent + extends XBaseContainerComponent + implements IXNextPrevResetContainer +{ + // + //#region Props ... + // + //#region Next ... + @Input() + hasNext: XStandardType = this.propertyProvider.hasNext; + + @Input() + disableNext: XStandardType = this.propertyProvider.disableNext; + + @Input() + nextConfirmer: () => XStandardType = + this.propertyProvider.nextConfirmer; + + @Input() + nextPosition: XStandardType = + this.propertyProvider.nextPosition; + + @Input() + nextCssClass: XStandardType> = + this.propertyProvider.nextCssClass; + + @Input() + nextButton: XStandardType> = + this.propertyProvider.nextButton; + //#endregion + + // + //#region Prev ... + @Input() + hasPrev: XStandardType = this.propertyProvider.hasPrev; + + @Input() + disablePrev: XStandardType = this.propertyProvider.disablePrev; + + @Input() + prevConfirmer: () => XStandardType = + this.propertyProvider.prevConfirmer; + + @Input() + prevPosition: XStandardType = + this.propertyProvider.prevPosition; + + @Input() + prevCssClass: XStandardType> = + this.propertyProvider.prevCssClass; + + @Input() + prevButton: XStandardType> = + this.propertyProvider.prevButton; + //#endregion + + // + //#region Reset ... + @Input() + hasReset: XStandardType = this.propertyProvider.hasReset; + + @Input() + disableReset: XStandardType = this.propertyProvider.disableReset; + + @Input() + resetConfirmer: () => XStandardType = + this.propertyProvider.resetConfirmer; + + @Input() + resetPosition: XStandardType = + this.propertyProvider.resetPosition; + + @Input() + resetCssClass: XStandardType> = + this.propertyProvider.resetCssClass; + + @Input() + resetButton: XStandardType> = + this.propertyProvider.resetButton; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XNextPrevResetContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXNextPrevResetContainer + ): Observable { + return forkJoin({ + // + hasNext: this.getValue(container ? container.hasNext : this.hasNext), + disableNext: this.getValue( + container ? container.disableNext : this.disableNext + ), + nextConfirmer: this.getValue( + container ? container.nextConfirmer : this.nextConfirmer + ), + nextPosition: this.getValue( + container ? container.nextPosition : this.nextPosition + ), + nextCssClass: this.getValue( + container ? container.nextCssClass : this.nextCssClass + ), + nextButton: this.getValue( + container ? container.nextButton : this.nextButton + ), + hasPrev: this.getValue(container ? container.hasPrev : this.hasPrev), + disablePrev: this.getValue( + container ? container.disablePrev : this.disablePrev + ), + prevConfirmer: this.getValue( + container ? container.prevConfirmer : this.prevConfirmer + ), + prevPosition: this.getValue( + container ? container.prevPosition : this.prevPosition + ), + prevCssClass: this.getValue( + container ? container.prevCssClass : this.prevCssClass + ), + prevButton: this.getValue( + container ? container.prevButton : this.prevButton + ), + hasReset: this.getValue(container ? container.hasReset : this.hasReset), + disableReset: this.getValue( + container ? container.disableReset : this.disableReset + ), + resetConfirmer: this.getValue( + container ? container.resetConfirmer : this.resetConfirmer + ), + resetPosition: this.getValue( + container ? container.resetPosition : this.resetPosition + ), + resetCssClass: this.getValue( + container ? container.resetCssClass : this.resetCssClass + ), + resetButton: this.getValue( + container ? container.resetButton : this.resetButton + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region IXNextPrevResetAndHostContainer ... +type TXNextPrevResetAndHostContainer = IXNextPrevResetContainer & + IXCardNextPrevResetHostContainer; +export interface IXNextPrevResetAndHostContainer + extends IXBaseContainer, + TXNextPrevResetAndHostContainer {} + +@Injectable({ + providedIn: 'root', +}) +export class XNextPrevResetAndHostContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + //#region Inteface ... + // + //#region Next ... + hasNext: XStandardType = this.nextPrevResetPropertyProvider.hasNext; + disableNext: XStandardType = + this.nextPrevResetPropertyProvider.disableNext; + nextConfirmer: () => XStandardType = + this.nextPrevResetPropertyProvider.nextConfirmer; + nextPosition: XStandardType = + this.nextPrevResetPropertyProvider.nextPosition; + nextCssClass: XStandardType> = + this.nextPrevResetPropertyProvider.nextCssClass; + nextButton: XStandardType> = + this.nextPrevResetPropertyProvider.nextButton; + //#endregion + + // + //#region Prev ... + hasPrev: XStandardType = this.nextPrevResetPropertyProvider.hasPrev; + disablePrev: XStandardType = + this.nextPrevResetPropertyProvider.disablePrev; + prevConfirmer: () => XStandardType = + this.nextPrevResetPropertyProvider.prevConfirmer; + prevPosition: XStandardType = + this.nextPrevResetPropertyProvider.prevPosition; + prevCssClass: XStandardType> = + this.nextPrevResetPropertyProvider.prevCssClass; + prevButton: XStandardType> = + this.nextPrevResetPropertyProvider.prevButton; + //#endregion + + // + //#region Reset ... + hasReset: XStandardType = + this.nextPrevResetPropertyProvider.hasReset; + disableReset: XStandardType = + this.nextPrevResetPropertyProvider.disableReset; + resetConfirmer: () => XStandardType = + this.nextPrevResetPropertyProvider.resetConfirmer; + resetPosition: XStandardType = + this.nextPrevResetPropertyProvider.resetPosition; + resetCssClass: XStandardType> = + this.nextPrevResetPropertyProvider.resetCssClass; + resetButton: XStandardType> = + this.nextPrevResetPropertyProvider.resetButton; + //#endregion + //#endregion + + // + //#region Host ... + // + //#region Next ... + readonly canNext: XStandardType = + this.cardNextPrevResetPropertyProvider.canNext; + readonly onNext: EventEmitter = + this.cardNextPrevResetPropertyProvider.onNext; + //#endregion + + // + //#region Prev ... + readonly canPrev: XStandardType = + this.cardNextPrevResetPropertyProvider.canPrev; + readonly onPrev: EventEmitter = + this.cardNextPrevResetPropertyProvider.onPrev; + //#endregion + + // + //#region Reset ... + readonly canReset: XStandardType = + this.cardNextPrevResetPropertyProvider.canReset; + readonly onReset: EventEmitter = + this.cardNextPrevResetPropertyProvider.onReset; + //#endregion + //#endregion + + // + //#region XComponentCard ... + readonly isInPage: XStandardType = + this.cardNextPrevResetPropertyProvider.isInPage; + readonly wrapWithCard: XStandardType = + this.cardNextPrevResetPropertyProvider.wrapWithCard; + readonly cardShowFooter: XStandardType = + this.cardNextPrevResetPropertyProvider.cardShowFooter; + readonly cardShowActions: XStandardType = + this.cardNextPrevResetPropertyProvider.cardShowActions; + readonly cardColor: XStandardType = + this.cardNextPrevResetPropertyProvider.cardColor; + readonly cardForegroundColor: XStandardType = + this.cardNextPrevResetPropertyProvider.cardForegroundColor; + readonly cardTitle: XStandardType = + this.cardNextPrevResetPropertyProvider.cardTitle; + readonly cardActionProvider: Subject = + this.cardNextPrevResetPropertyProvider.cardActionProvider; + readonly cardSubTitle: XStandardType = + this.cardNextPrevResetPropertyProvider.cardSubTitle; + readonly cardCssClass: XStandardType> = + this.cardNextPrevResetPropertyProvider.cardCssClass; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected nextPrevResetPropertyProvider: XNextPrevResetContainer, + protected cardNextPrevResetPropertyProvider: XCardNextPrevResetHostContainer + ) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export class XNextPrevResetAndHostContainerComponent + extends XBaseContainerComponent + implements IXNextPrevResetAndHostContainer +{ + // + //#region Props ... + // + //#region XComponentCard ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + //#endregion + + // + //#region XNextPrevResetHost ... + // + //#region Next ... + @Input() + canNext: XStandardType = this.propertyProvider.canNext; + + @Output() + onNext: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Prev ... + @Input() + canPrev: XStandardType = this.propertyProvider.canPrev; + + @Output() + onPrev: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Reset ... + @Input() + canReset: XStandardType = this.propertyProvider.canReset; + + @Output() + onReset: EventEmitter = new EventEmitter(); + //#endregion + //#endregion + + // + //#region Props ... + // + //#region Next ... + @Input() + hasNext: XStandardType = this.propertyProvider.hasNext; + + @Input() + disableNext: XStandardType = this.propertyProvider.disableNext; + + @Input() + nextConfirmer: () => XStandardType = + this.propertyProvider.nextConfirmer; + + @Input() + nextPosition: XStandardType = + this.propertyProvider.nextPosition; + + @Input() + nextCssClass: XStandardType> = + this.propertyProvider.nextCssClass; + + @Input() + nextButton: XStandardType> = + this.propertyProvider.nextButton; + //#endregion + + // + //#region Prev ... + @Input() + hasPrev: XStandardType = this.propertyProvider.hasPrev; + + @Input() + disablePrev: XStandardType = this.propertyProvider.disablePrev; + + @Input() + prevConfirmer: () => XStandardType = + this.propertyProvider.prevConfirmer; + + @Input() + prevPosition: XStandardType = + this.propertyProvider.prevPosition; + + @Input() + prevCssClass: XStandardType> = + this.propertyProvider.prevCssClass; + + @Input() + prevButton: XStandardType> = + this.propertyProvider.prevButton; + //#endregion + + // + //#region Reset ... + @Input() + hasReset: XStandardType = this.propertyProvider.hasReset; + + @Input() + disableReset: XStandardType = this.propertyProvider.disableReset; + + @Input() + resetConfirmer: () => XStandardType = + this.propertyProvider.resetConfirmer; + + @Input() + resetPosition: XStandardType = + this.propertyProvider.resetPosition; + + @Input() + resetCssClass: XStandardType> = + this.propertyProvider.resetCssClass; + + @Input() + resetButton: XStandardType> = + this.propertyProvider.resetButton; + //#endregion + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XNextPrevResetAndHostContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXNextPrevResetAndHostContainer + ): Observable { + return forkJoin({ + // + //#region XComponentCard ... + cardCssClass: this.getArrayValue( + container ? container.cardCssClass : this.cardCssClass + ), + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + //#endregion + + // + //#region XNextPrevResetHost ... + // + canNext: this.getValue(container ? container.canNext : this.canNext), + canPrev: this.getValue(container ? container.canPrev : this.canPrev), + canReset: this.getValue(container ? container.canReset : this.canReset), + //#endregion + + // + //#region Interface ... + // + hasNext: this.getValue(container ? container.hasNext : this.hasNext), + disableNext: this.getValue( + container ? container.disableNext : this.disableNext + ), + nextConfirmer: this.getValue( + container ? container.nextConfirmer : this.nextConfirmer + ), + nextPosition: this.getValue( + container ? container.nextPosition : this.nextPosition + ), + nextCssClass: this.getValue( + container ? container.nextCssClass : this.nextCssClass + ), + nextButton: this.getValue( + container ? container.nextButton : this.nextButton + ), + hasPrev: this.getValue(container ? container.hasPrev : this.hasPrev), + disablePrev: this.getValue( + container ? container.disablePrev : this.disablePrev + ), + prevConfirmer: this.getValue( + container ? container.prevConfirmer : this.prevConfirmer + ), + prevPosition: this.getValue( + container ? container.prevPosition : this.prevPosition + ), + prevCssClass: this.getValue( + container ? container.prevCssClass : this.prevCssClass + ), + prevButton: this.getValue( + container ? container.prevButton : this.prevButton + ), + hasReset: this.getValue(container ? container.hasReset : this.hasReset), + disableReset: this.getValue( + container ? container.disableReset : this.disableReset + ), + resetConfirmer: this.getValue( + container ? container.resetConfirmer : this.resetConfirmer + ), + resetPosition: this.getValue( + container ? container.resetPosition : this.resetPosition + ), + resetCssClass: this.getValue( + container ? container.resetCssClass : this.resetCssClass + ), + resetButton: this.getValue( + container ? container.resetButton : this.resetButton + ), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + + // + onNext: this.onNext, + onPrev: this.onPrev, + onReset: this.onReset, + }; + }) + ); + } + //#endregion +} +//#endregion +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\i-x-toolbar-footer.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, +} from 'x-framework-core'; +import { + XBackSlotIdentifier, + XLogoSlotIdentifier, + XMenuSlotIdentifier, + XTitleSlotIdentifier, + XContentSlotIdentifier, +} from '../x-page/x-page.typings'; +import { map } from 'rxjs/operators'; +import { + XFooterActionModel, + XComponentFooterContainer, + IXComponentFooterContainer, +} from '../x-page/x-footer/x-footer.typings'; +import { + XToolbarActionModel, + XComponentToolbarContainer, + IXComponentToolbarContainer, +} from '../x-page/x-toolbar/x-toolbar.typings'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { XManagerService } from 'x-framework-services'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XBaseContainer, XBaseContainerComponent } from './i-x-base.container'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region Container ... +type TXToolbarFooterContainer = IXComponentToolbarContainer & + IXComponentFooterContainer; +export interface IXToolbarFooterContainer extends TXToolbarFooterContainer {} + +@Injectable({ + providedIn: 'root', +}) +export class XToolbarFooterContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + //#region Toolbar ... + // + readonly showToolbar: XStandardType = + this.toolbarPropertyProvider.showToolbar; + + // + readonly toolbarActionProvider: Subject = + this.toolbarPropertyProvider.toolbarActionProvider; + readonly toolbarCssClass: XStandardType> = + this.toolbarPropertyProvider.toolbarCssClass; + + // + readonly toolbarColor: XStandardType = + this.toolbarPropertyProvider.toolbarColor; + + // + readonly toolbarShowProgressBar: XStandardType = + this.toolbarPropertyProvider.toolbarShowProgressBar; + readonly toolbarProgressBarColor: XStandardType = + this.toolbarPropertyProvider.toolbarProgressBarColor; + + // + readonly toolbarShowTitle: XStandardType = + this.toolbarPropertyProvider.toolbarShowTitle; + readonly toolbarTitleSlot: XStandardType = + this.toolbarPropertyProvider.toolbarTitleSlot; + readonly toolbarTitle: XStandardType = + this.toolbarPropertyProvider.toolbarTitle; + + // + readonly toolbarShowSubTitle: XStandardType = + this.toolbarPropertyProvider.toolbarShowSubTitle; + readonly toolbarSubTitle: XStandardType = + this.toolbarPropertyProvider.toolbarSubTitle; + + // + readonly toolbarShowLogo: XStandardType = + this.toolbarPropertyProvider.toolbarShowLogo; + readonly toolbarLogoSlot: XStandardType = + this.toolbarPropertyProvider.toolbarLogoSlot; + readonly toolbarLogoUrl: XStandardType = + this.toolbarPropertyProvider.toolbarLogoUrl; + + // + readonly toolbarHasBack: XStandardType = + this.toolbarPropertyProvider.toolbarHasBack; + readonly toolbarShowBack: XStandardType = + this.toolbarPropertyProvider.toolbarShowBack; + readonly toolbarBackSlot: XStandardType = + this.toolbarPropertyProvider.toolbarBackSlot; + readonly toolbarBackHandler?: () => XStandardType = + this.toolbarPropertyProvider.toolbarBackHandler; + readonly toolbarDefaultHref: string = + this.toolbarPropertyProvider.toolbarDefaultHref; + + // + readonly toolbarHasMenu: XStandardType = + this.toolbarPropertyProvider.toolbarHasMenu; + readonly toolbarShowMenu: XStandardType = + this.toolbarPropertyProvider.toolbarShowMenu; + readonly toolbarAutoHideMenu: XStandardType = + this.toolbarPropertyProvider.toolbarAutoHideMenu; + readonly toolbarMenuSlot: XStandardType = + this.toolbarPropertyProvider.toolbarMenuSlot; + + // + readonly toolbarStartSlotButtonsTemplate: TemplateRef = + this.toolbarPropertyProvider.toolbarStartSlotButtonsTemplate; + readonly toolbarEndSlotButtonsTemplate: TemplateRef = + this.toolbarPropertyProvider.toolbarEndSlotButtonsTemplate; + + // + readonly toolbarContentSlot: XStandardType = + this.toolbarPropertyProvider.toolbarContentSlot; + readonly toolbarContentTemplate: TemplateRef = + this.toolbarPropertyProvider.toolbarContentTemplate; + //#endregion + + // + //#region Footer ... + // + readonly showFooter: XStandardType = + this.footerPropertyProvider.showFooter; + + // + readonly footerActionProvider: Subject = + this.footerPropertyProvider.footerActionProvider; + readonly footerCssClass: XStandardType> = + this.footerPropertyProvider.footerCssClass; + + // + readonly footerColor: XStandardType = + this.footerPropertyProvider.footerColor; + readonly footerContentTemplate: TemplateRef = + this.footerPropertyProvider.footerContentTemplate; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected toolbarPropertyProvider: XComponentToolbarContainer, + protected footerPropertyProvider: XComponentFooterContainer + ) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XToolbarFooterContainerComponent + extends XBaseContainerComponent + implements IXToolbarFooterContainer +{ + // + //#region Props ... + // + //#region Toolbar ... + // + @Input() + showToolbar: XStandardType = this.propertyProvider.showToolbar; + + // + @Input() + toolbarActionProvider: Subject = + this.propertyProvider.toolbarActionProvider; + + @Input() + toolbarCssClass: XStandardType> = + this.propertyProvider.toolbarCssClass; + + // + @Input() + toolbarColor: XStandardType = + this.propertyProvider.toolbarColor; + + // + @Input() + toolbarShowProgressBar: XStandardType = + this.propertyProvider.toolbarShowProgressBar; + + @Input() + toolbarProgressBarColor: XStandardType = + this.propertyProvider.toolbarProgressBarColor; + + // + @Input() + toolbarShowTitle: XStandardType = + this.propertyProvider.toolbarShowTitle; + + @Input() + toolbarTitleSlot: XStandardType = + this.propertyProvider.toolbarTitleSlot; + + @Input() + toolbarTitle: XStandardType = this.propertyProvider.toolbarTitle; + + // + @Input() + toolbarShowSubTitle: XStandardType = + this.propertyProvider.toolbarShowSubTitle; + + @Input() + toolbarSubTitle: XStandardType = + this.propertyProvider.toolbarSubTitle; + + // + @Input() + toolbarShowLogo: XStandardType = + this.propertyProvider.toolbarShowLogo; + + @Input() + toolbarLogoSlot: XStandardType = + this.propertyProvider.toolbarLogoSlot; + + @Input() + toolbarLogoUrl: XStandardType = this.propertyProvider.toolbarLogoUrl; + + // + @Input() + toolbarHasBack: XStandardType = this.propertyProvider.toolbarHasBack; + + @Input() + toolbarShowBack: XStandardType = + this.propertyProvider.toolbarShowBack; + + @Input() + toolbarBackSlot: XStandardType = + this.propertyProvider.toolbarBackSlot; + + @Input() + toolbarBackHandler?: () => XStandardType = + this.propertyProvider.toolbarBackHandler; + + @Input() + toolbarDefaultHref: string = this.propertyProvider.toolbarDefaultHref; + + // + @Input() + toolbarHasMenu: XStandardType = this.propertyProvider.toolbarHasMenu; + + @Input() + toolbarShowMenu: XStandardType = + this.propertyProvider.toolbarShowMenu; + + @Input() + toolbarAutoHideMenu: XStandardType = + this.propertyProvider.toolbarAutoHideMenu; + + @Input() + toolbarMenuSlot: XStandardType = + this.propertyProvider.toolbarMenuSlot; + + // + @Input() + toolbarStartSlotButtonsTemplate: TemplateRef = + this.propertyProvider.toolbarStartSlotButtonsTemplate; + + @Input() + toolbarEndSlotButtonsTemplate: TemplateRef = + this.propertyProvider.toolbarEndSlotButtonsTemplate; + + // + @Input() + toolbarContentSlot: XStandardType = + this.propertyProvider.toolbarContentSlot; + + @Input() + toolbarContentTemplate: TemplateRef = + this.propertyProvider.toolbarContentTemplate; + //#endregion + + // + //#region Footer ... + // + @Input() + showFooter: XStandardType = this.propertyProvider.showFooter; + + // + @Input() + footerActionProvider: Subject = + this.propertyProvider.footerActionProvider; + + @Input() + footerCssClass: XStandardType> = + this.propertyProvider.footerCssClass; + + // + @Input() + footerColor: XStandardType = + this.propertyProvider.footerColor; + + @Input() + footerContentTemplate: TemplateRef = + this.propertyProvider.footerContentTemplate; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XToolbarFooterContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXToolbarFooterContainer + ): Observable { + return forkJoin({ + // + //#region Toolbar ... + showToolbar: this.getValue( + container ? container.showToolbar : this.showToolbar + ), + toolbarCssClass: this.getValue( + container ? container.toolbarCssClass : this.toolbarCssClass + ), + toolbarColor: this.getValue( + container ? container.toolbarColor : this.toolbarColor + ), + toolbarShowProgressBar: this.getValue( + container + ? container.toolbarShowProgressBar + : this.toolbarShowProgressBar + ), + toolbarProgressBarColor: this.getValue( + container + ? container.toolbarProgressBarColor + : this.toolbarProgressBarColor + ), + toolbarShowTitle: this.getValue( + container ? container.toolbarShowTitle : this.toolbarShowTitle + ), + toolbarTitleSlot: this.getValue( + container ? container.toolbarTitleSlot : this.toolbarTitleSlot + ), + toolbarTitle: this.getValue( + container ? container.toolbarTitle : this.toolbarTitle + ), + toolbarShowSubTitle: this.getValue( + container ? container.toolbarShowSubTitle : this.toolbarShowSubTitle + ), + toolbarSubTitle: this.getValue( + container ? container.toolbarSubTitle : this.toolbarSubTitle + ), + toolbarShowLogo: this.getValue( + container ? container.toolbarShowLogo : this.toolbarShowLogo + ), + toolbarLogoSlot: this.getValue( + container ? container.toolbarLogoSlot : this.toolbarLogoSlot + ), + toolbarLogoUrl: this.getValue( + container ? container.toolbarLogoUrl : this.toolbarLogoUrl + ), + toolbarHasBack: this.getValue( + container ? container.toolbarHasBack : this.toolbarHasBack + ), + toolbarShowBack: this.getValue( + container ? container.toolbarShowBack : this.toolbarShowBack + ), + toolbarBackSlot: this.getValue( + container ? container.toolbarBackSlot : this.toolbarBackSlot + ), + toolbarBackHandler: this.getValue( + container ? container.toolbarBackHandler : this.toolbarBackHandler + ), + toolbarDefaultHref: this.getValue( + container ? container.toolbarDefaultHref : this.toolbarDefaultHref + ), + toolbarHasMenu: this.getValue( + container ? container.toolbarHasMenu : this.toolbarHasMenu + ), + toolbarShowMenu: this.getValue( + container ? container.toolbarShowMenu : this.toolbarShowMenu + ), + toolbarAutoHideMenu: this.getValue( + container ? container.toolbarAutoHideMenu : this.toolbarAutoHideMenu + ), + toolbarMenuSlot: this.getValue( + container ? container.toolbarMenuSlot : this.toolbarMenuSlot + ), + toolbarStartSlotButtonsTemplate: this.getValue( + container + ? container.toolbarStartSlotButtonsTemplate + : this.toolbarStartSlotButtonsTemplate + ), + toolbarEndSlotButtonsTemplate: this.getValue( + container + ? container.toolbarEndSlotButtonsTemplate + : this.toolbarEndSlotButtonsTemplate + ), + toolbarContentSlot: this.getValue( + container ? container.toolbarContentSlot : this.toolbarContentSlot + ), + toolbarContentTemplate: this.getValue( + container + ? container.toolbarContentTemplate + : this.toolbarContentTemplate + ), + //#endregion + + // + //#region Footer ... + showFooter: this.getValue( + container ? container.showFooter : this.showFooter + ), + footerCssClass: this.getValue( + container ? container.footerCssClass : this.footerCssClass + ), + footerColor: this.getValue( + container ? container.footerColor : this.footerColor + ), + footerContentTemplate: this.getValue( + container ? container.footerContentTemplate : this.footerContentTemplate + ), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + toolbarActionProvider: container + ? container.toolbarActionProvider + : this.toolbarActionProvider, + + // + footerActionProvider: container + ? container.footerActionProvider + : this.footerActionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\x-base-action.model.ts + +export interface XBaseActionModel { + action: T; + payload?: any; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\x-base-component.ts + +import { + Input, + OnInit, + Inject, + Output, + NgZone, + OnDestroy, + Component, + Renderer2, + OnChanges, + ElementRef, + EventEmitter, + AfterViewInit, + SimpleChanges, + AfterContentInit, + AfterViewChecked, + ChangeDetectorRef, + AfterContentChecked, +} from '@angular/core'; +import { + tap, + map, + first, + filter, + takeUntil, + concatMap, + catchError, +} from 'rxjs/operators'; +import { + keys, + XTask, + XColor, + XParam, + toArray, + getValue, + notValue, + isNumber, + XUiEvent, + hasChild, + XRenderer, + isFunction, + XException, + formatDate, + isValueDate, + applyLocale, + toObservable, + XResourceIDs, + XExceptionIDs, + XStandardType, + fromUtcString, + XOneOrManyType, + toNormalString, + stringFormatter, + getDefaultValue, + XFormControlState, + isNullOrUndefined, + getErrorException, + getDateFormatString, + isNullOrEmptyString, + XResourceIdentifier, + XExceptionIdentifier, + XModalButtonRoleIdentifier, + nameof, + generateUuid, + XKey, +} from 'x-framework-core'; +import { NgModel } from '@angular/forms'; +import { NavigationExtras } from '@angular/router'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { IXBaseObservableTools } from './x-base.observable.tools'; +import { Subject, of, Observable, throwError, forkJoin } from 'rxjs'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; +import { XBaseActionModel } from './x-base-action.model'; + +@Component({ + template: '', +}) +export abstract class XBaseComponent + extends XRenderer + implements + OnInit, + OnDestroy, + OnChanges, + AfterViewInit, + AfterViewChecked, + AfterContentInit, + AfterContentChecked, + IXBaseObservableTools +{ + // + //#region Props ... + // + public readonly isMobileUi$ = this.managerService.isMobileUi$; + public readonly isNotMobileUi$ = this.managerService.isNotMobileUi$; + + // + public readonly ResourceIDs = Object.assign({}, XResourceIDs); + public readonly ExceptionIDs = Object.assign({}, XExceptionIDs); + + // + public TaskRepo: XTask[] = []; + public RunningTasks: XTask[] = []; + + // + public unsubscriber = new Subject(); + + // + protected applyThemeColors = false; + + /** + * Unique Component Identifier ... + */ + readonly uuid = generateUuid('X[' + this.constructor.name + ']'); + + // + //#region Component reload ... + private SHOW_COMPONENT: boolean = true; + set showComponent(v: boolean) { + // + if (this.SHOW_COMPONENT !== v) { + this.onComponentVisibilityChanged(v); + } + + // + this.SHOW_COMPONENT = v; + } + get showComponent() { + return this.SHOW_COMPONENT; + } + async reloadComponent(delay?: number) { + // + if (!isNullOrUndefined(delay) && delay < 100) { + delay = 100; + } + + // + this.showComponent = false; + this.detectChanges(); + return this.waitForElement(this.element.nativeElement.tagName).then( + (el) => { + // + setTimeout(() => { + // + this.showComponent = true; + this.detectChanges(); + }, delay); + } + ); + } + onComponentVisibilityChanged(visible: boolean) {} + //#endregion + + // + //#region Loading ... + /** + * Notifiy Loading Changed ... + */ + @Output() + loadingNotifier = new EventEmitter<{ id: string; state: boolean }>(); + + private LOADING: boolean = false; + + /** + * Loading State of Component Representation ... + */ + @Input() + set loading(v: boolean) { + // + this.LOADING = v; + this.onLoading(v); + this.loadingNotifier.emit({ id: this.uuid, state: v }); + } + get loading() { + return this.LOADING; + } + + onLoading(state: boolean) {} + //#endregion + + // + //#region Ui Disabled ... + /** + * Notifiy Ui Disabled Changed ... + */ + @Output() + uiDisabledNotifier = new EventEmitter<{ id: string; state: boolean }>(); + + private UI_DISABLED: boolean = false; + + /** + * Set UI State Disables / Enabled of Component ... + */ + @Input() + set uiDisabled(v: boolean) { + // + this.UI_DISABLED = v; + this.onUiDisabled(v); + this.uiDisabledNotifier.emit({ id: this.uuid, state: v }); + } + get uiDisabled() { + return this.UI_DISABLED; + } + + onUiDisabled(state: boolean) {} + //#endregion + + // + //#region Error ... + /** + * Notify Error Changed ... + */ + errorNotifier = new EventEmitter<{ id: string; state: boolean }>(); + + private ERROR: XException = undefined; + + /** + * Set Specified Error of Component if Exists ... + */ + @Input() + set error(v: XException) { + // + this.ERROR = v; + this.onError(v); + + // + const hasError = isNullOrUndefined(v); + this.errorNotifier.emit({ id: this.uuid, state: hasError }); + } + get error() { + return this.ERROR; + } + + onError(error: XException) {} + //#endregion + + // + //#region Lock ... + /** + * Notify Lock Changed ... + */ + @Output() + lockNotifier = new EventEmitter<{ id: string; state: boolean }>(); + + private LOCK: boolean = false; + + /** + * Specified Component Locked or not ... + */ + @Input() + set lock(v: boolean) { + // + this.LOCK = v; + this.onLock(v); + this.lockNotifier.emit({ id: this.uuid, state: v }); + } + get lock() { + return this.LOCK; + } + + onLock(state: boolean) {} + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef + ) { + super(config, element, renderer, ruler, zone); + } + //#endregion + + // + //#region Abstracted ... + //#endregion + + // + //#region LifeCycle ... + ngOnInit(): void { + // + this.clearTasks(); + this.registerComponentHandlers(); + + // + this.onInit(); + } + + ngAfterViewInit(): void { + // + this.registerViewHandlers(); + + // + // Apply theme Colors only when it's required ... + if (this.applyThemeColors) { + this.managerService.themeManagerService + .applyCurrentTheme(this.element) + .then(() => this.detectChanges()); + } + + // + // Handle Apply Manager Service UI State Changed Events is Base Components ... + this.applyTakeUntilWrapper( + this.managerService.uiStateChanged.asObservable() + ).subscribe((state) => { + // + if (state === XUiEvent.StartLoading && !this.loading) { + this.loading = true; + } else if (state === XUiEvent.StopLoading && this.loading) { + this.loading = false; + } else if (state === XUiEvent.Disabled && !this.uiDisabled) { + this.uiDisabled = true; + } else if (state === XUiEvent.Enabled && this.uiDisabled) { + this.uiDisabled = false; + } + + // + this.detectChanges(); + }); + + // + // Set UUID attribute ... + this.setAttribute('xFramework', this.uuid); + + // + this.afterViewInit(); + } + + ngAfterViewChecked(): void { + this.afterViewChecked(); + } + + ngAfterContentInit(): void { + this.afterContentInit(); + } + + ngAfterContentChecked(): void { + this.afterContentChecked(); + } + + ngOnDestroy(): void { + // + this.clearTasks(); + this.unregisterHandlers(); + + // + this.onDestroy(); + } + + ngOnChanges(changes: SimpleChanges): void { + // + const changeKeys = keys(changes); + + // + let isChanged = false; + let applyChanges = false; + + // + // Error Changes ... + isChanged = changeKeys.includes(nameof('error')); + if (isChanged) { + // + const hasError = !isNullOrUndefined(this.error); + if (hasError) { + this.addClass('x-has-error'); + } else { + this.removeClass('x-has-error'); + } + + // + applyChanges = true; + this.onError(this.error); + this.errorNotifier.emit({ id: this.uuid, state: hasError }); + } + + // + // Loading Changes ... + isChanged = changeKeys.includes(nameof('loading')); + if (isChanged) { + // + if (this.loading) { + this.addClass('x-loading'); + } else { + this.removeClass('x-loading'); + } + + // + applyChanges = true; + this.onLoading(this.loading); + this.loadingNotifier.emit({ id: this.uuid, state: this.loading }); + } + + // + // uiDisabled Changes ... + isChanged = changeKeys.includes(nameof('uiDisabled')); + if (isChanged) { + // + if (this.uiDisabled) { + this.addClass('x-disabled'); + } else { + this.removeClass('x-disabled'); + } + + // + applyChanges = true; + this.onUiDisabled(this.uiDisabled); + this.uiDisabledNotifier.emit({ id: this.uuid, state: this.uiDisabled }); + } + + // + // Lock Change ... + isChanged = changeKeys.includes(nameof('lock')); + if (isChanged) { + // + if (this.lock) { + this.addClass('x-locked'); + } else { + this.removeClass('x-locked'); + } + + // + applyChanges = true; + this.onLock(this.lock); + this.lockNotifier.emit({ id: this.uuid, state: this.lock }); + } + + // + if (applyChanges) { + this.detectChanges(); + } + + // + this.onChange(changeKeys); + } + + // + //#region Ionic LifeCycles ... + ionViewWillEnter() { + this.willEnter(); + } + + ionViewDidEnter() { + this.didEnter(); + } + + ionViewWillLeave() { + this.willLeave(); + } + + ionViewDidLeave() { + this.didLeave(); + } + //#endregion + + // + //#region Abstracted LifeCycles ... + public onInit() {} + + public willEnter() {} + + public didEnter() {} + + public afterViewInit() {} + + public afterViewChecked() {} + + public afterContentInit() {} + + public afterContentChecked() {} + + public onChange(changeKeys: string[]) {} + + public willLeave() {} + + public didLeave() {} + + public onDestroy() {} + //#endregion + //#endregion + + // + //#region Providers ... + public registerInstanceHandlers(): void {} + public registerViewHandlers(): void {} + + /** + * Retrieve Specified Property Names ... + */ + getFieldName(obj: T, name: XKey): string { + return name.toString(); + } + //#endregion + + // + //#region Notifier Handlers ... + public onResize(event?: Event) {} + + /** + * notify a Task Start ... + * + * @param name task name + */ + public onTaskStart(name: string, task: XTask) {} + + /** + * notify an error on running task ... + * + * @param name task name + * @param task task object + */ + public onTaskFailed(name: string, task: XTask) {} + + /** + * notify a task finished successfully ... + * + * @param name task name + * @param task task object + */ + public onTaskFinished(name: string, task: XTask) {} + //#endregion + + // + //#region Handlers ... + /** + * Clear Error of Component ... + */ + public clearError() { + // + setTimeout(() => { + // + this.error = null; + this.managerService.error = null; + }, 500); + } + + /** + * show error ... + */ + public showError( + exception: + | Error + | string + | XException + | XExceptionIDs + | XExceptionIdentifier + | any = undefined, + next?: () => void + ) { + // + let mException: any = getErrorException(exception); + if (isNullOrUndefined(mException) && !isNullOrUndefined(this.error)) { + mException = this.error; + } else if (isNullOrUndefined(mException)) { + mException = XExceptionIDs.ActionFailed; + } + + // + this.managerService.notificationService.presentErrorNotification({ + exception: mException, + dissmissable: true, + onWillDismiss: () => { + // + this.clearError(); + + // + if (next && isFunction(next)) { + next(); + } + }, + }); + } + + /** + * handle error state ... + */ + public async handleErrorState( + exception?: XExceptionIdentifier, + presentNotification: boolean = true, + redirectToUrl?: string, + returnUrl?: string, + params?: NavigationExtras, + duration: number = this.config.defaultNotificationDelay, + replaceUrl = true + ) { + // + //#region Inside Content ... + let errorMessage = + this.managerService.localizationService.getErrorMessage(exception); + + // + if ( + exception.hasOwnProperty('payload') && + hasChild((exception as any)['payload']) + ) { + // + const payload = (exception as any)['payload'] as Array; + + // + const localizedPayload = payload.map((pl) => { + // + if (isValueDate(pl)) { + // + const plDate = fromUtcString(pl); + const locale = this.managerService.currentLocale; + + // + const result = formatDate( + plDate, + locale, + getDateFormatString(locale) + ); + + // + return result; + } else { + return this.applyLocale(pl); + } + }); + + // + errorMessage = stringFormatter(errorMessage, localizedPayload); + } + + // + if (!params || !params.state) { + params = { + ...params, + state: {}, + }; + } + + // + params.state[XParam.ReturnUrl] = returnUrl; + params.state[XParam.Exception] = exception; + + // + if (presentNotification) { + // + if (duration < 500 || duration > 10000) { + duration = this.config.defaultNotificationDelay; + } + + // + await this.managerService.notificationService.presentDangerNotification({ + message: errorMessage, + dissmissable: true, + opt: { + duration, + }, + }); + } + + // + if (redirectToUrl && !isNullOrEmptyString(redirectToUrl)) { + await this.managerService.navigateByUrlReplace( + redirectToUrl, + params, + replaceUrl + ); + } + //#endregion + } + + /** + * show success ... + */ + public showSuccess(message?: XResourceIdentifier, next?: () => void) { + // + if (!message) { + message = XResourceIDs.success_msg; + } + + // + this.managerService.notificationService.presentSuccessNotification({ + message, + dissmissable: true, + onWillDismiss: () => { + // + if (next && isFunction(next)) { + next(); + } + }, + }); + } + + /** + * present an action modal with yes and no button ... + * + * @param header the action title resource/msg ... + * @param yesHandler the handler which must run when user accept action ... + * @param yesRole the yes button role ... + * @param noHandler the handler which must run when user reject action ... + * @param noRole the no button role ... + */ + public async presentYesNoDialog( + header: XResourceIdentifier, + yesHandler: () => void, + yesRole?: XModalButtonRoleIdentifier, + noHandler?: () => void, + noRole?: XModalButtonRoleIdentifier + ) { + // + if (isNullOrUndefined(header)) { + header = this.ResourceIDs.are_you_sure; + } + + // + await this.managerService.dialogService.presentActionModal({ + header, + buttons: [], + keyboardClose: true, + backdropDismiss: true, + hasNo: true, + noRole, + noHandler, + hasYes: true, + yesRole, + yesHandler, + }); + } + + /** + * present an action modal with yes and no button ... + * + * @param yesHandler the handler which must run when user accept action ... + * @param yesRole the yes button role ... + * @param noHandler the handler which must run when user reject action ... + * @param noRole the no button role ... + */ + public async presentDefaultYesNoDialog( + yesHandler: () => void, + yesRole?: XModalButtonRoleIdentifier, + noHandler?: () => void, + noRole?: XModalButtonRoleIdentifier + ) { + // + await this.managerService.dialogService.presentActionModal({ + header: this.ResourceIDs.are_you_sure, + buttons: [], + keyboardClose: true, + backdropDismiss: true, + hasNo: true, + noRole, + noHandler, + hasYes: true, + yesRole, + yesHandler, + }); + } + + /** + * lock page for an action + * + * @param isLoading specify state + */ + public async setLoadingState(isLoading: boolean) { + // + this.loading = isLoading; + this.uiDisabled = isLoading; + + // + this.detectChanges(); + } + + /** + * handle Default Action for Closing open Dialog ... + */ + public handleDefaultDialogCloseAction() { + // + this.managerService.dialogService.modalController.dismiss({ + dismissed: true, + }); + } + + /** + * Release all Subscription ... + */ + public releaseUnsubscriber() { + this.unregisterHandlers(); + } + + /** + * apply localization on numbers of a text ... + * + * @param content the text + */ + public applyLocale(content: string | any) { + return applyLocale( + content || isNumber(content) ? content.toString() : '', + this.managerService.localizationService.locale + ); + } + + /** + * apply locale and get current resource translated value ... + * + * @param id tes resource id + */ + public resourceProvider(id: XResourceIdentifier): string { + return ( + this.managerService.localizationService.getValue(id) || id.toString() + ); + } + + /** + * default join strings ... + */ + public combinedResourceProvider(...ids: XResourceIdentifier[]) { + return this.managerService.localizationService.combine( + this.managerService.currentLocale, + ' ', + ...ids + ); + } + + /** + * get translation of exception id ... + * + * @param id exception id + */ + public exceptionResourceProvider(id: XExceptionIdentifier) { + return this.managerService.localizationService.getErrorMessage(id); + } + + /** + * retrieve property value ... + * + * @param object the context object ... + * @param prop the property name ... + */ + public getPropValue(object: T, prop: keyof T) { + return getValue(object, prop); + } + + /** + * retrieve desiered color name based on form control state ... + * + * @param status form status + */ + public getIconColor(status: XFormControlState) { + switch (status) { + case 'DISABLED': + case 'PENDING': + default: + return XColor.Warning; + + case 'VALID': + return XColor.Primary; + + case 'INVALID': + return XColor.Danger; + } + } + + /** + * determines an element contains child nodes or not ... + * + * @param ref the element refrence + */ + public hasChildNodes(ref: ElementRef) { + return ref && (ref.nativeElement as HTMLElement).children.length > 0; + } + + public getLengthError(model: NgModel, identifier: string) { + // + if (!model || !model.errors) { + return ''; + } + + // + const errorObject = model.getError(identifier); + const requiredLength = errorObject.requiredLength; + const actualLength = errorObject.actualLength; + + // + return `(${actualLength}/${requiredLength})`; + } + + /** + * usefull for doing nothing ... + */ + public nothing() {} + //#endregion + + // + //#region Task Methods ... + /** + * check a task is Exists or not ... + * + * @param name task name + */ + public async hasTask(name: string) { + // + const hasTask = this.TaskRepo.findIndex((t) => t.name === name) > -1; + return hasTask; + } + + /** + * retrieve a task by it's name ... + * + * @param name task name + */ + public async getTask(name: string) { + // + const isExists = await this.hasTask(name); + if (!isExists) { + return null; + } + + // + const task = this.TaskRepo.find((t) => t.name === name); + return task; + } + + /** + * add or update a task in Task Repo ... + * + * @param task the task object + */ + public async addOrUpdateTask(task: XTask) { + // + if (!task) { + return; + } + + // + const isExists = await this.hasTask(task.name); + if (!isExists) { + this.TaskRepo.push(task); + } else { + await this.updateTask(task); + } + } + + /** + * Add a New Task to Task Repo ... + * + * @param task task object + */ + public async addTask(task: XTask) { + // + if (!task) { + return; + } + + // + const isExists = await this.hasTask(task.name); + if (isExists) { + return; + } + + // + this.TaskRepo.push(task); + } + + /** + * Update a Task in Task Repo ... + * + * @param task the task object + */ + public async updateTask(task: XTask) { + // + const isExists = await this.hasTask(task.name); + if (!isExists) { + return; + } + + // + await this.removeTask(task.name); + this.TaskRepo.push(task); + } + + /** + * remove a task by it's name ... + * + * @param name the task name + */ + public async removeTask(name: string) { + // + const isExists = await this.hasTask(name); + if (!isExists) { + return; + } + + // + if (this.managerService.taskManagerService.has(name)) { + this.managerService.taskManagerService.delete(name); + } + + // + this.TaskRepo = this.TaskRepo.filter((t) => t.name !== name); + } + + /** + * call a task for running on task manager ... + * + * @param name task name + */ + public async dispatchTask(name: string) { + // + if (isNullOrEmptyString(name)) { + return; + } + + // + const task = await this.getTask(name); + if (!task) { + return; + } + + // + this.managerService.taskManagerService.add(task); + } + + /** + * check a Task is running or not by it's name + * + * @param name task name + */ + public async isTaskRunning(name: string) { + // + if (isNullOrEmptyString(name)) { + return false; + } + + // + name = toNormalString(name); + + // + return ( + this.RunningTasks.findIndex((rt) => name === toNormalString(rt.name)) > -1 + ); + } + + /** + * clear all tasks for component ... + */ + public async clearTasks() { + // + this.TaskRepo = []; + this.RunningTasks = []; + } + + /** + * do some actions related to Task Repo when a Task Start ... + * + * @param name task name + * @param task task object + */ + private async handlTaskStart(name: string, task: XTask) { + // + const isRunning = await this.isTaskRunning(name); + if (isRunning) { + return; + } + + // + this.RunningTasks.push(task); + } + + /** + * do some actions related to Task Repo when a Task Failed ... + * + * @param name task name + * @param task task object + */ + private async handleTaskFailed(name: string, task: XTask) { + // + const isRunning = await this.isTaskRunning(name); + if (!isRunning) { + return; + } + + // + const tName = toNormalString(task.name); + this.RunningTasks = this.RunningTasks.filter( + (t) => tName !== toNormalString(t.name) + ); + } + + /** + * do some actions related to Task Repo when a Task Finished ... + * + * @param name task name + * @param task task object + */ + private async handleTaskFinished(name: string, task: XTask) { + // + const isRunning = await this.isTaskRunning(name); + if (!isRunning) { + return; + } + + // + const tName = toNormalString(task.name); + this.TaskRepo = this.TaskRepo.filter( + (t) => tName !== toNormalString(t.name) + ); + this.RunningTasks = this.RunningTasks.filter( + (t) => tName !== toNormalString(t.name) + ); + } + //#endregion + + // + //#region Observable Actions ... + /** + * Check if items has childs or not ... + * + * @param items a list item ... + */ + public hasChild(items: XOneOrManyType) { + return hasChild(items); + } + + /** + * convert an standard UI Value to Observable for consuming ... + * + * @param value the standard UI Value + */ + public getValue(value: XStandardType) { + return toObservable(value); + } + + /** + * get stream Observble of specified standard value + */ + public getValueStream(value: XStandardType) { + return this.applyTakeUntilWrapper(this.getValue(value)); + } + + /** + * retrieve an standard type var in promise + * + * @param value an standard type var + */ + public async getValueAsync(value: XStandardType) { + // + if (!value) { + return null; + } + + // + return await this.toPromise(this.getValue(value)); + } + + /** + * convert to array ... + * + * @param value an standard type one or many arg ... + */ + public getArrayValue(value: XStandardType>) { + return this.getValue(value).pipe(map((valueType) => toArray(valueType))); + } + + /** + * convert to array ... + * + * @param value an standard type one or many arg ... + */ + public async getArrayValueAsync(value: XStandardType>) { + return await this.getValueAsync(this.getArrayValue(value)); + } + + /** + * check a value has child or not ... + * + * @param value an standard one or many of type + */ + public hasChildValue(value: XStandardType>) { + // + return this.getValue(value).pipe( + map((valueType) => hasChild(toArray(valueType))) + ); + } + + /** + * check a value has child or not ... + * + * @param value an standard one or many of type + */ + public async hasChildValueAsync(value: XStandardType>) { + // + const valueType = await this.getValueAsync(value); + if (!valueType) { + return false; + } + + // + return hasChild(toArray(valueType)); + } + + /** + * count a value's child ... + * + * @param value an standard one or many of type + */ + public countChildsValue(value: XStandardType>) { + return this.getValue(value).pipe( + map((valueType) => toArray(valueType).length) + ); + } + + /** + * count a value's child ... + * + * @param value an standard one or many of type + */ + public async countChildsValueAsync( + value: XStandardType> + ) { + // + const valueType = await this.getValueAsync(value); + if (!valueType) { + return 0; + } + + // + return toArray(valueType).length; + } + + /** + * providing string resources ... + * + * @param value an standard one or many of type + */ + public getResourceValue(value: XStandardType) { + // + return !value + ? of('') + : this.getValue(value).pipe( + map((val) => { + return this.resourceProvider(val) || (value as string); + }) + ); + } + + /** + * providing string resources ... + * + * @param value an standard one or many of type + */ + public async getResourceValueAsync( + value: XStandardType + ) { + // + if (!value) { + return ''; + } + + // + const val = await this.getValueAsync(value); + return this.resourceProvider(val) || (val as string); + } + + /** + * determines a given value has content and not empty + * + * @param value an Standard type of string + */ + public notEmptyValue(value: XStandardType) { + return this.getValue(value).pipe(map((val) => !isNullOrEmptyString(val))); + } + + /** + * determines a given value has content and not empty + * + * @param value an Standard type of string + */ + public async notEmptyValueAsync(value: any) { + return await this.toPromise(this.notEmptyValue(value)); + } + + /** + * determines a given value has not content and empty + * + * @param value an Standard type of string + */ + public emptyValue(value: XStandardType) { + return this.getValue(value).pipe(map((val) => isNullOrEmptyString(val))); + } + + /** + * determines a given value has not content and empty + * + * @param value an Standard type of string + */ + public async emptyValueAsync(value: XStandardType) { + return await this.toPromise(this.emptyValue(value)); + } + + /** + * apply not operator to a boolean ... + */ + public notValue(value: XStandardType) { + return notValue(this.getValue(value)); + } + + /** + * apply not operator to a boolean ... + */ + public async notValueAsync(value: XStandardType) { + return await this.getValueAsync(this.notValue(value)); + } + + /** + * check all input args be same as value ... + * + * @param value any value which going to check with all args to be same as them ... + * @param values the input args which be checked by value ... + */ + public mustBeValue(value: any, ...values: XStandardType[]) { + // + return forkJoin(values.map((v) => toObservable(v))).pipe( + map((res) => { + return res.every((i) => i === value); + }) + ); + } + + /** + * check all input args be same as value ... + * + * @param value any value which going to check with all args to be same as them ... + * @param values the input args which be checked by value ... + */ + public async mustBeValueAsync(value: any, ...values: XStandardType[]) { + // + return ( + await Promise.all(values.map(async (v) => await this.getValueAsync(v))) + ).every((v) => v === value); + } + + /** + * check all input args contains at least one value ... + * + * @param value any value which going to check with all args to find one same ... + * @param values the input args which be checked by value ... + */ + public atLeastHasOneValue(value: any, ...values: XStandardType[]) { + // + return forkJoin(values.map((v) => toObservable(v))).pipe( + map((res) => { + return res.includes(value); + }) + ); + } + + /** + * check all input args contains at least one value ... + * + * @param value any value which going to check with all args to find one same ... + * @param values the input args which be checked by value ... + */ + public async atLeastHasOneValueAsync( + value: any, + ...values: XStandardType[] + ) { + // + return ( + await Promise.all(values.map(async (v) => await this.getValueAsync(v))) + ).includes(value); + } + + /** + * determines a given value has content and not empty + * + * @param value an Standard type of any + */ + public notNullOrUndefinedValue(value: XStandardType) { + return this.getValue(value).pipe(map((val) => !isNullOrUndefined(val))); + } + + /** + * determines a given value has content and not empty + * + * @param value an Standard type of any + */ + public async notNullOrUndefinedValueAsync(value: XStandardType) { + return await this.toPromise(this.notNullOrUndefinedValue(value)); + } + + /** + * determines a given value content must empty + * + * @param value an Standard type of any + */ + public nullOrUndefinedValue(value: XStandardType) { + return this.getValue(value).pipe(map((val) => isNullOrUndefined(val))); + } + + /** + * determines a given value content must empty + * + * @param value an Standard type of any + */ + public async nullOrUndefinedValueAsync(value: XStandardType) { + return await this.toPromise(this.nullOrUndefinedValue(value)); + } + + /** + * retrieve an optional value ... + * + * @param value the optional standard value ... + */ + public getOptionalValue(value?: XStandardType): Observable { + // + if (value) { + return this.getValue(value); + } + + // + return this.getValue(getDefaultValue(value)); + } + + /** + * retrieve an optional value ... + * + * @param value the optional standard value ... + */ + public async getOptionalValueAsync(value?: XStandardType) { + return await this.toPromise(this.getOptionalValue(value)); + } + + /** + * convert all values as observable of them ... + * + * @param values the standard list of specific type ... + * @returns observable of values array of specific type ... + */ + public getValues(values?: XOneOrManyType>) { + // + interface ForkSourceType { + [index: number]: XStandardType; + } + + // + return this.getArrayValue(values).pipe( + concatMap((values) => { + // + if (!hasChild(values)) { + return this.getValue([] as T[]); + } else { + // + const forkSource: ForkSourceType = {}; + values.forEach((value, index) => { + forkSource[index] = this.getValue(value); + }); + + // + return forkJoin(forkSource).pipe( + map((forkResult) => { + // + const respObj = forkResult as any; + const mKeys = Object.keys(forkResult) + .map((key) => key) + .sort((a, b) => +a - +b); + const result = mKeys.map((mKey: any) => respObj[mKey] as T); + + // + return result; + }) + ); + } + }) + ); + } + + /** + * convert all values as observable of them ... + * + * @param values the standard list of specific type ... + * @returns observable of values array of specific type ... + */ + public async getValuesAsync(values?: XOneOrManyType>) { + return await this.toPromise(this.getValues(values)); + } + + /** + * apply take until wrapper arround an observable ... + * + * @param task the observable object + */ + public applyTakeUntilWrapper(task: Observable): Observable { + return task.pipe(takeUntil(this.unsubscriber)); + } + + /** + * apply inside component loading wrapper arround an observable ... + * + * @param task the observable object + */ + public applyLoadingWrapper(task: Observable): Observable { + return of(null).pipe( + tap((_) => { + this.uiDisabled = true; + this.loading = true; + }), + concatMap((_) => + task.pipe( + tap(() => { + this.uiDisabled = false; + this.loading = false; + }) + ) + ), + catchError((err) => { + // + this.uiDisabled = false; + this.loading = false; + return throwError(err); + }) + ); + } + + /** + * apply get first result of an observable ... + * + * @param task the observable object + */ + public applyFirstWrapper(task: Observable): Observable { + return task.pipe(first()); + } + + /** + * convert an observable to promise object ... + * + * @param task the observable object + */ + public toPromise(task: Observable): Promise { + return this.applyFirstWrapper(task).toPromise(); + } + //#endregion + + // + //#region Actions ... + /** + * Call Change Detector's Detect Change method ... + */ + detectChanges() { + this.changeDetector.detectChanges(); + } + //#endregion + + // + //#region Private ... + /** + * Register All Required Subscriptions ... + */ + private registerComponentHandlers() { + // + this.registerUiHandlers(); + this.registerTaskManager(); + this.registerInstanceHandlers(); + } + + /** + * Release all Subscriptions based on the Component ... + */ + private unregisterHandlers() { + // + this.unsubscriber.next(); + this.unsubscriber.complete(); + } + + /** + * register Ui Event Handlers ... + */ + private registerUiHandlers() { + // + this.applyTakeUntilWrapper(this.ruler.change()).subscribe((resizeEvent) => + this.onResize(resizeEvent) + ); + + // + this.applyTakeUntilWrapper( + this.managerService.themeManagerService.notifyChanges.asObservable() + ).subscribe(() => { + this.detectChanges(); + }); + } + + /** + * Register Task Handlers for Finish and Error of Tasks ... + */ + private registerTaskManager() { + // + // Task Start ... + this.applyTakeUntilWrapper( + this.managerService.taskManagerService.taskStart.asObservable() + ) + .pipe(filter((task) => !isNullOrUndefined(task))) + .subscribe((task) => { + // + this.onTaskStart(task.name, task); + + // + this.handlTaskStart(task.name, task); + }); + + // + // Task Error ... + this.applyTakeUntilWrapper( + this.managerService.taskManagerService.taskError.asObservable() + ) + .pipe(filter((task) => !isNullOrUndefined(task))) + .subscribe((task) => { + // + this.onTaskFailed(task.name, task); + + // + this.handleTaskFailed(task.name, task); + }); + + // + // Task Finished ... + this.applyTakeUntilWrapper( + this.managerService.taskManagerService.taskFinished.asObservable() + ) + .pipe(filter((task) => !isNullOrUndefined(task))) + .subscribe((task) => { + // + this.onTaskFinished(task.name, task); + + // + this.handleTaskFinished(task.name, task); + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\x-base-dialog.service.ts + +import { + XBaseService, + XDialogResult, + isNullOrUndefined, + XResourceIdentifier, +} from 'x-framework-core'; +import { + MatDialog, + MatDialogRef, + MatDialogConfig, +} from '@angular/material/dialog'; +import { + IXBaseDialogConfig, + IXBaseDialogResult, + XBaseDialogComponent, + IXBaseDialogActionModel, +} from './x-base.dialog.component'; +import { XManagerService } from 'x-framework-services'; +import { EventEmitter, Inject, Injectable, Output } from '@angular/core'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +@Injectable({ + providedIn: 'root' +}) +export abstract class XBaseDialogService< + TItem, + TActionProviderModel extends IXBaseDialogActionModel, + TConfig extends IXBaseDialogConfig, + TDialogCompnent extends XBaseDialogComponent< + TItem, + TActionProviderModel, + TConfig + > +> extends XBaseService { + // + //#region Props ... + private MOBILE_CLASS_NAME = 'mobile'; + private DIALOG_REF: MatDialogRef; + + // + isMobile$ = this.managerService.isMobileUi$; + + // tslint:disable-next-line:no-output-on-prefix + @Output() + onResult = new EventEmitter(); + //#endregion + + // + //#region Abstract ... + abstract defaultTitleResourceIdentifier: XResourceIdentifier; + + abstract openDialogAndReturnRef( + config: MatDialogConfig + ): MatDialogRef; + //#endregion + + // + //#region Constructor ... + constructor( + protected dialog: MatDialog, + protected managerService: XManagerService, + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig + ) { + super(config); + } + //#endregion + + // + //#region Actions ... + /** + * Open Select Map Dialog ... + */ + async openDialog(config?: TConfig) { + // + // Prevent Double Bubble ... + if (this.DIALOG_REF) { + return; + } + + // + // Prepare Config ... + if (!config) { + config = {} as TConfig; + } + + // + //#region Default Config for Map Component ... + // + // Set Default Title ... + if (config && !config.title) { + config.title = this.defaultTitleResourceIdentifier; + } + //#endregion + + // + // Prepare Dialog Config ... + const data: MatDialogConfig = { + data: config, + }; + + // + data.panelClass = 'fullscreen-dialog'; + + // + // Show Cropper inside a Dialog ... + this.DIALOG_REF = this.openDialogAndReturnRef(data); + this.DIALOG_REF.disableClose = true; + + // + // Attach Mobile Class ... + this.isMobile$.subscribe((isMobile) => { + // + if (isMobile) { + if (this.DIALOG_REF) { + this.DIALOG_REF.addPanelClass(this.MOBILE_CLASS_NAME); + } + } else { + if (this.DIALOG_REF) { + this.DIALOG_REF.removePanelClass(this.MOBILE_CLASS_NAME); + } + } + }); + + // + // Handle Backdrop ... + this.DIALOG_REF.backdropClick().subscribe(() => { + // + // Cancel Dialog ... + this.onResult.emit({ result: XDialogResult.Cancel }); + + // + this.closeDialog(); + }); + + // + // Handle Dialog Result ... + this.DIALOG_REF.afterClosed().subscribe((model: IXBaseDialogResult) => { + // + if (isNullOrUndefined(model) || model.result !== XDialogResult.Ok) { + // + // Cancel Dialog ... + this.onResult.emit({ result: XDialogResult.Cancel }); + } else { + // + // Handle Results ... + this.onResult.emit(model); + } + + // + this.closeDialog(); + }); + } + + /** + * Close Select Map Dialog ... + */ + closeDialog() { + // + // Prevent Go Forward ... + if (!this.DIALOG_REF) { + return; + } + + // + // Close Dialog ... + this.DIALOG_REF.close(); + this.DIALOG_REF = null; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\x-base-page.ts + +import { + NgZone, + Inject, + Output, + Renderer2, + Component, + ElementRef, + EventEmitter, + HostListener, + ChangeDetectorRef, +} from '@angular/core'; +import { + nameof, + XResourceIDs, + XStandardType, + XCanDeactivate, + XResourceIdentifier, + XBehaviourEventType, + isNullOrUndefined, +} from 'x-framework-core'; +import { Subject, Observable } from 'rxjs'; +import { XBaseComponent } from './x-base-component'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +@Component({ + template: '', +}) +export abstract class XBasePage + extends XBaseComponent + implements XCanDeactivate +{ + /** + * Since Page Components Manipulate Angular Router ... + * and Angular Router has Deactivate Guards ... + * and it Works where component is Locked ... + * this Param Specified for Deactivation Guard to Do Unlock by Confirmation or not ... + */ + protected confirmUnlock: boolean = true; + + /** + * Specified when Page Component is Locked, make Guards available for Prevent Back ... + */ + protected preventBackHandlingWhenHasLock: boolean = true; + + // + @Output() + backPreventNotifier = new EventEmitter<{ id: string; state: boolean }>(); + + // + private PREVENT_BACK = false; + get isBackPreveted() { + return this.PREVENT_BACK; + } + set preventBack(v: boolean) { + // + if (v && !this.PREVENT_BACK) { + this.managerService.historyService.pushEmptyState(); + } else if (v && this.PREVENT_BACK) { + this.managerService.historyService.replaceEmptyState(); + } + + // + this.PREVENT_BACK = v; + + // + if (v) { + this.addClass('x-back-prevented'); + } else { + this.addClass('x-back-prevented'); + } + + // + this.detectChanges(); + this.onBackPrevent(v); + this.backPreventNotifier.emit({ id: this.uuid, state: v }); + } + + // + onBackPrevent(state: boolean) {} + + // + private IS_PARSING_PARAMS = false; + private NOT_PREVENT_REFRESH = true; + + // + public titleRes?: XResourceIdentifier; + + // + public scrollEventSimulator = new Subject(); + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Guards ... + public canDeactivate( + component: XCanDeactivate, + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): boolean | Observable | Promise { + return this.handleCanDeactivateGuard(component, route, state); + } + + @HostListener('window:beforeunload', ['$event']) + beforeUnloadHandler($event: any) { + // + if (!this.NOT_PREVENT_REFRESH) { + $event.returnValue = true; + } + } + //#endregion + + // + //#region LifeCycle ... + onInit() { + super.onInit(); + + // + this.handleParseParams(); + this.registerPageHandlers(); + } + + public afterViewInit(): void { + super.afterViewInit(); + + // + // Register Lock Notifier for Prevent Handling ... + this.applyTakeUntilWrapper(this.lockNotifier.asObservable()).subscribe( + (event) => { + // + if (isNullOrUndefined(event)) { + return; + } + + // + if (this.preventBackHandlingWhenHasLock) { + this.preventBack = event.state; + } + + // + this.detectChanges(); + } + ); + } + + public onChange(changeKeys: string[]): void { + super.onChange(changeKeys); + + // + let isChanged = false; + let applyChanges = false; + + // + // Looking for Lock Changes ... + isChanged = changeKeys.includes(nameof('lock')); + if (isChanged) { + // + applyChanges = true; + if (this.preventBackHandlingWhenHasLock) { + this.preventBack = this.lock; + } + } + + // + if (applyChanges) { + this.detectChanges(); + } + } + + public onBack() {} + + didEnter() { + super.didEnter(); + + // + this.handleResourcePresenting(); + } + + willLeave() { + super.willLeave(); + + // + this.clearError(); + this.clearTasks(); + } + + public parseParams() {} + //#endregion + + // + //#region Notifiers ... + public simulateScrollStart() { + this.scrollEventSimulator.next('scrollStart'); + } + + public simulateScrollEnd() { + this.scrollEventSimulator.next('scrollEnd'); + } + + public simulateScrolling() { + this.scrollEventSimulator.next('scrolling'); + } + //#endregion + + // + //#region Protected ... + /** + * Do Lock Breakes Works ... + * + * @param acceptCb + * @param cancelCb + */ + protected async presentDeactiveGuardDialog( + acceptCb?: () => XStandardType, + cancelCb?: () => XStandardType + ) { + if (this.lock) { + // + const a = await this.managerService.dialogService.presentActionModal({ + header: XResourceIDs.locked_msg, + hasAccept: true, + acceptHandler: () => { + // + if (acceptCb) { + acceptCb(); + } + + // + this.lock = false; + }, + hasCancel: true, + cancelHandler: () => { + // + if (cancelCb) { + cancelCb(); + } + + // + this.lock = true; + }, + buttons: [], + }); + + // + await a.onDidDismiss(); + } + } + //#endregion + + // + //#region Private ... + private handleParseParams() { + // + if (this.IS_PARSING_PARAMS) { + return; + } + + // + this.IS_PARSING_PARAMS = true; + + // + this.getValueAsync(this.parseParams()) + .then(() => (this.IS_PARSING_PARAMS = false)) + .catch( + () => + // + (this.IS_PARSING_PARAMS = false) + ); + } + + private registerPageHandlers() { + // + this.registerBackHandler(); + this.registerParamsHandler(); + } + + private handleResourcePresenting() { + // + if (!this.titleRes) { + return; + } + + // + this.managerService.settingsService.setTitle( + this.managerService.localizationService.getValue(this.titleRes) + ); + } + + private async handleCanDeactivateGuard( + component: XCanDeactivate, + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ) { + // + let result = !this.lock; + if (this.lock && this.confirmUnlock) { + // + await this.presentDeactiveGuardDialog( + () => { + result = true; + }, + () => { + result = false; + } + ); + } + + // + return result; + } + + private registerBackHandler() { + // + this.managerService.historyService.platformLocation.onPopState(() => { + // + if (this.PREVENT_BACK) { + this.managerService.historyService.pushEmptyState(); + } + }); + + // + this.applyTakeUntilWrapper( + this.managerService.historyService.backHandler.asObservable() + ).subscribe(() => { + this.onBack(); + }); + } + + private registerParamsHandler() { + // + this.applyTakeUntilWrapper(this.managerService.routeDataChanged$).subscribe( + (res) => { + this.handleParseParams(); + } + ); + + // + this.applyTakeUntilWrapper(this.managerService.routeParmChanged$).subscribe( + (res) => { + this.handleParseParams(); + } + ); + + // + this.applyTakeUntilWrapper( + this.managerService.routeQueryParmChanged$ + ).subscribe((res) => { + this.handleParseParams(); + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\x-base.dialog.component.html + +
+ + + + + +
+ + + +
+ +
+
+ + + + +

+ {{ title | async }} +

+
+
+ + + + +
+ +
+
+
+
+ + + +
+
+ + + + +
+ + + + {{ item.data | json }} + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\x-base.dialog.component.scss + +::ng-deep.fullscreen-dialog { + // + width: 80vw !important; + max-width: 80vw !important; + + // + height: 80vh !important; + max-height: 80vh !important; + + // + &.mobile { + // + width: 100vw !important; + max-width: 100vw !important; + + // + height: 100vh !important; + max-height: 100vh !important; + } + + // + .mat-dialog-container { + padding: 0 !important; + } +} + +.x-dialog-wrapper { + width: 100% !important; + height: 100% !important; + position: relative !important; +} + +.x-dialog-header { + position: sticky !important; + top: 0; + z-index: 1000; +} + +.x-dialog-content-wrapper { + height: calc(100%); + + &.has-toolbar { + height: calc(100% - 56px); + } +} + +x-icon { + --size: 24px; +} + +.toolbar-area { + margin: 0px; +} + +.toolbar-item { + width: auto; + height: 48px; +} + +.toolbar-inline-item { + display: inline-flex !important; +} + +.toolbar-button { + width: 32px; + margin: { + left: 4px; + right: 4px; + } +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\x-base.dialog.component.ts + +import { + Inject, + NgZone, + Output, + Renderer2, + Component, + ElementRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XTask, + XColor, + toArray, + isFunction, + getTaskName, + XStandardType, + XDialogResult, + XOneOrManyType, + XColorIdentifier, + isNullOrUndefined, + isNullOrEmptyString, + XResourceIdentifier, +} from 'x-framework-core'; +import { of, Subject } from 'rxjs'; +import { concatMap, map } from 'rxjs/operators'; +import { XBaseComponent } from './x-base-component'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from './x-base-action.model'; +import { XButtonType } from '../x-button/x-button.typings'; +import { XSlotLayout, XSlotName } from '../x-slotter/x-slotter.typings'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XBaseDialogAction { + Refresh = 'refresh', +} + +export type XBaseDialogActionIdentifier = XBaseDialogAction | string; + +export interface IXBaseDialogActionModel + extends XBaseActionModel {} + +export interface IXBaseDialogConfig< + TActionProviderModel extends IXBaseDialogActionModel +> { + // + showToolbar?: boolean; + + // + title?: XResourceIdentifier; + cssClass?: XOneOrManyType; + progressBarColor?: XColorIdentifier; + + // + showSearchBar?: boolean; + searchBarColor?: XColorIdentifier; + searchBarInputColor?: XColorIdentifier; + + // + actionProvider?: Subject; + + // + canMultipleSelect?: boolean; + + // + onItemSelect?: (item: XOneOrManyType) => XStandardType; + + // + onTaskStart?: (name: string) => XStandardType; + onTaskFinished?: (name: string) => XStandardType; + onTaskFailed?: (name: string) => XStandardType; +} + +export interface IXBaseDialogResult { + payload?: any; + result: XDialogResult; +} + +@Component({ + template: '', +}) +export abstract class XBaseDialogComponent< + TItem, + TActionProviderModel extends IXBaseDialogActionModel, + TConfig extends IXBaseDialogConfig +> extends XBaseComponent { + // + //#region Props ... + // + public cancelIcon = XIconNames.close; + public selectIcon = XIconNames.checked; + public backIcon = XIconNames.arrow_back; + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly ButtonTypes = Object.assign({}, XButtonType); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + + // + readonly isMobile$ = this.managerService.isMobileUi$; + readonly isNotMobile$ = this.managerService.isNotMobileUi$; + + // + public selectedItem: XOneOrManyType; + + // + //#region Prevent Back ... + @Output() + backPreventNotifier = new EventEmitter<{ id: string; state: boolean }>(); + + // + private PREVENT_BACK = false; + get isBackPreveted() { + return this.PREVENT_BACK; + } + set preventBack(v: boolean) { + // + if (v && !this.PREVENT_BACK) { + this.managerService.historyService.pushEmptyState(); + } else if (v && this.PREVENT_BACK) { + this.managerService.historyService.replaceEmptyState(); + } + + // + this.PREVENT_BACK = v; + + // + if (v) { + this.addClass('x-back-prevented'); + } else { + this.addClass('x-back-prevented'); + } + + // + this.detectChanges(); + this.onBackPrevent(v); + this.backPreventNotifier.emit({ id: this.uuid, state: v }); + } + + // + onBackPrevent(state: boolean) {} + //#endregion + + // + public baseActionProvider = new Subject(); + //#endregion + + // + //#region Constructor ... + constructor( + public zone: NgZone, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public managerService: XManagerService, + public changeDetector: ChangeDetectorRef, + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + @Inject(MAT_DIALOG_DATA) public data: any, + public dialogRef: MatDialogRef< + XBaseDialogComponent + > + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + this.preventBack = true; + } + + public afterViewInit(): void { + super.afterViewInit(); + + // + // Register Lock Notifier for Prevent Handling ... + this.applyTakeUntilWrapper(this.lockNotifier.asObservable()).subscribe( + (event) => { + // + if (isNullOrUndefined(event)) { + return; + } + + // + this.preventBack = event.state; + this.detectChanges(); + } + ); + } + //#endregion + + // + //#region Task Handlers ... + async onTaskStart(name: string, task: XTask) { + super.onTaskStart(name, task); + + // + const isDialogTask = await this.isDialogTask(name); + + // + if (isDialogTask) { + // + this.setLoadingState(true); + await this.notifyTaskStartEvent(name); + } + } + + async onTaskFinished(name: string, task: XTask) { + super.onTaskFinished(name, task); + + // + const isDialogTask = await this.isDialogTask(name); + + // + if (isDialogTask) { + // + this.setLoadingState(false); + await this.notifyTaskFinishedEvent(name); + } + } + + async onTaskFailed(name: string, task: XTask) { + super.onTaskFailed(name, task); + + // + const isDialogTask = await this.isDialogTask(name); + + // + if (isDialogTask) { + // + this.showError(task.error); + this.setLoadingState(false); + await this.notifyTaskFailedEvent(name); + } + } + //#endregion + + // + //#region Handlers ... + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerBackHandler(); + } + //#endregion + + // + //#region UI Providers ... + /** + * retrieve map dialog config ... + */ + public getData() { + return this.getValue(this.data as TConfig); + } + + /** + * determines map dialog config exists or not ... + */ + public hasData() { + return !isNullOrUndefined(this.getData()); + } + + /** + * determines dialog can select result or not ... + */ + public dialogCanSelect() { + return this.hasChild(this.selectedItem); + } + + /** + * retrieve dialog title ... + */ + public get title() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config ? config.title : '')) + ); + } + + /** + * retrieve dialog showTollbar ... + */ + public get showToolbar() { + return this.getData().pipe( + concatMap((config) => + !config || isNullOrUndefined(config.showToolbar) + ? of(true) + : this.getOptionalValue(config.showToolbar) + ) + ); + } + + public get cssClass() { + return this.getData().pipe( + concatMap((config) => this.getArrayValue(config ? config.cssClass : [])) + ); + } + + public get progressBarColor() { + return this.getData().pipe( + concatMap((config) => + this.getValue(config ? config.progressBarColor : XColor.Warning) + ), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Warning)) + ); + } + + public get actionProvider() { + return this.getData().pipe( + map((config) => + config ? config.actionProvider : this.baseActionProvider + ), + map((actionProvider) => + !isNullOrUndefined(actionProvider) + ? actionProvider + : this.baseActionProvider + ) + ); + } + + public get canMultipleSelect() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config ? config.canMultipleSelect : false) + ) + ); + } + + // + //#region SearchBar ... + public get showSearchBar() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config ? config.showSearchBar : false) + ) + ); + } + + public get searchBarColor() { + return this.getData().pipe( + concatMap((config) => + this.getValue(config ? config.searchBarColor : XColor.Primary) + ), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Primary)) + ); + } + + public get searchBarInputColor() { + return this.getData().pipe( + concatMap((config) => + this.getValue(config ? config.searchBarInputColor : XColor.Primary) + ), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Primary)) + ); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + async handleBack() { + this.dialogRef.close(); + } + + async handleSelect() { + // + if (!this.dialogCanSelect()) { + return; + } + + // + this.dialogRef.close({ + result: XDialogResult.Ok, + payload: this.selectedItem, + }); + } + + async handleDeselect() { + // + if (!this.dialogCanSelect()) { + return; + } + + // + const canSelectMultipleItems = await this.getValueAsync( + this.canMultipleSelect + ); + if (canSelectMultipleItems) { + await this.deselectAllListItems(); + } else { + // + const selectedItem = toArray(this.selectedItem)[0]; + if (!isNullOrUndefined(selectedItem)) { + await this.deselectItemInList(selectedItem); + } + } + + // + this.selectedItem = undefined; + this.detectChanges(); + } + //#endregion + + // + //#region Abstract ... + abstract getTaskNames(): Promise>; + + abstract deselectAllListItems(): Promise; + + abstract deselectItemInList(item: TItem): Promise; + + abstract handleSearchbarQuryChanged(query: string): Promise; + //#endregion + + // + //#region Protected ... + public async onBack() {} + //#endregion + + // + //#region Private ... + private async isDialogTask(name: string) { + // + const pageTasks = await this.getTaskNames(); + const mTaskKey = getTaskName(name); + const result = pageTasks.includes(mTaskKey); + + // + return result; + } + + private async notifyTaskStartEvent(name: string) { + // + if (isNullOrEmptyString(name)) { + return; + } + + // + const config = await this.getValueAsync(this.getData()); + if (config && config.onTaskStart && isFunction(config.onTaskStart)) { + await this.getValueAsync(config.onTaskStart(name)); + } + } + + private async notifyTaskFinishedEvent(name: string) { + // + if (isNullOrEmptyString(name)) { + return; + } + + // + const config = await this.getValueAsync(this.getData()); + if (config && config.onTaskFinished && isFunction(config.onTaskFinished)) { + await this.getValueAsync(config.onTaskFinished(name)); + } + } + + private async notifyTaskFailedEvent(name: string) { + // + if (isNullOrEmptyString(name)) { + return; + } + + // + const config = await this.getValueAsync(this.getData()); + if (config && config.onTaskFailed && isFunction(config.onTaskFailed)) { + await this.getValueAsync(config.onTaskFailed(name)); + } + } + + private registerBackHandler() { + // + this.managerService.historyService.platformLocation.onPopState(() => { + // + if (this.PREVENT_BACK) { + this.managerService.historyService.pushEmptyState(); + } + }); + + // + this.applyTakeUntilWrapper( + this.managerService.historyService.backHandler.asObservable() + ).subscribe(async () => { + await this.onBack(); + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\x-base.observable.tools.ts + +import { + toArray, + hasChild, + notValue, + toPromise, + toObservable, + XStandardType, + XOneOrManyType, + getDefaultValue, + isNullOrUndefined, + XResourceIdentifier, + isNullOrEmptyString, +} from 'x-framework-core'; +import { concatMap, map } from 'rxjs/operators'; +import { Observable, forkJoin, of } from 'rxjs'; + +export interface IXBaseObservableTools { + // + //#region Action ... + hasChild: (items: XOneOrManyType) => boolean; + //#endregion + + // + //#region Observables ... + getValue: (value: XStandardType) => Observable; + + getArrayValue: ( + value: XStandardType> + ) => Observable; + + hasChildValue: ( + value: XStandardType> + ) => Observable; + + countChildsValue: ( + value: XStandardType> + ) => Observable; + + getResourceValue: ( + value: XStandardType + ) => Observable; + + notEmptyValue: (value: XStandardType) => Observable; + + emptyValue: (value: XStandardType) => Observable; + + notValue: (value: XStandardType) => Observable; + + mustBeValue: ( + value: any, + ...values: XStandardType[] + ) => Observable; + + atLeastHasOneValue: ( + value: any, + ...values: XStandardType[] + ) => Observable; + + notNullOrUndefinedValue: (value: XStandardType) => Observable; + + nullOrUndefinedValue: (value: XStandardType) => Observable; + + getOptionalValue: (value?: XStandardType) => Observable; + + getValues: (values?: XOneOrManyType>) => Observable; + //#endregion + + // + //#region Async of Observables ... + getValueAsync: (value: XStandardType) => Promise; + + getArrayValueAsync: ( + value: XStandardType> + ) => Promise; + + hasChildValueAsync: ( + value: XStandardType> + ) => Promise; + + countChildsValueAsync: ( + value: XStandardType> + ) => Promise; + + getResourceValueAsync: ( + value: XStandardType + ) => Promise; + + notEmptyValueAsync: (value: XStandardType) => Promise; + + emptyValueAsync: (value: XStandardType) => Promise; + + notValueAsync: (value: XStandardType) => Promise; + + mustBeValueAsync: ( + value: any, + ...values: XStandardType[] + ) => Promise; + + atLeastHasOneValueAsync: ( + value: any, + ...values: XStandardType[] + ) => Promise; + + notNullOrUndefinedValueAsync: (value: XStandardType) => Promise; + + nullOrUndefinedValueAsync: (value: XStandardType) => Promise; + + getOptionalValueAsync: (value?: XStandardType) => Promise; + + getValuesAsync: ( + values?: XOneOrManyType> + ) => Promise; + //#endregion +} + +export abstract class XBaseObservableTools implements IXBaseObservableTools { + // + //#region Actions ... + // + //#region Observable Actions ... + /** + * Check if items has childs or not ... + * + * @param items a list item ... + */ + public hasChild(items: XOneOrManyType) { + return hasChild(items); + } + + /** + * convert an standard UI Value to Observable for consuming ... + * + * @param value the standard UI Value + */ + public getValue(value: XStandardType) { + return toObservable(value); + } + + /** + * retrieve an standard type var in promise + * + * @param value an standard type var + */ + public async getValueAsync(value: XStandardType) { + // + if (!value) { + return null; + } + + // + return await toPromise(this.getValue(value)); + } + + /** + * convert to array ... + * + * @param value an standard type one or many arg ... + */ + public getArrayValue(value: XStandardType>) { + return this.getValue(value).pipe(map((valueType) => toArray(valueType))); + } + + /** + * convert to array ... + * + * @param value an standard type one or many arg ... + */ + public async getArrayValueAsync(value: XStandardType>) { + return this.getValueAsync(value).then((valueType) => toArray(valueType)); + } + + /** + * check a value has child or not ... + * + * @param value an standard one or many of type + */ + public hasChildValue(value: XStandardType>) { + // + return this.getValue(value).pipe( + map((valueType) => hasChild(toArray(valueType))) + ); + } + + /** + * check a value has child or not ... + * + * @param value an standard one or many of type + */ + public async hasChildValueAsync(value: XStandardType>) { + // + const valueType = await this.getValueAsync(value); + if (!valueType) { + return false; + } + + // + return hasChild(toArray(valueType)); + } + + /** + * count a value's child ... + * + * @param value an standard one or many of type + */ + public countChildsValue(value: XStandardType>) { + return this.getValue(value).pipe( + map((valueType) => toArray(valueType).length) + ); + } + + /** + * count a value's child ... + * + * @param value an standard one or many of type + */ + public async countChildsValueAsync( + value: XStandardType> + ) { + // + const valueType = await this.getValueAsync(value); + if (!valueType) { + return 0; + } + + // + return toArray(valueType).length; + } + + /** + * providing string resources ... + * + * @param value an standard one or many of type + */ + public getResourceValue(value: XStandardType) { + // + return !value + ? of('') + : this.getValue(value).pipe( + map((val) => { + return val.toString(); + // return this.resourceProvider(val) || (value as string); + }) + ); + } + + /** + * providing string resources ... + * + * @param value an standard one or many of type + */ + public async getResourceValueAsync( + value: XStandardType + ) { + // + if (!value) { + return ''; + } + + // + const val = await this.getValueAsync(value); + return val.toString(); + } + + /** + * determines a given value has content and not empty + * + * @param value an Standard type of string + */ + public notEmptyValue(value: XStandardType) { + return this.getValue(value).pipe(map((val) => !isNullOrEmptyString(val))); + } + + /** + * determines a given value has content and not empty + * + * @param value an Standard type of string + */ + public async notEmptyValueAsync(value: any) { + return await toPromise(this.notEmptyValue(value)); + } + + /** + * determines a given value has not content and empty + * + * @param value an Standard type of string + */ + public emptyValue(value: XStandardType) { + return this.getValue(value).pipe(map((val) => isNullOrEmptyString(val))); + } + + /** + * determines a given value has not content and empty + * + * @param value an Standard type of string + */ + public async emptyValueAsync(value: XStandardType) { + return await toPromise(this.emptyValue(value)); + } + + /** + * apply not operator to a boolean ... + */ + public notValue(value: XStandardType) { + return notValue(this.getValue(value)); + } + + /** + * apply not operator to a boolean ... + */ + public async notValueAsync(value: XStandardType) { + return await this.getValueAsync(this.notValue(value)); + } + + /** + * check all input args be same as value ... + * + * @param value any value which going to check with all args to be same as them ... + * @param values the input args which be checked by value ... + */ + public mustBeValue(value: any, ...values: XStandardType[]) { + // + return forkJoin(values.map((v) => toObservable(v))).pipe( + map((res) => { + return res.every((i) => i === value); + }) + ); + } + + /** + * check all input args be same as value ... + * + * @param value any value which going to check with all args to be same as them ... + * @param values the input args which be checked by value ... + */ + public async mustBeValueAsync(value: any, ...values: XStandardType[]) { + // + return ( + await Promise.all(values.map(async (v) => await this.getValueAsync(v))) + ).every((v) => v === value); + } + + /** + * check all input args contains at least one value ... + * + * @param value any value which going to check with all args to find one same ... + * @param values the input args which be checked by value ... + */ + public atLeastHasOneValue(value: any, ...values: XStandardType[]) { + // + return forkJoin(values.map((v) => toObservable(v))).pipe( + map((res) => { + return res.includes(value); + }) + ); + } + + /** + * check all input args contains at least one value ... + * + * @param value any value which going to check with all args to find one same ... + * @param values the input args which be checked by value ... + */ + public async atLeastHasOneValueAsync( + value: any, + ...values: XStandardType[] + ) { + // + return ( + await Promise.all(values.map(async (v) => await this.getValueAsync(v))) + ).includes(value); + } + + /** + * determines a given value has content and not empty + * + * @param value an Standard type of any + */ + public notNullOrUndefinedValue(value: XStandardType) { + return this.getValue(value).pipe(map((val) => !isNullOrUndefined(val))); + } + + /** + * determines a given value has content and not empty + * + * @param value an Standard type of any + */ + public async notNullOrUndefinedValueAsync(value: XStandardType) { + return await toPromise(this.notNullOrUndefinedValue(value)); + } + + /** + * determines a given value content must empty + * + * @param value an Standard type of any + */ + public nullOrUndefinedValue(value: XStandardType) { + return this.getValue(value).pipe(map((val) => isNullOrUndefined(val))); + } + + /** + * determines a given value content must empty + * + * @param value an Standard type of any + */ + public async nullOrUndefinedValueAsync(value: XStandardType) { + return await toPromise(this.nullOrUndefinedValue(value)); + } + + /** + * retrieve an optional value ... + * + * @param value the optional standard value ... + */ + public getOptionalValue(value?: XStandardType): Observable { + // + if (value) { + return this.getValue(value); + } + + // + return this.getValue(getDefaultValue(value)); + } + + /** + * retrieve an optional value ... + * + * @param value the optional standard value ... + */ + public async getOptionalValueAsync(value?: XStandardType) { + return await toPromise(this.getOptionalValue(value)); + } + + /** + * convert all values as observable of them ... + * + * @param values the standard list of specific type ... + * @returns observable of values array of specific type ... + */ + public getValues(values?: XOneOrManyType>) { + // + interface ForkSourceType { + [index: number]: XStandardType; + } + + // + return this.getArrayValue(values).pipe( + concatMap((values) => { + // + if (!hasChild(values)) { + return this.getValue([] as T[]); + } else { + // + const forkSource: ForkSourceType = {}; + values.forEach((value, index) => { + forkSource[index] = this.getValue(value); + }); + + // + return forkJoin(forkSource).pipe( + map((forkResult) => { + // + const respObj = forkResult as any; + const mKeys = Object.keys(forkResult) + .map((key) => key) + .sort((a, b) => +a - +b); + const result = mKeys.map((mKey: any) => respObj[mKey] as T); + + // + return result; + }) + ); + } + }) + ); + } + + /** + * convert all values as observable of them ... + * + * @param values the standard list of specific type ... + * @returns observable of values array of specific type ... + */ + public async getValuesAsync(values?: XOneOrManyType>) { + return await toPromise(this.getValues(values)); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\component-templates.module.ts + +import { + XCardTestComponent, + XComponentCardTestComponent, +} from './x-card.component.templates'; +import { + XListTestComponent, + XComponentListTestComponent, +} from './x-list.component.templates'; +import { + XTabTestComponent, + XTabsTestComponent, +} from './x-tabs.component.templates'; +import { + XEmptyTestComponent, + XComponentEmptyTestComponent, +} from './x-empty.component.templates'; +import { + XSlideTestComponent, + XSliderTestComponent, +} from './x-slider.component.templates'; +import { + XSlotTestComponent, + XSlotterTestComponent, +} from './x-slotter.component.templates'; +import { + XStepTestComponent, + XStepperTestComponent, +} from './x-stepper.component.templates'; +import { NgModule } from '@angular/core'; +import { + XActionBarTestComponent, + XComponentActionBarTestComponent, +} from './x-action-bar.component.templates'; +import { + XSearchBarTestComponent, + XComponentSearchBarTestComponent, +} from './x-search-bar.component.templates'; +import { CommonModule } from '@angular/common'; +import { + XDescriptionListTestComponent, + XDescriptionListItemTestComponent, +} from './x-description-list.component.templates'; +import { XMapModule } from '../x-map/x-map.module'; +import { XFabModule } from '../x-fab/x-fab.module'; +import { XIDEModule } from '../x-ide/x-ide.module'; +import { XFormModule } from '../x-form/x-form.module'; +import { XGridModule } from '../x-grid/x-grid.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XListModule } from '../x-list/x-list.module'; +import { XPageModule } from '../x-page/x-page.module'; +import { XTabsModule } from '../x-tabs/x-tabs.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XTableModule } from '../x-table/x-table.module'; +import { XThemeModule } from '../x-theme/x-theme.module'; +import { XColorModule } from '../x-color/x-color.module'; +import { XEmptyModule } from '../x-empty/x-empty.module'; +import { XChartModule } from '../x-chart/x-chart.module'; +import { XSliderModule } from '../x-slider/x-slider.module'; +import { XButtonModule } from '../x-button/x-button.module'; +import { XIdeTestComponent } from './x-ide.component.template'; +import { XCounterModule } from '../x-counter/x-counter.module'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XSpinnerModule } from '../x-spinner/x-spinner.module'; +import { XStepperModule } from '../x-stepper/x-stepper.module'; +import { XMapTestComponent } from './x-map.component.templates'; +import { XFabTestComponent } from './x-fab.component.templates'; +import { XPageTestComponent } from './x-page.component.templates'; +import { XMarkdownModule } from '../x-markdown/x-markdown.module'; +import { XFormTestComponent } from './x-form.component.templates'; +import { XGridTestComponent } from './x-grid.component.templates'; +import { XIconTestComponent } from './x-icon.component.templates'; +import { XChartTestComponent } from './x-chart.component.template'; +import { XTableTestComponent } from './x-table.component.templates'; +import { XColorTestComponent } from './x-color.component.templates'; +import { XThumbnailModule } from '../x-thumbnail/x-thumbnail.module'; +import { XFooterTestComponent } from './x-footer.component.templates'; +import { XButtonTestComponent } from './x-button.component.templates'; +import { XSearchBarModule } from '../x-search-bar/x-search-bar.module'; +import { XActionBarModule } from '../x-action-bar/x-action-bar.module'; +import { XToolbarTestComponent } from './x-toolbar.component.templates'; +import { XSpinnerTestComponent } from './x-spinner.component.templates'; +import { XCounterTestComponent } from './x-counter.component.templates'; +import { XDataPagerModule } from './../x-data-pager/x-data-pager.module'; +import { XMarkdownTestComponent } from './x-markdown.component.templates'; +import { XFileUploadModule } from '../x-file-upload/x-file-upload.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XThumbnailTestComponent } from './x-thumbnail.component.templates'; +import { XNavigatorTestComponent } from './x-navigator.component.templates'; +import { XDataPagerTestComponent } from './x-data-pager.component.templates'; +import { XColorPickerModule } from '../x-color-picker/x-color-picker.module'; +import { XFileUploadTestComponent } from './x-file-upload.component.templates'; +import { XImageCropperModule } from '../x-image-cropper/x-image-cropper.module'; +import { XAvatarUploadModule } from '../x-avatar-upload/x-avatar-upload.module'; +import { XTabNavigatorModule } from '../x-tab-navigator/x-tab-navigator.module'; +import { XDataScrollerModule } from '../x-data-scroller/x-data-scroller.module'; +import { XColorPickerTestComponent } from './x-color-picker.component.templates'; +import { XFileDropAreaModule } from '../x-file-drop-area/x-file-drop-area.module'; +import { XTabNavigatorTestComponent } from './x-tab-navigator.component.templates'; +import { XThemeManagerTestComponent } from './x-theme-manager.component.templates'; +import { XAvatarUploadTestComponent } from './x-avatar-upload.component.templates'; +import { XImageCropperTestComponent } from './x-image-cropper.component.templates'; +import { XDataScrollerTestComponent } from './x-data-scroller.component.templates'; +import { XFileDropAreaTestComponent } from './x-file-drop-area.component.templates'; +import { XScreenCaptureTestComponent } from './x-screen-capture.component.templates'; +import { XModelPresenterModule } from '../x-model-presenter/x-model-presenter.module'; +import { XExpandableListModule } from '../x-expandable-list/x-expandable-list.module'; +import { XExpandableListTestComponent } from './x-expandable-list.component.templates'; +import { XModelPresenterTestComponent } from './x-model-presenter.component.templates'; +import { XDescriptionListModule } from '../x-description-list/x-description-list.module'; +import { XExpandablePanelModule } from '../x-expandable-panel/x-expandable-panel.module'; +import { XExpandablePandelTestComponent } from './x-expandable-panel.component.templates'; + +@NgModule({ + declarations: [ + // + // x-action-bar ... + XActionBarTestComponent, + XComponentActionBarTestComponent, + // + // x-avatar-upload ... + XAvatarUploadTestComponent, + // + // x-button ... + XButtonTestComponent, + // + // x-ide ... + XIdeTestComponent, + // + // x-chart ... + XChartTestComponent, + // + // x-card ... + XCardTestComponent, + XComponentCardTestComponent, + // + // x-color ... + XColorTestComponent, + // + // x-color-picker ... + XColorPickerTestComponent, + // + // x-counter ... + XCounterTestComponent, + // + // x-data-pager ... + XDataPagerTestComponent, + // + // x-data-scroller ... + XDataScrollerTestComponent, + // + // x-description-list ... + XDescriptionListTestComponent, + XDescriptionListItemTestComponent, + // + // x-empty ... + XEmptyTestComponent, + XComponentEmptyTestComponent, + // + // x-expandable-list ... + XExpandableListTestComponent, + // + // x-expandable-panel ... + XExpandablePandelTestComponent, + // + // x-fab ... + XFabTestComponent, + // + // x-file-drop-area ... + XFileDropAreaTestComponent, + // + // x-file-upload ... + XFileUploadTestComponent, + // + // x-form ... + XFormTestComponent, + // + // x-grid ... + XGridTestComponent, + // + // x-icon ... + XIconTestComponent, + // + // x-image-cropper ... + XImageCropperTestComponent, + // + // x-list ... + XListTestComponent, + XComponentListTestComponent, + // + // x-map ... + XMapTestComponent, + // + // x-markdown ... + XMarkdownTestComponent, + // + // x-model-presenter ... + XModelPresenterTestComponent, + // + // x-page ... + XPageTestComponent, + // + // x-footer ... + XFooterTestComponent, + // + // x-navigator ... + XNavigatorTestComponent, + // + // x-toolbar ... + XToolbarTestComponent, + // + // x-screen-capture ... + XScreenCaptureTestComponent, + // + // x-search-bar ... + XSearchBarTestComponent, + XComponentSearchBarTestComponent, + // + // x-slider ... + XSlideTestComponent, + XSliderTestComponent, + // + // x-slotter ... + XSlotTestComponent, + XSlotterTestComponent, + // + // x-spinner ... + XSpinnerTestComponent, + // + // x-stepper ... + XStepTestComponent, + XStepperTestComponent, + // + // x-tab-navigator ... + XTabNavigatorTestComponent, + // + // x-table ... + XTableTestComponent, + // + // x-tabs ... + XTabTestComponent, + XTabsTestComponent, + // + // x-theme-manager ... + XThemeManagerTestComponent, + // + // x-thumbnail ... + XThumbnailTestComponent, + ], + imports: [ + // + CommonModule, + + // + XEmptyModule, + XBaseComponentModule, + + // + XIDEModule, + XFabModule, + XMapModule, + XPageModule, + XCardModule, + XIconModule, + XFormModule, + XTabsModule, + XTabsModule, + XListModule, + XGridModule, + XChartModule, + XColorModule, + XTableModule, + XThemeModule, + XButtonModule, + XSliderModule, + XSpinnerModule, + XStepperModule, + XSlotterModule, + XCounterModule, + XMarkdownModule, + XDataPagerModule, + XSearchBarModule, + XThumbnailModule, + XActionBarModule, + XFileUploadModule, + XColorPickerModule, + XImageCropperModule, + XFileDropAreaModule, + XDataScrollerModule, + XTabNavigatorModule, + XAvatarUploadModule, + XExpandableListModule, + XModelPresenterModule, + XExpandablePanelModule, + XDescriptionListModule, + ], + exports: [], +}) +export class XComponentTemplatesModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-action-bar.component.templates.ts + +import { Component } from '@angular/core'; +import { + XActionBarContainerComponent, + XComponentActionBarContainerComponent, +} from '../x-action-bar/x-action-bar.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XActionBarTestComponent extends XActionBarContainerComponent {} + +@Component({ + template: ` + + + + + + `, +}) +export class XComponentActionBarTestComponent extends XComponentActionBarContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-avatar-upload.component.templates.ts + +import { Component } from '@angular/core'; +import { XAvatarUploadContainerComponent } from '../x-avatar-upload/x-avatar-upload.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XAvatarUploadTestComponent extends XAvatarUploadContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-button.component.templates.ts + +import { Component } from '@angular/core'; +import { XButtonContainerComponent } from '../x-button/x-button.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XButtonTestComponent extends XButtonContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-card.component.templates.ts + +import { Component } from '@angular/core'; +import { + XCardContainerComponent, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XCardTestComponent extends XCardContainerComponent {} + +@Component({ + template: ` + + + + + + + + + + + + + + + + +
+ +
+ + +
+ +
+
+
+
+ + + + `, +}) +export class XComponentCardTestComponent extends XComponentCardContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-chart.component.template.ts + +import { Component } from '@angular/core'; +import { XChartContainerComponent } from '../x-chart/x-chart.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XChartTestComponent extends XChartContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-color-picker.component.templates.ts + +import { Component } from '@angular/core'; +import { XColorPickerContainerComponent } from '../x-color-picker/x-color-picker.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XColorPickerTestComponent extends XColorPickerContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-color.component.templates.ts + +import { Component } from "@angular/core"; +import { XColorContainerComponent } from "../x-color/x-color.typings"; + +@Component({ + template: ` + + + + + ` +}) +export class XColorTestComponent extends XColorContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-counter.component.templates.ts + + +import { Component } from "@angular/core"; +import { XCounterContainerComponent } from "../x-counter/x-counter.typings"; + +@Component({ + template: ` + + + + + ` +}) +export class XCounterTestComponent extends XCounterContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-data-pager.component.templates.ts + +import { Component } from '@angular/core'; +import { XDataPagerContainerComponent } from '../x-data-pager/x-data-pager.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XDataPagerTestComponent extends XDataPagerContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-data-scroller.component.templates.ts + +import { Component } from '@angular/core'; +import { XDataScrollerContainerComponent } from '../x-data-scroller/x-data-scroller.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XDataScrollerTestComponent extends XDataScrollerContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-description-list.component.templates.ts + +import { Component } from '@angular/core'; +import { XDescriptionListContainerComponent } from '../x-description-list/x-description-list.typings'; +import { XDescriptionListItemContainerComponent } from '../x-description-list/x-description-list-item/x-description-list-item.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XDescriptionListTestComponent extends XDescriptionListContainerComponent {} + +@Component({ + template: ` + + + + + `, +}) +export class XDescriptionListItemTestComponent extends XDescriptionListItemContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-empty.component.templates.ts + +import { Component } from '@angular/core'; +import { + XComponentEmptyContainerComponent, + XEmptyContainerComponent, +} from '../x-empty/x-empty.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XEmptyTestComponent extends XEmptyContainerComponent {} + +@Component({ + template: ` + + + + + + + `, +}) +export class XComponentEmptyTestComponent extends XComponentEmptyContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-expandable-list.component.templates.ts + +import { Component } from '@angular/core'; +import { XExpandableListContainerComponent } from '../x-expandable-list/x-expandable-list.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XExpandableListTestComponent extends XExpandableListContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-expandable-panel.component.templates.ts + +import { Component } from '@angular/core'; +import { XExpandablePandelContainerComponent } from '../x-expandable-panel/x-expandable-panel.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XExpandablePandelTestComponent extends XExpandablePandelContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-fab.component.templates.ts + +import { Component } from '@angular/core'; +import { XFabContainerComponent } from '../x-fab/x-fab.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XFabTestComponent extends XFabContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-file-drop-area.component.templates.ts + +import { Component } from '@angular/core'; +import { XFileDropAreaContainerComponent } from '../x-file-drop-area/x-file-drop-area.typings'; + +@Component({ + template: ` + + + + `, +}) +export class XFileDropAreaTestComponent extends XFileDropAreaContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-file-upload.component.templates.ts + +import { Component } from '@angular/core'; +import { XFileUploadContainerComponent } from '../x-file-upload/x-file-upload.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XFileUploadTestComponent extends XFileUploadContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-footer.component.templates.ts + +import { Component } from '@angular/core'; +import { XFooterContainerComponent } from '../x-page/x-footer/x-footer.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XFooterTestComponent extends XFooterContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-form.component.templates.ts + +import { Component } from '@angular/core'; +import { XFormContainerComponent } from '../x-form/x-form.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XFormTestComponent extends XFormContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-grid.component.templates.ts + +import { Component } from '@angular/core'; +import { XGridContainerComponent } from '../x-grid/x-grid.typings'; + +@Component({ + template: ` + + + + `, +}) +export class XGridTestComponent extends XGridContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-icon.component.templates.ts + +import { Component } from '@angular/core'; +import { XIconContainerComponent } from '../x-icon/x-icon.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XIconTestComponent extends XIconContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-ide.component.template.ts + +import { Component } from '@angular/core'; +import { XIDEContainerComponent } from '../x-ide/x-ide.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XIdeTestComponent extends XIDEContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-image-cropper.component.templates.ts + +import { Component } from '@angular/core'; +import { XImageCropperContainerComponent } from '../x-image-cropper/x-image-cropper.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XImageCropperTestComponent extends XImageCropperContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-list.component.templates.ts + +import { Component } from '@angular/core'; +import { + XComponentListContainerComponent, + XListContainerComponent, +} from '../x-list/x-list.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XListTestComponent extends XListContainerComponent {} + +@Component({ + template: ` + + + + + + + `, +}) +export class XComponentListTestComponent extends XComponentListContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-map.component.templates.ts + +import { Component } from '@angular/core'; +import { XMapContainerComponent } from '../x-map/x-map.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XMapTestComponent extends XMapContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-markdown.component.templates.ts + +import { Component } from '@angular/core'; +import { XMarkdownContainerComponent } from '../x-markdown/x-markdown.typings'; + +@Component({ + template: ` + + + + `, +}) +export class XMarkdownTestComponent extends XMarkdownContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-model-presenter.component.templates.ts + +import { Component } from '@angular/core'; +import { XModelPresenterContainerComponent } from '../x-model-presenter/x-model-presenter.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XModelPresenterTestComponent extends XModelPresenterContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-navigator.component.templates.ts + +import { Component } from '@angular/core'; +import { XNavigatorContainerComponent } from '../x-page/x-navigator/x-navigator.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XNavigatorTestComponent extends XNavigatorContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-page.component.templates.ts + + +import { Component } from "@angular/core"; +import { XPageContainerComponent } from "../x-page/x-page.container"; + +@Component({ + template: ` + + + + + ` +}) +export class XPageTestComponent extends XPageContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-screen-capture.component.templates.ts + +import { Component } from '@angular/core'; +import { XScreenCaptureContainerComponent } from '../x-screen-capture/x-screen-capture.container'; + +@Component({ + template: ` + + + + + `, +}) +export class XScreenCaptureTestComponent extends XScreenCaptureContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-search-bar.component.templates.ts + +import { Component } from '@angular/core'; +import { + XComponentSearchBarContainerComponent, + XSearchBarContainerComponent, +} from '../x-search-bar/x-search-bar.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XSearchBarTestComponent extends XSearchBarContainerComponent {} + +@Component({ + template: ` + + + + + + + `, +}) +export class XComponentSearchBarTestComponent extends XComponentSearchBarContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-slider.component.templates.ts + +import { Component } from '@angular/core'; +import { XSliderContainerComponent } from '../x-slider/x-slider.typings'; +import { XSlideContainerComponent } from '../x-slider/x-slide/x-slide.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XSliderTestComponent extends XSliderContainerComponent {} + +@Component({ + template: ` + + + + `, +}) +export class XSlideTestComponent extends XSlideContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-slotter.component.templates.ts + +import { Component } from '@angular/core'; +import { XSlotterContainerComponent } from '../x-slotter/x-slotter.typings'; +import { XSlotContainerComponent } from '../x-slotter/x-slot/x-slot.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XSlotterTestComponent extends XSlotterContainerComponent {} + +@Component({ + template: ` + + + + + `, +}) +export class XSlotTestComponent extends XSlotContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-spinner.component.templates.ts + +import { Component } from '@angular/core'; +import { XSpinnerContainerComponent } from '../x-spinner/x-spinner.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XSpinnerTestComponent extends XSpinnerContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-stepper.component.templates.ts + +import { Component } from '@angular/core'; +import { XStepperContainerComponent } from '../x-stepper/x-stepper.typings'; +import { XStepContainerComponent } from '../x-stepper/x-step/x-step.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XStepperTestComponent extends XStepperContainerComponent {} + +@Component({ + template: ` + + + + + `, +}) +export class XStepTestComponent extends XStepContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-tab-navigator.component.templates.ts + +import { Component } from '@angular/core'; +import { XTabNavigatorContainerComponent } from '../x-tab-navigator/x-tab-navigator.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XTabNavigatorTestComponent extends XTabNavigatorContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-table.component.templates.ts + +import { Component } from '@angular/core'; +import { XTableContainerComponent } from '../x-table/x-table.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XTableTestComponent extends XTableContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-tabs.component.templates.ts + +import { Component } from '@angular/core'; +import { XTabsContainerComponent } from '../x-tabs/x-tabs.typings'; +import { XTabContainerComponent } from '../x-tabs/x-tab/x-tab.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XTabsTestComponent extends XTabsContainerComponent {} + +@Component({ + template: ` + + + + `, +}) +export class XTabTestComponent extends XTabContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-theme-manager.component.templates.ts + +import { Component } from '@angular/core'; +import { XThemeManagerContainerComponent } from '../x-theme/x-theme-manager/x-theme-manager.typings'; + +@Component({ + template: ` + + + + `, +}) +export class XThemeManagerTestComponent extends XThemeManagerContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-thumbnail.component.templates.ts + +import { Component } from '@angular/core'; +import { XThumbnailContainerComponent } from '../x-thumbnail/x-thumbnail.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XThumbnailTestComponent extends XThumbnailContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-toolbar.component.templates.ts + +import { Component } from '@angular/core'; +import { XToolbarContainerComponent } from '../x-page/x-toolbar/x-toolbar.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XToolbarTestComponent extends XToolbarContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\config\x-framework-components.config.ts + +import { XFrameworkCoreConfig } from 'x-framework-core'; +import { XFrameworkServicesConfig } from 'x-framework-services'; + +export type XSharedConfig = XFrameworkCoreConfig & XFrameworkServicesConfig; + +// tslint:disable-next-line:no-empty-interface +export interface XFrameworkComponentsConfig extends XSharedConfig { + // + // File Config ... + maxAllowedSize: number; + maxUploadFiles: number; + minAllowedFileSize: number; + maxAllowedFileSize: number; + + // + defaultSearchDebounceTime: number; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-dialog.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XListModule } from '../x-list/x-list.module'; +import { XPopoverService } from './x-popover/x-popover.service'; +import { XSlotterModule } from './../x-slotter/x-slotter.module'; +import { XPopoverComponent } from './x-popover/x-popover.component'; +import { XSearchBarModule } from '../x-search-bar/x-search-bar.module'; +import { XDataPagerModule } from '../x-data-pager/x-data-pager.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XSelectorDialogService } from './x-selector-dialog/x-selector-dialog.service'; +import { XSelectorDialogComponent } from './x-selector-dialog/x-selector-dialog.component'; + +@NgModule({ + imports: [ + XListModule, + CommonModule, + XSlotterModule, + XSearchBarModule, + XDataPagerModule, + XBaseComponentModule, + ], + exports: [ + XListModule, + CommonModule, + XSlotterModule, + XSearchBarModule, + XDataPagerModule, + XBaseComponentModule, + ], + providers: [XPopoverService, XSelectorDialogService], + declarations: [XPopoverComponent, XSelectorDialogComponent], + entryComponents: [XPopoverComponent, XSelectorDialogComponent], +}) +export class XDialogsModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-dialog\x-dialog.component.html + +
+ +
+ + + +
+ + + +
+ +
+
+ + + + +

+ {{ getTitle() | async }} +

+
+
+ + + +
+ +
+
+
+
+
+ + + +
+ + +
+ +
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-dialog\x-dialog.component.scss + +x-icon { + --size: 24px; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 !important; +} + +.x-base-dialog-container { + height: 100%; +} + +.x-base-dialog-content { + height: 100%; + overflow-y: scroll; +} + +.x-base-dialog-toolbar-area { + margin: 8px; +} + +.x-base-dialog-toolbar-item { + width: auto; + height: 48px; +} + +.x-base-dialog-toolbar-button { + width: 32px; + margin: { + left: 4px; + right: 4px; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-dialog\x-dialog.component.ts + +import { + XDialogConfig, + XDialogContext, + defaultBaseDialogConfig, +} from './x-dialog.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { Input, Component, ViewChild, ElementRef } from '@angular/core'; +import { XIconNames } from '../../x-icon/x-icon.typings'; +import { XSlotName } from '../../x-slotter/x-slotter.typings'; +import { XBaseComponent } from '../../classes/x-base-component'; +import { XStandardType, isNullOrUndefined } from 'x-framework-core'; + +@Component({ + template: '', +}) +export abstract class XDialogComponent extends XBaseComponent { + // + //#region Props ... + // + //#region Private/Readonly ... + readonly SlotNames = Object.assign({}, XSlotName); + //#endregion + + // + //#region Self Props ... + // + @Input() + context: XStandardType; + //#endregion + + // + //#region ViewChilds ... + @ViewChild('xBaseDialogToolbar') + dialogToolbar: ElementRef; + //#endregion + + // + selectedValue: any; + applyThemeColors = true; + selectIcon = XIconNames.checked; + backIcon = XIconNames.arrow_back; + //#endregion + + // + //#region Constructor ... + //#endregion + + // + //#region LifeCycles ... + //#endregion + + // + //#region Register Handlers ... + //#endregion + + // + //#region Task Handlers ... + //#endregion + + // + //#region UI Providers ... + // + //#region Context Options UI Providers ... + /** + * retrieve id of dialog ... + * + * @returns Observable of Id ... + */ + getId() { + return this.getData().pipe(map((config) => config.id)); + } + + /** + * retrieve map dialog config ... + */ + getData() { + return this.getValue(this.context); + } + + /** + * retrieve dialog title ... + */ + getTitle() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config.title)) + ); + } + + /** + * retrieve dialog can Select values or not ... + * + * @returns observable of boolean ... + */ + canSelect() { + return this.getData().pipe(map((config) => !!config.canSelect)); + } + + /** + * retrieve payload of data for dialog ... + * + * @returns observable of any ... + */ + getPayload() { + return this.getData().pipe(map((config) => config.payload)); + } + + /** + * retrieve toolbar color ... + * + * @returns observable of XColorIdentifier ... + */ + getToolbarColor() { + return this.getData().pipe( + map( + (config) => config.toolbarColor || defaultBaseDialogConfig.toolbarColor + ) + ); + } + + /** + * retrieve background color ... + * + * @returns observable of XColorIdentifier ... + */ + getBackgroundColor() { + return this.getData().pipe( + map( + (config) => + config.backgroundColor || defaultBaseDialogConfig.backgroundColor + ) + ); + } + + /** + * retrieve progressBar color ... + * + * @returns observable of XColorIdentifier ... + */ + getProgressBarColor() { + return this.getData().pipe( + map( + (config) => + config.progressBarColor || defaultBaseDialogConfig.progressBarColor + ) + ); + } + //#endregion + + // + //#region Class Providers ... + getDialogToolbarClasses() { + return this.getValue(this.getId()).pipe( + map((id) => { + return ['x-base-dialog-toolbar', `x-base-dialog-${id}-toolbar`]; + }) + ); + } + + getDialogContainerClasses() { + return this.getValue(this.getId()).pipe( + map((id) => { + return ['x-base-dialog-container', `x-base-dialog-${id}-container`]; + }) + ); + } + //#endregion + + hasSelectedValue() { + return this.notNullOrUndefinedValue(this.selectedValue); + } + + canShowProgressBar() { + return this.getValue(this.loading || this.uiDisabled); + } + //#endregion + + // + //#region UI Handlers ... + async handleClosePopup(payload: any = undefined) { + // + const id = await this.getValueAsync(this.getId()); + const context = await this.getValueAsync(this.context); + if (isNullOrUndefined(context)) { + return; + } + + // + switch (context.type) { + // + case XDialogContext.Modal: + await this.managerService.dialogService.modalController.dismiss( + payload, + undefined, + id + ); + break; + + // + case XDialogContext.PopOver: + await this.managerService.dialogService.popoverController.dismiss( + payload, + undefined, + id + ); + break; + } + } + + async handleSelect(payload: any) { + // + if (isNullOrUndefined(payload)) { + return; + } + + // + const hasSelectedValue = await this.getValueAsync(this.hasSelectedValue()); + if (!hasSelectedValue) { + return; + } + + // + await this.handleClosePopup(payload); + } + //#endregion + + // + //#region Actions ... + //#endregion + + // + //#region Abstracts ... + + //#endregion + + // + //#region Private ... + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-dialog\x-dialog.typings.ts + +import { XColorIdentifier, XColorWithBrightness } from 'x-framework-core'; + +export type XDialogConfigKeys = 'title' | string; + +export type XDialogConfigType = { + [key in XDialogConfigKeys]: any; +}; + +export enum XDialogContext { + Modal = 'modal', + PopOver = 'pop_over', +} + +export interface XDialogConfig extends XDialogConfigType { + id: string; + title: string; + payload?: any; + canSelect?: boolean; + type: XDialogContext; + toolbarColor?: XColorIdentifier; + backgroundColor?: XColorIdentifier; + progressBarColor?: XColorIdentifier; +} + +export const defaultBaseDialogConfig: Partial = { + // + toolbarColor: XColorWithBrightness.Primary, + backgroundColor: XColorWithBrightness.Light, + progressBarColor: XColorWithBrightness.Warning, +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-popover\x-popover.component.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ + getResourceValue(title) | async + }} + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-popover\x-popover.component.scss + +:host { + --max-popover-height: 260px; + --background-color: var(--x-color-light); + --divider-color: var(--x-color-dark-shade); +} + +:host::ng-deep { + mat-card { + background-color: var(--background-color) !important; + } +} + +:host::ng-deep.align-self-center { + align-self: center !important; +} + +ion-list, +ion-item.item, +ion-item-divider, +ion-list-header { + --background: var(--background-color); +} + +ion-item-divider { + --color: var(--divider-color); +} + +ion-list-header.x-list-header { + text-align: center !important; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; +} + +.x-popover-wrapper { + max-height: var(--max-popover-height); + overflow-y: scroll; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-popover\x-popover.component.ts + +import { + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + ChangeDetectorRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + isFunction, + parseXColor, + XExceptionIDs, + XOneOrManyType, + XColorIdentifier, + isNullOrUndefined, + XColorWithBrightness, + isNullOrEmptyString, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XPopoverItem, + XPopoverCssVar, + XPopoverOption, + XPopoverResult, +} from './x-popover.typings'; +import { NavParams } from '@ionic/angular'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XPopoverService } from './x-popover.service'; +import { generatePopoverId } from './x-popover.tools'; +import { XManagerService } from 'x-framework-services'; +import { XBaseComponent } from '../../classes/x-base-component'; +import { XSlotLayout, XSlotName } from '../../x-slotter/x-slotter.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-popover', + templateUrl: './x-popover.component.html', + styleUrls: ['./x-popover.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XPopoverComponent extends XBaseComponent { + // + //#region Props ... + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + readonly ColorNames = Object.assign({}, XColorWithBrightness); + + // + items: XOneOrManyType; + options: XOneOrManyType; + + // + height: string; + dividerColor: XColorIdentifier; + backgroundColor: XColorIdentifier; + //#endregion + + // + //#region Constructor ... + constructor( + public zone: NgZone, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public navParams: NavParams, + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public changeDetector: ChangeDetectorRef, + protected popOverService: XPopoverService + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + + // + // Options ... + const options = this.navParams.get( + nameof('options') + ) as XPopoverOption[]; + this.options = options; + + // + // Items ... + const items = this.navParams.get( + nameof('items') + ) as XPopoverItem[]; + this.items = items; + + // + // Error if not Options or Items ... + if (!this.hasChild(options) && !this.hasChild(items)) { + this.showError(XExceptionIDs.InvalidArgs, () => { + this.managerService.dialogService.popoverController.dismiss(); + }); + } + } + //#endregion + + // + //#region LifeCycles ... + public afterViewInit(): void { + super.afterViewInit(); + + // + // Height ... + this.height = this.navParams.get(nameof('height')); + if (!isNullOrEmptyString(this.height)) { + this.applyHeight(); + } + + // + // DividerColor ... + this.dividerColor = this.navParams.get( + nameof('dividerColor') + ); + if (!isNullOrEmptyString(this.dividerColor)) { + this.applyDividerColor(); + } + + // + // BackgroundColor ... + this.backgroundColor = this.navParams.get( + nameof('backgroundColor') + ); + if (!isNullOrEmptyString(this.backgroundColor)) { + this.applyBackgroundColor(); + } + } + //#endregion + + // + //#region UI Providers ... + //#endregion + + // + //#region UI Handlers ... + handleMenuSelected( + isItem: boolean, + item: XPopoverItem | XPopoverOption, + event: Event + ) { + // + if (!!isItem) { + this.handleItemSelected(item as XPopoverItem, event); + } else { + this.handleOptionSelected(item as XPopoverOption); + } + } + //#endregion + + // + //#region Privates ... + private async handleItemSelected(item: XPopoverItem, event: Event) { + // + const itemResult = + !isNullOrUndefined(item) && + !isNullOrUndefined(item.handler) && + isFunction(item.handler) + ? await this.getValueAsync(item.handler()) + : undefined; + + // + const optionsResultObj = !this.hasChild(item.options) + ? undefined + : await this.popOverService.presentContextMenu(item.options, event); + + // + if ( + isNullOrUndefined( + optionsResultObj || isNullOrUndefined(optionsResultObj.option) + ) + ) { + return; + } + + // + const popoverId = generatePopoverId(this.items); + + // + await this.managerService.dialogService.popoverController.dismiss( + { + item, + itemResult, + ...optionsResultObj, + } as XPopoverResult, + undefined, + popoverId + ); + } + + private async handleOptionSelected(option: XPopoverOption) { + // + const optionResult = + !isNullOrUndefined(option) && + !isNullOrUndefined(option.handler) && + isFunction(option.handler) + ? await this.getValueAsync(option.handler()) + : undefined; + + // + await this.managerService.dialogService.popoverController.dismiss({ + option, + optionResult, + } as XPopoverResult); + } + + private applyHeight() { + // + if (isNullOrEmptyString(this.height)) { + return; + } + + // + this.setCssVar(XPopoverCssVar.MaxPopoverHeight, this.height); + this.detectChanges(); + } + + private applyDividerColor() { + // + if (isNullOrEmptyString(this.dividerColor)) { + return; + } + + // + const color = this.dividerColor; + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XPopoverCssVar.DividerColor, `var(${bgColor})`); + this.detectChanges(); + } + + private applyBackgroundColor() { + // + if (isNullOrEmptyString(this.backgroundColor)) { + return; + } + + // + const color = this.backgroundColor; + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XPopoverCssVar.BackgroundColor, `var(${bgColor})`); + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-popover\x-popover.service.ts + +import { + toArray, + hasChild, + XBaseService, + XOneOrManyType, + isNullOrUndefined, +} from 'x-framework-core'; +import { + XPopoverItem, + XPopoverOption, + XPopoverResult, + XPopoverComponentProps, + XPopoverPresentOptions, + XPopoverPresenterOptions, +} from './x-popover.typings'; +import { Inject, Injectable } from '@angular/core'; +import { generatePopoverId } from './x-popover.tools'; +import { XManagerService } from 'x-framework-services'; +import { XPopoverComponent } from './x-popover.component'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XPopoverService extends XBaseService { + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService + ) { + super(config); + } + //#endregion + + // + //#region Actions ... + /** + * Present Context Menu by Providinf XPopOverOption instance array ... + * + * @param options menu options which going to show to user ... + * @param event click event for positioning ... + */ + public async presentContextMenu( + options: XOneOrManyType, + event: Event, + props?: XPopoverPresentOptions, + presentProps?: XPopoverPresenterOptions + ) { + // + if (!hasChild(options)) { + return null; + } + + // + const overrideWidth = toArray(options).every((option) => !!option.onlyIcon); + if (overrideWidth) { + // + let currentCssClass = !isNullOrUndefined(presentProps) + ? toArray(presentProps.cssClass).join(' ') + : ''; + currentCssClass += `${ + currentCssClass.length > 0 ? ' ' : '' + }x-minimal-popover`; + + // + presentProps = { + ...presentProps, + cssClass: currentCssClass, + }; + } + + // + const mProps: XPopoverComponentProps = { + options, + ...props, + }; + + // + // Present Popover as Context Menu ... + const popOver = await this.managerService.dialogService.presentPopover({ + // + ...presentProps, + // + event, + translucent: true, + showBackdrop: true, + componentProps: mProps, + component: XPopoverComponent, + }); + + // + // Extract Data from PopOver ... + const { data } = await popOver.onWillDismiss(); + if (!data) { + return undefined; + } + + // + const result = data as XPopoverResult; + return result; + } + + /** + * Present Context Menu by Providinf XPopoverItem instance array ... + * + * @param items + * @param event + */ + public async presentContextMenuItems( + items: XOneOrManyType, + event: Event, + props?: XPopoverPresentOptions, + presentProps?: XPopoverPresenterOptions + ) { + // + if (!hasChild(items)) { + return null; + } + + // + const overrideWidth = toArray(items).every((i) => !!i.onlyIcon); + if (overrideWidth) { + // + let currentCssClass = !isNullOrUndefined(presentProps) + ? toArray(presentProps.cssClass).join(' ') + : ''; + currentCssClass += `${ + currentCssClass.length > 0 ? ' ' : '' + }x-minimal-popover`; + + // + presentProps = { + ...presentProps, + cssClass: currentCssClass, + }; + } + + // + // Generate unique id from items ... + const popoverId = generatePopoverId(items); + + // + const mProps: XPopoverComponentProps = { + items, + ...props, + }; + + // + // Present Popover as Context Menu ... + const popOver = await this.managerService.dialogService.presentPopover({ + // + ...presentProps, + // + event, + id: popoverId, + translucent: true, + showBackdrop: true, + componentProps: mProps, + component: XPopoverComponent, + }); + + // + // Extract Data from PopOver ... + const { data } = await popOver.onWillDismiss(); + if (!data) { + return null; + } + + // + const result = data as XPopoverResult; + return result; + } + //#endregion + + // + //#region Private ... + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-popover\x-popover.tools.ts + +import { XListItem } from '../../x-list/x-list.typings'; +import { + toMd5, + toArray, + hasChild, + XOneOrManyType, + isNullOrUndefined, +} from 'x-framework-core'; +import { XPopoverItem, XPopoverOption } from './x-popover.typings'; + +export const generatePopoverId: ( + items: XOneOrManyType +) => string = (items) => { + // + const itemIds = toArray(items) + .map((i) => i.id) + .join('|'); + const result = toMd5(itemIds); + + // + return result; +}; + +export const toPopoverOptions: ( + item: XListItem +) => XOneOrManyType = (item) => { + // + let result: XPopoverOption[] = []; + + // + if (isNullOrUndefined(item) || !hasChild(item.slideOptions)) { + return result; + } + + // + result = item.slideOptions.map((so) => { + return { + id: so.id, + onlyIcon: so.onlyIcon, + disabled: so.disabled, + icon: so.icon, + color: so.color, + title: so.title, + slot: so.slot, + handler: so.handler, + } as XPopoverOption; + }); + + // + return result; +}; + +export const defaultPopoverItem: XPopoverItem = { + id: '', + icon: '', + color: '', + title: '', + options: [], + iconColor: '', + iconSlot: 'end', + onlyIcon: false, + disabled: false, + handler: () => {}, + showDetailedIcon: false, +}; + +export const defaultPopoverOption: XPopoverOption = { + id: '', + icon: '', + color: '', + title: '', + slot: 'end', + onlyIcon: false, + disabled: false, + handler: () => {}, +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-popover\x-popover.typings.ts + +import { + XStandardType, + XOneOrManyType, + XPopoverOptions, + XColorIdentifier, + XResourceIdentifier, +} from 'x-framework-core'; +import { XPopoverComponent } from './x-popover.component'; +import { XIconIdentifier } from '../../x-icon/x-icon.typings'; + +export type XPopoverItemOptionSlot = 'end' | 'start'; + +export enum XPopoverCssVar { + DividerColor = 'divider-color', + BackgroundColor = 'background-color', + MaxPopoverHeight = 'max-popover-height', +} + +export type XPopoverCssVarIdentifier = XPopoverCssVar | string; + +export interface XPopoverOption { + id: string; + onlyIcon?: boolean; + disabled?: boolean; + icon?: XIconIdentifier; + color?: XColorIdentifier; + title?: XResourceIdentifier; + slot?: XPopoverItemOptionSlot; + handler?: () => XStandardType; +} + +export interface XPopoverItem { + id: string; + disabled?: boolean; + onlyIcon?: boolean; + icon?: XIconIdentifier; + color: XColorIdentifier; + title: XResourceIdentifier; + showDetailedIcon?: boolean; + iconColor?: XColorIdentifier; + options?: XPopoverOption[]; + iconSlot?: XPopoverItemOptionSlot; + handler?: () => XStandardType; +} + +export interface XPopoverResult { + // + item?: XPopoverItem; + itemResult?: any; + // + option?: XPopoverOption; + optionResult?: any; +} + +type SelectedKeysOfXpopoverComponent = Pick< + Partial, + 'items' | 'options' | 'height' | 'dividerColor' | 'backgroundColor' +>; +export type XPopoverComponentPropsType = { + [key in keyof SelectedKeysOfXpopoverComponent]: any; +}; + +export interface XPopoverComponentProps extends XPopoverComponentPropsType { + height?: string; + dividerColor?: XColorIdentifier; + backgroundColor?: XColorIdentifier; + items?: XOneOrManyType; + options?: XOneOrManyType; +} + +export type XPopoverPresentOptions = Pick< + Partial, + 'height' | 'dividerColor' | 'backgroundColor' +>; + +export type XPopoverPresenterOptions = Partial< + Omit< + XPopoverOptions, + | 'backdropDismiss' + | 'componentProps' + | 'component' + | 'id' + | 'translucent' + | 'showBackdrop' + | 'event' + > +>; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-selector-dialog\x-selector-dialog.component.html + +
+ + + + + +
+ + + +
+ +
+
+ + + + +

+ {{ title | async }} +

+
+
+ + + + +
+ +
+ + +
+ +
+
+
+
+ + + +
+
+ + + + + + + + + +
+ + + + + + + + + + + {{ item.data | json }} + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-selector-dialog\x-selector-dialog.component.scss + +::ng-deep.fullscreen-dialog { + // + width: 80vw !important; + max-width: 80vw !important; + + // + height: 80vh !important; + max-height: 80vh !important; + + // + &.mobile { + // + width: 100vw !important; + max-width: 100vw !important; + + // + height: 100vh !important; + max-height: 100vh !important; + } + + // + .mat-dialog-container { + padding: 0 !important; + } +} + +.x-dialog-wrapper { + width: 100% !important; + height: 100% !important; + position: relative !important; +} + +.x-dialog-header { + position: sticky !important; + top: 0; + z-index: 1000; +} + +.x-dialog-content-wrapper { + height: calc(100%); + + &.has-toolbar { + height: calc(100% - 56px); + } +} + +x-icon { + --size: 24px; +} + +.toolbar-area { + margin: 0px; +} + +.toolbar-item { + width: auto; + height: 48px; +} + +.toolbar-inline-item { + display: inline-flex !important; +} + +.toolbar-button { + width: 32px; + margin: { + left: 4px; + right: 4px; + } +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-selector-dialog\x-selector-dialog.component.ts + +import { + Inject, + NgZone, + Renderer2, + Component, + ElementRef, + ChangeDetectorRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XTask, + XColor, + toArray, + isFunction, + generateUuid, + XOneOrManyType, + isNullOrUndefined, + isNullOrEmptyString, + XBaseDataPagerQuery, + XBaseDataPagerQueryResult, +} from 'x-framework-core'; +import { Observable, Subject } from 'rxjs'; +import { concatMap, map } from 'rxjs/operators'; +import { + XDataPagerActionModel, + XDataPagerPresentType, + XDataPagerPaginatorPosition, +} from '../../x-data-pager/x-data-pager.typings'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XSelectorDialogConfig } from './x-selector-dialog.typings'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { XListItem, XListActionModel } from '../../x-list/x-list.typings'; +import { XBaseDialogComponent } from '../../classes/x-base.dialog.component'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-selector-dialog', + templateUrl: './x-selector-dialog.component.html', + styleUrls: ['./x-selector-dialog.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XSelectorDialogComponent extends XBaseDialogComponent< + any, + XDataPagerActionModel, + XSelectorDialogConfig +> { + // + //#region Props ... + // + uuid = generateUuid('XFormSelector'); + + // + canSearch: boolean; + dataPagerQuery: XBaseDataPagerQuery = { + ...this.getDataPagerQuery(), + }; + showDataPagerPaginator: boolean = true; + dataPagerResultListItems: XListItem[] = []; + dataPagerResult: XBaseDataPagerQueryResult; + dataPagerActionProvider = new Subject(); + + // + pageListActionProvider = new Subject(); + + // + readonly DataPagerPresentTypes = Object.assign({}, XDataPagerPresentType); + //#endregion + + // + //#region Constructor ... + constructor( + public zone: NgZone, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public managerService: XManagerService, + public changeDetector: ChangeDetectorRef, + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + @Inject(MAT_DIALOG_DATA) public data: any, + public dialogRef: MatDialogRef + ) { + super( + zone, + element, + renderer, + ruler, + managerService, + changeDetector, + config, + data, + dialogRef + ); + } + //#endregion + + // + //#region Task Handlers ... + async onTaskFinished(name: string, task: XTask) { + await super.onTaskFinished(name, task); + + // + const tName = await this.getValueAsync(this.taskName); + if (name === tName) { + // + this.setLoadingState(false); + await this.handleDataPagerTaskFinished(task.result); + } + } + //#endregion + + // + //#region UI Providers ... + // + get taskName() { + return this.getData().pipe( + concatMap((config) => + this.getValue(config ? config.taskName : this.uuid) + ), + map((name) => (!isNullOrEmptyString(name) ? name : this.uuid)) + ); + } + + get queryFetcher() { + return this.getData().pipe( + concatMap((config) => + this.getValue(config ? config.queryFetcher : undefined) + ) + ); + } + + get itemTemplate() { + return this.getData().pipe( + concatMap((config) => + this.getValue(config ? config.itemTemplate : undefined) + ) + ); + } + + // + //#region Data Pager Paginator ... + get showPaginator() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config ? config.showPaginator : false) + ) + ); + } + + get paginatorColor() { + return this.getData().pipe( + concatMap((config) => + this.getValue(config ? config.paginatorColor : XColor.Primary) + ), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Primary)) + ); + } + + get paginatorPosition() { + return this.getData().pipe( + concatMap((config) => + this.getValue( + config ? config.paginatorPosition : XDataPagerPaginatorPosition.Above + ) + ), + map((position) => + !isNullOrEmptyString(position) + ? position + : XDataPagerPaginatorPosition.Above + ) + ); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + async handleResetDataPagerQuery() { + // + this.dataPagerQuery = { + ...this.getDataPagerQuery(), + }; + + // + this.detectChanges(); + } + + async handleDataPagerTaskReady( + task: Observable> + ) { + // + // XBaseDataPagerQueryResult ... + if (!task || this.uiDisabled || this.loading) { + return; + } + + // + await this.issueDataPagerTask(task); + } + + async handleDataPagerQueryChanged(query: XBaseDataPagerQuery) { + // + if (!query) { + return; + } + + // + const config = await this.getValueAsync(this.getData()); + if (config && config.onQueryChanged && isFunction(config.onQueryChanged)) { + await this.getValueAsync(config.onQueryChanged(query)); + } + } + + async handleListItemSelected(event: XListItem) { + // + const canSelectMultipleItems = await this.getValueAsync( + this.canMultipleSelect + ); + + // + if (!event || !event.data || canSelectMultipleItems) { + return; + } + + // + const index = this.dataPagerResultListItems.findIndex((i) => + Object.is(i, event) + ); + if (index > -1) { + // + // Cancel Previous Selected Item(s) ... + this.dataPagerResultListItems.forEach((i) => { + i.disabled = false; + }); + + // + // Disable Current Selected Item ... + this.dataPagerResultListItems[index].disabled = true; + + // + this.detectChanges(); + } + + // + await this.handleSelectedItems(event); + } + + async handleSelectedItems(event: XOneOrManyType>) { + // + this.selectedItem = toArray(event).map((i) => i.data); + + // + const config = await this.getValueAsync(this.getData()); + if (config && config.onItemSelect && isFunction(config.onItemSelect)) { + await this.getValueAsync(config.onItemSelect(event)); + } + + // + this.detectChanges(); + } + + async handleListItemsSelected(event: XListItem[]) { + // + const canSelectMultipleItems = await this.getValueAsync( + this.canMultipleSelect + ); + + // + if (!this.hasChild(event) || !canSelectMultipleItems) { + return; + } + + // + await this.handleSelectedItems(event); + } + //#endregion + + // + //#region Abstracts ... + async getTaskNames(): Promise { + return [await this.getValueAsync(this.taskName)]; + } + + async deselectAllListItems(): Promise { + // + if (!this.hasChild(this.dataPagerResultListItems)) { + return; + } + + // + this.dataPagerResultListItems.forEach((i) => { + i.disabled = false; + }); + + // + this.dataPagerResultListItems = [...this.dataPagerResultListItems]; + + // + this.detectChanges(); + } + + async deselectItemInList(item: any): Promise { + // + if ( + isNullOrUndefined(item) || + !this.hasChild(this.dataPagerResultListItems) + ) { + return; + } + + // + const index = this.dataPagerResultListItems.findIndex((i) => + Object.is(i.data, item) + ); + if (index > -1) { + // + this.dataPagerResultListItems[index].disabled = false; + + // + this.dataPagerResultListItems = [...this.dataPagerResultListItems]; + + // + this.detectChanges(); + } + } + + async handleSearchbarQuryChanged(query: string): Promise { + // + this.dataPagerQuery = { + ...this.dataPagerQuery, + filter: query, + }; + + // + this.detectChanges(); + } + //#endregion + + // + //#region Private ... + private getDataPagerQuery(): XBaseDataPagerQuery { + return { + filter: '', + pageNumber: 1, + rowsInPage: this.config.defaultPageSize, + } as XBaseDataPagerQuery; + } + + private async issueDataPagerTask( + task: Observable> + ) { + // + if (!task) { + return; + } + + // + const mTaskName = await this.getValueAsync(this.taskName); + const mTask = task; + + // + await this.addOrUpdateTask({ + priority: 0, + affectLoading: true, + name: mTaskName, + task: mTask, + }); + + // + await this.dispatchTask(mTaskName); + } + + private async handleDataPagerTaskFinished( + result: XBaseDataPagerQueryResult + ) { + // + this.dataPagerResult = null; + this.dataPagerResultListItems = []; + this.detectChanges(); + + // + if (!result) { + return; + } + + // + this.dataPagerResult = { + ...result, + }; + + // + this.canSearch = + (this.hasChild(this.dataPagerResult.items) && + this.dataPagerResult.items.length > 1) || + !isNullOrEmptyString(this.dataPagerQuery.filter); + + // + this.showDataPagerPaginator = + this.hasChild(this.dataPagerResult.items) && + this.dataPagerResult.items.length > 1; + + // + const config = await this.getValueAsync(this.getData()); + if (config && config.onPageLoad && isFunction(config.onPageLoad)) { + await this.getValueAsync(config.onPageLoad(this.dataPagerResult)); + } + + // + this.dataPagerResultListItems = await this.prepareListItems(); + + // + this.detectChanges(); + } + + private async prepareListItems() { + // + if (!this.dataPagerResult || !this.hasChild(this.dataPagerResult.items)) { + return []; + } + + // + return this.dataPagerResult.items.map((sr) => { + return { + data: sr, + } as XListItem; + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-selector-dialog\x-selector-dialog.service.ts + +import { + MatDialog, + MatDialogRef, + MatDialogConfig, +} from '@angular/material/dialog'; +import { Inject, Injectable } from '@angular/core'; +import { XResourceIdentifier } from 'x-framework-core'; +import { XManagerService } from 'x-framework-services'; +import { XSelectorDialogConfig } from './x-selector-dialog.typings'; +import { XSelectorDialogComponent } from './x-selector-dialog.component'; +import { XBaseDialogService } from '../../classes/x-base-dialog.service'; +import { XDataPagerActionModel } from '../../x-data-pager/x-data-pager.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XSelectorDialogService extends XBaseDialogService< + any, + XDataPagerActionModel, + XSelectorDialogConfig, + XSelectorDialogComponent +> { + // + defaultTitleResourceIdentifier: XResourceIdentifier; + + // + //#region Constructor ... + constructor( + protected dialog: MatDialog, + protected managerService: XManagerService, + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig + ) { + super(dialog, managerService, config); + } + //#endregion + + // + openDialogAndReturnRef( + config: MatDialogConfig + ): MatDialogRef { + return this.dialog.open(XSelectorDialogComponent, config); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-selector-dialog\x-selector-dialog.typings.ts + +import { + XStandardType, + XColorIdentifier, + XBaseDataPagerQuery, + XBaseDataPagerQueryResult, +} from 'x-framework-core'; +import { Observable } from 'rxjs'; +import { TemplateRef } from '@angular/core'; +import { + IXBaseDialogConfig, + IXBaseDialogResult, +} from '../../classes/x-base.dialog.component'; +import { + XDataPagerActionModel, + XDataPagerPaginatorPositionIdentifier, +} from '../../x-data-pager/x-data-pager.typings'; + +export interface XSelectorDialogResult extends IXBaseDialogResult {} + +export interface XSelectorDialogConfig + extends IXBaseDialogConfig { + // + showPaginator?: XStandardType; + paginatorColor?: XStandardType; + paginatorPosition?: XStandardType; + + // + queryFetcher?: ( + query: XBaseDataPagerQuery + ) => Observable>; + onQueryChanged?: (query: XBaseDataPagerQuery) => XStandardType; + onPageLoad?: (result: XBaseDataPagerQueryResult) => XStandardType; + itemTemplate?: XStandardType>; + + // + taskName?: XStandardType; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\modules\x-base-component.module.ts + +import { NgModule } from '@angular/core'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { XMaterialModule } from './x-material.module'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { XDirectivesModule as XFrameworkCoreDirectivesModule } from 'x-framework-core'; +import { XDirectivesModule as XFrameworkServicesDirectivesModule } from 'x-framework-services'; + +@NgModule({ + imports: [ + IonicModule, + FormsModule, + CommonModule, + RouterModule, + XMaterialModule, + ReactiveFormsModule, + XFrameworkCoreDirectivesModule, + XFrameworkServicesDirectivesModule, + ], + exports: [ + FormsModule, + IonicModule, + RouterModule, + CommonModule, + XMaterialModule, + ReactiveFormsModule, + XFrameworkCoreDirectivesModule, + XFrameworkServicesDirectivesModule, + ], +}) +export class XBaseComponentModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\modules\x-material.module.ts + +// +import { NgModule } from '@angular/core'; + +// +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { ScrollingModule } from '@angular/cdk/scrolling'; +import { CdkTableModule } from '@angular/cdk/table'; +import { CdkTreeModule } from '@angular/cdk/tree'; + +// +// Form Controls ... +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatSelectModule } from '@angular/material/select'; +import { MatSliderModule } from '@angular/material/slider'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; + +// +// Navigations / Menus ... +import { MatMenuModule } from '@angular/material/menu'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatToolbarModule } from '@angular/material/toolbar'; + +// +// Layout ... +import { MatCardModule } from '@angular/material/card'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatGridListModule } from '@angular/material/grid-list'; +import { MatListModule } from '@angular/material/list'; +import { MatStepperModule } from '@angular/material/stepper'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatTreeModule } from '@angular/material/tree'; + +// +// Buttons and Indicators ... +import { MatButtonModule } from '@angular/material/button'; +import { MatButtonToggleModule } from '@angular/material/button-toggle'; +import { MatBadgeModule } from '@angular/material/badge'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatIconModule } from '@angular/material/icon'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatRippleModule } from '@angular/material/core'; +import { MatNativeDateModule } from '@angular/material/core'; + +// +// Popups and Dialogs ... +import { MatBottomSheetModule } from '@angular/material/bottom-sheet'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatTooltipModule } from '@angular/material/tooltip'; + +// +// Data Table ... +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatSortModule } from '@angular/material/sort'; +import { MatTableModule } from '@angular/material/table'; + +// +import { + MomentDateModule, + MatMomentDateModule, +} from '@angular/material-moment-adapter'; +import { LayoutModule } from '@angular/cdk/layout'; +import { FlexLayoutModule } from '@angular/flex-layout'; +import { CommonModule } from '@angular/common'; + +@NgModule({ + imports: [ + CommonModule, + + // + CdkTableModule, + CdkTreeModule, + + // + DragDropModule, + ScrollingModule, + + // + LayoutModule, + FlexLayoutModule, + + // + MatAutocompleteModule, + MatBadgeModule, + MatBottomSheetModule, + MatButtonModule, + MatButtonToggleModule, + MatCardModule, + MatCheckboxModule, + MatChipsModule, + MatStepperModule, + MatDatepickerModule, + MatDialogModule, + MatDividerModule, + MatExpansionModule, + MatGridListModule, + MatIconModule, + MatInputModule, + MatListModule, + MatNativeDateModule, + MatPaginatorModule, + MatProgressBarModule, + MatProgressSpinnerModule, + MatRadioModule, + MatRippleModule, + MatSelectModule, + MatSidenavModule, + MatSliderModule, + MatSlideToggleModule, + MatSnackBarModule, + MatSortModule, + MatTableModule, + MatTabsModule, + MatToolbarModule, + MatTooltipModule, + MatTreeModule, + MatFormFieldModule, + MatMenuModule, + + // + MomentDateModule, + MatMomentDateModule, + ], + exports: [ + // + CdkTableModule, + CdkTreeModule, + + // + DragDropModule, + ScrollingModule, + + // + LayoutModule, + FlexLayoutModule, + + // + MatAutocompleteModule, + MatBadgeModule, + MatBottomSheetModule, + MatButtonModule, + MatButtonToggleModule, + MatCardModule, + MatCheckboxModule, + MatChipsModule, + MatStepperModule, + MatDatepickerModule, + MatDialogModule, + MatDividerModule, + MatExpansionModule, + MatGridListModule, + MatIconModule, + MatInputModule, + MatListModule, + MatNativeDateModule, + MatPaginatorModule, + MatProgressBarModule, + MatProgressSpinnerModule, + MatRadioModule, + MatRippleModule, + MatSelectModule, + MatSidenavModule, + MatSliderModule, + MatSlideToggleModule, + MatSnackBarModule, + MatSortModule, + MatTableModule, + MatTabsModule, + MatToolbarModule, + MatTooltipModule, + MatTreeModule, + MatFormFieldModule, + MatMenuModule, + + // + MomentDateModule, + MatMomentDateModule, + ], +}) +export class XMaterialModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\_tools.scss + +@import "./animate.scss"; + +// +//#region Tools ... + +// +//#region Dimensions ... +// +// These are default values which can be used for margins and +// paddings ... +$d-xsm: 2px; +$d-sm: 4px; +$d-md: 8px; +$d-l: 16px; +$d-el: 32px; +$d-exl: 48px; + +// +// Box Sizing ... +$d-min: 300px; +$d-medium: 500px; +$d-max: 800px; + +// +// Percentages ... +$d-25p: 25%; +$d-50p: 50%; +$d-75p: 75%; +$d-100p: 100%; +//#endregion + +// +//#region Margin adnd Padding mixins ... +.m-0 { + margin: 0 !important; +} + +.p-0 { + padding: 0 !important; +} + +@mixin m-rl($size: $d-sm) { + margin-left: $size; + margin-right: $size; +} + +@mixin p-rl($size: $d-sm) { + padding-left: $size; + padding-right: $size; +} + +@mixin m-tb($size: $d-sm) { + margin-top: $size; + margin-bottom: $size; +} + +@mixin p-tb($size: $d-sm) { + padding-top: $size; + padding-bottom: $size; +} + +@mixin m($size: $d-sm) { + margin: $size !important; +} + +@mixin p($size: $d-sm) { + padding: $size !important; +} + +@mixin m-full($top: 0px, $left: 0px, $bottom: 0px, $right: 0px) { + margin: { + top: $top; + left: $left; + bottom: $bottom; + right: $right; + } +} + +@mixin p-full($top: 0px, $left: 0px, $bottom: 0px, $right: 0px) { + padding: { + top: $top; + left: $left; + bottom: $bottom; + right: $right; + } +} + +@mixin push-auto { + margin: { + left: auto; + right: auto; + } +} + +.push-auto { + margin: { + left: auto; + right: auto; + } +} + +// #endregion + +// +//#region Animation mixins and classes ... +@mixin animation( + $animation-name: ".fadeIn", + $animation-duration: 500ms, + $animation-delay: "", + $animation-repeat: 1 +) { + @extend .animated; + @extend .#{$animation-name}; + animation-delay: $animation-delay; + animation-duration: $animation-duration; + animation-iteration-count: $animation-repeat; +} + +@mixin transition-duration($duration: 500ms, $delay: 0ms) { + // + transition-delay: $delay; + -webkit-transition-delay: $delay; + + // + transition-duration: $duration; + -webkit-transition-duration: $duration; +} + +@mixin transformer($transform, $duration: 500ms, $delay: 0ms) { + // + transform: $transform; + -ms-transform: $transform; + -webkit-transform: $transform; + + // + @include transition(transform, $duration, $delay); +} + +@mixin transition($transition, $duration: 500ms, $delay: 0ms) { + // + transition: $transition; + -webkit-transition: $transition; + + @include transition-duration($duration, $delay); +} + +@mixin show { + @include animation("fadeIn", "250ms"); + @include transition("height", "250ms"); +} + +@mixin hide { + @include animation("fadeOut", "250ms"); + + height: 0; + @include transition("height", "250ms", "250ms"); +} + +.show { + @include show(); +} + +.hide { + @include hide(); +} + +//#endregion + +// +//#region Width Height and Alignment ... +@mixin applyWidthHeight($size: $d-75p, $min-width: $d-min, $max-width: $d-max) { + width: calc(#{$size}); + max-width: $max-width; + min-width: $min-width; +} + +@mixin horizontal-spacer { + flex: 1 1 auto; +} + +.horizontal-spacer { + @include horizontal-spacer(); +} + +@mixin center-vertical-flex { + // + display: flex !important; + flex-direction: column !important; + justify-content: center !important; +} + +.center-vertical-flex { + @include center-vertical-flex(); +} + +@mixin center-vertical { + position: relative; + top: 50%; + transform: translateY(-50%); +} + +.center-vertical { + @include center-vertical(); +} + +@mixin center { + @extend .center; +} + +.center { + text-align: center; + margin-left: auto; + margin-right: auto; +} + +@mixin text-truncate { + @extend .text-truncate; +} + +.text-truncate { + display: inline-block; + overflow-x: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap; + min-width: 0; +} + +//#endregion + +// +//#region Shadows Tools ... +@mixin add-shadow-box($vert, $horz, $blur, $size, $color: $shadow-color) { + // + $box-shadow-data: $vert $horz $blur $size $shadow-color !important; + + // + box-shadow: $box-shadow-data; + -moz-box-shadow: $box-shadow-data; + -webkit-box-shadow: $box-shadow-data; +} + +@mixin remove-shadow-box() { + // + $box-shadow-data: none !important; + + // + box-shadow: $box-shadow-data; + -moz-box-shadow: $box-shadow-data; + -webkit-box-shadow: $box-shadow-data; +} + +@mixin filterBlur($level: 4px) { + -webkit-filter: blur($level); + filter: blur($level); +} + +@mixin filterBrightness($level: 0.3) { + -webkit-filter: brightness($level); + filter: brightness($level); +} + +@mixin filterContrast($level: 180%) { + -webkit-filter: contrast($level); + filter: contrast($level); +} + +@mixin filterGrayscale($level: 100%) { + -webkit-filter: grayscale($level); + filter: grayscale($level); +} + +@mixin filterHuerotate($level: 180deg) { + -webkit-filter: hue-rotate($level); + filter: hue-rotate($level); +} + +@mixin filterInvert($level: 100%) { + -webkit-filter: invert($level); + filter: invert($level); +} + +@mixin filterOpacity($level: 50%) { + -webkit-filter: opacity($level); + filter: opacity($level); +} + +@mixin filterSaturate($level: 7) { + -webkit-filter: saturate($level); + filter: saturate($level); +} + +@mixin filterSepia($level: 100%) { + -webkit-filter: sepia($level); + filter: sepia(100%); +} + +//#endregion + +// +//#region Other ... +@mixin disableLink { + pointer-events: none; + + * { + pointer-events: none; + } +} +//#endregion +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\animate.scss + +@charset "UTF-8"; + +/*! + * animate.css -http://daneden.me/animate + * Version - 3.7.0 + * Licensed under the MIT license - http://opensource.org/licenses/MIT + * + * Copyright (c) 2018 Daniel Eden + */ + +@-webkit-keyframes bounce { + from, + 20%, + 53%, + 80%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 40%, + 43% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + -webkit-transform: translate3d(0, -30px, 0); + transform: translate3d(0, -30px, 0); + } + + 70% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + -webkit-transform: translate3d(0, -15px, 0); + transform: translate3d(0, -15px, 0); + } + + 90% { + -webkit-transform: translate3d(0, -4px, 0); + transform: translate3d(0, -4px, 0); + } +} + +@keyframes bounce { + from, + 20%, + 53%, + 80%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 40%, + 43% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + -webkit-transform: translate3d(0, -30px, 0); + transform: translate3d(0, -30px, 0); + } + + 70% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + -webkit-transform: translate3d(0, -15px, 0); + transform: translate3d(0, -15px, 0); + } + + 90% { + -webkit-transform: translate3d(0, -4px, 0); + transform: translate3d(0, -4px, 0); + } +} + +.bounce { + -webkit-animation-name: bounce; + animation-name: bounce; + -webkit-transform-origin: center bottom; + transform-origin: center bottom; +} + +@-webkit-keyframes flash { + from, + 50%, + to { + opacity: 1; + } + + 25%, + 75% { + opacity: 0; + } +} + +@keyframes flash { + from, + 50%, + to { + opacity: 1; + } + + 25%, + 75% { + opacity: 0; + } +} + +.flash { + -webkit-animation-name: flash; + animation-name: flash; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes pulse { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 50% { + -webkit-transform: scale3d(1.05, 1.05, 1.05); + transform: scale3d(1.05, 1.05, 1.05); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes pulse { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 50% { + -webkit-transform: scale3d(1.05, 1.05, 1.05); + transform: scale3d(1.05, 1.05, 1.05); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.pulse { + -webkit-animation-name: pulse; + animation-name: pulse; +} + +@-webkit-keyframes rubberBand { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 30% { + -webkit-transform: scale3d(1.25, 0.75, 1); + transform: scale3d(1.25, 0.75, 1); + } + + 40% { + -webkit-transform: scale3d(0.75, 1.25, 1); + transform: scale3d(0.75, 1.25, 1); + } + + 50% { + -webkit-transform: scale3d(1.15, 0.85, 1); + transform: scale3d(1.15, 0.85, 1); + } + + 65% { + -webkit-transform: scale3d(0.95, 1.05, 1); + transform: scale3d(0.95, 1.05, 1); + } + + 75% { + -webkit-transform: scale3d(1.05, 0.95, 1); + transform: scale3d(1.05, 0.95, 1); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes rubberBand { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 30% { + -webkit-transform: scale3d(1.25, 0.75, 1); + transform: scale3d(1.25, 0.75, 1); + } + + 40% { + -webkit-transform: scale3d(0.75, 1.25, 1); + transform: scale3d(0.75, 1.25, 1); + } + + 50% { + -webkit-transform: scale3d(1.15, 0.85, 1); + transform: scale3d(1.15, 0.85, 1); + } + + 65% { + -webkit-transform: scale3d(0.95, 1.05, 1); + transform: scale3d(0.95, 1.05, 1); + } + + 75% { + -webkit-transform: scale3d(1.05, 0.95, 1); + transform: scale3d(1.05, 0.95, 1); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.rubberBand { + -webkit-animation-name: rubberBand; + animation-name: rubberBand; +} + +@-webkit-keyframes shake { + from, + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 10%, + 30%, + 50%, + 70%, + 90% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 20%, + 40%, + 60%, + 80% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } +} + +@keyframes shake { + from, + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 10%, + 30%, + 50%, + 70%, + 90% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 20%, + 40%, + 60%, + 80% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } +} + +.shake { + -webkit-animation-name: shake; + animation-name: shake; +} + +@-webkit-keyframes headShake { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 6.5% { + -webkit-transform: translateX(-6px) rotateY(-9deg); + transform: translateX(-6px) rotateY(-9deg); + } + + 18.5% { + -webkit-transform: translateX(5px) rotateY(7deg); + transform: translateX(5px) rotateY(7deg); + } + + 31.5% { + -webkit-transform: translateX(-3px) rotateY(-5deg); + transform: translateX(-3px) rotateY(-5deg); + } + + 43.5% { + -webkit-transform: translateX(2px) rotateY(3deg); + transform: translateX(2px) rotateY(3deg); + } + + 50% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes headShake { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 6.5% { + -webkit-transform: translateX(-6px) rotateY(-9deg); + transform: translateX(-6px) rotateY(-9deg); + } + + 18.5% { + -webkit-transform: translateX(5px) rotateY(7deg); + transform: translateX(5px) rotateY(7deg); + } + + 31.5% { + -webkit-transform: translateX(-3px) rotateY(-5deg); + transform: translateX(-3px) rotateY(-5deg); + } + + 43.5% { + -webkit-transform: translateX(2px) rotateY(3deg); + transform: translateX(2px) rotateY(3deg); + } + + 50% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +.headShake { + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + -webkit-animation-name: headShake; + animation-name: headShake; +} + +@-webkit-keyframes swing { + 20% { + -webkit-transform: rotate3d(0, 0, 1, 15deg); + transform: rotate3d(0, 0, 1, 15deg); + } + + 40% { + -webkit-transform: rotate3d(0, 0, 1, -10deg); + transform: rotate3d(0, 0, 1, -10deg); + } + + 60% { + -webkit-transform: rotate3d(0, 0, 1, 5deg); + transform: rotate3d(0, 0, 1, 5deg); + } + + 80% { + -webkit-transform: rotate3d(0, 0, 1, -5deg); + transform: rotate3d(0, 0, 1, -5deg); + } + + to { + -webkit-transform: rotate3d(0, 0, 1, 0deg); + transform: rotate3d(0, 0, 1, 0deg); + } +} + +@keyframes swing { + 20% { + -webkit-transform: rotate3d(0, 0, 1, 15deg); + transform: rotate3d(0, 0, 1, 15deg); + } + + 40% { + -webkit-transform: rotate3d(0, 0, 1, -10deg); + transform: rotate3d(0, 0, 1, -10deg); + } + + 60% { + -webkit-transform: rotate3d(0, 0, 1, 5deg); + transform: rotate3d(0, 0, 1, 5deg); + } + + 80% { + -webkit-transform: rotate3d(0, 0, 1, -5deg); + transform: rotate3d(0, 0, 1, -5deg); + } + + to { + -webkit-transform: rotate3d(0, 0, 1, 0deg); + transform: rotate3d(0, 0, 1, 0deg); + } +} + +.swing { + -webkit-transform-origin: top center; + transform-origin: top center; + -webkit-animation-name: swing; + animation-name: swing; +} + +@-webkit-keyframes tada { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 10%, + 20% { + -webkit-transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); + transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); + } + + 30%, + 50%, + 70%, + 90% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 40%, + 60%, + 80% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes tada { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 10%, + 20% { + -webkit-transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); + transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); + } + + 30%, + 50%, + 70%, + 90% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 40%, + 60%, + 80% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.tada { + -webkit-animation-name: tada; + animation-name: tada; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes wobble { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 15% { + -webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + } + + 30% { + -webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + } + + 45% { + -webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + } + + 60% { + -webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + } + + 75% { + -webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes wobble { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 15% { + -webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + } + + 30% { + -webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + } + + 45% { + -webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + } + + 60% { + -webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + } + + 75% { + -webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.wobble { + -webkit-animation-name: wobble; + animation-name: wobble; +} + +@-webkit-keyframes jello { + from, + 11.1%, + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 22.2% { + -webkit-transform: skewX(-12.5deg) skewY(-12.5deg); + transform: skewX(-12.5deg) skewY(-12.5deg); + } + + 33.3% { + -webkit-transform: skewX(6.25deg) skewY(6.25deg); + transform: skewX(6.25deg) skewY(6.25deg); + } + + 44.4% { + -webkit-transform: skewX(-3.125deg) skewY(-3.125deg); + transform: skewX(-3.125deg) skewY(-3.125deg); + } + + 55.5% { + -webkit-transform: skewX(1.5625deg) skewY(1.5625deg); + transform: skewX(1.5625deg) skewY(1.5625deg); + } + + 66.6% { + -webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg); + transform: skewX(-0.78125deg) skewY(-0.78125deg); + } + + 77.7% { + -webkit-transform: skewX(0.390625deg) skewY(0.390625deg); + transform: skewX(0.390625deg) skewY(0.390625deg); + } + + 88.8% { + -webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg); + transform: skewX(-0.1953125deg) skewY(-0.1953125deg); + } +} + +@keyframes jello { + from, + 11.1%, + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 22.2% { + -webkit-transform: skewX(-12.5deg) skewY(-12.5deg); + transform: skewX(-12.5deg) skewY(-12.5deg); + } + + 33.3% { + -webkit-transform: skewX(6.25deg) skewY(6.25deg); + transform: skewX(6.25deg) skewY(6.25deg); + } + + 44.4% { + -webkit-transform: skewX(-3.125deg) skewY(-3.125deg); + transform: skewX(-3.125deg) skewY(-3.125deg); + } + + 55.5% { + -webkit-transform: skewX(1.5625deg) skewY(1.5625deg); + transform: skewX(1.5625deg) skewY(1.5625deg); + } + + 66.6% { + -webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg); + transform: skewX(-0.78125deg) skewY(-0.78125deg); + } + + 77.7% { + -webkit-transform: skewX(0.390625deg) skewY(0.390625deg); + transform: skewX(0.390625deg) skewY(0.390625deg); + } + + 88.8% { + -webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg); + transform: skewX(-0.1953125deg) skewY(-0.1953125deg); + } +} + +.jello { + -webkit-animation-name: jello; + animation-name: jello; + -webkit-transform-origin: center; + transform-origin: center; +} + +@-webkit-keyframes heartBeat { + 0% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 14% { + -webkit-transform: scale(1.3); + transform: scale(1.3); + } + + 28% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 42% { + -webkit-transform: scale(1.3); + transform: scale(1.3); + } + + 70% { + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@keyframes heartBeat { + 0% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 14% { + -webkit-transform: scale(1.3); + transform: scale(1.3); + } + + 28% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 42% { + -webkit-transform: scale(1.3); + transform: scale(1.3); + } + + 70% { + -webkit-transform: scale(1); + transform: scale(1); + } +} + +.heartBeat { + -webkit-animation-name: heartBeat; + animation-name: heartBeat; + -webkit-animation-duration: 1.3s; + animation-duration: 1.3s; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; +} + +@-webkit-keyframes bounceIn { + from, + 20%, + 40%, + 60%, + 80%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + 0% { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + transform: scale3d(0.3, 0.3, 0.3); + } + + 20% { + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + 40% { + -webkit-transform: scale3d(0.9, 0.9, 0.9); + transform: scale3d(0.9, 0.9, 0.9); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(1.03, 1.03, 1.03); + transform: scale3d(1.03, 1.03, 1.03); + } + + 80% { + -webkit-transform: scale3d(0.97, 0.97, 0.97); + transform: scale3d(0.97, 0.97, 0.97); + } + + to { + opacity: 1; + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes bounceIn { + from, + 20%, + 40%, + 60%, + 80%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + 0% { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + transform: scale3d(0.3, 0.3, 0.3); + } + + 20% { + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + 40% { + -webkit-transform: scale3d(0.9, 0.9, 0.9); + transform: scale3d(0.9, 0.9, 0.9); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(1.03, 1.03, 1.03); + transform: scale3d(1.03, 1.03, 1.03); + } + + 80% { + -webkit-transform: scale3d(0.97, 0.97, 0.97); + transform: scale3d(0.97, 0.97, 0.97); + } + + to { + opacity: 1; + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.bounceIn { + -webkit-animation-duration: 0.75s; + animation-duration: 0.75s; + -webkit-animation-name: bounceIn; + animation-name: bounceIn; +} + +@-webkit-keyframes bounceInDown { + from, + 60%, + 75%, + 90%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -3000px, 0); + transform: translate3d(0, -3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, 25px, 0); + transform: translate3d(0, 25px, 0); + } + + 75% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, 5px, 0); + transform: translate3d(0, 5px, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes bounceInDown { + from, + 60%, + 75%, + 90%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -3000px, 0); + transform: translate3d(0, -3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, 25px, 0); + transform: translate3d(0, 25px, 0); + } + + 75% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, 5px, 0); + transform: translate3d(0, 5px, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.bounceInDown { + -webkit-animation-name: bounceInDown; + animation-name: bounceInDown; +} + +@-webkit-keyframes bounceInLeft { + from, + 60%, + 75%, + 90%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(-3000px, 0, 0); + transform: translate3d(-3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(25px, 0, 0); + transform: translate3d(25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(5px, 0, 0); + transform: translate3d(5px, 0, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes bounceInLeft { + from, + 60%, + 75%, + 90%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(-3000px, 0, 0); + transform: translate3d(-3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(25px, 0, 0); + transform: translate3d(25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(5px, 0, 0); + transform: translate3d(5px, 0, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.bounceInLeft { + -webkit-animation-name: bounceInLeft; + animation-name: bounceInLeft; +} + +@-webkit-keyframes bounceInRight { + from, + 60%, + 75%, + 90%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + from { + opacity: 0; + -webkit-transform: translate3d(3000px, 0, 0); + transform: translate3d(3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(-25px, 0, 0); + transform: translate3d(-25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(-5px, 0, 0); + transform: translate3d(-5px, 0, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes bounceInRight { + from, + 60%, + 75%, + 90%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + from { + opacity: 0; + -webkit-transform: translate3d(3000px, 0, 0); + transform: translate3d(3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(-25px, 0, 0); + transform: translate3d(-25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(-5px, 0, 0); + transform: translate3d(-5px, 0, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.bounceInRight { + -webkit-animation-name: bounceInRight; + animation-name: bounceInRight; +} + +@-webkit-keyframes bounceInUp { + from, + 60%, + 75%, + 90%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + from { + opacity: 0; + -webkit-transform: translate3d(0, 3000px, 0); + transform: translate3d(0, 3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + 75% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, -5px, 0); + transform: translate3d(0, -5px, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes bounceInUp { + from, + 60%, + 75%, + 90%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + from { + opacity: 0; + -webkit-transform: translate3d(0, 3000px, 0); + transform: translate3d(0, 3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + 75% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, -5px, 0); + transform: translate3d(0, -5px, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.bounceInUp { + -webkit-animation-name: bounceInUp; + animation-name: bounceInUp; +} + +@-webkit-keyframes bounceOut { + 20% { + -webkit-transform: scale3d(0.9, 0.9, 0.9); + transform: scale3d(0.9, 0.9, 0.9); + } + + 50%, + 55% { + opacity: 1; + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + to { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + transform: scale3d(0.3, 0.3, 0.3); + } +} + +@keyframes bounceOut { + 20% { + -webkit-transform: scale3d(0.9, 0.9, 0.9); + transform: scale3d(0.9, 0.9, 0.9); + } + + 50%, + 55% { + opacity: 1; + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + to { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + transform: scale3d(0.3, 0.3, 0.3); + } +} + +.bounceOut { + -webkit-animation-duration: 0.75s; + animation-duration: 0.75s; + -webkit-animation-name: bounceOut; + animation-name: bounceOut; +} + +@-webkit-keyframes bounceOutDown { + 20% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 40%, + 45% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +@keyframes bounceOutDown { + 20% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 40%, + 45% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +.bounceOutDown { + -webkit-animation-name: bounceOutDown; + animation-name: bounceOutDown; +} + +@-webkit-keyframes bounceOutLeft { + 20% { + opacity: 1; + -webkit-transform: translate3d(20px, 0, 0); + transform: translate3d(20px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +@keyframes bounceOutLeft { + 20% { + opacity: 1; + -webkit-transform: translate3d(20px, 0, 0); + transform: translate3d(20px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +.bounceOutLeft { + -webkit-animation-name: bounceOutLeft; + animation-name: bounceOutLeft; +} + +@-webkit-keyframes bounceOutRight { + 20% { + opacity: 1; + -webkit-transform: translate3d(-20px, 0, 0); + transform: translate3d(-20px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +@keyframes bounceOutRight { + 20% { + opacity: 1; + -webkit-transform: translate3d(-20px, 0, 0); + transform: translate3d(-20px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +.bounceOutRight { + -webkit-animation-name: bounceOutRight; + animation-name: bounceOutRight; +} + +@-webkit-keyframes bounceOutUp { + 20% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 40%, + 45% { + opacity: 1; + -webkit-transform: translate3d(0, 20px, 0); + transform: translate3d(0, 20px, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +@keyframes bounceOutUp { + 20% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 40%, + 45% { + opacity: 1; + -webkit-transform: translate3d(0, 20px, 0); + transform: translate3d(0, 20px, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +.bounceOutUp { + -webkit-animation-name: bounceOutUp; + animation-name: bounceOutUp; +} + +@-webkit-keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +.fadeIn { + -webkit-animation-name: fadeIn; + animation-name: fadeIn; +} + +@-webkit-keyframes fadeInDown { + from { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes fadeInDown { + from { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.fadeInDown { + -webkit-animation-name: fadeInDown; + animation-name: fadeInDown; +} + +@-webkit-keyframes fadeInDownBig { + from { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes fadeInDownBig { + from { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.fadeInDownBig { + -webkit-animation-name: fadeInDownBig; + animation-name: fadeInDownBig; +} + +@-webkit-keyframes fadeInLeft { + from { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes fadeInLeft { + from { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.fadeInLeft { + -webkit-animation-name: fadeInLeft; + animation-name: fadeInLeft; +} + +@-webkit-keyframes fadeInLeftBig { + from { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes fadeInLeftBig { + from { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.fadeInLeftBig { + -webkit-animation-name: fadeInLeftBig; + animation-name: fadeInLeftBig; +} + +@-webkit-keyframes fadeInRight { + from { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes fadeInRight { + from { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.fadeInRight { + -webkit-animation-name: fadeInRight; + animation-name: fadeInRight; +} + +@-webkit-keyframes fadeInRightBig { + from { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes fadeInRightBig { + from { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.fadeInRightBig { + -webkit-animation-name: fadeInRightBig; + animation-name: fadeInRightBig; +} + +@-webkit-keyframes fadeInUp { + from { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes fadeInUp { + from { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.fadeInUp { + -webkit-animation-name: fadeInUp; + animation-name: fadeInUp; +} + +@-webkit-keyframes fadeInUpBig { + from { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes fadeInUpBig { + from { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.fadeInUpBig { + -webkit-animation-name: fadeInUpBig; + animation-name: fadeInUpBig; +} + +@-webkit-keyframes fadeOut { + from { + opacity: 1; + } + + to { + opacity: 0; + } +} + +@keyframes fadeOut { + from { + opacity: 1; + } + + to { + opacity: 0; + } +} + +.fadeOut { + -webkit-animation-name: fadeOut; + animation-name: fadeOut; +} + +@-webkit-keyframes fadeOutDown { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +@keyframes fadeOutDown { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +.fadeOutDown { + -webkit-animation-name: fadeOutDown; + animation-name: fadeOutDown; +} + +@-webkit-keyframes fadeOutDownBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +@keyframes fadeOutDownBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +.fadeOutDownBig { + -webkit-animation-name: fadeOutDownBig; + animation-name: fadeOutDownBig; +} + +@-webkit-keyframes fadeOutLeft { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +@keyframes fadeOutLeft { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +.fadeOutLeft { + -webkit-animation-name: fadeOutLeft; + animation-name: fadeOutLeft; +} + +@-webkit-keyframes fadeOutLeftBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +@keyframes fadeOutLeftBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +.fadeOutLeftBig { + -webkit-animation-name: fadeOutLeftBig; + animation-name: fadeOutLeftBig; +} + +@-webkit-keyframes fadeOutRight { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +@keyframes fadeOutRight { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +.fadeOutRight { + -webkit-animation-name: fadeOutRight; + animation-name: fadeOutRight; +} + +@-webkit-keyframes fadeOutRightBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +@keyframes fadeOutRightBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +.fadeOutRightBig { + -webkit-animation-name: fadeOutRightBig; + animation-name: fadeOutRightBig; +} + +@-webkit-keyframes fadeOutUp { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +@keyframes fadeOutUp { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +.fadeOutUp { + -webkit-animation-name: fadeOutUp; + animation-name: fadeOutUp; +} + +@-webkit-keyframes fadeOutUpBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +@keyframes fadeOutUpBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +.fadeOutUpBig { + -webkit-animation-name: fadeOutUpBig; + animation-name: fadeOutUpBig; +} + +@-webkit-keyframes flip { + from { + -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) + rotate3d(0, 1, 0, -360deg); + transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, -360deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 40% { + -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) + rotate3d(0, 1, 0, -190deg); + transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) + rotate3d(0, 1, 0, -190deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 50% { + -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) + rotate3d(0, 1, 0, -170deg); + transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) + rotate3d(0, 1, 0, -170deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 80% { + -webkit-transform: perspective(400px) scale3d(0.95, 0.95, 0.95) translate3d(0, 0, 0) + rotate3d(0, 1, 0, 0deg); + transform: perspective(400px) scale3d(0.95, 0.95, 0.95) translate3d(0, 0, 0) + rotate3d(0, 1, 0, 0deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + to { + -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) + rotate3d(0, 1, 0, 0deg); + transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } +} + +@keyframes flip { + from { + -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) + rotate3d(0, 1, 0, -360deg); + transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, -360deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 40% { + -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) + rotate3d(0, 1, 0, -190deg); + transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) + rotate3d(0, 1, 0, -190deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 50% { + -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) + rotate3d(0, 1, 0, -170deg); + transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) + rotate3d(0, 1, 0, -170deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 80% { + -webkit-transform: perspective(400px) scale3d(0.95, 0.95, 0.95) translate3d(0, 0, 0) + rotate3d(0, 1, 0, 0deg); + transform: perspective(400px) scale3d(0.95, 0.95, 0.95) translate3d(0, 0, 0) + rotate3d(0, 1, 0, 0deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + to { + -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) + rotate3d(0, 1, 0, 0deg); + transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } +} + +.animated.flip { + -webkit-backface-visibility: visible; + backface-visibility: visible; + -webkit-animation-name: flip; + animation-name: flip; +} + +@-webkit-keyframes flipInX { + from { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +@keyframes flipInX { + from { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +.flipInX { + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipInX; + animation-name: flipInX; +} + +@-webkit-keyframes flipInY { + from { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +@keyframes flipInY { + from { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +.flipInY { + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipInY; + animation-name: flipInY; +} + +@-webkit-keyframes flipOutX { + from { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + opacity: 1; + } + + to { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + opacity: 0; + } +} + +@keyframes flipOutX { + from { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + opacity: 1; + } + + to { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + opacity: 0; + } +} + +.flipOutX { + -webkit-animation-duration: 0.75s; + animation-duration: 0.75s; + -webkit-animation-name: flipOutX; + animation-name: flipOutX; + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; +} + +@-webkit-keyframes flipOutY { + from { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + opacity: 1; + } + + to { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + opacity: 0; + } +} + +@keyframes flipOutY { + from { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + opacity: 1; + } + + to { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + opacity: 0; + } +} + +.flipOutY { + -webkit-animation-duration: 0.75s; + animation-duration: 0.75s; + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipOutY; + animation-name: flipOutY; +} + +@-webkit-keyframes lightSpeedIn { + from { + -webkit-transform: translate3d(100%, 0, 0) skewX(-30deg); + transform: translate3d(100%, 0, 0) skewX(-30deg); + opacity: 0; + } + + 60% { + -webkit-transform: skewX(20deg); + transform: skewX(20deg); + opacity: 1; + } + + 80% { + -webkit-transform: skewX(-5deg); + transform: skewX(-5deg); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes lightSpeedIn { + from { + -webkit-transform: translate3d(100%, 0, 0) skewX(-30deg); + transform: translate3d(100%, 0, 0) skewX(-30deg); + opacity: 0; + } + + 60% { + -webkit-transform: skewX(20deg); + transform: skewX(20deg); + opacity: 1; + } + + 80% { + -webkit-transform: skewX(-5deg); + transform: skewX(-5deg); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.lightSpeedIn { + -webkit-animation-name: lightSpeedIn; + animation-name: lightSpeedIn; + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; +} + +@-webkit-keyframes lightSpeedOut { + from { + opacity: 1; + } + + to { + -webkit-transform: translate3d(100%, 0, 0) skewX(30deg); + transform: translate3d(100%, 0, 0) skewX(30deg); + opacity: 0; + } +} + +@keyframes lightSpeedOut { + from { + opacity: 1; + } + + to { + -webkit-transform: translate3d(100%, 0, 0) skewX(30deg); + transform: translate3d(100%, 0, 0) skewX(30deg); + opacity: 0; + } +} + +.lightSpeedOut { + -webkit-animation-name: lightSpeedOut; + animation-name: lightSpeedOut; + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; +} + +@-webkit-keyframes rotateIn { + from { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, -200deg); + transform: rotate3d(0, 0, 1, -200deg); + opacity: 0; + } + + to { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +@keyframes rotateIn { + from { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, -200deg); + transform: rotate3d(0, 0, 1, -200deg); + opacity: 0; + } + + to { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +.rotateIn { + -webkit-animation-name: rotateIn; + animation-name: rotateIn; +} + +@-webkit-keyframes rotateInDownLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +@keyframes rotateInDownLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +.rotateInDownLeft { + -webkit-animation-name: rotateInDownLeft; + animation-name: rotateInDownLeft; +} + +@-webkit-keyframes rotateInDownRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +@keyframes rotateInDownRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +.rotateInDownRight { + -webkit-animation-name: rotateInDownRight; + animation-name: rotateInDownRight; +} + +@-webkit-keyframes rotateInUpLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +@keyframes rotateInUpLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +.rotateInUpLeft { + -webkit-animation-name: rotateInUpLeft; + animation-name: rotateInUpLeft; +} + +@-webkit-keyframes rotateInUpRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -90deg); + transform: rotate3d(0, 0, 1, -90deg); + opacity: 0; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +@keyframes rotateInUpRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -90deg); + transform: rotate3d(0, 0, 1, -90deg); + opacity: 0; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +.rotateInUpRight { + -webkit-animation-name: rotateInUpRight; + animation-name: rotateInUpRight; +} + +@-webkit-keyframes rotateOut { + from { + -webkit-transform-origin: center; + transform-origin: center; + opacity: 1; + } + + to { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, 200deg); + transform: rotate3d(0, 0, 1, 200deg); + opacity: 0; + } +} + +@keyframes rotateOut { + from { + -webkit-transform-origin: center; + transform-origin: center; + opacity: 1; + } + + to { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, 200deg); + transform: rotate3d(0, 0, 1, 200deg); + opacity: 0; + } +} + +.rotateOut { + -webkit-animation-name: rotateOut; + animation-name: rotateOut; +} + +@-webkit-keyframes rotateOutDownLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } +} + +@keyframes rotateOutDownLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } +} + +.rotateOutDownLeft { + -webkit-animation-name: rotateOutDownLeft; + animation-name: rotateOutDownLeft; +} + +@-webkit-keyframes rotateOutDownRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +@keyframes rotateOutDownRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +.rotateOutDownRight { + -webkit-animation-name: rotateOutDownRight; + animation-name: rotateOutDownRight; +} + +@-webkit-keyframes rotateOutUpLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +@keyframes rotateOutUpLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +.rotateOutUpLeft { + -webkit-animation-name: rotateOutUpLeft; + animation-name: rotateOutUpLeft; +} + +@-webkit-keyframes rotateOutUpRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 90deg); + transform: rotate3d(0, 0, 1, 90deg); + opacity: 0; + } +} + +@keyframes rotateOutUpRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 90deg); + transform: rotate3d(0, 0, 1, 90deg); + opacity: 0; + } +} + +.rotateOutUpRight { + -webkit-animation-name: rotateOutUpRight; + animation-name: rotateOutUpRight; +} + +@-webkit-keyframes hinge { + 0% { + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 20%, + 60% { + -webkit-transform: rotate3d(0, 0, 1, 80deg); + transform: rotate3d(0, 0, 1, 80deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 40%, + 80% { + -webkit-transform: rotate3d(0, 0, 1, 60deg); + transform: rotate3d(0, 0, 1, 60deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + opacity: 1; + } + + to { + -webkit-transform: translate3d(0, 700px, 0); + transform: translate3d(0, 700px, 0); + opacity: 0; + } +} + +@keyframes hinge { + 0% { + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 20%, + 60% { + -webkit-transform: rotate3d(0, 0, 1, 80deg); + transform: rotate3d(0, 0, 1, 80deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 40%, + 80% { + -webkit-transform: rotate3d(0, 0, 1, 60deg); + transform: rotate3d(0, 0, 1, 60deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + opacity: 1; + } + + to { + -webkit-transform: translate3d(0, 700px, 0); + transform: translate3d(0, 700px, 0); + opacity: 0; + } +} + +.hinge { + -webkit-animation-duration: 2s; + animation-duration: 2s; + -webkit-animation-name: hinge; + animation-name: hinge; +} + +@-webkit-keyframes jackInTheBox { + from { + opacity: 0; + -webkit-transform: scale(0.1) rotate(30deg); + transform: scale(0.1) rotate(30deg); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + } + + 50% { + -webkit-transform: rotate(-10deg); + transform: rotate(-10deg); + } + + 70% { + -webkit-transform: rotate(3deg); + transform: rotate(3deg); + } + + to { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@keyframes jackInTheBox { + from { + opacity: 0; + -webkit-transform: scale(0.1) rotate(30deg); + transform: scale(0.1) rotate(30deg); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + } + + 50% { + -webkit-transform: rotate(-10deg); + transform: rotate(-10deg); + } + + 70% { + -webkit-transform: rotate(3deg); + transform: rotate(3deg); + } + + to { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } +} + +.jackInTheBox { + -webkit-animation-name: jackInTheBox; + animation-name: jackInTheBox; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes rollIn { + from { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes rollIn { + from { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.rollIn { + -webkit-animation-name: rollIn; + animation-name: rollIn; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes rollOut { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + } +} + +@keyframes rollOut { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + } +} + +.rollOut { + -webkit-animation-name: rollOut; + animation-name: rollOut; +} + +@-webkit-keyframes zoomIn { + from { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + transform: scale3d(0.3, 0.3, 0.3); + } + + 50% { + opacity: 1; + } +} + +@keyframes zoomIn { + from { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + transform: scale3d(0.3, 0.3, 0.3); + } + + 50% { + opacity: 1; + } +} + +.zoomIn { + -webkit-animation-name: zoomIn; + animation-name: zoomIn; +} + +@-webkit-keyframes zoomInDown { + from { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +@keyframes zoomInDown { + from { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +.zoomInDown { + -webkit-animation-name: zoomInDown; + animation-name: zoomInDown; +} + +@-webkit-keyframes zoomInLeft { + from { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +@keyframes zoomInLeft { + from { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +.zoomInLeft { + -webkit-animation-name: zoomInLeft; + animation-name: zoomInLeft; +} + +@-webkit-keyframes zoomInRight { + from { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +@keyframes zoomInRight { + from { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +.zoomInRight { + -webkit-animation-name: zoomInRight; + animation-name: zoomInRight; +} + +@-webkit-keyframes zoomInUp { + from { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +@keyframes zoomInUp { + from { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +.zoomInUp { + -webkit-animation-name: zoomInUp; + animation-name: zoomInUp; +} + +@-webkit-keyframes zoomOut { + from { + opacity: 1; + } + + 50% { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + transform: scale3d(0.3, 0.3, 0.3); + } + + to { + opacity: 0; + } +} + +@keyframes zoomOut { + from { + opacity: 1; + } + + 50% { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + transform: scale3d(0.3, 0.3, 0.3); + } + + to { + opacity: 0; + } +} + +.zoomOut { + -webkit-animation-name: zoomOut; + animation-name: zoomOut; +} + +@-webkit-keyframes zoomOutDown { + 40% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + to { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +@keyframes zoomOutDown { + 40% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + to { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +.zoomOutDown { + -webkit-animation-name: zoomOutDown; + animation-name: zoomOutDown; +} + +@-webkit-keyframes zoomOutLeft { + 40% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: scale(0.1) translate3d(-2000px, 0, 0); + transform: scale(0.1) translate3d(-2000px, 0, 0); + -webkit-transform-origin: left center; + transform-origin: left center; + } +} + +@keyframes zoomOutLeft { + 40% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: scale(0.1) translate3d(-2000px, 0, 0); + transform: scale(0.1) translate3d(-2000px, 0, 0); + -webkit-transform-origin: left center; + transform-origin: left center; + } +} + +.zoomOutLeft { + -webkit-animation-name: zoomOutLeft; + animation-name: zoomOutLeft; +} + +@-webkit-keyframes zoomOutRight { + 40% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: scale(0.1) translate3d(2000px, 0, 0); + transform: scale(0.1) translate3d(2000px, 0, 0); + -webkit-transform-origin: right center; + transform-origin: right center; + } +} + +@keyframes zoomOutRight { + 40% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: scale(0.1) translate3d(2000px, 0, 0); + transform: scale(0.1) translate3d(2000px, 0, 0); + -webkit-transform-origin: right center; + transform-origin: right center; + } +} + +.zoomOutRight { + -webkit-animation-name: zoomOutRight; + animation-name: zoomOutRight; +} + +@-webkit-keyframes zoomOutUp { + 40% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + to { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +@keyframes zoomOutUp { + 40% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + to { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +.zoomOutUp { + -webkit-animation-name: zoomOutUp; + animation-name: zoomOutUp; +} + +@-webkit-keyframes slideInDown { + from { + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInDown { + from { + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slideInDown { + -webkit-animation-name: slideInDown; + animation-name: slideInDown; +} + +@-webkit-keyframes slideInLeft { + from { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInLeft { + from { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slideInLeft { + -webkit-animation-name: slideInLeft; + animation-name: slideInLeft; +} + +@-webkit-keyframes slideInRight { + from { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInRight { + from { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slideInRight { + -webkit-animation-name: slideInRight; + animation-name: slideInRight; +} + +@-webkit-keyframes slideInUp { + from { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInUp { + from { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slideInUp { + -webkit-animation-name: slideInUp; + animation-name: slideInUp; +} + +@-webkit-keyframes slideOutDown { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +@keyframes slideOutDown { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +.slideOutDown { + -webkit-animation-name: slideOutDown; + animation-name: slideOutDown; +} + +@-webkit-keyframes slideOutLeft { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +@keyframes slideOutLeft { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +.slideOutLeft { + -webkit-animation-name: slideOutLeft; + animation-name: slideOutLeft; +} + +@-webkit-keyframes slideOutRight { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +@keyframes slideOutRight { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +.slideOutRight { + -webkit-animation-name: slideOutRight; + animation-name: slideOutRight; +} + +@-webkit-keyframes slideOutUp { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +@keyframes slideOutUp { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +.slideOutUp { + -webkit-animation-name: slideOutUp; + animation-name: slideOutUp; +} + +.animated { + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; +} + +.animated.infinite { + -webkit-animation-iteration-count: infinite; + animation-iteration-count: infinite; +} + +.animated.delay-1s { + -webkit-animation-delay: 1s; + animation-delay: 1s; +} + +.animated.delay-2s { + -webkit-animation-delay: 2s; + animation-delay: 2s; +} + +.animated.delay-3s { + -webkit-animation-delay: 3s; + animation-delay: 3s; +} + +.animated.delay-4s { + -webkit-animation-delay: 4s; + animation-delay: 4s; +} + +.animated.delay-5s { + -webkit-animation-delay: 5s; + animation-delay: 5s; +} + +.animated.fast { + -webkit-animation-duration: 800ms; + animation-duration: 800ms; +} + +.animated.faster { + -webkit-animation-duration: 500ms; + animation-duration: 500ms; +} + +.animated.slow { + -webkit-animation-duration: 2s; + animation-duration: 2s; +} + +.animated.slower { + -webkit-animation-duration: 3s; + animation-duration: 3s; +} + +@media (prefers-reduced-motion) { + .animated { + -webkit-animation: unset !important; + animation: unset !important; + -webkit-transition: none !important; + transition: none !important; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\base-theme.scss + +// Import all the theming functionality. +// We can use relative imports for imports from the cdk because we bundle everything +// up into a single flat scss file for material. +// We want overlays to always appear over user content, so set a baseline +// very high z-index for the overlay container, which is where we create the new +// stacking context for all overlays. +$cdk-z-index-overlay-container: 1000 !default; +$cdk-z-index-overlay: 1000 !default; +$cdk-z-index-overlay-backdrop: 1000 !default; + +// Background color for all of the backdrops +$cdk-overlay-dark-backdrop-background: rgba(0, 0, 0, 0.32) !default; + +// Default backdrop animation is based on the Material Design swift-ease-out. +$backdrop-animation-duration: 400ms !default; +$backdrop-animation-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default; + + +@mixin cdk-overlay() { + .cdk-overlay-container, .cdk-global-overlay-wrapper { + // Disable events from being captured on the overlay container. + pointer-events: none; + + // The container should be the size of the viewport. + top: 0; + left: 0; + height: 100%; + width: 100%; + } + + // The overlay-container is an invisible element which contains all individual overlays. + .cdk-overlay-container { + position: fixed; + z-index: $cdk-z-index-overlay-container; + + &:empty { + // Hide the element when it doesn't have any child nodes. This doesn't + // include overlays that have been detached, rather than disposed. + display: none; + } + } + + // We use an extra wrapper element in order to use make the overlay itself a flex item. + // This makes centering the overlay easy without running into the subpixel rendering + // problems tied to using `transform` and without interfering with the other position + // strategies. + .cdk-global-overlay-wrapper { + display: flex; + position: absolute; + z-index: $cdk-z-index-overlay; + } + + // A single overlay pane. + .cdk-overlay-pane { + // Note: it's important for this one to start off `absolute`, + // in order for us to be able to measure it correctly. + position: absolute; + pointer-events: auto; + box-sizing: border-box; + z-index: $cdk-z-index-overlay; + + // For connected-position overlays, we set `display: flex` in + // order to force `max-width` and `max-height` to take effect. + display: flex; + max-width: 100%; + max-height: 100%; + } + + .cdk-overlay-backdrop { + // TODO(jelbourn): reuse sidenav fullscreen mixin. + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + + z-index: $cdk-z-index-overlay-backdrop; + pointer-events: auto; + -webkit-tap-highlight-color: transparent; + transition: opacity $backdrop-animation-duration $backdrop-animation-timing-function; + opacity: 0; + + &.cdk-overlay-backdrop-showing { + opacity: 1; + + // In high contrast mode the rgba background will become solid so we need to fall back + // to making it opaque using `opacity`. Note that we can't use the `cdk-high-contrast` + // mixin, because we can't normalize the import path to the _a11y.scss both for the + // source and when this file is distributed. See #10908. + @media screen and (-ms-high-contrast: active) { + opacity: 0.6; + } + } + } + + .cdk-overlay-dark-backdrop { + background: $cdk-overlay-dark-backdrop-background; + } + + .cdk-overlay-transparent-backdrop { + // Note: as of Firefox 57, having the backdrop be `background: none` will prevent it from + // capturing the user's mouse scroll events. Since we also can't use something like + // `rgba(0, 0, 0, 0)`, we work around the inconsistency by not setting the background at + // all and using `opacity` to make the element transparent. + &, &.cdk-overlay-backdrop-showing { + opacity: 0; + } + } + + // Overlay parent element used with the connected position strategy. Used to constrain the + // overlay element's size to fit within the viewport. + .cdk-overlay-connected-position-bounding-box { + position: absolute; + z-index: $cdk-z-index-overlay; + + // We use `display: flex` on this element exclusively for centering connected overlays. + // When *not* centering, a top/left/bottom/right will be set which overrides the normal + // flex layout. + display: flex; + + // We use the `column` direction here to avoid some flexbox issues in Edge + // when using the "grow after open" options. + flex-direction: column; + + // Add some dimensions so the element has an `innerText` which some people depend on in tests. + min-width: 1px; + min-height: 1px; + } + + // Used when disabling global scrolling. + .cdk-global-scrollblock { + position: fixed; + + // Necessary for the content not to lose its width. Note that we're using 100%, instead of + // 100vw, because 100vw includes the width plus the scrollbar, whereas 100% is the width + // that the element had before we made it `fixed`. + width: 100%; + + // Note: this will always add a scrollbar to whatever element it is on, which can + // potentially result in double scrollbars. It shouldn't be an issue, because we won't + // block scrolling on a page that doesn't have a scrollbar in the first place. + overflow-y: scroll; + } +} + +@mixin cdk-a11y { + .cdk-visually-hidden { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + + // Avoid browsers rendering the focus ring in some cases. + outline: 0; + + // Avoid some cases where the browser will still render the native controls (see #9049). + -webkit-appearance: none; + -moz-appearance: none; + } +} + +// Applies styles for users in high contrast mode. Note that this only applies +// to Microsoft browsers. Chrome can be included by checking for the `html[hc]` +// attribute, however Chrome handles high contrast differently. +// +// @param target Which kind of high contrast setting to target. Defaults to `active`, can be +// `white-on-black` or `black-on-white`. +@mixin cdk-high-contrast($target: active) { + @media (-ms-high-contrast: $target) { + @content; + } +} + +// Core styles that enable monitoring autofill state of text fields. +@mixin cdk-text-field { + // Keyframes that apply no styles, but allow us to monitor when an text field becomes autofilled + // by watching for the animation events that are fired when they start. Note: the /*!*/ comment is + // needed to prevent LibSass from stripping the keyframes out. + // Based on: https://medium.com/@brunn/detecting-autofilled-fields-in-javascript-aed598d25da7 + @keyframes cdk-text-field-autofill-start {/*!*/} + @keyframes cdk-text-field-autofill-end {/*!*/} + + .cdk-text-field-autofill-monitored:-webkit-autofill { + animation-name: cdk-text-field-autofill-start; + } + + .cdk-text-field-autofill-monitored:not(:-webkit-autofill) { + animation-name: cdk-text-field-autofill-end; + } + + // Remove the resize handle on autosizing textareas, because whatever height + // the user resized to will be overwritten once they start typing again. + textarea.cdk-textarea-autosize { + resize: none; + } + + // This class is temporarily applied to the textarea when it is being measured. It is immediately + // removed when measuring is complete. We use `!important` rules here to make sure user-specified + // rules do not interfere with the measurement. + textarea.cdk-textarea-autosize-measuring { + height: auto !important; + overflow: hidden !important; + // Having 2px top and bottom padding seems to fix a bug where Chrome gets an incorrect + // measurement. We just have to account for it later and subtract it off the final result. + padding: 2px 0 !important; + box-sizing: content-box !important; + } +} + +// Used to generate UIDs for keyframes used to change the text field autofill styles. +$cdk-text-field-autofill-color-frame-count: 0; + +// Mixin used to apply custom background and foreground colors to an autofilled text field. +// Based on: https://stackoverflow.com/questions/2781549/ +// removing-input-background-colour-for-chrome-autocomplete#answer-37432260 +@mixin cdk-text-field-autofill-color($background, $foreground:'') { + @keyframes cdk-text-field-autofill-color-#{$cdk-text-field-autofill-color-frame-count} { + to { + background: $background; + @if $foreground != '' { color: $foreground; } + } + } + + &:-webkit-autofill { + animation-name: cdk-text-field-autofill-color-#{$cdk-text-field-autofill-color-frame-count}; + animation-fill-mode: both; + } + + &.cdk-text-field-autofill-monitored:-webkit-autofill { + animation-name: cdk-text-field-autofill-start, + cdk-text-field-autofill-color-#{$cdk-text-field-autofill-color-frame-count}; + } + + $cdk-text-field-autofill-color-frame-count: + $cdk-text-field-autofill-color-frame-count + 1 !global; +} + + +// Core styles that can be used to apply material design treatments to any element. +// Media queries +// TODO(josephperrott): Change $mat-xsmall and $mat-small usages to rely on BreakpointObserver, +$mat-xsmall: 'max-width: 599px'; +$mat-small: 'max-width: 959px'; + +// TODO: Revisit all z-indices before beta +// z-index master list + +$z-index-fab: 20 !default; +$z-index-drawer: 100 !default; + +// Global constants +$pi: 3.14159265; + +// Padding between input toggles and their labels +$mat-toggle-padding: 8px !default; +// Width and height of input toggles +$mat-toggle-size: 20px !default; + +// Easing Curves +// TODO(jelbourn): all of these need to be revisited + +// The default animation curves used by material design. +$mat-linear-out-slow-in-timing-function: cubic-bezier(0, 0, 0.2, 0.1) !default; +$mat-fast-out-slow-in-timing-function: cubic-bezier(0.4, 0, 0.2, 1) !default; +$mat-fast-out-linear-in-timing-function: cubic-bezier(0.4, 0, 1, 1) !default; + +$ease-in-out-curve-function: cubic-bezier(0.35, 0, 0.25, 1) !default; + +$swift-ease-out-duration: 400ms !default; +$swift-ease-out-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default; +$swift-ease-out: all $swift-ease-out-duration $swift-ease-out-timing-function !default; + +$swift-ease-in-duration: 300ms !default; +$swift-ease-in-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2) !default; +$swift-ease-in: all $swift-ease-in-duration $swift-ease-in-timing-function !default; + +$swift-ease-in-out-duration: 500ms !default; +$swift-ease-in-out-timing-function: $ease-in-out-curve-function !default; +$swift-ease-in-out: all $swift-ease-in-out-duration $swift-ease-in-out-timing-function !default; + +$swift-linear-duration: 80ms !default; +$swift-linear-timing-function: linear !default; +$swift-linear: all $swift-linear-duration $swift-linear-timing-function !default; + + + +// A collection of mixins and CSS classes that can be used to apply elevation to a material +// element. +// See: https://material.io/design/environment/elevation.html +// Examples: +// +// +// .mat-foo { +// @include $mat-elevation(2); +// +// &:active { +// @include $mat-elevation(8); +// } +// } +// +//

Some content

+// +// For an explanation of the design behind how elevation is implemented, see the design doc at +// https://goo.gl/Kq0k9Z. + +// Colors for umbra, penumbra, and ambient shadows. As described in the design doc, each elevation +// level is created using a set of 3 shadow values, one for umbra (the shadow representing the +// space completely obscured by an object relative to its light source), one for penumbra (the +// space partially obscured by an object), and one for ambient (the space which contains the object +// itself). For a further explanation of these terms and their meanings, see +// https://en.wikipedia.org/wiki/Umbra,_penumbra_and_antumbra. + +// Maps for the different shadow sets and their values within each z-space. These values were +// created by taking a few reference shadow sets created by Google's Designers and interpolating +// all of the values between them. + +@function _get-umbra-map($color, $opacity) { + $shadow-color: if(type-of($color) == color, rgba($color, $opacity * 0.2), $color); + + @return ( + 0: '0px 0px 0px 0px #{$shadow-color}', + 1: '0px 2px 1px -1px #{$shadow-color}', + 2: '0px 3px 1px -2px #{$shadow-color}', + 3: '0px 3px 3px -2px #{$shadow-color}', + 4: '0px 2px 4px -1px #{$shadow-color}', + 5: '0px 3px 5px -1px #{$shadow-color}', + 6: '0px 3px 5px -1px #{$shadow-color}', + 7: '0px 4px 5px -2px #{$shadow-color}', + 8: '0px 5px 5px -3px #{$shadow-color}', + 9: '0px 5px 6px -3px #{$shadow-color}', + 10: '0px 6px 6px -3px #{$shadow-color}', + 11: '0px 6px 7px -4px #{$shadow-color}', + 12: '0px 7px 8px -4px #{$shadow-color}', + 13: '0px 7px 8px -4px #{$shadow-color}', + 14: '0px 7px 9px -4px #{$shadow-color}', + 15: '0px 8px 9px -5px #{$shadow-color}', + 16: '0px 8px 10px -5px #{$shadow-color}', + 17: '0px 8px 11px -5px #{$shadow-color}', + 18: '0px 9px 11px -5px #{$shadow-color}', + 19: '0px 9px 12px -6px #{$shadow-color}', + 20: '0px 10px 13px -6px #{$shadow-color}', + 21: '0px 10px 13px -6px #{$shadow-color}', + 22: '0px 10px 14px -6px #{$shadow-color}', + 23: '0px 11px 14px -7px #{$shadow-color}', + 24: '0px 11px 15px -7px #{$shadow-color}' + ); +} + +@function _get-penumbra-map($color, $opacity) { + $shadow-color: if(type-of($color) == color, rgba($color, $opacity * 0.14), $color); + + @return ( + 0: '0px 0px 0px 0px #{$shadow-color}', + 1: '0px 1px 1px 0px #{$shadow-color}', + 2: '0px 2px 2px 0px #{$shadow-color}', + 3: '0px 3px 4px 0px #{$shadow-color}', + 4: '0px 4px 5px 0px #{$shadow-color}', + 5: '0px 5px 8px 0px #{$shadow-color}', + 6: '0px 6px 10px 0px #{$shadow-color}', + 7: '0px 7px 10px 1px #{$shadow-color}', + 8: '0px 8px 10px 1px #{$shadow-color}', + 9: '0px 9px 12px 1px #{$shadow-color}', + 10: '0px 10px 14px 1px #{$shadow-color}', + 11: '0px 11px 15px 1px #{$shadow-color}', + 12: '0px 12px 17px 2px #{$shadow-color}', + 13: '0px 13px 19px 2px #{$shadow-color}', + 14: '0px 14px 21px 2px #{$shadow-color}', + 15: '0px 15px 22px 2px #{$shadow-color}', + 16: '0px 16px 24px 2px #{$shadow-color}', + 17: '0px 17px 26px 2px #{$shadow-color}', + 18: '0px 18px 28px 2px #{$shadow-color}', + 19: '0px 19px 29px 2px #{$shadow-color}', + 20: '0px 20px 31px 3px #{$shadow-color}', + 21: '0px 21px 33px 3px #{$shadow-color}', + 22: '0px 22px 35px 3px #{$shadow-color}', + 23: '0px 23px 36px 3px #{$shadow-color}', + 24: '0px 24px 38px 3px #{$shadow-color}' + ); +} + +@function _get-ambient-map($color, $opacity) { + $shadow-color: if(type-of($color) == color, rgba($color, $opacity * 0.12), $color); + + @return ( + 0: '0px 0px 0px 0px #{$shadow-color}', + 1: '0px 1px 3px 0px #{$shadow-color}', + 2: '0px 1px 5px 0px #{$shadow-color}', + 3: '0px 1px 8px 0px #{$shadow-color}', + 4: '0px 1px 10px 0px #{$shadow-color}', + 5: '0px 1px 14px 0px #{$shadow-color}', + 6: '0px 1px 18px 0px #{$shadow-color}', + 7: '0px 2px 16px 1px #{$shadow-color}', + 8: '0px 3px 14px 2px #{$shadow-color}', + 9: '0px 3px 16px 2px #{$shadow-color}', + 10: '0px 4px 18px 3px #{$shadow-color}', + 11: '0px 4px 20px 3px #{$shadow-color}', + 12: '0px 5px 22px 4px #{$shadow-color}', + 13: '0px 5px 24px 4px #{$shadow-color}', + 14: '0px 5px 26px 4px #{$shadow-color}', + 15: '0px 6px 28px 5px #{$shadow-color}', + 16: '0px 6px 30px 5px #{$shadow-color}', + 17: '0px 6px 32px 5px #{$shadow-color}', + 18: '0px 7px 34px 6px #{$shadow-color}', + 19: '0px 7px 36px 6px #{$shadow-color}', + 20: '0px 8px 38px 7px #{$shadow-color}', + 21: '0px 8px 40px 7px #{$shadow-color}', + 22: '0px 8px 42px 7px #{$shadow-color}', + 23: '0px 9px 44px 8px #{$shadow-color}', + 24: '0px 9px 46px 8px #{$shadow-color}' + ); +} + +// The default duration value for elevation transitions. +$mat-elevation-transition-duration: 280ms !default; + +// The default easing value for elevation transitions. +$mat-elevation-transition-timing-function: $mat-fast-out-slow-in-timing-function; + +// The default color for elevation shadows. +$mat-elevation-color: black !default; + +// The default opacity scaling value for elevation shadows. +$mat-elevation-opacity: 1 !default; + +// Prefix for elevation-related selectors. +$_mat-elevation-prefix: 'mat-elevation-z'; + +// Applies the correct css rules to an element to give it the elevation specified by $zValue. +// The $zValue must be between 0 and 24. +@mixin mat-elevation($zValue, $color: $mat-elevation-color, $opacity: $mat-elevation-opacity) { + @if type-of($zValue) != number or not unitless($zValue) { + @error '$zValue must be a unitless number'; + } + @if $zValue < 0 or $zValue > 24 { + @error '$zValue must be between 0 and 24'; + } + + box-shadow: #{map-get(_get-umbra-map($color, $opacity), $zValue)}, + #{map-get(_get-penumbra-map($color, $opacity), $zValue)}, + #{map-get(_get-ambient-map($color, $opacity), $zValue)}; +} + +@mixin _mat-theme-elevation($zValue, $theme, $opacity: $mat-elevation-opacity) { + $foreground: map-get($theme, foreground); + $elevation-color: map-get($foreground, elevation); + $elevation-color-or-default: if($elevation-color == null, $mat-elevation-color, $elevation-color); + + @include mat-elevation($zValue, $elevation-color-or-default, $opacity); +} + +// Applies the elevation to an element in a manner that allows +// consumers to override it via the Material elevation classes. +@mixin mat-overridable-elevation( + $zValue, + $color: $mat-elevation-color, + $opacity: $mat-elevation-opacity) { + &:not([class*='#{$_mat-elevation-prefix}']) { + @include mat-elevation($zValue, $color, $opacity); + } +} + +@mixin _mat-theme-overridable-elevation($zValue, $theme, $opacity: $mat-elevation-opacity) { + $foreground: map-get($theme, foreground); + $elevation-color: map-get($foreground, elevation); + $elevation-color-or-default: if($elevation-color == null, $mat-elevation-color, $elevation-color); + + @include mat-overridable-elevation($zValue, $elevation-color-or-default, $opacity); +} + +// Returns a string that can be used as the value for a transition property for elevation. +// Calling this function directly is useful in situations where a component needs to transition +// more than one property. +// +// .foo { +// transition: mat-elevation-transition-property-value(), opacity 100ms ease; +// } +@function mat-elevation-transition-property-value( + $duration: $mat-elevation-transition-duration, + $easing: $mat-elevation-transition-timing-function) { + @return box-shadow #{$duration} #{$easing}; +} + +// Applies the correct css rules needed to have an element transition between elevations. +// This mixin should be applied to elements whose elevation values will change depending on their +// context (e.g. when active or disabled). +// +// NOTE(traviskaufman): Both this mixin and the above function use default parameters so they can +// be used in the same way by clients. +@mixin mat-elevation-transition( + $duration: $mat-elevation-transition-duration, + $easing: $mat-elevation-transition-timing-function) { + transition: mat-elevation-transition-property-value($duration, $easing); +} + +// Color palettes from the Material Design spec. +// See https://material.io/design/color/ +// +// Contrast colors are hard-coded because it is too difficult (probably impossible) to +// calculate them. These contrast colors are pulled from the public Material Design spec swatches. +// While the contrast colors in the spec are not prescriptive, we use them for convenience. + + +// @deprecated renamed to $dark-primary-text. +// @breaking-change 8.0.0 +$black-87-opacity: rgba(black, 0.87); +// @deprecated renamed to $light-primary-text. +// @breaking-change 8.0.0 +$white-87-opacity: rgba(white, 0.87); +// @deprecated use $dark-[secondary-text,disabled-text,dividers,focused] instead. +// @breaking-change 8.0.0 +$black-12-opacity: rgba(black, 0.12); +// @deprecated use $light-[secondary-text,disabled-text,dividers,focused] instead. +// @breaking-change 8.0.0 +$white-12-opacity: rgba(white, 0.12); +// @deprecated use $dark-[secondary-text,disabled-text,dividers,focused] instead. +// @breaking-change 8.0.0 +$black-6-opacity: rgba(black, 0.06); +// @deprecated use $light-[secondary-text,disabled-text,dividers,focused] instead. +// @breaking-change 8.0.0 +$white-6-opacity: rgba(white, 0.06); + +$dark-primary-text: rgba(black, 0.87); +$dark-secondary-text: rgba(black, 0.54); +$dark-disabled-text: rgba(black, 0.38); +$dark-dividers: rgba(black, 0.12); +$dark-focused: rgba(black, 0.12); +$light-primary-text: white; +$light-secondary-text: rgba(white, 0.7); +$light-disabled-text: rgba(white, 0.5); +$light-dividers: rgba(white, 0.12); +$light-focused: rgba(white, 0.12); + +$mat-red: ( + 50: #ffebee, + 100: #ffcdd2, + 200: #ef9a9a, + 300: #e57373, + 400: #ef5350, + 500: #f44336, + 600: #e53935, + 700: #d32f2f, + 800: #c62828, + 900: #b71c1c, + A100: #ff8a80, + A200: #ff5252, + A400: #ff1744, + A700: #d50000, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $light-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +$mat-pink: ( + 50: #fce4ec, + 100: #f8bbd0, + 200: #f48fb1, + 300: #f06292, + 400: #ec407a, + 500: #e91e63, + 600: #d81b60, + 700: #c2185b, + 800: #ad1457, + 900: #880e4f, + A100: #ff80ab, + A200: #ff4081, + A400: #f50057, + A700: #c51162, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $light-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +$mat-purple: ( + 50: #f3e5f5, + 100: #e1bee7, + 200: #ce93d8, + 300: #ba68c8, + 400: #ab47bc, + 500: #9c27b0, + 600: #8e24aa, + 700: #7b1fa2, + 800: #6a1b9a, + 900: #4a148c, + A100: #ea80fc, + A200: #e040fb, + A400: #d500f9, + A700: #aa00ff, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $light-primary-text, + 400: $light-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $light-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +$mat-deep-purple: ( + 50: #ede7f6, + 100: #d1c4e9, + 200: #b39ddb, + 300: #9575cd, + 400: #7e57c2, + 500: #673ab7, + 600: #5e35b1, + 700: #512da8, + 800: #4527a0, + 900: #311b92, + A100: #b388ff, + A200: #7c4dff, + A400: #651fff, + A700: #6200ea, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $light-primary-text, + 400: $light-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $light-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +$mat-indigo: ( + 50: #e8eaf6, + 100: #c5cae9, + 200: #9fa8da, + 300: #7986cb, + 400: #5c6bc0, + 500: #3f51b5, + 600: #3949ab, + 700: #303f9f, + 800: #283593, + 900: #1a237e, + A100: #8c9eff, + A200: #536dfe, + A400: #3d5afe, + A700: #304ffe, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $light-primary-text, + 400: $light-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $light-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +$mat-blue: ( + 50: #e3f2fd, + 100: #bbdefb, + 200: #90caf9, + 300: #64b5f6, + 400: #42a5f5, + 500: #2196f3, + 600: #1e88e5, + 700: #1976d2, + 800: #1565c0, + 900: #0d47a1, + A100: #82b1ff, + A200: #448aff, + A400: #2979ff, + A700: #2962ff, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $light-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +$mat-light-blue: ( + 50: #e1f5fe, + 100: #b3e5fc, + 200: #81d4fa, + 300: #4fc3f7, + 400: #29b6f6, + 500: #03a9f4, + 600: #039be5, + 700: #0288d1, + 800: #0277bd, + 900: #01579b, + A100: #80d8ff, + A200: #40c4ff, + A400: #00b0ff, + A700: #0091ea, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $light-primary-text, + ) +); + +$mat-cyan: ( + 50: #e0f7fa, + 100: #b2ebf2, + 200: #80deea, + 300: #4dd0e1, + 400: #26c6da, + 500: #00bcd4, + 600: #00acc1, + 700: #0097a7, + 800: #00838f, + 900: #006064, + A100: #84ffff, + A200: #18ffff, + A400: #00e5ff, + A700: #00b8d4, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $dark-primary-text, + ) +); + +$mat-teal: ( + 50: #e0f2f1, + 100: #b2dfdb, + 200: #80cbc4, + 300: #4db6ac, + 400: #26a69a, + 500: #009688, + 600: #00897b, + 700: #00796b, + 800: #00695c, + 900: #004d40, + A100: #a7ffeb, + A200: #64ffda, + A400: #1de9b6, + A700: #00bfa5, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $dark-primary-text, + ) +); + +$mat-green: ( + 50: #e8f5e9, + 100: #c8e6c9, + 200: #a5d6a7, + 300: #81c784, + 400: #66bb6a, + 500: #4caf50, + 600: #43a047, + 700: #388e3c, + 800: #2e7d32, + 900: #1b5e20, + A100: #b9f6ca, + A200: #69f0ae, + A400: #00e676, + A700: #00c853, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $dark-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $dark-primary-text, + ) +); + +$mat-light-green: ( + 50: #f1f8e9, + 100: #dcedc8, + 200: #c5e1a5, + 300: #aed581, + 400: #9ccc65, + 500: #8bc34a, + 600: #7cb342, + 700: #689f38, + 800: #558b2f, + 900: #33691e, + A100: #ccff90, + A200: #b2ff59, + A400: #76ff03, + A700: #64dd17, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $dark-primary-text, + 600: $dark-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $dark-primary-text, + ) +); + +$mat-lime: ( + 50: #f9fbe7, + 100: #f0f4c3, + 200: #e6ee9c, + 300: #dce775, + 400: #d4e157, + 500: #cddc39, + 600: #c0ca33, + 700: #afb42b, + 800: #9e9d24, + 900: #827717, + A100: #f4ff81, + A200: #eeff41, + A400: #c6ff00, + A700: #aeea00, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $dark-primary-text, + 600: $dark-primary-text, + 700: $dark-primary-text, + 800: $dark-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $dark-primary-text, + ) +); + +$mat-yellow: ( + 50: #fffde7, + 100: #fff9c4, + 200: #fff59d, + 300: #fff176, + 400: #ffee58, + 500: #ffeb3b, + 600: #fdd835, + 700: #fbc02d, + 800: #f9a825, + 900: #f57f17, + A100: #ffff8d, + A200: #ffff00, + A400: #ffea00, + A700: #ffd600, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $dark-primary-text, + 600: $dark-primary-text, + 700: $dark-primary-text, + 800: $dark-primary-text, + 900: $dark-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $dark-primary-text, + ) +); + +$mat-amber: ( + 50: #fff8e1, + 100: #ffecb3, + 200: #ffe082, + 300: #ffd54f, + 400: #ffca28, + 500: #ffc107, + 600: #ffb300, + 700: #ffa000, + 800: #ff8f00, + 900: #ff6f00, + A100: #ffe57f, + A200: #ffd740, + A400: #ffc400, + A700: #ffab00, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $dark-primary-text, + 600: $dark-primary-text, + 700: $dark-primary-text, + 800: $dark-primary-text, + 900: $dark-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $dark-primary-text, + ) +); + +$mat-orange: ( + 50: #fff3e0, + 100: #ffe0b2, + 200: #ffcc80, + 300: #ffb74d, + 400: #ffa726, + 500: #ff9800, + 600: #fb8c00, + 700: #f57c00, + 800: #ef6c00, + 900: #e65100, + A100: #ffd180, + A200: #ffab40, + A400: #ff9100, + A700: #ff6d00, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $dark-primary-text, + 600: $dark-primary-text, + 700: $dark-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: black, + ) +); + +$mat-deep-orange: ( + 50: #fbe9e7, + 100: #ffccbc, + 200: #ffab91, + 300: #ff8a65, + 400: #ff7043, + 500: #ff5722, + 600: #f4511e, + 700: #e64a19, + 800: #d84315, + 900: #bf360c, + A100: #ff9e80, + A200: #ff6e40, + A400: #ff3d00, + A700: #dd2c00, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +$mat-brown: ( + 50: #efebe9, + 100: #d7ccc8, + 200: #bcaaa4, + 300: #a1887f, + 400: #8d6e63, + 500: #795548, + 600: #6d4c41, + 700: #5d4037, + 800: #4e342e, + 900: #3e2723, + A100: #d7ccc8, + A200: #bcaaa4, + A400: #8d6e63, + A700: #5d4037, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $light-primary-text, + 400: $light-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +$mat-grey: ( + 50: #fafafa, + 100: #f5f5f5, + 200: #eeeeee, + 300: #e0e0e0, + 400: #bdbdbd, + 500: #9e9e9e, + 600: #757575, + 700: #616161, + 800: #424242, + 900: #212121, + A100: #ffffff, + A200: #eeeeee, + A400: #bdbdbd, + A700: #616161, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $dark-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $light-primary-text, + ) +); + +// Alias for alternate spelling. +$mat-gray: $mat-grey; + +$mat-blue-grey: ( + 50: #eceff1, + 100: #cfd8dc, + 200: #b0bec5, + 300: #90a4ae, + 400: #78909c, + 500: #607d8b, + 600: #546e7a, + 700: #455a64, + 800: #37474f, + 900: #263238, + A100: #cfd8dc, + A200: #b0bec5, + A400: #78909c, + A700: #455a64, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $light-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +// Alias for alternate spelling. +$mat-blue-gray: $mat-blue-grey; + + +// Background palette for light themes. +$mat-light-theme-background: ( + status-bar: map_get($mat-grey, 300), + app-bar: map_get($mat-grey, 100), + background: map_get($mat-grey, 50), + hover: rgba(black, 0.04), // TODO(kara): check style with Material Design UX + card: white, + dialog: white, + disabled-button: rgba(black, 0.12), + raised-button: white, + focused-button: $dark-focused, + selected-button: map_get($mat-grey, 300), + selected-disabled-button: map_get($mat-grey, 400), + disabled-button-toggle: map_get($mat-grey, 200), + unselected-chip: map_get($mat-grey, 300), + disabled-list-option: map_get($mat-grey, 200), +); + +// Background palette for dark themes. +$mat-dark-theme-background: ( + status-bar: black, + app-bar: map_get($mat-grey, 900), + background: #303030, + hover: rgba(white, 0.04), // TODO(kara): check style with Material Design UX + card: map_get($mat-grey, 800), + dialog: map_get($mat-grey, 800), + disabled-button: rgba(white, 0.12), + raised-button: map-get($mat-grey, 800), + focused-button: $light-focused, + selected-button: map_get($mat-grey, 900), + selected-disabled-button: map_get($mat-grey, 800), + disabled-button-toggle: black, + unselected-chip: map_get($mat-grey, 700), + disabled-list-option: black, +); + +// Foreground palette for light themes. +$mat-light-theme-foreground: ( + base: black, + divider: $dark-dividers, + dividers: $dark-dividers, + disabled: $dark-disabled-text, + disabled-button: rgba(black, 0.26), + disabled-text: $dark-disabled-text, + elevation: black, + hint-text: $dark-disabled-text, + secondary-text: $dark-secondary-text, + icon: rgba(black, 0.54), + icons: rgba(black, 0.54), + text: rgba(black, 0.87), + slider-min: rgba(black, 0.87), + slider-off: rgba(black, 0.26), + slider-off-active: rgba(black, 0.38), +); + +// Foreground palette for dark themes. +$mat-dark-theme-foreground: ( + base: white, + divider: $light-dividers, + dividers: $light-dividers, + disabled: $light-disabled-text, + disabled-button: rgba(white, 0.3), + disabled-text: $light-disabled-text, + elevation: black, + hint-text: $light-disabled-text, + secondary-text: $light-secondary-text, + icon: white, + icons: white, + text: white, + slider-min: white, + slider-off: rgba(white, 0.3), + slider-off-active: rgba(white, 0.3), +); + + + +// For a given hue in a palette, return the contrast color from the map of contrast palettes. +// @param $color-map +// @param $hue +@function mat-contrast($palette, $hue) { + @return map-get(map-get($palette, contrast), $hue); +} + + +// Creates a map of hues to colors for a theme. This is used to define a theme palette in terms +// of the Material Design hues. +// @param $color-map +// @param $primary +// @param $lighter +@function mat-palette($base-palette, $default: 500, $lighter: 100, $darker: 700, $text: $default) { + $result: map_merge($base-palette, ( + default: map-get($base-palette, $default), + lighter: map-get($base-palette, $lighter), + darker: map-get($base-palette, $darker), + text: map-get($base-palette, $text), + + default-contrast: mat-contrast($base-palette, $default), + lighter-contrast: mat-contrast($base-palette, $lighter), + darker-contrast: mat-contrast($base-palette, $darker) + )); + + // For each hue in the palette, add a "-contrast" color to the map. + @each $hue, $color in $base-palette { + $result: map_merge($result, ( + '#{$hue}-contrast': mat-contrast($base-palette, $hue) + )); + } + + @return $result; +} + + +// Gets a color from a theme palette (the output of mat-palette). +// The hue can be one of the standard values (500, A400, etc.), one of the three preconfigured +// hues (default, lighter, darker), or any of the aforementioned prefixed with "-contrast". +// +// @param $color-map The theme palette (output of mat-palette). +// @param $hue The hue from the palette to use. If this is a value between 0 and 1, it will +// be treated as opacity. +// @param $opacity The alpha channel value for the color. +@function mat-color($palette, $hue: default, $opacity: null) { + // If hueKey is a number between zero and one, then it actually contains an + // opacity value, so recall this function with the default hue and that given opacity. + @if type-of($hue) == number and $hue >= 0 and $hue <= 1 { + @return mat-color($palette, default, $hue); + } + + $color: map-get($palette, $hue); + + @if (type-of($color) != color) { + // If the $color resolved to something different from a color (e.g. a CSS variable), + // we can't apply the opacity anyway so we return the value as is, otherwise Sass can + // throw an error or output something invalid. + @return $color; + } + + @return rgba($color, if($opacity == null, opacity($color), $opacity)); +} + + +// Creates a container object for a light theme to be given to individual component theme mixins. +@function mat-light-theme($primary, $accent, $warn: mat-palette($mat-red)) { + @return ( + primary: $primary, + accent: $accent, + warn: $warn, + is-dark: false, + foreground: $mat-light-theme-foreground, + background: $mat-light-theme-background, + ); +} + + +// Creates a container object for a dark theme to be given to individual component theme mixins. +@function mat-dark-theme($primary, $accent, $warn: mat-palette($mat-red)) { + @return ( + primary: $primary, + accent: $accent, + warn: $warn, + is-dark: true, + foreground: $mat-dark-theme-foreground, + background: $mat-dark-theme-background, + ); +} + + + +$mat-ripple-color-opacity: 0.1; + +@mixin mat-ripple() { + + // The host element of an mat-ripple directive should always have a position of "absolute" or + // "relative" so that the ripples inside are correctly positioned relatively to the container. + .mat-ripple { + overflow: hidden; + + // By default, every ripple container should have position: relative in favor of creating an + // easy API for developers using the MatRipple directive. + position: relative; + } + + .mat-ripple.mat-ripple-unbounded { + overflow: visible; + } + + .mat-ripple-element { + position: absolute; + border-radius: 50%; + pointer-events: none; + + transition: opacity, transform 0ms cubic-bezier(0, 0, 0.2, 1); + transform: scale(0); + + // In high contrast mode the ripple is opaque, causing it to obstruct the content. + @include cdk-high-contrast { + display: none; + } + } +} + +/* Theme for the ripple elements.*/ +@mixin mat-ripple-theme($theme) { + $foreground: map_get($theme, foreground); + $foreground-base: map_get($foreground, base); + + .mat-ripple-element { + // If the ripple color is resolves to a color *type*, we can use it directly, otherwise + // (e.g. it resolves to a CSS variable) we fall back to using the color and setting an opacity. + @if (type-of($foreground-base) == color) { + background-color: rgba($foreground-base, $mat-ripple-color-opacity); + } + @else { + background-color: $foreground-base; + opacity: $mat-ripple-color-opacity; + } + } +} + + + +// Utility for fetching a nested value from a typography config. +@function _mat-get-type-value($config, $level, $name) { + @return map-get(map-get($config, $level), $name); +} + +// Gets the font size for a level inside a typography config. +@function mat-font-size($config, $level) { + @return _mat-get-type-value($config, $level, font-size); +} + +// Gets the line height for a level inside a typography config. +@function mat-line-height($config, $level) { + @return _mat-get-type-value($config, $level, line-height); +} + +// Gets the font weight for a level inside a typography config. +@function mat-font-weight($config, $level) { + @return _mat-get-type-value($config, $level, font-weight); +} + +// Gets the letter spacing for a level inside a typography config. +@function mat-letter-spacing($config, $level) { + @return _mat-get-type-value($config, $level, letter-spacing); +} + +// Gets the font-family from a typography config and removes the quotes around it. +@function mat-font-family($config, $level: null) { + $font-family: map-get($config, font-family); + + @if $level != null { + $font-family: _mat-get-type-value($config, $level, font-family); + } + + // Guard against unquoting non-string values, because it's deprecated. + @return if(type-of($font-family) == string, unquote($font-family), $font-family); +} + +// Outputs the shorthand `font` CSS property, based on a set of typography values. Falls back to +// the individual properties if a value that isn't allowed in the shorthand is passed in. +@mixin mat-typography-font-shorthand($font-size, $font-weight, $line-height, $font-family) { + // If any of the values are set to `inherit`, we can't use the shorthand + // so we fall back to passing in the individual properties. + @if ($font-size == inherit or + $font-weight == inherit or + $line-height == inherit or + $font-family == inherit or + $font-size == null or + $font-weight == null or + $line-height == null or + $font-family == null) { + + font-size: $font-size; + font-weight: $font-weight; + line-height: $line-height; + font-family: $font-family; + } + @else { + // Otherwise use the shorthand `font`, because it's the least amount of bytes. Note + // that we need to use interpolation for `font-size/line-height` in order to prevent + // Sass from dividing the two values. + font: $font-weight #{$font-size}/#{$line-height} $font-family; + } +} + +// Converts a typography level into CSS styles. +@mixin mat-typography-level-to-styles($config, $level) { + $font-size: mat-font-size($config, $level); + $font-weight: mat-font-weight($config, $level); + $line-height: mat-line-height($config, $level); + $font-family: mat-font-family($config, $level); + + @include mat-typography-font-shorthand($font-size, $font-weight, $line-height, $font-family); + letter-spacing: mat-letter-spacing($config, $level); +} + + +@mixin mat-option-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + + .mat-option { + color: mat-color($foreground, text); + + &:hover:not(.mat-option-disabled), + &:focus:not(.mat-option-disabled) { + background: mat-color($background, hover); + } + + // In multiple mode there is a checkbox to show that the option is selected. + &.mat-selected:not(.mat-option-multiple):not(.mat-option-disabled) { + background: mat-color($background, hover); + } + + &.mat-active { + background: mat-color($background, hover); + color: mat-color($foreground, text); + } + + &.mat-option-disabled { + color: mat-color($foreground, hint-text); + } + } + + .mat-primary .mat-option.mat-selected:not(.mat-option-disabled) { + color: mat-color($primary, text); + } + + .mat-accent .mat-option.mat-selected:not(.mat-option-disabled) { + color: mat-color($accent, text); + } + + .mat-warn .mat-option.mat-selected:not(.mat-option-disabled) { + color: mat-color($warn, text); + } +} + +@mixin mat-option-typography($config) { + .mat-option { + font: { + family: mat-font-family($config); + size: mat-font-size($config, subheading-2); + } + } +} + + + + + +@mixin mat-optgroup-theme($theme) { + $foreground: map-get($theme, foreground); + + .mat-optgroup-label { + color: mat-color($foreground, secondary-text); + } + + .mat-optgroup-disabled .mat-optgroup-label { + color: mat-color($foreground, hint-text); + } +} + +@mixin mat-optgroup-typography($config) { + .mat-optgroup-label { + @include mat-typography-level-to-styles($config, body-2); + } +} + + + +@mixin mat-pseudo-checkbox-theme($theme) { + $is-dark-theme: map-get($theme, is-dark); + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + + // NOTE(traviskaufman): While the spec calls for translucent blacks/whites for disabled colors, + // this does not work well with elements layered on top of one another. To get around this we + // blend the colors together based on the base color and the theme background. + $white-30pct-opacity-on-dark: #686868; + $black-26pct-opacity-on-light: #b0b0b0; + $disabled-color: if($is-dark-theme, $white-30pct-opacity-on-dark, $black-26pct-opacity-on-light); + $colored-box-selector: '.mat-pseudo-checkbox-checked, .mat-pseudo-checkbox-indeterminate'; + + .mat-pseudo-checkbox { + color: mat-color(map-get($theme, foreground), secondary-text); + + &::after { + color: mat-color($background, background); + } + } + + .mat-pseudo-checkbox-disabled { + color: $disabled-color; + } + + .mat-primary .mat-pseudo-checkbox-checked, + .mat-primary .mat-pseudo-checkbox-indeterminate { + background: mat-color(map-get($theme, primary)); + } + + // Default to the accent color. Note that the pseudo checkboxes are meant to inherit the + // theme from their parent, rather than implementing their own theming, which is why we + // don't attach to the `mat-*` classes. Also note that this needs to be below `.mat-primary` + // in order to allow for the color to be overwritten if the checkbox is inside a parent that + // has `mat-accent` and is placed inside another parent that has `mat-primary`. + .mat-pseudo-checkbox-checked, + .mat-pseudo-checkbox-indeterminate, + .mat-accent .mat-pseudo-checkbox-checked, + .mat-accent .mat-pseudo-checkbox-indeterminate { + background: mat-color(map-get($theme, accent)); + } + + .mat-warn .mat-pseudo-checkbox-checked, + .mat-warn .mat-pseudo-checkbox-indeterminate { + background: mat-color(map-get($theme, warn)); + } + + .mat-pseudo-checkbox-checked, + .mat-pseudo-checkbox-indeterminate { + &.mat-pseudo-checkbox-disabled { + background: $disabled-color; + } + } +} + + + +// Represents a typography level from the Material design spec. +@function mat-typography-level( + $font-size, + $line-height: $font-size, + $font-weight: 400, + $font-family: null, + $letter-spacing: null) { + + @return ( + font-size: $font-size, + line-height: $line-height, + font-weight: $font-weight, + font-family: $font-family, + letter-spacing: $letter-spacing + ); +} + +// Represents a collection of typography levels. +// Defaults come from https://material.io/guidelines/style/typography.html +// Note: The spec doesn't mention letter spacing. The values here come from +// eyeballing it until it looked exactly like the spec examples. +@function mat-typography-config( + $font-family: 'Roboto, "Helvetica Neue", sans-serif', + $display-4: mat-typography-level(112px, 112px, 300, $letter-spacing: -0.05em), + $display-3: mat-typography-level(56px, 56px, 400, $letter-spacing: -0.02em), + $display-2: mat-typography-level(45px, 48px, 400, $letter-spacing: -0.005em), + $display-1: mat-typography-level(34px, 40px, 400), + $headline: mat-typography-level(24px, 32px, 400), + $title: mat-typography-level(20px, 32px, 500), + $subheading-2: mat-typography-level(16px, 28px, 400), + $subheading-1: mat-typography-level(15px, 24px, 400), + $body-2: mat-typography-level(14px, 24px, 500), + $body-1: mat-typography-level(14px, 20px, 400), + $caption: mat-typography-level(12px, 20px, 400), + $button: mat-typography-level(14px, 14px, 500), + // Line-height must be unit-less fraction of the font-size. + $input: mat-typography-level(inherit, 1.125, 400) +) { + + // Declare an initial map with all of the levels. + $config: ( + display-4: $display-4, + display-3: $display-3, + display-2: $display-2, + display-1: $display-1, + headline: $headline, + title: $title, + subheading-2: $subheading-2, + subheading-1: $subheading-1, + body-2: $body-2, + body-1: $body-1, + caption: $caption, + button: $button, + input: $input, + ); + + // Loop through the levels and set the `font-family` of the ones that don't have one to the base. + // Note that Sass can't modify maps in place, which means that we need to merge and re-assign. + @each $key, $level in $config { + @if map-get($level, font-family) == null { + $new-level: map-merge($level, (font-family: $font-family)); + $config: map-merge($config, ($key: $new-level)); + } + } + + // Add the base font family to the config. + @return map-merge($config, (font-family: $font-family)); +} + +// Adds the base typography styles, based on a config. +@mixin mat-base-typography($config, $selector: '.mat-typography') { + .mat-h1, .mat-headline, #{$selector} h1 { + @include mat-typography-level-to-styles($config, headline); + margin: 0 0 16px; + } + + .mat-h2, .mat-title, #{$selector} h2 { + @include mat-typography-level-to-styles($config, title); + margin: 0 0 16px; + } + + .mat-h3, .mat-subheading-2, #{$selector} h3 { + @include mat-typography-level-to-styles($config, subheading-2); + margin: 0 0 16px; + } + + .mat-h4, .mat-subheading-1, #{$selector} h4 { + @include mat-typography-level-to-styles($config, subheading-1); + margin: 0 0 16px; + } + + // Note: the spec doesn't have anything that would correspond to h5 and h6, but we add these for + // consistency. The font sizes come from the Chrome user agent styles which have h5 at 0.83em + // and h6 at 0.67em. + .mat-h5, #{$selector} h5 { + @include mat-typography-font-shorthand( + // calc is used here to support css variables + calc(#{mat-font-size($config, body-1)} * 0.83), + mat-font-weight($config, body-1), + mat-line-height($config, body-1), + mat-font-family($config, body-1) + ); + + margin: 0 0 12px; + } + + .mat-h6, #{$selector} h6 { + @include mat-typography-font-shorthand( + // calc is used here to support css variables + calc(#{mat-font-size($config, body-1)} * 0.67), + mat-font-weight($config, body-1), + mat-line-height($config, body-1), + mat-font-family($config, body-1) + ); + + margin: 0 0 12px; + } + + .mat-body-strong, .mat-body-2 { + @include mat-typography-level-to-styles($config, body-2); + } + + .mat-body, .mat-body-1, #{$selector} { + @include mat-typography-level-to-styles($config, body-1); + + p { + margin: 0 0 12px; + } + } + + .mat-small, .mat-caption { + @include mat-typography-level-to-styles($config, caption); + } + + .mat-display-4, #{$selector} .mat-display-4 { + @include mat-typography-level-to-styles($config, display-4); + margin: 0 0 56px; + } + + .mat-display-3, #{$selector} .mat-display-3 { + @include mat-typography-level-to-styles($config, display-3); + margin: 0 0 64px; + } + + .mat-display-2, #{$selector} .mat-display-2 { + @include mat-typography-level-to-styles($config, display-2); + margin: 0 0 64px; + } + + .mat-display-1, #{$selector} .mat-display-1 { + @include mat-typography-level-to-styles($config, display-1); + margin: 0 0 64px; + } +} + + + + +@mixin mat-autocomplete-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + + .mat-autocomplete-panel { + @include _mat-theme-overridable-elevation(4, $theme); + background: mat-color($background, card); + color: mat-color($foreground, text); + + // Selected options in autocompletes should not be gray, but we + // only want to override the background for selected options if + // they are *not* in hover or focus state. This change has to be + // made here because base option styles are shared between the + // autocomplete and the select. + .mat-option.mat-selected:not(.mat-active):not(:hover) { + background: mat-color($background, card); + + &:not(.mat-option-disabled) { + color: mat-color($foreground, text); + } + } + } + +} + +@mixin mat-autocomplete-typography($config) { } + +// This contains all of the styles for the badge +// rather than just the color/theme because of +// no style sheet support for directives. + + + + + +$mat-badge-font-size: 12px; +$mat-badge-font-weight: 600; +$mat-badge-default-size: 22px !default; +$mat-badge-small-size: $mat-badge-default-size - 6; +$mat-badge-large-size: $mat-badge-default-size + 6; + +// Mixin for building offset given different sizes +@mixin _mat-badge-size($size) { + .mat-badge-content { + width: $size; + height: $size; + line-height: $size; + } + + &.mat-badge-above { + .mat-badge-content { + top: -$size / 2; + } + } + + &.mat-badge-below { + .mat-badge-content { + bottom: -$size / 2; + } + } + + &.mat-badge-before { + .mat-badge-content { + left: -$size; + } + } + + [dir='rtl'] &.mat-badge-before { + .mat-badge-content { + left: auto; + right: -$size; + } + } + + &.mat-badge-after { + .mat-badge-content { + right: -$size; + } + } + + [dir='rtl'] &.mat-badge-after { + .mat-badge-content { + right: auto; + left: -$size; + } + } + + &.mat-badge-overlap { + &.mat-badge-before { + .mat-badge-content { + left: -$size / 2; + } + } + + [dir='rtl'] &.mat-badge-before { + .mat-badge-content { + left: auto; + right: -$size / 2; + } + } + + &.mat-badge-after { + .mat-badge-content { + right: -$size / 2; + } + } + + [dir='rtl'] &.mat-badge-after { + .mat-badge-content { + right: auto; + left: -$size / 2; + } + } + } +} + +@mixin mat-badge-theme($theme) { + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $primary: map-get($theme, primary); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-badge-content { + color: mat-color($primary, default-contrast); + background: mat-color($primary); + + @include cdk-high-contrast { + outline: solid 1px; + border-radius: 0; + } + } + + .mat-badge-accent { + .mat-badge-content { + background: mat-color($accent); + color: mat-color($accent, default-contrast); + } + } + + .mat-badge-warn { + .mat-badge-content { + color: mat-color($warn, default-contrast); + background: mat-color($warn); + } + } + + .mat-badge { + position: relative; + } + + .mat-badge-hidden { + .mat-badge-content { + display: none; + } + } + + .mat-badge-disabled { + .mat-badge-content { + $app-background: mat-color($background, 'background'); + $badge-color: mat-color($foreground, disabled-button); + + // The disabled color usually has some kind of opacity, but because the badge is overlayed + // on top of something else, it won't look good if it's opaque. If it is a color *type*, + // we convert it into a solid color by taking the opacity from the rgba value and using + // the value to determine the percentage of the background to put into foreground when + // mixing the colors together. + @if (type-of($badge-color) == color and type-of($app-background) == color) { + $badge-opacity: opacity($badge-color); + background: mix($app-background, rgba($badge-color, 1), (1 - $badge-opacity) * 100%); + } + @else { + background: $badge-color; + } + + color: mat-color($foreground, disabled-text); + } + } + + .mat-badge-content { + position: absolute; + text-align: center; + display: inline-block; + border-radius: 50%; + transition: transform 200ms ease-in-out; + transform: scale(0.6); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + pointer-events: none; + } + + .ng-animate-disabled .mat-badge-content, + .mat-badge-content._mat-animation-noopable { + transition: none; + } + + // The active class is added after the element is added + // so it can animate scale to default + .mat-badge-content.mat-badge-active { + // Scale to `none` instead of `1` to avoid blurry text in some browsers. + transform: none; + } + + .mat-badge-small { + @include _mat-badge-size($mat-badge-small-size); + } + .mat-badge-medium { + @include _mat-badge-size($mat-badge-default-size); + } + .mat-badge-large { + @include _mat-badge-size($mat-badge-large-size); + } +} + +@mixin mat-badge-typography($config) { + .mat-badge-content { + font-weight: $mat-badge-font-weight; + font-size: $mat-badge-font-size; + font-family: mat-font-family($config); + } + + .mat-badge-small .mat-badge-content { + // Set the font size to 75% of the original. + font-size: $mat-badge-font-size * 0.75; + } + + .mat-badge-large .mat-badge-content { + font-size: $mat-badge-font-size * 2; + } +} + + + + + +@mixin mat-bottom-sheet-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-bottom-sheet-container { + @include _mat-theme-elevation(16, $theme); + background: mat-color($background, dialog); + color: mat-color($foreground, text); + } +} + +@mixin mat-bottom-sheet-typography($config) { + .mat-bottom-sheet-container { + @include mat-typography-level-to-styles($config, body-1); + } +} + + + + + +$_mat-button-ripple-opacity: 0.1; + +// Applies a focus style to an mat-button element for each of the supported palettes. +@mixin _mat-button-focus-overlay-color($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + + &.mat-primary .mat-button-focus-overlay { + background-color: mat-color($primary); + } + + &.mat-accent .mat-button-focus-overlay { + background-color: mat-color($accent); + } + + &.mat-warn .mat-button-focus-overlay { + background-color: mat-color($warn); + } + + &[disabled] .mat-button-focus-overlay { + background-color: transparent; + } +} + +// Applies the background color for a ripple. If the value provided is not a Sass color, +// we assume that we've been given a CSS variable. Since we can't perform alpha-blending +// on a CSS variable, we instead add the opacity directly to the ripple element. +@mixin _mat-button-ripple-background($palette, $hue, $opacity) { + $background-color: mat-color($palette, $hue, $opacity); + background-color: $background-color; + @if (type-of($background-color) != color) { + opacity: $opacity; + } +} + +@mixin _mat-button-ripple-color($theme, $hue, $opacity: $_mat-button-ripple-opacity) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + + &.mat-primary .mat-ripple-element { + @include _mat-button-ripple-background($primary, $hue, $opacity); + } + + &.mat-accent .mat-ripple-element { + @include _mat-button-ripple-background($accent, $hue, $opacity); + } + + &.mat-warn .mat-ripple-element { + @include _mat-button-ripple-background($warn, $hue, $opacity); + } +} + +// Applies a property to an mat-button element for each of the supported palettes. +@mixin _mat-button-theme-property($theme, $property, $hue) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + &.mat-primary { + #{$property}: mat-color($primary, $hue); + } + &.mat-accent { + #{$property}: mat-color($accent, $hue); + } + &.mat-warn { + #{$property}: mat-color($warn, $hue); + } + + &.mat-primary, &.mat-accent, &.mat-warn, &[disabled] { + &[disabled] { + $palette: if($property == 'color', $foreground, $background); + #{$property}: mat-color($palette, disabled-button); + } + } +} + +@mixin mat-button-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-button, .mat-icon-button, .mat-stroked-button { + // Buttons without a background color should inherit the font color. This is necessary to + // ensure that the button is readable on custom background colors. It's wrong to always assume + // that those buttons are always placed inside of containers with the default background + // color of the theme (e.g. themed toolbars). + color: inherit; + background: transparent; + + @include _mat-button-theme-property($theme, 'color', text); + @include _mat-button-focus-overlay-color($theme); + + // Setup the ripple color to be based on the text color. This ensures that the ripples + // are matching with the current theme palette and are in contrast to the background color + // (e.g in themed toolbars). + .mat-ripple-element { + opacity: $_mat-button-ripple-opacity; + background-color: currentColor; + } + } + + .mat-button-focus-overlay { + background: map_get($foreground, base); + } + + // Note: this needs a bit extra specificity, because we're not guaranteed the inclusion + // order of the theme styles and the button reset may end up resetting this as well. + .mat-stroked-button:not([disabled]) { + border-color: mat-color($foreground, divider); + } + + .mat-flat-button, .mat-raised-button, .mat-fab, .mat-mini-fab { + // Default font and background color when not using any color palette. + color: mat-color($foreground, text); + background-color: mat-color($background, raised-button); + + @include _mat-button-theme-property($theme, 'color', default-contrast); + @include _mat-button-theme-property($theme, 'background-color', default); + @include _mat-button-ripple-color($theme, default-contrast); + } + + .mat-stroked-button, .mat-flat-button { + @include _mat-theme-overridable-elevation(0, $theme); + } + + .mat-raised-button { + @include _mat-theme-overridable-elevation(2, $theme); + + &:not([disabled]):active { + @include _mat-theme-overridable-elevation(8, $theme); + } + + &[disabled] { + @include _mat-theme-overridable-elevation(0, $theme); + } + } + + .mat-fab, .mat-mini-fab { + @include _mat-theme-overridable-elevation(6, $theme); + + &:not([disabled]):active { + @include _mat-theme-overridable-elevation(12, $theme); + } + + &[disabled] { + @include _mat-theme-overridable-elevation(0, $theme); + } + } +} + +@mixin mat-button-typography($config) { + .mat-button, .mat-raised-button, .mat-icon-button, .mat-stroked-button, + .mat-flat-button, .mat-fab, .mat-mini-fab { + font: { + family: mat-font-family($config, button); + size: mat-font-size($config, button); + weight: mat-font-weight($config, button); + } + } +} + + + + + + +@mixin mat-button-toggle-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + $divider-color: mat-color($foreground, divider); + + .mat-button-toggle-standalone, + .mat-button-toggle-group { + @include _mat-theme-elevation(2, $theme); + } + + .mat-button-toggle-standalone.mat-button-toggle-appearance-standard, + .mat-button-toggle-group-appearance-standard { + box-shadow: none; + } + + .mat-button-toggle { + color: mat-color($foreground, hint-text); + + .mat-button-toggle-focus-overlay { + background-color: mat-color($background, focused-button); + } + } + + .mat-button-toggle-appearance-standard { + color: mat-color($foreground, text); + background: mat-color($background, card); + + .mat-button-toggle-focus-overlay { + background-color: mat-color($background, focused-button, 1); + } + } + + .mat-button-toggle-group-appearance-standard .mat-button-toggle + .mat-button-toggle { + border-left: solid 1px $divider-color; + } + + [dir='rtl'] .mat-button-toggle-group-appearance-standard .mat-button-toggle + .mat-button-toggle { + border-left: none; + border-right: solid 1px $divider-color; + } + + .mat-button-toggle-group-appearance-standard.mat-button-toggle-vertical { + .mat-button-toggle + .mat-button-toggle { + border-left: none; + border-right: none; + border-top: solid 1px $divider-color; + } + } + + .mat-button-toggle-checked { + background-color: mat-color($background, selected-button); + color: mat-color($foreground, secondary-text); + + &.mat-button-toggle-appearance-standard { + color: mat-color($foreground, text); + } + } + + .mat-button-toggle-disabled { + color: mat-color($foreground, disabled-button); + background-color: mat-color($background, disabled-button-toggle); + + &.mat-button-toggle-appearance-standard { + background: mat-color($background, card); + } + + &.mat-button-toggle-checked { + background-color: mat-color($background, selected-disabled-button); + } + } + + .mat-button-toggle-standalone.mat-button-toggle-appearance-standard, + .mat-button-toggle-group-appearance-standard { + border: solid 1px $divider-color; + } +} + +@mixin mat-button-toggle-typography($config) { + .mat-button-toggle { + font-family: mat-font-family($config); + } +} + + + + + + + +@mixin mat-card-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-card { + @include _mat-theme-overridable-elevation(1, $theme); + background: mat-color($background, card); + color: mat-color($foreground, text); + + // Needs extra specificity to be able to override the elevation selectors. + &.mat-card-flat { + @include _mat-theme-overridable-elevation(0, $theme); + } + } + + .mat-card-subtitle { + color: mat-color($foreground, secondary-text); + } +} + +@mixin mat-card-typography($config) { + .mat-card { + font-family: mat-font-family($config); + } + + .mat-card-title { + font: { + size: mat-font-size($config, headline); + weight: mat-font-weight($config, title); + } + } + + .mat-card-header .mat-card-title { + font-size: mat-font-size($config, title); + } + + .mat-card-subtitle, + .mat-card-content { + font-size: mat-font-size($config, body-1); + } +} + + + + + + +@mixin mat-checkbox-theme($theme) { + $is-dark-theme: map-get($theme, is-dark); + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + + // The color of the checkbox's checkmark / mixedmark. + $checkbox-mark-color: mat-color($background, background); + + // NOTE(traviskaufman): While the spec calls for translucent blacks/whites for disabled colors, + // this does not work well with elements layered on top of one another. To get around this we + // blend the colors together based on the base color and the theme background. + $white-30pct-opacity-on-dark: #686868; + $black-26pct-opacity-on-light: #b0b0b0; + $disabled-color: if($is-dark-theme, $white-30pct-opacity-on-dark, $black-26pct-opacity-on-light); + + .mat-checkbox-frame { + border-color: mat-color($foreground, secondary-text); + } + + .mat-checkbox-checkmark { + fill: $checkbox-mark-color; + } + + .mat-checkbox-checkmark-path { + // !important is needed here because a stroke must be set as an + // attribute on the SVG in order for line animation to work properly. + stroke: $checkbox-mark-color !important; + + @include cdk-high-contrast(black-on-white) { + // Having the one above be !important ends up overriding the browser's automatic + // color inversion so we need to re-invert it ourselves for black-on-white. + stroke: #000 !important; + } + } + + .mat-checkbox-mixedmark { + background-color: $checkbox-mark-color; + } + + .mat-checkbox-indeterminate, .mat-checkbox-checked { + &.mat-primary .mat-checkbox-background { + background-color: mat-color($primary); + } + + &.mat-accent .mat-checkbox-background { + background-color: mat-color($accent); + } + + &.mat-warn .mat-checkbox-background { + background-color: mat-color($warn); + } + } + + .mat-checkbox-disabled { + &.mat-checkbox-checked, + &.mat-checkbox-indeterminate { + .mat-checkbox-background { + background-color: $disabled-color; + } + } + + &:not(.mat-checkbox-checked) { + .mat-checkbox-frame { + border-color: $disabled-color; + } + } + + .mat-checkbox-label { + color: mat-color($foreground, secondary-text); + } + + @include cdk-high-contrast { + opacity: 0.5; + } + } + + // This one is moved down here so it can target both + // the theme colors and the disabled state. + @include cdk-high-contrast { + .mat-checkbox-background { + // Needs to be removed because it hides the checkbox outline. + background: none; + } + } + + // Switch this to a solid color since we're using `opacity` + // to control how opaque the ripple should be. + .mat-checkbox .mat-ripple-element { + background-color: map_get(map-get($theme, foreground), base); + } + + .mat-checkbox-checked:not(.mat-checkbox-disabled), + .mat-checkbox:active:not(.mat-checkbox-disabled) { + &.mat-primary .mat-ripple-element { + background: mat-color($primary); + } + + &.mat-accent .mat-ripple-element { + background: mat-color($accent); + } + + &.mat-warn .mat-ripple-element { + background: mat-color($warn); + } + } +} + +@mixin mat-checkbox-typography($config) { + .mat-checkbox { + font-family: mat-font-family($config); + } + + // TODO(kara): Remove this style when fixing vertical baseline + .mat-checkbox-layout .mat-checkbox-label { + line-height: mat-line-height($config, body-2); + } +} + + + + + + +$mat-chip-remove-font-size: 18px; + +@mixin mat-chips-color($foreground, $background) { + background-color: $background; + color: $foreground; + + .mat-chip-remove { + color: $foreground; + opacity: 0.4; + } +} + +@mixin mat-chips-theme-color($palette) { + @include mat-chips-color(mat-color($palette, default-contrast), mat-color($palette)); + + .mat-ripple-element { + background: mat-color($palette, default-contrast, 0.1); + } +} + +@mixin mat-chips-theme($theme) { + $is-dark-theme: map-get($theme, is-dark); + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + $unselected-background: mat-color($background, unselected-chip); + $unselected-foreground: mat-color($foreground, text); + + .mat-chip.mat-standard-chip { + @include mat-chips-color($unselected-foreground, $unselected-background); + + &:not(.mat-chip-disabled) { + &:active { + @include _mat-theme-elevation(3, $theme); + } + + .mat-chip-remove:hover { + opacity: 0.54; + } + } + + &.mat-chip-disabled { + opacity: 0.4; + } + + &::after { + background: map_get($foreground, base); + } + } + + .mat-chip.mat-standard-chip.mat-chip-selected { + &.mat-primary { + @include mat-chips-theme-color($primary); + } + + &.mat-warn { + @include mat-chips-theme-color($warn); + } + + &.mat-accent { + @include mat-chips-theme-color($accent); + } + } +} + +@mixin mat-chips-typography($config) { + .mat-chip { + font-size: mat-font-size($config, body-2); + font-weight: mat-font-weight($config, body-2); + + .mat-chip-trailing-icon.mat-icon, + .mat-chip-remove.mat-icon { + font-size: $mat-chip-remove-font-size; + } + } +} + + + + + +@mixin mat-table-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-table { + background: mat-color($background, 'card'); + } + + .mat-table thead, .mat-table tbody, .mat-table tfoot, + mat-header-row, mat-row, mat-footer-row, + [mat-header-row], [mat-row], [mat-footer-row], + .mat-table-sticky { + background: inherit; + } + + mat-row, mat-header-row, mat-footer-row, + th.mat-header-cell, td.mat-cell, td.mat-footer-cell { + border-bottom-color: mat-color($foreground, divider); + } + + .mat-header-cell { + color: mat-color($foreground, secondary-text); + } + + .mat-cell, .mat-footer-cell { + color: mat-color($foreground, text); + } +} + +@mixin mat-table-typography($config) { + .mat-table { + font-family: mat-font-family($config); + } + + .mat-header-cell { + font-size: mat-font-size($config, caption); + font-weight: mat-font-weight($config, body-2); + } + + .mat-cell, .mat-footer-cell { + font-size: mat-font-size($config, body-1); + } +} + + + + + + + +$mat-datepicker-selected-today-box-shadow-width: 1px; +$mat-datepicker-selected-fade-amount: 0.6; +$mat-datepicker-today-fade-amount: 0.2; +$mat-calendar-body-font-size: 13px !default; +$mat-calendar-weekday-table-font-size: 11px !default; + +@mixin _mat-datepicker-color($palette) { + .mat-calendar-body-selected { + background-color: mat-color($palette); + color: mat-color($palette, default-contrast); + } + + .mat-calendar-body-disabled > .mat-calendar-body-selected { + $background: mat-color($palette); + + @if (type-of($background) == color) { + background-color: fade-out($background, $mat-datepicker-selected-fade-amount); + } + @else { + // If we couldn't resolve to background to a color (e.g. it's a CSS variable), + // fall back to fading the content out via `opacity`. + opacity: $mat-datepicker-today-fade-amount; + } + } + + .mat-calendar-body-today.mat-calendar-body-selected { + box-shadow: inset 0 0 0 $mat-datepicker-selected-today-box-shadow-width + mat-color($palette, default-contrast); + } +} + +@mixin mat-datepicker-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + + .mat-calendar-arrow { + border-top-color: mat-color($foreground, icon); + } + + // The prev/next buttons need a bit more specificity to + // avoid being overwritten by the .mat-icon-button. + .mat-datepicker-toggle, + .mat-datepicker-content .mat-calendar-next-button, + .mat-datepicker-content .mat-calendar-previous-button { + color: mat-color($foreground, icon); + } + + .mat-calendar-table-header { + color: mat-color($foreground, hint-text); + } + + .mat-calendar-table-header-divider::after { + background: mat-color($foreground, divider); + } + + .mat-calendar-body-label { + color: mat-color($foreground, secondary-text); + } + + .mat-calendar-body-cell-content { + color: mat-color($foreground, text); + border-color: transparent; + } + + .mat-calendar-body-disabled > .mat-calendar-body-cell-content:not(.mat-calendar-body-selected) { + color: mat-color($foreground, disabled-text); + } + + .mat-calendar-body-cell:not(.mat-calendar-body-disabled):hover, + .cdk-keyboard-focused .mat-calendar-body-active, + .cdk-program-focused .mat-calendar-body-active { + & > .mat-calendar-body-cell-content:not(.mat-calendar-body-selected) { + background-color: mat-color($background, hover); + } + } + + .mat-calendar-body-today:not(.mat-calendar-body-selected) { + // Note: though it's not text, the border is a hint about the fact that this is today's date, + // so we use the hint color. + border-color: mat-color($foreground, hint-text); + } + + .mat-calendar-body-disabled > .mat-calendar-body-today:not(.mat-calendar-body-selected) { + $color: mat-color($foreground, hint-text); + + @if (type-of($color) == color) { + border-color: fade-out($color, $mat-datepicker-today-fade-amount); + } + @else { + // If the color didn't resolve to a color value, but something like a CSS variable, we can't + // fade it out so we fall back to reducing the element opacity. Note that we don't use the + // $mat-datepicker-today-fade-amount, because hint text usually has some opacity applied + // to it already and we don't want them to stack on top of each other. + opacity: 0.5; + } + } + + @include _mat-datepicker-color(map-get($theme, primary)); + + .mat-datepicker-content { + @include _mat-theme-elevation(4, $theme); + background-color: mat-color($background, card); + color: mat-color($foreground, text); + + &.mat-accent { + @include _mat-datepicker-color(map-get($theme, accent)); + } + + &.mat-warn { + @include _mat-datepicker-color(map-get($theme, warn)); + } + } + + .mat-datepicker-content-touch { + @include _mat-theme-elevation(0, $theme); + } + + .mat-datepicker-toggle-active { + color: mat-color(map-get($theme, primary), text); + + &.mat-accent { + color: mat-color(map-get($theme, accent), text); + } + + &.mat-warn { + color: mat-color(map-get($theme, warn), text); + } + } +} + +@mixin mat-datepicker-typography($config) { + .mat-calendar { + font-family: mat-font-family($config); + } + + .mat-calendar-body { + font-size: $mat-calendar-body-font-size; + } + + .mat-calendar-body-label, + .mat-calendar-period-button { + font: { + size: mat-font-size($config, button); + weight: mat-font-weight($config, button); + } + } + + .mat-calendar-table-header th { + font: { + size: $mat-calendar-weekday-table-font-size; + weight: mat-font-weight($config, body-1); + } + } +} + + + + + + + +@mixin mat-dialog-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-dialog-container { + @include _mat-theme-elevation(24, $theme); + background: mat-color($background, dialog); + color: mat-color($foreground, text); + } +} + +@mixin mat-dialog-typography($config) { + .mat-dialog-title { + @include mat-typography-level-to-styles($config, title); + } +} + + + + + + +@mixin mat-expansion-panel-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-expansion-panel { + @include _mat-theme-overridable-elevation(2, $theme); + background: mat-color($background, card); + color: mat-color($foreground, text); + } + + .mat-action-row { + border-top-color: mat-color($foreground, divider); + } + + .mat-expansion-panel { + & .mat-expansion-panel-header.cdk-keyboard-focused, + & .mat-expansion-panel-header.cdk-program-focused, + &:not(.mat-expanded) .mat-expansion-panel-header:hover { + &:not([aria-disabled='true']) { + background: mat-color($background, hover); + } + } + } + + // Disable the hover on touch devices since it can appear like it is stuck. We can't use + // `@media (hover)` above, because the desktop support browser support isn't great. + @media (hover: none) { + .mat-expansion-panel:not(.mat-expanded):not([aria-disabled='true']) + .mat-expansion-panel-header:hover { + background: mat-color($background, card); + } + } + + .mat-expansion-panel-header-title { + color: mat-color($foreground, text); + } + + .mat-expansion-panel-header-description, + .mat-expansion-indicator::after { + color: mat-color($foreground, secondary-text); + } + + .mat-expansion-panel-header[aria-disabled='true'] { + color: mat-color($foreground, disabled-button); + + .mat-expansion-panel-header-title, + .mat-expansion-panel-header-description { + color: inherit; + } + } +} + +@mixin mat-expansion-panel-typography($config) { + .mat-expansion-panel-header { + font: { + family: mat-font-family($config, subheading-1); + size: mat-font-size($config, subheading-1); + weight: mat-font-weight($config, subheading-1); + } + } + + .mat-expansion-panel-content { + @include mat-typography-level-to-styles($config, body-1); + } +} + + + + +// This mixin will ensure that lines that overflow the container will hide the overflow and +// truncate neatly with an ellipsis. +@mixin mat-truncate-line() { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +// Mixin to provide all mat-line styles, changing secondary font size based on whether the list +// is in dense mode. +@mixin mat-line-base($secondary-font-size) { + .mat-line { + @include mat-truncate-line(); + display: block; + box-sizing: border-box; + + // all lines but the top line should have smaller text + &:nth-child(n+2) { + font-size: $secondary-font-size; + } + } +} + +// This mixin normalizes default element styles, e.g. font weight for heading text. +@mixin mat-normalize-text() { + & > * { + margin: 0; + padding: 0; + font-weight: normal; + font-size: inherit; + } +} + +// This mixin provides base styles for the wrapper around mat-line elements in a list. +@mixin mat-line-wrapper-base() { + @include mat-normalize-text(); + + display: flex; + flex-direction: column; + width: 100%; + box-sizing: border-box; + overflow: hidden; + + // Must remove wrapper when lines are empty or it takes up horizontal + // space and pushes other elements to the right. + &:empty { + display: none; + } +} + + + +// Include this empty mixin for consistency with the other components. +@mixin mat-grid-list-theme($theme) { } + +@mixin mat-grid-list-typography($config) { + .mat-grid-tile-header, + .mat-grid-tile-footer { + @include mat-line-base(mat-font-size($config, caption)); + font-size: mat-font-size($config, body-1); + } +} + + + + +// Include this empty mixin for consistency with the other components. +@mixin mat-icon-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-icon { + &.mat-primary { + color: mat-color($primary, text); + } + + &.mat-accent { + color: mat-color($accent, text); + } + + &.mat-warn { + color: mat-color($warn, text); + } + } +} + +@mixin mat-icon-typography($config) { } + + + + + +// Renders a gradient for showing the dashed line when the input is disabled. +// Unlike using a border, a gradient allows us to adjust the spacing of the dotted line +// to match the Material Design spec. +@mixin mat-control-disabled-underline($color) { + background-image: linear-gradient(to right, $color 0%, $color 33%, transparent 0%); + background-size: 4px 100%; + background-repeat: repeat-x; +} + +// Figures out the color of the placeholder for a form control. +// Used primarily to prevent the various form controls from +// becoming out of sync since these colors aren't in a palette. +@function _mat-control-placeholder-color($theme) { + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + @return mat-color($foreground, secondary-text, if($is-dark-theme, 0.5, 0.42)); +} + + +/* stylelint-disable material/no-prefixes */ +@mixin user-select($value) { + -webkit-user-select: $value; + -moz-user-select: $value; + -ms-user-select: $value; + user-select: $value; +} + +@mixin input-placeholder { + &::placeholder { + @content; + } + + &::-moz-placeholder { + @content; + } + + &::-webkit-input-placeholder { + @content; + } + + &:-ms-input-placeholder { + @content; + } +} + +@mixin cursor-grab { + cursor: -webkit-grab; + cursor: grab; +} + +@mixin cursor-grabbing { + cursor: -webkit-grabbing; + cursor: grabbing; +} + +@mixin backface-visibility($value) { + -webkit-backface-visibility: $value; + backface-visibility: $value; +} +/* stylelint-enable */ + + + +@mixin mat-input-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $foreground: map-get($theme, foreground); + + .mat-form-field-type-mat-native-select .mat-form-field-infix::after { + color: mat-color($foreground, secondary-text); + } + + .mat-input-element:disabled, + .mat-form-field-type-mat-native-select.mat-form-field-disabled .mat-form-field-infix::after { + color: mat-color($foreground, disabled-text); + } + + .mat-input-element { + caret-color: mat-color($primary, text); + + @include input-placeholder { + color: _mat-control-placeholder-color($theme); + } + + // On dark themes we set the native `select` color to some shade of white, + // however the color propagates to all of the `option` elements, which are + // always on a white background inside the dropdown, causing them to blend in. + // Since we can't change background of the dropdown, we need to explicitly + // reset the color of the options to something dark. + @if (map-get($theme, is-dark)) { + option { + color: $dark-primary-text; + } + + option:disabled { + color: $dark-disabled-text; + } + } + } + + .mat-accent .mat-input-element { + caret-color: mat-color($accent, text); + } + + .mat-warn .mat-input-element, + .mat-form-field-invalid .mat-input-element { + caret-color: mat-color($warn, text); + } + + .mat-form-field-type-mat-native-select.mat-form-field-invalid .mat-form-field-infix::after { + color: mat-color($warn, text); + } +} + +@mixin mat-input-typography($config) { + // The unit-less line-height from the font config. + $line-height: mat-line-height($config, input); + + // The amount of space between the top of the line and the top of the actual text + // (as a fraction of the font-size). + $line-spacing: ($line-height - 1) / 2; + + // elements seem to have their height set slightly too large on Safari causing the text to + // be misaligned w.r.t. the placeholder. Adding this margin corrects it. + input.mat-input-element { + margin-top: -$line-spacing * 1em; + } +} + + + + + + + +@mixin mat-list-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-list-base { + .mat-list-item { + color: mat-color($foreground, text); + } + + .mat-list-option { + color: mat-color($foreground, text); + } + + .mat-subheader { + color: mat-color($foreground, secondary-text); + } + } + + .mat-list-item-disabled { + background-color: mat-color($background, disabled-list-option); + } + + .mat-list-option, + .mat-nav-list .mat-list-item, + .mat-action-list .mat-list-item { + &:hover, &:focus { + background: mat-color($background, 'hover'); + } + } +} + +@mixin mat-list-typography($config) { + $font-family: mat-font-family($config); + + .mat-list-item { + font-family: $font-family; + } + + .mat-list-option { + font-family: $font-family; + } + + // Default list + .mat-list-base { + .mat-list-item { + font-size: mat-font-size($config, subheading-2); + @include mat-line-base(mat-font-size($config, body-1)); + } + + .mat-list-option { + font-size: mat-font-size($config, subheading-2); + @include mat-line-base(mat-font-size($config, body-1)); + } + + .mat-subheader { + font-family: mat-font-family($config, body-2); + font-size: mat-font-size($config, body-2); + font-weight: mat-font-weight($config, body-2); + } + } + + // Dense list + .mat-list-base[dense] { + .mat-list-item { + font-size: mat-font-size($config, caption); + @include mat-line-base(mat-font-size($config, caption)); + } + + .mat-list-option { + font-size: mat-font-size($config, caption); + @include mat-line-base(mat-font-size($config, caption)); + } + + .mat-subheader { + font-family: $font-family; + font-size: mat-font-size($config, caption); + font-weight: mat-font-weight($config, body-2); + } + } +} + + + + + + + +@mixin mat-menu-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-menu-panel { + @include _mat-theme-overridable-elevation(4, $theme); + background: mat-color($background, 'card'); + } + + .mat-menu-item { + background: transparent; + color: mat-color($foreground, 'text'); + + &[disabled] { + &, &::after { + color: mat-color($foreground, 'disabled'); + } + } + } + + .mat-menu-item .mat-icon-no-color, + .mat-menu-item-submenu-trigger::after { + color: mat-color($foreground, 'icon'); + } + + .mat-menu-item:hover, + .mat-menu-item.cdk-program-focused, + .mat-menu-item.cdk-keyboard-focused, + .mat-menu-item-highlighted { + &:not([disabled]) { + background: mat-color($background, 'hover'); + } + } +} + +@mixin mat-menu-typography($config) { + .mat-menu-item { + font: { + family: mat-font-family($config, body-1); + size: mat-font-size($config, body-1); + weight: mat-font-weight($config, body-1); + } + } +} + + + + + + +@mixin mat-paginator-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + + .mat-paginator { + background: mat-color($background, 'card'); + } + + .mat-paginator, + .mat-paginator-page-size .mat-select-trigger { + color: mat-color($foreground, secondary-text); + } + + .mat-paginator-decrement, + .mat-paginator-increment { + border-top: 2px solid mat-color($foreground, 'icon'); + border-right: 2px solid mat-color($foreground, 'icon'); + } + + .mat-paginator-first, + .mat-paginator-last { + border-top: 2px solid mat-color($foreground, 'icon'); + } + + .mat-icon-button[disabled] { + .mat-paginator-decrement, + .mat-paginator-increment, + .mat-paginator-first, + .mat-paginator-last { + border-color: mat-color($foreground, 'disabled'); + } + } +} + +@mixin mat-paginator-typography($config) { + .mat-paginator, + .mat-paginator-page-size .mat-select-trigger { + font: { + family: mat-font-family($config, caption); + size: mat-font-size($config, caption); + } + } +} + + + + + +@mixin mat-progress-bar-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + + .mat-progress-bar-background { + fill: mat-color($primary, lighter); + } + + .mat-progress-bar-buffer { + background-color: mat-color($primary, lighter); + } + + .mat-progress-bar-fill::after { + background-color: mat-color($primary); + } + + .mat-progress-bar.mat-accent { + .mat-progress-bar-background { + fill: mat-color($accent, lighter); + } + + .mat-progress-bar-buffer { + background-color: mat-color($accent, lighter); + } + + .mat-progress-bar-fill::after { + background-color: mat-color($accent); + } + } + + .mat-progress-bar.mat-warn { + .mat-progress-bar-background { + fill: mat-color($warn, lighter); + } + + .mat-progress-bar-buffer { + background-color: mat-color($warn, lighter); + } + + .mat-progress-bar-fill::after { + background-color: mat-color($warn); + } + } +} + +@mixin mat-progress-bar-typography($config) { } + + + + + + +@mixin mat-progress-spinner-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + + .mat-progress-spinner, .mat-spinner { + circle { + stroke: mat-color($primary); + } + + &.mat-accent circle { + stroke: mat-color($accent); + } + + &.mat-warn circle { + stroke: mat-color($warn); + } + } +} + +@mixin mat-progress-spinner-typography($config) { } + + + + + +@mixin _mat-radio-color($palette) { + &.mat-radio-checked .mat-radio-outer-circle { + border-color: mat-color($palette); + } + + .mat-radio-inner-circle, + .mat-radio-ripple .mat-ripple-element:not(.mat-radio-persistent-ripple), + &.mat-radio-checked .mat-radio-persistent-ripple, + &:active .mat-radio-persistent-ripple { + background-color: mat-color($palette); + } +} + +@mixin mat-radio-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-radio-outer-circle { + border-color: mat-color($foreground, secondary-text); + } + + .mat-radio-button { + &.mat-primary { + @include _mat-radio-color($primary); + } + + &.mat-accent { + @include _mat-radio-color($accent); + } + + &.mat-warn { + @include _mat-radio-color($warn); + } + + // This needs extra specificity, because the classes above are combined + // (e.g. `.mat-radio-button.mat-accent`) which increases their specificity a lot. + // TODO: consider making the selectors into descendants (`.mat-primary .mat-radio-button`). + &.mat-radio-disabled { + &.mat-radio-checked .mat-radio-outer-circle, + .mat-radio-outer-circle { + border-color: mat-color($foreground, disabled); + } + + .mat-radio-ripple .mat-ripple-element, + .mat-radio-inner-circle { + background-color: mat-color($foreground, disabled); + } + + .mat-radio-label-content { + color: mat-color($foreground, disabled); + } + } + + // Switch this to a solid color since we're using `opacity` + // to control how opaque the ripple should be. + .mat-ripple-element { + background-color: map_get($foreground, base); + } + } +} + +@mixin mat-radio-typography($config) { + .mat-radio-button { + font-family: mat-font-family($config); + } +} + + + + + + + + +@mixin mat-select-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + + .mat-select-value { + color: mat-color($foreground, text); + } + + .mat-select-placeholder { + color: _mat-control-placeholder-color($theme); + } + + .mat-select-disabled .mat-select-value { + color: mat-color($foreground, disabled-text); + } + + .mat-select-arrow { + color: mat-color($foreground, secondary-text); + } + + .mat-select-panel { + background: mat-color($background, card); + @include _mat-theme-overridable-elevation(4, $theme); + + .mat-option.mat-selected:not(.mat-option-multiple) { + background: mat-color($background, hover, 0.12); + } + } + + .mat-form-field { + &.mat-focused { + &.mat-primary .mat-select-arrow { + color: mat-color($primary, text); + } + + &.mat-accent .mat-select-arrow { + color: mat-color($accent, text); + } + + &.mat-warn .mat-select-arrow { + color: mat-color($warn, text); + } + } + + .mat-select.mat-select-invalid .mat-select-arrow { + color: mat-color($warn, text); + } + + .mat-select.mat-select-disabled .mat-select-arrow { + color: mat-color($foreground, disabled-text); + } + } +} + +@mixin mat-select-typography($config) { + // The unit-less line-height from the font config. + $line-height: mat-line-height($config, input); + + .mat-select { + font-family: mat-font-family($config); + } + + .mat-select-trigger { + height: $line-height * 1em; + } +} + + + + + + +@mixin mat-sidenav-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + $drawer-background-color: mat-color($background, dialog); + $drawer-container-background-color: mat-color($background, background); + $drawer-push-background-color: mat-color($background, dialog); + $drawer-side-border: solid 1px mat-color($foreground, divider); + + .mat-drawer-container { + background-color: $drawer-container-background-color; + color: mat-color($foreground, text); + } + + .mat-drawer { + background-color: $drawer-background-color; + color: mat-color($foreground, text); + + &.mat-drawer-push { + background-color: $drawer-push-background-color; + } + + &:not(.mat-drawer-side) { + // The elevation of z-16 is noted in the design specifications. + // See https://material.io/design/components/navigation-drawer.html + @include _mat-theme-elevation(16, $theme); + } + } + + .mat-drawer-side { + border-right: $drawer-side-border; + + &.mat-drawer-end { + border-left: $drawer-side-border; + border-right: none; + } + } + + [dir='rtl'] .mat-drawer-side { + border-left: $drawer-side-border; + border-right: none; + + &.mat-drawer-end { + border-left: none; + border-right: $drawer-side-border; + } + } + + .mat-drawer-backdrop.mat-drawer-shown { + $opacity: 0.6; + $backdrop-color: mat-color($background, card, $opacity); + + @if (type-of($backdrop-color) == color) { + // We use invert() here to have the darken the background color expected to be used. If the + // background is light, we use a dark backdrop. If the background is dark, + // we use a light backdrop. + background-color: invert($backdrop-color); + } + @else { + // If we couldn't resolve the backdrop color to a color value, fall back to using + // `opacity` to make it opaque since its end value could be a solid color. + background-color: $backdrop-color; + opacity: $opacity; + } + } +} + +@mixin mat-sidenav-typography($config) { } + + + + + + +@mixin _mat-slide-toggle-checked($palette, $thumb-checked-hue) { + &.mat-checked { + .mat-slide-toggle-thumb { + background-color: mat-color($palette, $thumb-checked-hue); + } + + .mat-slide-toggle-bar { + // Opacity is determined from the specs for the selection controls. + // See: https://material.io/design/components/selection-controls.html#specs + background-color: mat-color($palette, $thumb-checked-hue, 0.54); + } + + .mat-ripple-element { + // Set no opacity for the ripples because the ripple opacity will be adjusted dynamically + // based on the type of interaction with the slide-toggle (e.g. for hover, focus) + background-color: mat-color($palette, $thumb-checked-hue); + } + } +} + +@mixin mat-slide-toggle-theme($theme) { + $is-dark: map_get($theme, is-dark); + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + // Color hues are based on the specs which briefly show the hues that are applied to a switch. + // The 2018 specs no longer describe how dark switches should look like. Due to the lack of + // information for dark themed switches, we partially keep the old behavior that is based on + // the previous specifications. For the checked color we always use the `default` hue because + // that follows MDC and also makes it easier for people to create a custom theme without needing + // to specify each hue individually. + $thumb-unchecked-hue: if($is-dark, 400, 50); + $thumb-checked-hue: default; + + $bar-unchecked-color: mat-color($foreground, disabled); + $ripple-unchecked-color: mat-color($foreground, base); + + .mat-slide-toggle { + @include _mat-slide-toggle-checked($accent, $thumb-checked-hue); + + &.mat-primary { + @include _mat-slide-toggle-checked($primary, $thumb-checked-hue); + } + + &.mat-warn { + @include _mat-slide-toggle-checked($warn, $thumb-checked-hue); + } + + &:not(.mat-checked) .mat-ripple-element { + // Set no opacity for the ripples because the ripple opacity will be adjusted dynamically + // based on the type of interaction with the slide-toggle (e.g. for hover, focus) + background-color: $ripple-unchecked-color; + } + } + + .mat-slide-toggle-thumb { + @include _mat-theme-elevation(1, $theme); + background-color: mat-color($mat-grey, $thumb-unchecked-hue); + } + + .mat-slide-toggle-bar { + background-color: $bar-unchecked-color; + } +} + +@mixin mat-slide-toggle-typography($config) { + .mat-slide-toggle-content { + font-family: mat-font-family($config); + } +} + + + + + +@mixin _mat-slider-inner-content-theme($palette) { + .mat-slider-track-fill, + .mat-slider-thumb, + .mat-slider-thumb-label { + background-color: mat-color($palette); + } + + .mat-slider-thumb-label-text { + color: mat-color($palette, default-contrast); + } +} + +@mixin mat-slider-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + $mat-slider-off-color: mat-color($foreground, slider-off); + $mat-slider-off-focused-color: mat-color($foreground, slider-off-active); + $mat-slider-disabled-color: mat-color($foreground, slider-off); + $mat-slider-labeled-min-value-thumb-color: mat-color($foreground, slider-min); + $mat-slider-labeled-min-value-thumb-label-color: mat-color($foreground, slider-off); + $mat-slider-focus-ring-color: mat-color($accent, default, 0.2); + $mat-slider-focus-ring-min-value-color: mat-color($foreground, base, 0.12); + $mat-slider-tick-color: mat-color($foreground, base, 0.7); + $mat-slider-tick-size: 2px; + + .mat-slider-track-background { + background-color: $mat-slider-off-color; + } + + .mat-primary { + @include _mat-slider-inner-content-theme($primary); + } + + .mat-accent { + @include _mat-slider-inner-content-theme($accent); + } + + .mat-warn { + @include _mat-slider-inner-content-theme($warn); + } + + .mat-slider-focus-ring { + background-color: $mat-slider-focus-ring-color; + } + + .mat-slider:hover, + .cdk-focused { + .mat-slider-track-background { + background-color: $mat-slider-off-focused-color; + } + } + + .mat-slider-disabled { + .mat-slider-track-background, + .mat-slider-track-fill, + .mat-slider-thumb { + background-color: $mat-slider-disabled-color; + } + + &:hover { + .mat-slider-track-background { + background-color: $mat-slider-disabled-color; + } + } + } + + .mat-slider-min-value { + .mat-slider-focus-ring { + background-color: $mat-slider-focus-ring-min-value-color; + } + + &.mat-slider-thumb-label-showing { + .mat-slider-thumb, + .mat-slider-thumb-label { + background-color: $mat-slider-labeled-min-value-thumb-color; + } + + &.cdk-focused { + .mat-slider-thumb, + .mat-slider-thumb-label { + background-color: $mat-slider-labeled-min-value-thumb-label-color; + } + } + } + + &:not(.mat-slider-thumb-label-showing) { + .mat-slider-thumb { + border-color: $mat-slider-off-color; + background-color: transparent; + } + + &:hover, + &.cdk-focused { + .mat-slider-thumb { + border-color: $mat-slider-off-focused-color; + } + + &.mat-slider-disabled .mat-slider-thumb { + border-color: $mat-slider-disabled-color; + } + } + } + } + + .mat-slider-has-ticks .mat-slider-wrapper::after { + border-color: $mat-slider-tick-color; + } + + .mat-slider-horizontal .mat-slider-ticks { + background-image: repeating-linear-gradient(to right, $mat-slider-tick-color, + $mat-slider-tick-color $mat-slider-tick-size, transparent 0, transparent); + // Firefox doesn't draw the gradient correctly with 'to right' + // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1314319). + background-image: -moz-repeating-linear-gradient(0.0001deg, $mat-slider-tick-color, + $mat-slider-tick-color $mat-slider-tick-size, transparent 0, transparent); + } + + .mat-slider-vertical .mat-slider-ticks { + background-image: repeating-linear-gradient(to bottom, $mat-slider-tick-color, + $mat-slider-tick-color $mat-slider-tick-size, transparent 0, transparent); + } +} + +@mixin mat-slider-typography($config) { + .mat-slider-thumb-label-text { + font: { + family: mat-font-family($config); + size: mat-font-size($config, caption); + weight: mat-font-weight($config, body-2); + } + } +} + + + + + +@mixin mat-stepper-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + $primary: map-get($theme, primary); + $warn: map-get($theme, warn); + + .mat-step-header { + &.cdk-keyboard-focused, + &.cdk-program-focused, + &:hover { + background-color: mat-color($background, hover); + } + + // On touch devices the :hover state will linger on the element after a tap. + // Reset it via `@media` after the declaration, because the media query isn't + // supported by all browsers yet. + @media (hover: none) { + &:hover { + background: none; + } + } + + .mat-step-label, + .mat-step-optional { + // TODO(josephperrott): Update to using a corrected disabled-text contrast + // instead of secondary-text. + color: mat-color($foreground, secondary-text); + } + + .mat-step-icon { + // TODO(josephperrott): Update to using a corrected disabled-text contrast + // instead of secondary-text. + background-color: mat-color($foreground, secondary-text); + color: mat-color($primary, default-contrast); + } + + .mat-step-icon-selected, + .mat-step-icon-state-done, + .mat-step-icon-state-edit { + background-color: mat-color($primary); + color: mat-color($primary, default-contrast); + } + + .mat-step-icon-state-error { + background-color: transparent; + color: mat-color($warn, text); + } + + .mat-step-label.mat-step-label-active { + color: mat-color($foreground, text); + } + + .mat-step-label.mat-step-label-error { + color: mat-color($warn, text); + } + } + + .mat-stepper-horizontal, .mat-stepper-vertical { + background-color: mat-color($background, card); + } + + .mat-stepper-vertical-line::before { + border-left-color: mat-color($foreground, divider); + } + + .mat-horizontal-stepper-header::before, + .mat-horizontal-stepper-header::after, + .mat-stepper-horizontal-line { + border-top-color: mat-color($foreground, divider); + } +} + +@mixin mat-stepper-typography($config) { + .mat-stepper-vertical, .mat-stepper-horizontal { + font-family: mat-font-family($config); + } + + .mat-step-label { + font: { + size: mat-font-size($config, body-1); + weight: mat-font-weight($config, body-1); + }; + } + + .mat-step-sub-label-error { + font-weight: normal; + } + + .mat-step-label-error { + font-size: mat-font-size($config, body-2); + } + + .mat-step-label-selected { + font: { + size: mat-font-size($config, body-2); + weight: mat-font-weight($config, body-2); + }; + } +} + +@mixin mat-sort-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-sort-header-arrow { + $table-background: mat-color($background, 'card'); + $text-color: mat-color($foreground, secondary-text); + + // Because the arrow is made up of multiple elements that are stacked on top of each other, + // we can't use the semi-trasparent color from the theme directly. If the value is a color + // *type*, we convert it into a solid color by taking the opacity from the rgba value and + // using the value to determine the percentage of the background to put into foreground + // when mixing the colors together. Otherwise, if it resolves to something different + // (e.g. it resolves to a CSS variable), we use the color directly. + @if (type-of($table-background) == color and type-of($text-color) == color) { + $text-opacity: opacity($text-color); + color: mix($table-background, rgba($text-color, 1), (1 - $text-opacity) * 100%); + } + @else { + color: $text-color; + } + } +} + +@mixin mat-sort-typography($config) { } + + + + + +@mixin mat-tabs-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + $header-border: 1px solid mat-color($foreground, divider); + + .mat-tab-nav-bar, + .mat-tab-header { + border-bottom: $header-border; + } + + .mat-tab-group-inverted-header { + .mat-tab-nav-bar, + .mat-tab-header { + border-top: $header-border; + border-bottom: none; + } + } + + .mat-tab-label, .mat-tab-link { + color: mat-color($foreground, text); + + &.mat-tab-disabled { + color: mat-color($foreground, disabled-text); + } + } + + .mat-tab-header-pagination-chevron { + border-color: mat-color($foreground, text); + } + + .mat-tab-header-pagination-disabled .mat-tab-header-pagination-chevron { + border-color: mat-color($foreground, disabled-text); + } + + // Remove header border when there is a background color + .mat-tab-group[class*='mat-background-'] .mat-tab-header, + .mat-tab-nav-bar[class*='mat-background-'] { + border-bottom: none; + border-top: none; + } + + .mat-tab-group, .mat-tab-nav-bar { + $theme-colors: ( + primary: $primary, + accent: $accent, + warn: $warn + ); + + @each $name, $color in $theme-colors { + // Set the foreground color of the tabs + &.mat-#{$name} { + @include _mat-tab-label-focus($color); + @include _mat-ink-bar($color); + + // Override ink bar when background color is the same + &.mat-background-#{$name} { + @include _mat-ink-bar($color, default-contrast); + } + } + } + + @each $name, $color in $theme-colors { + // Set background color of the tabs and override focus color + &.mat-background-#{$name} { + @include _mat-tab-label-focus($color); + @include _mat-tabs-background($color); + } + } + } +} + +@mixin _mat-ink-bar($color, $hue: default) { + .mat-ink-bar { + background-color: mat-color($color, $hue); + } +} + +@mixin _mat-tab-label-focus($tab-focus-color) { + .mat-tab-label, + .mat-tab-link { + &.cdk-keyboard-focused, + &.cdk-program-focused { + &:not(.mat-tab-disabled) { + background-color: mat-color($tab-focus-color, lighter, 0.3); + } + } + } +} + +@mixin _mat-tabs-background($background-color) { + // Set background color for the tab group + .mat-tab-header, .mat-tab-links, .mat-tab-header-pagination { + background-color: mat-color($background-color); + } + + // Set labels to contrast against background + .mat-tab-label, .mat-tab-link { + color: mat-color($background-color, default-contrast); + + &.mat-tab-disabled { + color: mat-color($background-color, default-contrast, 0.4); + } + } + + // Set pagination chevrons to contrast background + .mat-tab-header-pagination-chevron { + border-color: mat-color($background-color, default-contrast); + } + + .mat-tab-header-pagination-disabled .mat-tab-header-pagination-chevron { + border-color: mat-color($background-color, default-contrast, 0.4); + } + + // Set ripples color to be the contrast color of the new background. Otherwise the ripple + // color will be based on the app background color. + .mat-ripple-element { + background-color: mat-color($background-color, default-contrast, 0.12); + } +} + +@mixin mat-tabs-typography($config) { + .mat-tab-group { + font-family: mat-font-family($config); + } + + .mat-tab-label, .mat-tab-link { + font: { + family: mat-font-family($config, button); + size: mat-font-size($config, button); + weight: mat-font-weight($config, button); + } + } +} + + + + + + +@mixin _mat-toolbar-color($palette) { + background: mat-color($palette); + color: mat-color($palette, default-contrast); +} + +@mixin _mat-toolbar-form-field-overrides { + .mat-form-field-underline, + .mat-form-field-ripple, + .mat-focused .mat-form-field-ripple { + background-color: currentColor; + } + + .mat-form-field-label, + .mat-focused .mat-form-field-label, + .mat-select-value, + .mat-select-arrow, + .mat-form-field.mat-focused .mat-select-arrow { + color: inherit; + } + + .mat-input-element { + caret-color: currentColor; + } +} + +@mixin mat-toolbar-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-toolbar { + background: mat-color($background, app-bar); + color: mat-color($foreground, text); + + &.mat-primary { + @include _mat-toolbar-color($primary); + } + + &.mat-accent { + @include _mat-toolbar-color($accent); + } + + &.mat-warn { + @include _mat-toolbar-color($warn); + } + + @include _mat-toolbar-form-field-overrides; + } +} + +@mixin mat-toolbar-typography($config) { + .mat-toolbar, + .mat-toolbar h1, + .mat-toolbar h2, + .mat-toolbar h3, + .mat-toolbar h4, + .mat-toolbar h5, + .mat-toolbar h6 { + @include mat-typography-level-to-styles($config, title); + margin: 0; + } +} + + + + + +$mat-tooltip-target-height: 22px; +$mat-tooltip-font-size: 10px; +$mat-tooltip-vertical-padding: ($mat-tooltip-target-height - $mat-tooltip-font-size) / 2; + +$mat-tooltip-handset-target-height: 30px; +$mat-tooltip-handset-font-size: 14px; +$mat-tooltip-handset-vertical-padding: + ($mat-tooltip-handset-target-height - $mat-tooltip-handset-font-size) / 2; + +@mixin mat-tooltip-theme($theme) { + .mat-tooltip { + background: mat-color($mat-grey, 700, 0.9); + } +} + +@mixin mat-tooltip-typography($config) { + .mat-tooltip { + font-family: mat-font-family($config); + font-size: $mat-tooltip-font-size; + padding-top: $mat-tooltip-vertical-padding; + padding-bottom: $mat-tooltip-vertical-padding; + } + + .mat-tooltip-handset { + font-size: $mat-tooltip-handset-font-size; + padding-top: $mat-tooltip-handset-vertical-padding; + padding-bottom: $mat-tooltip-handset-vertical-padding; + } +} + + + + + +@mixin mat-snack-bar-theme($theme) { + $is-dark-theme: map-get($theme, is-dark); + $accent: map-get($theme, accent); + + .mat-snack-bar-container { + // Use the primary text on the dark theme, even though the lighter one uses + // a secondary, because the contrast on the light primary text is poor. + color: if($is-dark-theme, $dark-primary-text, $light-secondary-text); + background: if($is-dark-theme, map-get($mat-grey, 50), #323232); + + @include _mat-theme-elevation(6, $theme); + } + + .mat-simple-snackbar-action { + color: if($is-dark-theme, inherit, mat-color($accent, text)); + } +} + +@mixin mat-snack-bar-typography($config) { + .mat-simple-snackbar { + font: { + family: mat-font-family($config, body-1); + size: mat-font-size($config, body-1); + } + } + + .mat-simple-snackbar-action { + line-height: 1; + font: { + family: inherit; + size: inherit; + weight: mat-font-weight($config, button); + } + } +} + + + + + + + + + + + + + + +// Theme styles that only apply to the fill appearance of the form-field. + +@mixin mat-form-field-fill-theme($theme) { + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + + $fill-background: mat-color($foreground, base, if($is-dark-theme, 0.1, 0.04)); + $fill-disabled-background: mat-color($foreground, base, if($is-dark-theme, 0.05, 0.02)); + $underline-color: mat-color($foreground, divider, if($is-dark-theme, 0.5, 0.42)); + $label-disabled-color: mat-color($foreground, disabled-text); + + .mat-form-field-appearance-fill { + .mat-form-field-flex { + background-color: $fill-background; + } + + &.mat-form-field-disabled .mat-form-field-flex { + background-color: $fill-disabled-background; + } + + .mat-form-field-underline::before { + background-color: $underline-color; + } + + &.mat-form-field-disabled { + .mat-form-field-label { + color: $label-disabled-color; + } + + .mat-form-field-underline::before { + background-color: transparent; + } + } + } +} + +// Used to make instances of the _mat-form-field-label-floating mixin negligibly different, +// and prevent Google's CSS Optimizer from collapsing the declarations. This is needed because some +// of the selectors contain pseudo-classes not recognized in all browsers. If a browser encounters +// an unknown pseudo-class it will discard the entire rule set. +$mat-form-field-fill-dedupe: 0; + +// Applies a floating label above the form field control itself. +@mixin _mat-form-field-fill-label-floating($font-scale, $infix-padding, $infix-margin-top) { + transform: translateY(-$infix-margin-top - $infix-padding + $mat-form-field-fill-dedupe) + scale($font-scale); + width: 100% / $font-scale + $mat-form-field-fill-dedupe; + + $mat-form-field-fill-dedupe: $mat-form-field-fill-dedupe + 0.00001 !global; +} + +@mixin mat-form-field-fill-typography($config) { + // The unit-less line-height from the font config. + $line-height: mat-line-height($config, input); + // The amount to scale the font for the floating label and subscript. + $subscript-font-scale: 0.75; + // The padding on top of the infix. + $infix-padding-top: 0.25em; + // The padding below the infix. + $infix-padding-bottom: 0.75em; + // The margin applied to the form-field-infix to reserve space for the floating label. + $infix-margin-top: 1em * $line-height * $subscript-font-scale; + // The amount we offset the label from the input text in the fill appearance. + $fill-appearance-label-offset: -0.5em; + + .mat-form-field-appearance-fill { + .mat-form-field-infix { + padding: $infix-padding-top 0 $infix-padding-bottom 0; + } + + .mat-form-field-label { + top: $infix-margin-top + $infix-padding-top; + margin-top: $fill-appearance-label-offset; + } + + &.mat-form-field-can-float { + &.mat-form-field-should-float .mat-form-field-label, + .mat-input-server:focus + .mat-form-field-label-wrapper .mat-form-field-label { + @include _mat-form-field-fill-label-floating( + $subscript-font-scale, $infix-padding-top + $fill-appearance-label-offset, + $infix-margin-top); + } + + // Server-side rendered matInput with a label attribute but label not shown + // (used as a pure CSS stand-in for mat-form-field-should-float). + .mat-input-server[label]:not(:label-shown) + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-fill-label-floating( + $subscript-font-scale, $infix-padding-top + $fill-appearance-label-offset, + $infix-margin-top); + } + } + } +} + + + + + + + +// Theme styles that only apply to the legacy appearance of the form-field. + +@mixin mat-form-field-legacy-theme($theme) { + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + + $label-color: mat-color($foreground, secondary-text); + $underline-color: mat-color($foreground, divider, if($is-dark-theme, 0.7, 0.42)); + + .mat-form-field-appearance-legacy { + .mat-form-field-label { + color: $label-color; + } + + .mat-hint { + color: $label-color; + } + + .mat-form-field-underline { + background-color: $underline-color; + } + + &.mat-form-field-disabled .mat-form-field-underline { + @include mat-control-disabled-underline($underline-color); + } + } +} + +// Used to make instances of the _mat-form-field-label-floating mixin negligibly different, +// and prevent Google's CSS Optimizer from collapsing the declarations. This is needed because some +// of the selectors contain pseudo-classes not recognized in all browsers. If a browser encounters +// an unknown pseudo-class it will discard the entire rule set. +$mat-form-field-legacy-dedupe: 0; + +// Applies a floating label above the form field control itself. +@mixin _mat-form-field-legacy-label-floating($font-scale, $infix-padding, $infix-margin-top) { + // We use perspective to fix the text blurriness as described here: + // http://www.useragentman.com/blog/2014/05/04/fixing-typography-inside-of-2-d-css-transforms/ + // This results in a small jitter after the label floats on Firefox, which the + // translateZ fixes. + transform: translateY(-$infix-margin-top - $infix-padding) scale($font-scale) perspective(100px) + translateZ(0.001px + $mat-form-field-legacy-dedupe); + // The tricks above used to smooth out the animation on chrome and firefox actually make things + // worse on IE, so we don't include them in the IE version. + -ms-transform: translateY(-$infix-margin-top - $infix-padding + $mat-form-field-legacy-dedupe) + scale($font-scale); + + width: 100% / $font-scale + $mat-form-field-legacy-dedupe; + + $mat-form-field-legacy-dedupe: $mat-form-field-legacy-dedupe + 0.00001 !global; +} + +// Same as mixin above, but omits the translateZ for printing purposes. +@mixin _mat-form-field-legacy-label-floating-print($font-scale, $infix-padding, $infix-margin-top) { + // This results in a small jitter after the label floats on Firefox, which the + // translateZ fixes. + transform: translateY(-$infix-margin-top - $infix-padding + $mat-form-field-legacy-dedupe) + scale($font-scale); + // The tricks above used to smooth out the animation on chrome and firefox actually make things + // worse on IE, so we don't include them in the IE version. + $mat-form-field-legacy-dedupe: $mat-form-field-legacy-dedupe + 0.00001 !global; +} + +@mixin mat-form-field-legacy-typography($config) { + // The unit-less line-height from the font config. + $line-height: mat-line-height($config, input); + // The amount to scale the font for the floating label and subscript. + $subscript-font-scale: 0.75; + // The amount of space between the top of the line and the top of the actual text + // (as a fraction of the font-size). + $line-spacing: ($line-height - 1) / 2; + // The padding on the infix. Mocks show half of the text size, but seem to measure from the edge + // of the text itself, not the edge of the line; therefore we subtract off the line spacing. + $infix-padding: 0.5em - $line-spacing; + // The margin applied to the form-field-infix to reserve space for the floating label. + $infix-margin-top: 1em * $line-height * $subscript-font-scale; + // The space between the bottom of the .mat-form-field-flex area and the subscript wrapper. + // Mocks show half of the text size, but this margin is applied to an element with the subscript + // text font size, so we need to divide by the scale factor to make it half of the original text + // size. We again need to subtract off the line spacing since the mocks measure to the edge of the + // text, not the edge of the line. + $subscript-margin-top: 0.5em / $subscript-font-scale - ($line-spacing * 2); + // The padding applied to the form-field-wrapper to reserve space for the subscript, since it's + // absolutely positioned. This is a combination of the subscript's margin and line-height, but we + // need to multiply by the subscript font scale factor since the wrapper has a larger font size. + $wrapper-padding-bottom: ($subscript-margin-top + $line-height) * $subscript-font-scale; + + .mat-form-field-appearance-legacy { + .mat-form-field-wrapper { + padding-bottom: $wrapper-padding-bottom; + } + + .mat-form-field-infix { + padding: $infix-padding 0; + } + + &.mat-form-field-can-float { + &.mat-form-field-should-float .mat-form-field-label, + .mat-input-server:focus + .mat-form-field-label-wrapper .mat-form-field-label { + @include _mat-form-field-legacy-label-floating( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + + // @breaking-change 8.0.0 will rely on AutofillMonitor instead. + .mat-form-field-autofill-control:-webkit-autofill + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-legacy-label-floating( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + + // Server-side rendered matInput with a label attribute but label not shown + // (used as a pure CSS stand-in for mat-form-field-should-float). + .mat-input-server[label]:not(:label-shown) + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-legacy-label-floating( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + } + + .mat-form-field-label { + top: $infix-margin-top + $infix-padding; + } + + .mat-form-field-underline { + // We want the underline to start at the end of the content box, not the padding box, + // so we move it up by the padding amount. + bottom: $wrapper-padding-bottom; + } + + .mat-form-field-subscript-wrapper { + margin-top: $subscript-margin-top; + + // We want the subscript to start at the end of the content box, not the padding box, + // so we move it up by the padding amount (adjusted for the smaller font size); + top: calc(100% - #{$wrapper-padding-bottom / $subscript-font-scale}); + } + } + + // translateZ causes the label to not appear while printing, so we override it to not + // apply translateZ while printing + @media print { + .mat-form-field-appearance-legacy { + &.mat-form-field-can-float { + &.mat-form-field-should-float .mat-form-field-label, + .mat-input-server:focus + .mat-form-field-label-wrapper .mat-form-field-label { + @include _mat-form-field-legacy-label-floating-print( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + + // @breaking-change 8.0.0 will rely on AutofillMonitor instead. + .mat-form-field-autofill-control:-webkit-autofill + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-legacy-label-floating-print( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + + // Server-side rendered matInput with a label attribute but label not shown + // (used as a pure CSS stand-in for mat-form-field-should-float). + .mat-input-server[label]:not(:label-shown) + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-legacy-label-floating-print( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + } + } + } +} + + + + + + + +// Theme styles that only apply to the outline appearance of the form-field. + +@mixin mat-form-field-outline-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + + $label-disabled-color: mat-color($foreground, disabled-text); + $outline-color: mat-color($foreground, divider, if($is-dark-theme, 0.3, 0.12)); + $outline-color-hover: mat-color($foreground, divider, if($is-dark-theme, 1, 0.87)); + $outline-color-primary: mat-color($primary); + $outline-color-accent: mat-color($accent); + $outline-color-warn: mat-color($warn); + $outline-color-disabled: mat-color($foreground, divider, if($is-dark-theme, 0.15, 0.06)); + + .mat-form-field-appearance-outline { + .mat-form-field-outline { + color: $outline-color; + } + + .mat-form-field-outline-thick { + color: $outline-color-hover; + } + + &.mat-focused { + .mat-form-field-outline-thick { + color: $outline-color-primary; + } + + &.mat-accent .mat-form-field-outline-thick { + color: $outline-color-accent; + } + + &.mat-warn .mat-form-field-outline-thick { + color: $outline-color-warn; + } + } + + // Class repeated so that rule is specific enough to override focused accent color case. + &.mat-form-field-invalid.mat-form-field-invalid { + .mat-form-field-outline-thick { + color: $outline-color-warn; + } + } + + &.mat-form-field-disabled { + .mat-form-field-label { + color: $label-disabled-color; + } + + .mat-form-field-outline { + color: $outline-color-disabled; + } + } + } +} + +// Used to make instances of the _mat-form-field-label-floating mixin negligibly different, +// and prevent Google's CSS Optimizer from collapsing the declarations. This is needed because some +// of the selectors contain pseudo-classes not recognized in all browsers. If a browser encounters +// an unknown pseudo-class it will discard the entire rule set. +$mat-form-field-outline-dedupe: 0; + +// Applies a floating label above the form field control itself. +@mixin _mat-form-field-outline-label-floating($font-scale, $infix-padding, $infix-margin-top) { + transform: translateY(-$infix-margin-top - $infix-padding + $mat-form-field-outline-dedupe) + scale($font-scale); + width: 100% / $font-scale + $mat-form-field-outline-dedupe; + + $mat-form-field-outline-dedupe: $mat-form-field-outline-dedupe + 0.00001 !global; +} + +@mixin mat-form-field-outline-typography($config) { + // The unit-less line-height from the font config. + $line-height: mat-line-height($config, input); + // The amount to scale the font for the floating label and subscript. + $subscript-font-scale: 0.75; + // The padding above and below the infix. + $infix-padding: 1em; + // The margin applied to the form-field-infix to reserve space for the floating label. + $infix-margin-top: 1em * $line-height * $subscript-font-scale; + // The space between the bottom of the .mat-form-field-flex area and the subscript wrapper. + // Mocks show half of the text size, but this margin is applied to an element with the subscript + // text font size, so we need to divide by the scale factor to make it half of the original text + // size. + $subscript-margin-top: 0.5em / $subscript-font-scale; + // The padding applied to the form-field-wrapper to reserve space for the subscript, since it's + // absolutely positioned. This is a combination of the subscript's margin and line-height, but we + // need to multiply by the subscript font scale factor since the wrapper has a larger font size. + $wrapper-padding-bottom: ($subscript-margin-top + $line-height) * $subscript-font-scale; + // The amount we offset the label from the input text in the outline appearance. + $outline-appearance-label-offset: -0.25em; + + .mat-form-field-appearance-outline { + .mat-form-field-infix { + padding: $infix-padding 0 $infix-padding 0; + } + + .mat-form-field-label { + top: $infix-margin-top + $infix-padding; + margin-top: $outline-appearance-label-offset; + } + + &.mat-form-field-can-float { + &.mat-form-field-should-float .mat-form-field-label, + .mat-input-server:focus + .mat-form-field-label-wrapper .mat-form-field-label { + @include _mat-form-field-outline-label-floating( + $subscript-font-scale, $infix-padding + $outline-appearance-label-offset, + $infix-margin-top); + } + + // Server-side rendered matInput with a label attribute but label not shown + // (used as a pure CSS stand-in for mat-form-field-should-float). + .mat-input-server[label]:not(:label-shown) + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-outline-label-floating( + $subscript-font-scale, $infix-padding + $outline-appearance-label-offset, + $infix-margin-top); + } + } + } +} + + + + + + + +// Theme styles that only apply to the standard appearance of the form-field. + +@mixin mat-form-field-standard-theme($theme) { + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + + $underline-color: mat-color($foreground, divider, if($is-dark-theme, 0.7, 0.42)); + + .mat-form-field-appearance-standard { + .mat-form-field-underline { + background-color: $underline-color; + } + + &.mat-form-field-disabled .mat-form-field-underline { + @include mat-control-disabled-underline($underline-color); + } + } +} + +@mixin mat-form-field-standard-typography($config) {} + + +// Theme styles that apply to all appearances of the form-field. +@mixin mat-form-field-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + + // Label colors. Required is used for the `*` star shown in the label. + $label-color: mat-color($foreground, secondary-text, if($is-dark-theme, 0.7, 0.6)); + $focused-label-color: mat-color($primary, text); + $required-label-color: mat-color($accent, text); + + // Underline colors. + $underline-color-base: mat-color($foreground, divider, if($is-dark-theme, 1, 0.87)); + $underline-color-accent: mat-color($accent, text); + $underline-color-warn: mat-color($warn, text); + $underline-focused-color: mat-color($primary, text); + + .mat-form-field-label { + color: $label-color; + } + + .mat-hint { + color: $label-color; + } + + .mat-form-field.mat-focused .mat-form-field-label { + color: $focused-label-color; + + &.mat-accent { + color: $underline-color-accent; + } + + &.mat-warn { + color: $underline-color-warn; + } + } + + .mat-focused .mat-form-field-required-marker { + color: $required-label-color; + } + + .mat-form-field-ripple { + background-color: $underline-color-base; + } + + .mat-form-field.mat-focused { + .mat-form-field-ripple { + background-color: $underline-focused-color; + + &.mat-accent { + background-color: $underline-color-accent; + } + + &.mat-warn { + background-color: $underline-color-warn; + } + } + } + + .mat-form-field-type-mat-native-select.mat-focused:not(.mat-form-field-invalid) { + .mat-form-field-infix::after { + color: $underline-focused-color; + } + + &.mat-accent .mat-form-field-infix::after { + color: $underline-color-accent; + } + + &.mat-warn .mat-form-field-infix::after { + color: $underline-color-warn; + } + } + + // Styling for the error state of the form field. Note that while the same can be + // achieved with the ng-* classes, we use this approach in order to ensure that the same + // logic is used to style the error state and to show the error messages. + .mat-form-field.mat-form-field-invalid { + .mat-form-field-label { + color: $underline-color-warn; + + &.mat-accent, + .mat-form-field-required-marker { + color: $underline-color-warn; + } + } + + .mat-form-field-ripple, + .mat-form-field-ripple.mat-accent { + background-color: $underline-color-warn; + } + } + + .mat-error { + color: $underline-color-warn; + } + + @include mat-form-field-legacy-theme($theme); + @include mat-form-field-standard-theme($theme); + @include mat-form-field-fill-theme($theme); + @include mat-form-field-outline-theme($theme); +} + +// Used to make instances of the _mat-form-field-label-floating mixin negligibly different, +// and prevent Google's CSS Optimizer from collapsing the declarations. This is needed because some +// of the selectors contain pseudo-classes not recognized in all browsers. If a browser encounters +// an unknown pseudo-class it will discard the entire rule set. +$mat-form-field-dedupe: 0; + +// Applies a floating label above the form field control itself. +@mixin _mat-form-field-label-floating($font-scale, $infix-padding, $infix-margin-top) { + transform: translateY(-$infix-margin-top - $infix-padding + $mat-form-field-dedupe) + scale($font-scale); + width: 100% / $font-scale + $mat-form-field-dedupe; + + $mat-form-field-dedupe: $mat-form-field-dedupe + 0.00001 !global; +} + +@mixin mat-form-field-typography($config) { + // The unit-less line-height from the font config. + $line-height: mat-line-height($config, input); + + // The amount to scale the font for the floating label and subscript. + $subscript-font-scale: 0.75; + // The amount to scale the font for the prefix and suffix icons. + $prefix-suffix-icon-font-scale: 1.5; + + // The padding on the infix. Mocks show half of the text size. + $infix-padding: 0.5em; + // The margin applied to the form-field-infix to reserve space for the floating label. + $infix-margin-top: 1em * $line-height * $subscript-font-scale; + // Font size to use for the label and subscript text. + $subscript-font-size: $subscript-font-scale * 100%; + // Font size to use for the for the prefix and suffix icons. + $prefix-suffix-icon-font-size: $prefix-suffix-icon-font-scale * 100%; + // The space between the bottom of the .mat-form-field-flex area and the subscript wrapper. + // Mocks show half of the text size, but this margin is applied to an element with the subscript + // text font size, so we need to divide by the scale factor to make it half of the original text + // size. + $subscript-margin-top: 0.5em / $subscript-font-scale; + // The padding applied to the form-field-wrapper to reserve space for the subscript, since it's + // absolutely positioned. This is a combination of the subscript's margin and line-height, but we + // need to multiply by the subscript font scale factor since the wrapper has a larger font size. + $wrapper-padding-bottom: ($subscript-margin-top + $line-height) * $subscript-font-scale; + + .mat-form-field { + @include mat-typography-level-to-styles($config, input); + } + + .mat-form-field-wrapper { + padding-bottom: $wrapper-padding-bottom; + } + + .mat-form-field-prefix, + .mat-form-field-suffix { + // Allow icons in a prefix or suffix to adapt to the correct size. + .mat-icon { + font-size: $prefix-suffix-icon-font-size; + line-height: $line-height; + } + + // Allow icon buttons in a prefix or suffix to adapt to the correct size. + .mat-icon-button { + height: $prefix-suffix-icon-font-scale * 1em; + width: $prefix-suffix-icon-font-scale * 1em; + + .mat-icon { + height: $line-height * 1em; + line-height: $line-height; + } + } + } + + .mat-form-field-infix { + padding: $infix-padding 0; + // Throws off the baseline if we do it as a real margin, so we do it as a border instead. + border-top: $infix-margin-top solid transparent; + } + + .mat-form-field-can-float { + &.mat-form-field-should-float .mat-form-field-label, + .mat-input-server:focus + .mat-form-field-label-wrapper .mat-form-field-label { + @include _mat-form-field-label-floating( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + + // Server-side rendered matInput with a label attribute but label not shown + // (used as a pure CSS stand-in for mat-form-field-should-float). + .mat-input-server[label]:not(:label-shown) + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-label-floating( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + } + + .mat-form-field-label-wrapper { + top: -$infix-margin-top; + padding-top: $infix-margin-top; + } + + .mat-form-field-label { + top: $infix-margin-top + $infix-padding; + } + + .mat-form-field-underline { + // We want the underline to start at the end of the content box, not the padding box, + // so we move it up by the padding amount. + bottom: $wrapper-padding-bottom; + } + + .mat-form-field-subscript-wrapper { + font-size: $subscript-font-size; + margin-top: $subscript-margin-top; + + // We want the subscript to start at the end of the content box, not the padding box, + // so we move it up by the padding amount (adjusted for the smaller font size); + top: calc(100% - #{$wrapper-padding-bottom / $subscript-font-scale}); + } + + @include mat-form-field-legacy-typography($config); + @include mat-form-field-standard-typography($config); + @include mat-form-field-fill-typography($config); + @include mat-form-field-outline-typography($config); +} + + + + + +@mixin mat-tree-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-tree { + background: mat-color($background, 'card'); + } + + .mat-tree-node, + .mat-nested-tree-node { + color: mat-color($foreground, text); + } +} + +@mixin mat-tree-typography($config) { + .mat-tree { + font-family: mat-font-family($config); + } + + .mat-tree-node, + .mat-nested-tree-node { + font-weight: mat-font-weight($config, body-1); + font-size: mat-font-size($config, body-1); + } +} + + + +// Includes all of the typographic styles. +@mixin angular-material-typography($config: null) { + @if $config == null { + $config: mat-typography-config(); + } + + @include mat-badge-typography($config); + @include mat-base-typography($config); + @include mat-autocomplete-typography($config); + @include mat-bottom-sheet-typography($config); + @include mat-button-typography($config); + @include mat-button-toggle-typography($config); + @include mat-card-typography($config); + @include mat-checkbox-typography($config); + @include mat-chips-typography($config); + @include mat-table-typography($config); + @include mat-datepicker-typography($config); + @include mat-dialog-typography($config); + @include mat-expansion-panel-typography($config); + @include mat-form-field-typography($config); + @include mat-grid-list-typography($config); + @include mat-icon-typography($config); + @include mat-input-typography($config); + @include mat-menu-typography($config); + @include mat-paginator-typography($config); + @include mat-progress-bar-typography($config); + @include mat-progress-spinner-typography($config); + @include mat-radio-typography($config); + @include mat-select-typography($config); + @include mat-sidenav-typography($config); + @include mat-slide-toggle-typography($config); + @include mat-slider-typography($config); + @include mat-stepper-typography($config); + @include mat-sort-typography($config); + @include mat-tabs-typography($config); + @include mat-toolbar-typography($config); + @include mat-tooltip-typography($config); + @include mat-list-typography($config); + @include mat-option-typography($config); + @include mat-optgroup-typography($config); + @include mat-snack-bar-typography($config); + @include mat-tree-typography($config); +} + + +// Mixin that renders all of the core styles that are not theme-dependent. +@mixin mat-core($typography-config: null) { + @include angular-material-typography($typography-config); + @include mat-ripple(); + @include cdk-a11y(); + @include cdk-overlay(); + @include cdk-text-field(); +} + +// Mixin that renders all of the core styles that depend on the theme. +@mixin mat-core-theme($theme) { + @include mat-ripple-theme($theme); + @include mat-option-theme($theme); + @include mat-optgroup-theme($theme); + @include mat-pseudo-checkbox-theme($theme); + + // Provides external CSS classes for each elevation value. Each CSS class is formatted as + // `mat-elevation-z$zValue` where `$zValue` corresponds to the z-space to which the element is + // elevated. + @for $zValue from 0 through 24 { + .#{$_mat-elevation-prefix}#{$zValue} { + @include _mat-theme-elevation($zValue, $theme); + } + } + + // Wrapper element that provides the theme background when the user's content isn't + // inside of a `mat-sidenav-container`. Note that we need to exclude the ampersand + // selector in case the mixin is included at the top level. + .mat-app-background#{if(&, ', &.mat-app-background', '')} { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + background-color: mat-color($background, background); + color: mat-color($foreground, text); + } + + // Marker that is used to determine whether the user has added a theme to their page. + @at-root { + .mat-theme-loaded-marker { + display: none; + } + } +} + + + + + + + + + + + + + + + + +@mixin mat-divider-theme($theme) { + $foreground: map-get($theme, foreground); + + .mat-divider { + border-top-color: mat-color($foreground, divider); + } + + .mat-divider-vertical { + border-right-color: mat-color($foreground, divider); + } +} + + + + + + + + + + + + + + + + + + + + + + + + + +// Create a theme. +@mixin angular-material-theme($theme) { + @include mat-core-theme($theme); + @include mat-autocomplete-theme($theme); + @include mat-badge-theme($theme); + @include mat-bottom-sheet-theme($theme); + @include mat-button-theme($theme); + @include mat-button-toggle-theme($theme); + @include mat-card-theme($theme); + @include mat-checkbox-theme($theme); + @include mat-chips-theme($theme); + @include mat-table-theme($theme); + @include mat-datepicker-theme($theme); + @include mat-dialog-theme($theme); + @include mat-divider-theme($theme); + @include mat-expansion-panel-theme($theme); + @include mat-form-field-theme($theme); + @include mat-grid-list-theme($theme); + @include mat-icon-theme($theme); + @include mat-input-theme($theme); + @include mat-list-theme($theme); + @include mat-menu-theme($theme); + @include mat-paginator-theme($theme); + @include mat-progress-bar-theme($theme); + @include mat-progress-spinner-theme($theme); + @include mat-radio-theme($theme); + @include mat-select-theme($theme); + @include mat-sidenav-theme($theme); + @include mat-slide-toggle-theme($theme); + @include mat-slider-theme($theme); + @include mat-stepper-theme($theme); + @include mat-sort-theme($theme); + @include mat-tabs-theme($theme); + @include mat-toolbar-theme($theme); + @include mat-tooltip-theme($theme); + @include mat-tree-theme($theme); + @include mat-snack-bar-theme($theme); +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\custom-effects.scss + +// +button:hover:not([disabled]), +ion-button:hover:not([disabled]) { + @include animation("flash", 1500ms); +} + +.picker-opt-disabled { + opacity: 0.5; +} + +.action-sheet-cancel, +.action-sheet-setructive, +.action-sheet-group-cancel { + color: var(--x-color-danger) !important; +} + +// +// Dialog Theme ... +::ng-deep.mat-card, +::ng-deep.mat-expansion-panel, +::ng-deep.mat-dialog-container { + color: var(--x-color-light-contrast); + background-color: var(--x-color-light); +} + +::ng-deep.full-with { + .x-wrapper { + width: 100%; + } +} + +.full-with { + width: 100%; +} + +.x-minimal-popover { + .popover-content { + width: 90px !important; + max-width: 90px !important; + } +} + +.dynamic-slot { + flex: 0 auto !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\fab-theme.scss + +// +//#region Fab ... +$isRTL: var(--is-rtl); +$fabMargin: 16px; + +x-fab[pageFab] { + // + ion-fab { + // + @if $isRTL { + left: $fabMargin !important; + right: auto !important; + } @else { + right: $fabMargin !important; + left: auto !important; + } + + // + position: fixed !important; + margin-top: calc(var(--content-offset-bottom) * -1); + margin-bottom: calc(var(--content-offset-bottom) * -1); + + // + transition: transform 250ms ease 0s, opacity 250ms ease 0s; + } + + // + &.start-scroll { + ion-fab { + transform: translateY(200px) scale3d(0, 0, 0) !important; + } + } +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\markdown-theme.scss + +/** + * MIT License + * Copyright (c) 2021 Ayush Saini + * Holi Theme for prism.js + * @author Ayush Saini <@AyushCodes on Twitter> + */ + + code[class*='language-'], + pre[class*='language-'] { + color: #d6e7ff; + background: #030314; + text-shadow: none; + font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; + font-size: 1em; + line-height: 1.5; + letter-spacing: .2px; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + text-align: left; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; + } + + pre[class*='language-']::-moz-selection, + pre[class*='language-'] ::-moz-selection, + code[class*='language-']::-moz-selection, + code[class*='language-'] ::-moz-selection, + pre[class*='language-']::selection, + pre[class*='language-'] ::selection, + code[class*='language-']::selection, + code[class*='language-'] ::selection { + color: inherit; + background: #1d3b54; + text-shadow: none; + } + + pre[class*='language-'] { + border: 1px solid #2a4555; + border-radius: 5px; + padding: 1.5em 1em; + margin: 1em 0; + overflow: auto; + } + + :not(pre) > code[class*='language-'] { + color: #f0f6f6; + background: #2a4555; + padding: 0.2em 0.3em; + border-radius: 0.2em; + box-decoration-break: clone; + } + + .token.comment, + .token.prolog, + .token.doctype, + .token.cdata { + color: #446e69; + } + + .token.punctuation { + color: #d6b007; + } + + .token.property, + .token.tag, + .token.boolean, + .token.number, + .token.constant, + .token.symbol, + .token.deleted { + color: #d6e7ff; + } + + .token.selector, + .token.attr-name, + .token.builtin, + .token.inserted { + color: #e60067; + } + + .token.string, + .token.char { + color: #49c6ec; + } + + .token.operator, + .token.entity, + .token.url, + .language-css .token.string, + .style .token.string { + color: #ec8e01; + background: transparent; + } + + .token.atrule, + .token.attr-value, + .token.keyword { + color: #0fe468; + } + + .token.function, + .token.class-name { + color: #78f3e9; + } + + .token.regex, + .token.important, + .token.variable { + color: #d6e7ff; + } + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\material-fonts.scss + +// +//#region Material Fonts ... +/* cyrillic-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + src: local("Roboto Light"), local("Roboto-Light"), + url("../assets/font/KFOlCnqEu92Fr1MmSU5fCRc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, + U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + src: local("Roboto Light"), local("Roboto-Light"), + url("../assets/font/KFOlCnqEu92Fr1MmSU5fABc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + src: local("Roboto Light"), local("Roboto-Light"), + url("../assets/font/KFOlCnqEu92Fr1MmSU5fCBc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + src: local("Roboto Light"), local("Roboto-Light"), + url("../assets/font/KFOlCnqEu92Fr1MmSU5fBxc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + src: local("Roboto Light"), local("Roboto-Light"), + url("../assets/font/KFOlCnqEu92Fr1MmSU5fCxc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + src: local("Roboto Light"), local("Roboto-Light"), + url("../assets/font/KFOlCnqEu92Fr1MmSU5fChc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + src: local("Roboto Light"), local("Roboto-Light"), + url("../assets/font/KFOlCnqEu92Fr1MmSU5fBBc4AMP6lQ.woff2") + format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +/* Persian */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + src: url("../assets/font/irsans.ttf"); + unicode-renge: U+0621, U+0623, U+0626, U+0624, U+0627, U+0628, U+067E, U+062A, + U+062B, U+062C, U+0686, U+062D, U+062E, U+062F, U+0630, U+0631, U+0632, + U+0698, U+0633, U+0634, U+0635, U+0636, U+0637, U+0638, U+0639, U+063A, + U+0641, U+0642, U+06A9, U+06AF, U+0644, U+0645, U+0646, U+0648, U+0647, + U+06CC, U+0622, U+06C0, U+0644, U+0627, U+0640, U+067E, U+0686, U+0698, + U+06AF, U+0660, U+0661, U+0662, U+0663, U+0664, U+0665, U+0666, U+0667, + U+0668, U+0669, U+064A, U+0643; +} + +/* cyrillic-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + src: local("Roboto"), local("Roboto-Regular"), + url("../assets/font/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.woff2") + format("woff2"); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, + U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + src: local("Roboto"), local("Roboto-Regular"), + url("../assets/font/KFOmCnqEu92Fr1Mu5mxKKTU1Kvnz.woff2") + format("woff2"); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + src: local("Roboto"), local("Roboto-Regular"), + url("../assets/font/KFOmCnqEu92Fr1Mu7mxKKTU1Kvnz.woff2") + format("woff2"); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + src: local("Roboto"), local("Roboto-Regular"), + url("../assets/font/KFOmCnqEu92Fr1Mu4WxKKTU1Kvnz.woff2") + format("woff2"); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + src: local("Roboto"), local("Roboto-Regular"), + url("../assets/font/KFOmCnqEu92Fr1Mu7WxKKTU1Kvnz.woff2") + format("woff2"); + unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + src: local("Roboto"), local("Roboto-Regular"), + url("../assets/font/KFOmCnqEu92Fr1Mu7GxKKTU1Kvnz.woff2") + format("woff2"); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + src: local("Roboto"), local("Roboto-Regular"), + url("../assets/font/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2") + format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +/* Persian */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + src: url("../assets/font/irsans.ttf"); + unicode-renge: U+0621, U+0623, U+0626, U+0624, U+0627, U+0628, U+067E, U+062A, + U+062B, U+062C, U+0686, U+062D, U+062E, U+062F, U+0630, U+0631, U+0632, + U+0698, U+0633, U+0634, U+0635, U+0636, U+0637, U+0638, U+0639, U+063A, + U+0641, U+0642, U+06A9, U+06AF, U+0644, U+0645, U+0646, U+0648, U+0647, + U+06CC, U+0622, U+06C0, U+0644, U+0627, U+0640, U+067E, U+0686, U+0698, + U+06AF, U+0660, U+0661, U+0662, U+0663, U+0664, U+0665, U+0666, U+0667, + U+0668, U+0669, U+064A, U+0643; +} + + +/* cyrillic-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + src: local("Roboto Medium"), local("Roboto-Medium"), + url("../assets/font/KFOlCnqEu92Fr1MmEU9fCRc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, + U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + src: local("Roboto Medium"), local("Roboto-Medium"), + url("../assets/font/KFOlCnqEu92Fr1MmEU9fABc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + src: local("Roboto Medium"), local("Roboto-Medium"), + url("../assets/font/KFOlCnqEu92Fr1MmEU9fCBc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + src: local("Roboto Medium"), local("Roboto-Medium"), + url("../assets/font/KFOlCnqEu92Fr1MmEU9fBxc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + src: local("Roboto Medium"), local("Roboto-Medium"), + url("../assets/font/KFOlCnqEu92Fr1MmEU9fCxc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + src: local("Roboto Medium"), local("Roboto-Medium"), + url("../assets/font/KFOlCnqEu92Fr1MmEU9fChc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + src: local("Roboto Medium"), local("Roboto-Medium"), + url("../assets/font/KFOlCnqEu92Fr1MmEU9fBBc4AMP6lQ.woff2") + format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +/* Persian */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + src: url("../assets/font/irsans.ttf"); + unicode-renge: U+0621, U+0623, U+0626, U+0624, U+0627, U+0628, U+067E, U+062A, + U+062B, U+062C, U+0686, U+062D, U+062E, U+062F, U+0630, U+0631, U+0632, + U+0698, U+0633, U+0634, U+0635, U+0636, U+0637, U+0638, U+0639, U+063A, + U+0641, U+0642, U+06A9, U+06AF, U+0644, U+0645, U+0646, U+0648, U+0647, + U+06CC, U+0622, U+06C0, U+0644, U+0627, U+0640, U+067E, U+0686, U+0698, + U+06AF, U+0660, U+0661, U+0662, U+0663, U+0664, U+0665, U+0666, U+0667, + U+0668, U+0669, U+064A, U+0643; +} +//#endregion + +// +//#region Material Design Icons ... +@font-face { + font-family: "Material Icons"; + font-style: normal; + font-weight: 400; + src: url("../assets/font/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2") + format("woff2"); +} + +.material-icons { + font-family: "Material Icons"; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: "liga"; + -webkit-font-smoothing: antialiased; +} +//#endregion + + +@mixin x-toolbar-typography($config) { + .mat-toolbar { + @include mat-typography-level-to-styles($config, body-2); + margin: 0; + } + + .mat-toolbar h1 { + @include mat-typography-level-to-styles($config, headline); + margin: 0; + } + + .mat-toolbar h2 { + @include mat-typography-level-to-styles($config, title); + margin: 0; + } + + .mat-toolbar h3 { + @include mat-typography-level-to-styles($config, subheading-2); + margin: 0; + } + + .mat-toolbar h4 { + @include mat-typography-level-to-styles($config, subheading-1); + margin: 0; + } + + .mat-toolbar h5 { + @include mat-typography-level-to-styles($config, body-1); + margin: 0; + } + + .mat-toolbar h6 { + @include mat-typography-level-to-styles($config, caption); + margin: 0; + } +} + + +body { + @extend .mat-typography; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\material-icons.scss + +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url('../assets/font/MaterialIcons-Regular.eot'); /* For IE6-8 */ + src: local('Material Icons'), local('MaterialIcons-Regular'), + url('../assets/font/MaterialIcons-Regular.woff2') format('woff2'), + url('../assets/font/MaterialIcons-Regular.woff') format('woff'), + url('../assets/font/MaterialIcons-Regular.ttf') format('truetype'); +} + +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; /* Preferred icon size */ + display: inline-block; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + + /* Support for all WebKit browsers. */ + -webkit-font-smoothing: antialiased; + /* Support for Safari and Chrome. */ + text-rendering: optimizeLegibility; + + /* Support for Firefox. */ + -moz-osx-font-smoothing: grayscale; + + /* Support for IE. */ + font-feature-settings: 'liga'; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\theme.scss + +@import "./base-theme.scss"; +@import "./material-icons.scss"; +@import "./material-fonts.scss"; +@import "./markdown-theme.scss"; +@import "./fab-theme.scss"; + +@include mat-core(); + +$x-primary: mat-palette($mat-blue); +$x-accent: mat-palette($mat-cyan); +$x-warn: mat-palette($mat-red); + +// Create the theme object (a Sass map containing all of the palettes). +$x-theme: mat-light-theme($x-primary, $x-accent, $x-warn); +@include angular-material-theme($x-theme); + +// +@function generate-x-theme($primary, $accent, $warn) { + // + $x-primary: $primary; + $x-accent: $accent; + $x-warn: $warn; + + // + $x-theme: mat-light-theme($primary, $accent, $warn); + + // + @return $x-theme; +} + +// +@mixin x-theme($theme) { + @include angular-material-theme($theme); +} + +// +@mixin x-ion-color() { + // + // XFramework Color Mechanism ... + :root { + --x-color-primary: var(--ion-color-primary); + --x-color-primary-shade: var(--ion-color-primary-shade); + --x-color-primary-tint: var(--ion-color-primary-tint); + + --x-color-secondary: var(--ion-color-secondary); + --x-color-secondary-shade: var(--ion-color-secondary-shade); + --x-color-secondary-tint: var(--ion-color-secondary-tint); + + --x-color-tertiary: var(--ion-color-tertiary); + --x-color-tertiary-shade: var(--ion-color-tertiary-shade); + --x-color-tertiary-tint: var(--ion-color-tertiary-tint); + + --x-color-success: var(--ion-color-success); + --x-color-success-shade: var(--ion-color-success-shade); + --x-color-success-tint: var(--ion-color-success-tint); + + --x-color-warning: var(--ion-color-warning); + --x-color-warning-shade: var(--ion-color-warning-shade); + --x-color-warning-tint: var(--ion-color-warning-tint); + + --x-color-danger: var(--ion-color-danger); + --x-color-danger-shade: var(--ion-color-danger-shade); + --x-color-danger-tint: var(--ion-color-danger-tint); + + --x-color-dark: var(--ion-color-dark); + --x-color-dark-shade: var(--ion-color-dark-shade); + --x-color-dark-tint: var(--ion-color-dark-tint); + + --x-color-medium: var(--ion-color-medium); + --x-color-medium-shade: var(--ion-color-medium-shade); + --x-color-medium-tint: var(--ion-color-medium-tint); + + --x-color-light: var(--ion-color-light); + --x-color-light-shade: var(--ion-color-light-shade); + --x-color-light-tint: var(--ion-color-light-tint); + + --x-color-primary-contrast: var(--ion-color-primary-contrast); + --x-color-primary-shade-contrast: var(--ion-color-primary-contrast); + --x-color-primary-tint-contrast: var(--ion-color-primary-contrast); + + --x-color-secondary-contrast: var(--ion-color-secondary-contrast); + --x-color-secondary-shade-contrast: var(--ion-color-secondary-contrast); + --x-color-secondary-tint-contrast: var(--ion-color-secondary-contrast); + + --x-color-tertiary-contrast: var(--ion-color-tertiary-contrast); + --x-color-tertiary-shade-contrast: var(--ion-color-tertiary-contrast); + --x-color-tertiary-tint-contrast: var(--ion-color-tertiary-contrast); + + --x-color-success-contrast: var(--ion-color-success-contrast); + --x-color-success-shade-contrast: var(--ion-color-success-contrast); + --x-color-success-tint-contrast: var(--ion-color-success-contrast); + + --x-color-warning-contrast: var(--ion-color-warning-contrast); + --x-color-warning-shade-contrast: var(--ion-color-warning-contrast); + --x-color-warning-tint-contrast: var(--ion-color-warning-contrast); + + --x-color-danger-contrast: var(--ion-color-danger-contrast); + --x-color-danger-shade-contrast: var(--ion-color-danger-contrast); + --x-color-danger-tint-contrast: var(--ion-color-danger-contrast); + + --x-color-dark-contrast: var(--ion-color-dark-contrast); + --x-color-dark-shade-contrast: var(--ion-color-dark-contrast); + --x-color-dark-tint-contrast: var(--ion-color-dark-contrast); + + --x-color-medium-contrast: var(--ion-color-medium-contrast); + --x-color-medium-shade-contrast: var(--ion-color-medium-contrast); + --x-color-medium-tint-contrast: var(--ion-color-medium-contrast); + + --x-color-light-contrast: var(--ion-color-light-contrast); + --x-color-light-shade-contrast: var(--ion-color-light-contrast); + --x-color-light-tint-contras: var(--ion-color-light-contrast); + } + + // + // Define a Palette Based on Collection ... + $m-color-palette: mat-palette($m-color-collection, m-ionic); + + // + // Merge Defined Palette to Theme Object ... + $x-theme: map_merge( + $x-theme, + ( + m-ionic: $m-color-palette, + ) + ); + + // + // Generate a General Object for feauture Usages ... + $x-ionic: map-get($x-theme, m-ionic); +} + +// +@mixin x-to-ion-color() { + // + // XFramework Color Mechanism ... + :root { + --ion-color-primary: var(--x-color-primary); + --ion-color-primary-shade: var(--x-color-primary-shade); + --ion-color-primary-tint: var(--x-color-primary-tint); + + --ion-color-primary-contrast: var(--x-color-primary-contrast); + --ion-color-primary-contrast: var(--x-color-primary-contrast); + --ion-color-primary-contrast: var(--x-color-primary-contrast); + + --ion-color-secondary: var(--x-color-secondary); + --ion-color-secondary-shade: var(--x-color-secondary-shade); + --ion-color-secondary-tint: var(--x-color-secondary-tint); + + --ion-color-secondary-contrast: var(--x-color-secondary-contrast); + --ion-color-secondary-contrast: var(--x-color-secondary-contrast); + --ion-color-secondary-contrast: var(--x-color-secondary-contrast); + + --ion-color-tertiary: var(--x-color-tertiary); + --ion-color-tertiary-shade: var(--x-color-tertiary-shade); + --ion-color-tertiary-tint: var(--x-color-tertiary-tint); + + --ion-color-tertiary-contrast: var(--x-color-tertiary-contrast); + --ion-color-tertiary-contrast: var(--x-color-tertiary-contrast); + --ion-color-tertiary-contrast: var(--x-color-tertiary-contrast); + + --ion-color-success: var(--x-color-success); + --ion-color-success-shade: var(--x-color-success-shade); + --ion-color-success-tint: var(--x-color-success-tint); + + --ion-color-success-contrast: var(--x-color-success-contrast); + --ion-color-success-contrast: var(--x-color-success-contrast); + --ion-color-success-contrast: var(--x-color-success-contrast); + + --ion-color-warning: var(--x-color-warning); + --ion-color-warning-shade: var(--x-color-warning-shade); + --ion-color-warning-tint: var(--x-color-warning-tint); + + --ion-color-warning-contrast: var(--x-color-warning-contrast); + --ion-color-warning-contrast: var(--x-color-warning-contrast); + --ion-color-warning-contrast: var(--x-color-warning-contrast); + + --ion-color-danger: var(--x-color-danger); + --ion-color-danger-shade: var(--x-color-danger-shade); + --ion-color-danger-tint: var(--x-color-danger-tint); + + --ion-color-danger-contrast: var(--x-color-danger-contrast); + --ion-color-danger-contrast: var(--x-color-danger-contrast); + --ion-color-danger-contrast: var(--x-color-danger-contrast); + + --ion-color-dark: var(--x-color-dark); + --ion-color-dark-shade: var(--x-color-dark-shade); + --ion-color-dark-tint: var(--x-color-dark-tint); + + --ion-color-dark-contrast: var(--x-color-dark-contrast); + --ion-color-dark-contrast: var(--x-color-dark-contrast); + --ion-color-dark-contrast: var(--x-color-dark-contrast); + + --ion-color-medium: var(--x-color-medium); + --ion-color-medium-shade: var(--x-color-medium-shade); + --ion-color-medium-tint: var(--x-color-medium-tint); + + --ion-color-medium-contrast: var(--x-color-medium-contrast); + --ion-color-medium-contrast: var(--x-color-medium-contrast); + --ion-color-medium-contrast: var(--x-color-medium-contrast); + + --ion-color-light: var(--x-color-light); + --ion-color-light-shade: var(--x-color-light-shade); + --ion-color-light-tint: var(--x-color-light-tint); + + --ion-color-light-contrast: var(--x-color-light-contrast); + --ion-color-light-contrast: var(--x-color-light-contrast); + --ion-color-light-contrast: var(--x-color-light-contrast); + } + + // + // Define a Palette Based on Collection ... + $m-color-palette: mat-palette($m-color-collection, m-ionic); + + // + // Merge Defined Palette to Theme Object ... + $x-theme: map_merge( + $x-theme, + ( + m-ionic: $m-color-palette, + ) + ); + + // + // Generate a General Object for feauture Usages ... + $x-ionic: map-get($x-theme, m-ionic); +} + +// +//#region Custom Ionic Based Colors ... + +// +//#region Define a Collection ... +$m-color-collection: ( + primary: var(--x-color-primary), + primary-shade: var(--x-color-primary-shade), + primary-tint: var(--x-color-primary-tint), + secondary: var(--x-color-secondary), + secondary-shade: var(--x-color-secondary-shade), + secondary-tint: var(--x-color-secondary-tint), + tertiary: var(--x-color-tertiary), + tertiary-shade: var(--x-color-tertiary-shade), + tertiary-tint: var(--x-color-tertiary-tint), + success: var(--x-color-success), + success-shade: var(--x-color-success-shade), + success-tint: var(--x-color-success-tint), + warning: var(--x-color-warning), + warning-shade: var(--x-color-warning-shade), + warning-tint: var(--x-color-warning-tint), + danger: var(--x-color-danger), + danger-shade: var(--x-color-danger-shade), + danger-tint: var(--x-color-danger-tint), + dark: var(--x-color-dark), + dark-shade: var(--x-color-dark-shade), + dark-tint: var(--x-color-dark-tint), + medium: var(--x-color-medium), + medium-shade: var(--x-color-medium-shade), + medium-tint: var(--x-color-medium-tint), + light: var(--x-color-light), + light-shade: var(--x-color-light-shade), + light-tint: var(--x-color-light-tint), + contrast: ( + primary: var(--x-color-primary-contrast), + primary-shade: var(--x-color-primary-shade-contrast), + primary-tint: var(--x-color-primary-tint-contrast), + secondary: var(--x-color-secondary-contrast), + secondary-shade: var(--x-color-secondary-shade-contrast), + secondary-tint: var(--x-color-secondary-tint-contrast), + tertiary: var(--x-color-tertiary-contrast), + tertiary-shade: var(--x-color-tertiary-shade-contrast), + tertiary-tint: var(--x-color-tertiary-tint-contrast), + success: var(--x-color-success-contrast), + success-shade: var(--x-color-success-shade-contrast), + success-tint: var(--x-color-success-tint-contrast), + warning: var(--x-color-warning-contrast), + warning-shade: var(--x-color-warning-shade-contrast), + warning-tint: var(--x-color-warning-tint-contrast), + danger: var(--x-color-danger-contrast), + danger-shade: var(--x-color-danger-shade-contrast), + danger-tint: var(--x-color-danger-tint-contrast), + dark: var(--x-color-dark-contrast), + dark-shade: var(--x-color-dark-shade-contrast), + dark-tint: var(--x-color-dark-tint-contrast), + medium: var(--x-color-medium-contrast), + medium-shade: var(--x-color-medium-shade-contrast), + medium-tint: var(--x-color-medium-tint-contrast), + light: var(--x-color-light-contrast), + light-shade: var(--x-color-light-shade-contrast), + light-tint: var(--x-color-light-tint-contrast), + ), +); +//#endregion + +// +// Define a Palette Based on Collection ... +$m-color-palette: mat-palette($m-color-collection, m-ionic); + +// +// Merge Defined Palette to Theme Object ... +$x-theme: map_merge( + $x-theme, + ( + m-ionic: $m-color-palette, + ) +); + +// +// Generate a General Object for feauture Usages ... +$x-ionic: map-get($x-theme, m-ionic); + +// +//#region Generate General Classes for Support these Colors ... +// +//#region Primary ... +.mat-primary { + // + color: mat-color($x-ionic, primary-contrast) !important; + background-color: mat-color($x-ionic, primary) !important; + + // + --color: mat-color($x-ionic, primary-contrast) !important; + --background: mat-color($x-ionic, primary) !important; +} + +.mat-primary-shade { + // + color: mat-color($x-ionic, primary-shade-contrast) !important; + background-color: mat-color($x-ionic, primary-shade) !important; + + // + --color: mat-color($x-ionic, primary-shade-contrast) !important; + --background: mat-color($x-ionic, primary-shade) !important; +} + +.mat-primary-tint { + // + color: mat-color($x-ionic, primary-tint-contrast) !important; + background-color: mat-color($x-ionic, primary-tint) !important; + + // + --color: mat-color($x-ionic, primary-tint-contrast) !important; + --background: mat-color($x-ionic, primary-tint) !important; +} + +.c-primary { + // + color: mat-color($x-ionic, primary) !important; + + // + --color: mat-color($x-ionic, primary) !important; +} + +.c-primary-shade { + // + color: mat-color($x-ionic, primary-shade) !important; + + // + --color: mat-color($x-ionic, primary-shade) !important; +} + +.c-primary-tint { + // + color: mat-color($x-ionic, primary-tint) !important; + + // + --color: mat-color($x-ionic, primary-tint) !important; +} + +//#endregion + +// +//#region Secondary ... +.mat-secondary { + // + color: mat-color($x-ionic, secondary-contrast) !important; + background-color: mat-color($x-ionic, secondary) !important; + + // + --color: mat-color($x-ionic, secondary-contrast) !important; + --background: mat-color($x-ionic, secondary) !important; +} + +.mat-secondary-shade { + // + color: mat-color($x-ionic, secondary-shade-contrast) !important; + background-color: mat-color($x-ionic, secondary-shade) !important; + + // + --color: mat-color($x-ionic, secondary-shade-contrast) !important; + --background: mat-color($x-ionic, secondary-shade) !important; +} + +.mat-secondary-tint { + // + color: mat-color($x-ionic, secondary-tint-contrast) !important; + background-color: mat-color($x-ionic, secondary-tint) !important; + + // + --color: mat-color($x-ionic, secondary-tint-contrast) !important; + --background: mat-color($x-ionic, secondary-tint) !important; +} + +.c-secondary { + // + color: mat-color($x-ionic, secondary) !important; + + // + --color: mat-color($x-ionic, secondary) !important; +} + +.c-secondary-shade { + // + color: mat-color($x-ionic, secondary-shade) !important; + + // + --color: mat-color($x-ionic, secondary-shade) !important; +} + +.c-secondary-tint { + // + color: mat-color($x-ionic, secondary-tint) !important; + + // + --color: mat-color($x-ionic, secondary-tint) !important; +} + +//#endregion + +// +//#region Tertiary ... +.mat-tertiary { + // + color: mat-color($x-ionic, tertiary-contrast) !important; + background-color: mat-color($x-ionic, tertiary) !important; + + // + --color: mat-color($x-ionic, tertiary-contrast) !important; + --background: mat-color($x-ionic, tertiary) !important; +} + +.mat-tertiary-shade { + // + color: mat-color($x-ionic, tertiary-shade-contrast) !important; + background-color: mat-color($x-ionic, tertiary-shade) !important; + + // + --color: mat-color($x-ionic, tertiary-shade-contrast) !important; + --background: mat-color($x-ionic, tertiary-shade) !important; +} + +.mat-tertiary-tint { + // + color: mat-color($x-ionic, tertiary-tint-contrast) !important; + background-color: mat-color($x-ionic, tertiary-tint) !important; + + // + --color: mat-color($x-ionic, tertiary-tint-contrast) !important; + --background: mat-color($x-ionic, tertiary-tint) !important; +} + +.c-tertiary { + // + color: mat-color($x-ionic, tertiary) !important; + + // + --color: mat-color($x-ionic, tertiary) !important; +} + +.c-tertiary-shade { + // + color: mat-color($x-ionic, tertiary-shade) !important; + + // + --color: mat-color($x-ionic, tertiary-shade) !important; +} + +.c-tertiary-tint { + // + color: mat-color($x-ionic, tertiary-tint) !important; + + // + --color: mat-color($x-ionic, tertiary-tint) !important; +} + +//#endregion + +// +//#region Success ... +.mat-success { + // + color: mat-color($x-ionic, success-contrast) !important; + background-color: mat-color($x-ionic, success) !important; + + // + --color: mat-color($x-ionic, success-contrast) !important; + --background: mat-color($x-ionic, success) !important; +} + +.mat-success-shade { + // + color: mat-color($x-ionic, success-shade-contrast) !important; + background-color: mat-color($x-ionic, success-shade) !important; + + // + --color: mat-color($x-ionic, success-shade-contrast) !important; + --background: mat-color($x-ionic, success-shade) !important; +} + +.mat-success-tint { + // + color: mat-color($x-ionic, success-tint-contrast) !important; + background-color: mat-color($x-ionic, success-tint) !important; + + // + --color: mat-color($x-ionic, success-tint-contrast) !important; + --background: mat-color($x-ionic, success-tint) !important; +} + +.c-success { + // + color: mat-color($x-ionic, success) !important; + + // + --color: mat-color($x-ionic, success) !important; +} + +.c-success-shade { + // + color: mat-color($x-ionic, success-shade) !important; + + // + --color: mat-color($x-ionic, success-shade) !important; +} + +.c-success-tint { + // + color: mat-color($x-ionic, success-tint) !important; + + // + --color: mat-color($x-ionic, success-tint) !important; +} + +//#endregion + +// +//#region Warning ... +.mat-warning { + // + color: mat-color($x-ionic, warning-contrast) !important; + background-color: mat-color($x-ionic, warning) !important; + + // + --color: mat-color($x-ionic, warning-contrast) !important; + --background: mat-color($x-ionic, warning) !important; +} + +.mat-warning-shade { + // + color: mat-color($x-ionic, warning-shade-contrast) !important; + background-color: mat-color($x-ionic, warning-shade) !important; + + // + --color: mat-color($x-ionic, warning-shade-contrast) !important; + --background: mat-color($x-ionic, warning-shade) !important; +} + +.mat-warning-tint { + // + color: mat-color($x-ionic, warning-tint-contrast) !important; + background-color: mat-color($x-ionic, warning-tint) !important; + + // + --color: mat-color($x-ionic, warning-tint-contrast) !important; + --background: mat-color($x-ionic, warning-tint) !important; +} + +.c-warning { + // + color: mat-color($x-ionic, warning) !important; + + // + --color: mat-color($x-ionic, warning) !important; +} + +.c-warning-shade { + // + color: mat-color($x-ionic, warning-shade) !important; + + // + --color: mat-color($x-ionic, warning-shade) !important; +} + +.c-warning-tint { + // + color: mat-color($x-ionic, warning-tint) !important; + + // + --color: mat-color($x-ionic, warning-tint) !important; +} + +//#endregion + +// +//#region Danger ... +.mat-danger { + // + color: mat-color($x-ionic, danger-contrast) !important; + background-color: mat-color($x-ionic, danger) !important; + + // + --color: mat-color($x-ionic, danger-contrast) !important; + --background: mat-color($x-ionic, danger) !important; +} + +.mat-danger-shade { + // + color: mat-color($x-ionic, danger-shade-contrast) !important; + background-color: mat-color($x-ionic, danger-shade) !important; + + // + --color: mat-color($x-ionic, danger-shade-contrast) !important; + --background: mat-color($x-ionic, danger-shade) !important; +} + +.mat-danger-tint { + // + color: mat-color($x-ionic, danger-tint-contrast) !important; + background-color: mat-color($x-ionic, danger-tint) !important; + + // + --color: mat-color($x-ionic, danger-tint-contrast) !important; + --background: mat-color($x-ionic, danger-tint) !important; +} + +.c-danger { + // + color: mat-color($x-ionic, danger) !important; + + // + --color: mat-color($x-ionic, danger) !important; +} + +.c-danger-shade { + // + color: mat-color($x-ionic, danger-shade) !important; + + // + --color: mat-color($x-ionic, danger-shade) !important; +} + +.c-danger-tint { + // + color: mat-color($x-ionic, danger-tint) !important; + + // + --color: mat-color($x-ionic, danger-tint) !important; +} + +//#endregion + +// +//#region Dark ... +.mat-dark { + // + color: mat-color($x-ionic, dark-contrast) !important; + background-color: mat-color($x-ionic, dark) !important; + + // + --color: mat-color($x-ionic, dark-contrast) !important; + --background: mat-color($x-ionic, dark) !important; +} + +.mat-dark-shade { + // + color: mat-color($x-ionic, dark-shade-contrast) !important; + background-color: mat-color($x-ionic, dark-shade) !important; + + // + --color: mat-color($x-ionic, dark-shade-contrast) !important; + --background: mat-color($x-ionic, dark-shade) !important; +} + +.mat-dark-tint { + // + color: mat-color($x-ionic, dark-tint-contrast) !important; + background-color: mat-color($x-ionic, dark-tint) !important; + + // + --color: mat-color($x-ionic, dark-tint-contrast) !important; + --background: mat-color($x-ionic, dark-tint) !important; +} + +.c-dark { + // + color: mat-color($x-ionic, dark) !important; + + // + --color: mat-color($x-ionic, dark) !important; +} + +.c-dark-shade { + // + color: mat-color($x-ionic, dark-shade) !important; + + // + --color: mat-color($x-ionic, dark-shade) !important; +} + +.c-dark-tint { + // + color: mat-color($x-ionic, dark-tint) !important; + + // + --color: mat-color($x-ionic, dark-tint) !important; +} + +//#endregion + +// +//#region Medium ... +.mat-medium { + // + color: mat-color($x-ionic, medium-contrast) !important; + background-color: mat-color($x-ionic, medium) !important; + + // + --color: mat-color($x-ionic, medium-contrast) !important; + --background: mat-color($x-ionic, medium) !important; +} + +.mat-medium-shade { + // + color: mat-color($x-ionic, medium-shade-contrast) !important; + background-color: mat-color($x-ionic, medium-shade) !important; + + // + --color: mat-color($x-ionic, medium-shade-contrast) !important; + --background: mat-color($x-ionic, medium-shade) !important; +} + +.mat-medium-tint { + // + color: mat-color($x-ionic, medium-tint-contrast) !important; + background-color: mat-color($x-ionic, medium-tint) !important; + + // + --color: mat-color($x-ionic, medium-tint-contrast) !important; + --background: mat-color($x-ionic, medium-tint) !important; +} + +.c-medium { + // + color: mat-color($x-ionic, medium) !important; + + // + --color: mat-color($x-ionic, medium) !important; +} + +.c-medium-shade { + // + color: mat-color($x-ionic, medium-shade) !important; + + // + --color: mat-color($x-ionic, medium-shade) !important; +} + +.c-medium-tint { + // + color: mat-color($x-ionic, medium-tint) !important; + + // + --color: mat-color($x-ionic, medium-tint) !important; +} + +//#endregion + +// +//#region Light ... +.mat-light { + // + color: mat-color($x-ionic, light-contrast) !important; + background-color: mat-color($x-ionic, light) !important; + + // + --color: mat-color($x-ionic, light-contrast) !important; + --background: mat-color($x-ionic, light) !important; +} + +.mat-light-shade { + // + color: mat-color($x-ionic, light-shade-contrast) !important; + background-color: mat-color($x-ionic, light-shade) !important; + + // + --color: mat-color($x-ionic, light-shade-contrast) !important; + --background: mat-color($x-ionic, light-shade) !important; +} + +.mat-light-tint { + // + color: mat-color($x-ionic, light-tint-contrast) !important; + background-color: mat-color($x-ionic, light-tint) !important; + + // + --color: mat-color($x-ionic, light-tint-contrast) !important; + --background: mat-color($x-ionic, light-tint) !important; +} + +.c-light { + // + color: mat-color($x-ionic, light) !important; + + // + --color: mat-color($x-ionic, light) !important; +} + +.c-light-shade { + // + color: mat-color($x-ionic, light-shade) !important; + + // + --color: mat-color($x-ionic, light-shade) !important; +} + +.c-light-tint { + // + color: mat-color($x-ionic, light-tint) !important; + + // + --color: mat-color($x-ionic, light-tint) !important; +} + +//#endregion +//#endregion + +.cb-inherit { + // + color: inherit !important; + --color: inherit !important; + + // + background-color: inherit !important; + --background-color: inherit !important; +} + +.transparent { + --background: transparent !important; + background-color: transparent !important; +} + +//#endregion + +// +//#region Custom Styles ... +html, +body { + height: 100%; +} + +body { + margin: 0; + padding: 0; + overflow-x: hidden; + font-family: Roboto, "Helvetica Neue", sans-serif; +} + +* { + box-sizing: border-box; +} + +*::-webkit-scrollbar { + display: none; +} + +[hidden] { + display: none !important; +} +//#endregion + +// +@import "_tools"; + +// +// Since here we must able to use all Capabilities, +// so we import this at the end ... +@import "./custom-effects.scss"; + +// +// Implement to ionic them ... +@include x-to-ion-color(); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\tokens\x-injectable-tokens.ts + +import { InjectionToken } from '@angular/core'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export const X_FRAMEWORK_COMPONENTS_CONFIG = new InjectionToken< + XFrameworkComponentsConfig +>('x_config'); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-action-bar\x-action-bar.component.html + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + {{ getResourceValue(action.title) | async }} +
+
+
+ + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-action-bar\x-action-bar.component.scss + +$action-item-margin: var(--action-items-offset, 2px); +$action-item-gutter: var(--action-items-gutter, 8px); + +.x-actions-bar { + padding: { + left: $action-item-gutter !important; + right: $action-item-gutter !important; + } +} + +.x-action-bar-item { + margin: { + left: $action-item-margin !important; + right: $action-item-margin !important; + } +} + +.x-action-bar-item-inner { + display: flex; + align-items: center; + + > * { + margin: { + left: 2px !important; + right: 2px !important; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-action-bar\x-action-bar.component.ts + +import { + sort, + nameof, + toArray, + isFunction, + filterList, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XActionBarItem, + XActionBarAction, + XActionBarItemSlot, + XActionBarActionModel, + XActionBarActionIdentifier, + XActionBarItemSlotIdentifier, + XActionBarContainerComponent, +} from './x-action-bar.typings'; +import { map } from 'rxjs/operators'; +import { Subject, Subscription } from 'rxjs'; +import { XButtonType } from '../x-button/x-button.typings'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-action-bar', + templateUrl: './x-action-bar.component.html', + styleUrls: ['./x-action-bar.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XActionBarComponent extends XActionBarContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + private ACTIONS: XActionBarItem[] = []; + private ACTIONS_HIDDEN: Map = new Map(); + private ACTIONS_DISABLED: Map = + new Map(); + + // + readonly ButtonTypes = Object.assign({}, XButtonType); + + get preparedActions() { + return this.ACTIONS; + } + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Actions Changed ... + // + const isActionsChanged = changeKeys.includes( + nameof('actions') + ); + if (isActionsChanged) { + await this.prepareActions(); + } + //#endregion + + // + //#region Action Provider Changed ... + // + const isActionProviderChaged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChaged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + // + //#region Slot Based Items Fetcher ... + getSlotItems(slot: XActionBarItemSlotIdentifier) { + return filterList(this.actions, (action) => + this.getValue(action.slot).pipe(map((iSlot) => iSlot === slot)) + ); + } + + getStartSoltItems() { + return this.getSlotItems(XActionBarItemSlot.START); + } + + getEndSoltItems() { + return this.getSlotItems(XActionBarItemSlot.END); + } + + isOnlyIcon(item: XActionBarItem) { + return this.getOptionalValue(item.onlyIcon).pipe( + map((onlyIcon) => !!onlyIcon) + ); + } + + isDisabledAction(item: XActionBarItem) { + // + if (!item) { + return this.getValue(false); + } + + // + return this.getValue(item.id).pipe( + map((id) => + isNullOrEmptyString(id) + ? false + : this.ACTIONS_DISABLED.has(id) + ? this.ACTIONS_DISABLED.get(id) + : false + ) + ); + } + + isHidedAction(item: XActionBarItem) { + // + if (!item) { + return this.getValue(false); + } + + // + return this.getValue(item.id).pipe( + map((id) => + isNullOrEmptyString(id) + ? false + : this.ACTIONS_HIDDEN.has(id) + ? this.ACTIONS_HIDDEN.get(id) + : false + ) + ); + } + //#endregion + + async getActionIds() { + // + if (!this.hasChild(this.preparedActions)) { + return []; + } + + // + const IDS = this.preparedActions.map((a) => this.getValueAsync(a.id)); + + // + return await Promise.all(IDS); + } + //#endregion + + // + //#region UI Handlers ... + async handleActionClicked(item: XActionBarItem, event: Event) { + // + const disabled = await this.getValueAsync(item.disabled); + if (disabled || this.uiDisabled || this.loading) { + return; + } + + // + const id = await this.getValueAsync(item.id); + + // + if (item.handler && isFunction(item.handler)) { + await item.handler({ + action: id, + event, + }); + } + + // + this.actionFired.emit({ + action: id, + event, + }); + } + //#endregion + + // + //#region Actions ... + /** + * check an action with specific id exists in actions or not ... + * + * @param id required action id ... + */ + async isActionExists(id: string) { + return (await this.getActionIds()).findIndex((i) => i === id) > -1; + } + + /** + * retrieve all actions ... + */ + async getActions() { + // + const ACTIONS_TYPE = await this.getValueAsync(this.actions); + if (!ACTIONS_TYPE) { + return []; + } + + // + return toArray(ACTIONS_TYPE); + } + + /** + * retrieve an action ... + * + * @param id action id + */ + async getAction(id: string) { + // + const isExists = await this.isActionExists(id); + if (!isExists) { + return null; + } + + // + const ACTIONS = await this.getActions(); + if (!this.hasChild(ACTIONS)) { + return null; + } + + // + const existsAction = ACTIONS.find( + async (a) => (await this.getValueAsync(a.id)) === id + ); + + // + return this.toPreparedAction(existsAction); + } + + /** + * manually fire a registered action + * + * @param id action id + */ + async fireAction(id: string, event?: Event) { + // + const isExist = await this.isActionExists(id); + if (!isExist) { + return; + } + + // + const action = await this.getAction(id); + if (!action) { + return; + } + + // + const canFire = + !this.uiDisabled && + !this.loading && + !(await this.getOptionalValueAsync(action.disabled)); + if (!canFire) { + return; + } + + // + await this.handleActionClicked(action, event); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe( + async (action) => await this.handleActionProviderAction(action) + ); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + private async prepareActions() { + // + this.ACTIONS = null; + if (!this.actions) { + // + this.detectChanges(); + return; + } + + // + const ACTIONS_TYPE = await this.getValueAsync(this.actions); + if (!ACTIONS_TYPE) { + // + this.detectChanges(); + return; + } + + // + const ACTIONS = toArray(ACTIONS_TYPE); + if (!this.hasChild(ACTIONS)) { + // + this.detectChanges(); + return; + } + + // + this.ACTIONS = [ + ...sort(ACTIONS, (a, b) => (a.index && b.index ? a.index - b.index : -1)), + ]; + + // + for (const a of this.ACTIONS) { + // + const id = await this.getValueAsync(a.id); + + // + if (!this.ACTIONS_DISABLED.has(id)) { + this.ACTIONS_DISABLED.set(id, false); + } + + // + if (!this.ACTIONS_HIDDEN.has(id)) { + this.ACTIONS_HIDDEN.set(id, false); + } + } + + // + this.detectChanges(); + } + + private async actionsCount() { + return (await this.getActions()).length || 0; + } + + private async findActionIdex(id: string) { + // + const isExists = await this.isActionExists(id); + if (!isExists) { + return -1; + } + + // + const ACTIONS_TYPE = await this.getValueAsync(this.actions); + if (!ACTIONS_TYPE) { + return null; + } + + // + const ACTIONS = toArray(ACTIONS_TYPE); + if (!this.hasChild(ACTIONS)) { + return null; + } + + // + return ACTIONS.findIndex( + async (a) => (await this.getValueAsync(a.id)) === id + ); + } + + private async toPreparedAction(item: XActionBarItem) { + // + if (!item) { + return null; + } + + // + return { + index: item.index, + handler: item.handler, + id: await this.getValueAsync(item.id), + icon: await this.getValueAsync(item.icon), + slot: await this.getValueAsync(item.slot), + color: await this.getValueAsync(item.color), + title: await this.getValueAsync(item.title), + hidden: await this.getValueAsync(item.hidden), + onlyIcon: await this.getValueAsync(item.onlyIcon), + disabled: await this.getValueAsync(item.disabled), + cssClass: await this.getValueAsync(item.cssClass), + iconColor: await this.getValueAsync(item.iconColor), + titleColor: await this.getValueAsync(item.titleColor), + } as XActionBarItem; + } + + private async handleActionProviderAction(model: XActionBarActionModel) { + // + if (!model || isNullOrEmptyString(model.action)) { + return; + } + + // + const actionId = await this.getValueAsync(model.id); + + // + switch (model.action) { + // + case XActionBarAction.Refresh: + this.detectChanges(); + break; + + // + case XActionBarAction.FireAction: + await this.fireAction(model.id, model.event); + break; + + // + case XActionBarAction.EnableAction: + // + if (this.ACTIONS_DISABLED.has(actionId)) { + // + this.ACTIONS_DISABLED.set(actionId, false); + this.detectChanges(); + } else { + if (!this.hasChild(this.ACTIONS)) { + setTimeout(() => this.handleActionProviderAction(model), 500); + } + } + break; + + // + case XActionBarAction.DisbaleAction: + // + if (this.ACTIONS_DISABLED.has(actionId)) { + // + this.ACTIONS_DISABLED.set(actionId, true); + this.detectChanges(); + } else { + if (!this.hasChild(this.ACTIONS)) { + setTimeout(() => this.handleActionProviderAction(model), 500); + } + } + break; + + // + case XActionBarAction.ShowAction: + // + if (this.ACTIONS_HIDDEN.has(actionId)) { + // + this.ACTIONS_HIDDEN.set(actionId, false); + this.detectChanges(); + } else { + if (!this.hasChild(this.ACTIONS)) { + setTimeout(() => this.handleActionProviderAction(model), 500); + } + } + break; + + // + case XActionBarAction.HideAction: + // + if (this.ACTIONS_HIDDEN.has(actionId)) { + // + this.ACTIONS_HIDDEN.set(actionId, true); + this.detectChanges(); + } else { + if (!this.hasChild(this.ACTIONS)) { + setTimeout(() => this.handleActionProviderAction(model), 500); + } + } + break; + } + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-action-bar\x-action-bar.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XButtonModule } from '../x-button/x-button.module'; +import { XActionBarComponent } from './x-action-bar.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XActionBarComponent], + imports: [XIconModule, CommonModule, XButtonModule, XBaseComponentModule], + exports: [ + XIconModule, + CommonModule, + XButtonModule, + XActionBarComponent, + XBaseComponentModule, + ], +}) +export class XActionBarModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-action-bar\x-action-bar.typings.ts + +import { + XColor, + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + Input, + Inject, + NgZone, + Output, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XBaseContainer, + IXBaseContainer, + XBaseComponentContainer, + XBaseContainerComponent, + IXBaseComponentContainer, + XBaseComponentContainerComponent, +} from '../classes/i-x-base.container'; +import { map } from 'rxjs/operators'; +import { Observable, forkJoin, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XIconNames, XIconIdentifier } from '../x-icon/x-icon.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region ActionBar Item ... +export enum XActionBarItemSlot { + START = 'start', + END = 'end', +} + +export type XActionBarItemSlotIdentifier = XActionBarItemSlot | string; + +export interface XActionBarItem { + index?: number; + id: XStandardType; + hidden?: XStandardType; + onlyIcon?: XStandardType; + disabled?: XStandardType; + icon: XStandardType; + color?: XStandardType; + title?: XStandardType; + iconColor?: XStandardType; + titleColor?: XStandardType; + cssClass?: XStandardType>; + slot: XStandardType; + handler?: (model?: XActionBarActionFiredModel) => XStandardType; +} + +export interface XActionBarActionFiredModel { + action: string; + event?: Event; +} +//#endregion + +// +//#region Action Provider ... +export enum XActionBarAction { + Refresh = 'refresh', + FireAction = 'fire_action', + HideAction = 'hide_action', + ShowAction = 'show_action', + EnableAction = 'enable_action', + DisbaleAction = 'disable_action', +} + +export type XActionBarActionIdentifier = XActionBarAction | string; + +export interface XActionBarActionModel + extends XBaseActionModel { + id?: string; + event?: Event; +} +//#endregion + +// +//#region Default Actionbar Item IDs ... +export enum DefaultActionBarItemIds { + Add = 'add', + Set = 'set', + Edit = 'edit', + Remove = 'remove', + Upload = 'upload', + Download = 'download', + SelectAll = 'select_all', + DeSelectAll = 'de_select_all', + SelectAllGroup = 'select_all_group', + InverseSelection = 'inverse_selection', + DeSelectAllGroup = 'de_select_all_group', +} + +export type DefaultActionBarItemIdsIdentifier = + | DefaultActionBarItemIds + | string; +//#endregion + +// +//#region Default Items ... +export const defaultActionBarRemoveItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Danger, + icon: XIconNames.remove, + iconColor: XColor.Danger, + title: XResourceIDs.delete, + slot: XActionBarItemSlot.START, + id: DefaultActionBarItemIds.Remove, +}; + +export const defaultActionBarUploadItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Tertiary, + icon: XIconNames.upload, + iconColor: XColor.Tertiary, + title: XResourceIDs.upload, + slot: XActionBarItemSlot.START, + id: DefaultActionBarItemIds.Upload, +}; + +export const defaultActionBarDownloadItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Tertiary, + icon: XIconNames.download, + iconColor: XColor.Tertiary, + title: XResourceIDs.download, + slot: XActionBarItemSlot.START, + id: DefaultActionBarItemIds.Download, +}; + +export const defaultActionBarSelectAllItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Tertiary, + iconColor: XColor.Tertiary, + icon: XIconNames.select_all, + slot: XActionBarItemSlot.END, + title: XResourceIDs.select_all, + id: DefaultActionBarItemIds.SelectAll, +}; + +export const defaultActionBarDeSelectAllItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Tertiary, + iconColor: XColor.Tertiary, + slot: XActionBarItemSlot.END, + icon: XIconNames.deselect_all, + title: XResourceIDs.deselect_all, + id: DefaultActionBarItemIds.DeSelectAll, +}; + +export const defaultActionBarSelectAllGroupItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Tertiary, + iconColor: XColor.Tertiary, + slot: XActionBarItemSlot.END, + icon: XIconNames.select_all_group, + title: XResourceIDs.select_all_group, + id: DefaultActionBarItemIds.SelectAllGroup, +}; + +export const defaultActionBarDeSelectAllGroupItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Tertiary, + iconColor: XColor.Tertiary, + slot: XActionBarItemSlot.END, + icon: XIconNames.deselect_all_group, + title: XResourceIDs.deselect_all_group, + id: DefaultActionBarItemIds.DeSelectAllGroup, +}; + +export const defaultActionBarInverseSelectionItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Tertiary, + iconColor: XColor.Tertiary, + slot: XActionBarItemSlot.END, + icon: XIconNames.inverse_selection, + title: XResourceIDs.inverse_selection, + id: DefaultActionBarItemIds.InverseSelection, +}; + +export const defaultActionBarAddItem: XActionBarItem = { + onlyIcon: true, + icon: XIconNames.add, + color: XColor.Success, + title: XResourceIDs.add, + iconColor: XColor.Success, + slot: XActionBarItemSlot.START, + id: DefaultActionBarItemIds.Add, +}; + +export const defaultActionBarSetItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Success, + icon: XIconNames.done, + title: XResourceIDs.set, + iconColor: XColor.Success, + slot: XActionBarItemSlot.START, + id: DefaultActionBarItemIds.Set, +}; + +export const defaultActionBarEditItem: XActionBarItem = { + onlyIcon: true, + icon: XIconNames.edit, + color: XColor.Warning, + title: XResourceIDs.edit, + iconColor: XColor.Warning, + slot: XActionBarItemSlot.START, + id: DefaultActionBarItemIds.Edit, +}; +//#endregion + +// +//#region Container ... +export interface IXActionBarContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + actions: XStandardType>; + + // + actionFired: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XActionBarContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider = new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly actions: XStandardType>; + + // + readonly actionFired = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XActionBarContainerComponent + extends XBaseContainerComponent + implements IXActionBarContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Input() + actions: XStandardType> = + this.propertyProvider.actions; + + // + @Output() + actionFired: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XActionBarContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXActionBarContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + + // + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + + // + actions: this.getValue(container ? container.actions : this.actions), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + actionFired: this.actionFired, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentActionBarContainer = { + [TKey in keyof IXActionBarContainer as `actionBar${Capitalize< + string & TKey + >}`]: IXActionBarContainer[TKey]; +}; + +export interface IXComponentActionBarContainer + extends TXComponentActionBarContainer, + IXBaseComponentContainer { + showActionBar: XStandardType; + actionBarColor: XStandardType; + actionBarForegroundColor: XStandardType; + actionBarActionProvider: Subject; + actionBarCssClass: XStandardType>; + actionBarActionFired: EventEmitter; + actionBarActions: XStandardType>; +} + +@Injectable({ + providedIn: 'root', +}) +export class XComponentActionBarContainer + extends XBaseComponentContainer + implements Readonly +{ + // + //#region Props ... + // + readonly showActionBar: XStandardType = true; + + // + readonly actionBarActionProvider: Subject = + this.propertyProvider.actionProvider; + readonly actionBarCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly actionBarColor: XStandardType = + this.propertyProvider.color; + readonly actionBarForegroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + readonly actionBarActions: XStandardType> = + this.propertyProvider.actions; + + // + readonly actionBarActionFired: EventEmitter = + this.propertyProvider.actionFired; + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XActionBarContainer) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XComponentActionBarContainerComponent + extends XBaseComponentContainerComponent + implements IXComponentActionBarContainer +{ + // + //#region Props ... + // + @Input() + showActionBar: XStandardType = this.propertyProvider.showActionBar; + + // + @Input() + actionBarActionProvider: Subject = + this.propertyProvider.actionBarActionProvider; + + @Input() + actionBarCssClass: XStandardType> = + this.propertyProvider.actionBarCssClass; + + // + @Input() + actionBarColor: XStandardType = + this.propertyProvider.actionBarColor; + + @Input() + actionBarForegroundColor: XStandardType = + this.propertyProvider.actionBarForegroundColor; + + // + @Input() + actionBarActions: XStandardType> = + this.propertyProvider.actionBarActions; + + // + @Output() + actionBarActionFired: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XComponentActionBarContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXComponentActionBarContainer + ): Observable { + return forkJoin({ + // + showActionBar: this.getValue( + container ? container.showActionBar : this.showActionBar + ), + + // + actionBarCssClass: this.getValue( + container ? container.actionBarCssClass : this.actionBarCssClass + ), + + // + actionBarColor: this.getValue( + container ? container.actionBarColor : this.actionBarColor + ), + actionBarForegroundColor: this.getValue( + container + ? container.actionBarForegroundColor + : this.actionBarForegroundColor + ), + + // + actionBarActions: this.getValue( + container ? container.actionBarActions : this.actionBarActions + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionBarActionProvider: container + ? container.actionBarActionProvider + : this.actionBarActionProvider, + + // + actionBarActionFired: this.actionBarActionFired, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-avatar-upload\x-avatar-upload.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + + +
+ + + + + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+
+
+
+
+
+ + + +
+ +
+
+ + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-avatar-upload\x-avatar-upload.component.scss + +// +:host { + --avatar-size: 64px; + --top-radius: 5px; + --padding: 6px; +} + +.x-file-drag-drop-wrapper { + padding-bottom: var(--padding); + border-top-left-radius: var(--top-radius); + border-top-right-radius: var(--top-radius); +} + +.x-avatar-container { + padding: 6px; + border-radius: 5px; +} + +.thumbnail-wrapper { + height: var(--avatar-size, 64px) !important; +} + +x-thumbnail { + --thumbnail-size: var(--avatar-size, 64px); +} + +x-file-drop-area { + --padding: 0px; +} + +.avatar-actions { + // + @extend .thumbnail-wrapper; + + // + display: flex; + flex-direction: row; + align-items: stretch; + justify-content: flex-end; + + // + * { + flex: 0 0 auto; + margin: { + top: auto; + bottom: auto; + } + } + + // + * { + margin: { + left: 3px; + right: 3px; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-avatar-upload\x-avatar-upload.component.ts + +import { + Component, + ViewChild, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + sort, + nameof, + isArray, + toArray, + hasChild, + toBoolean, + getArrayOf, + XDialogResult, + isExtensionOk, + getErrorException, + getImageDimensions, + XColorWithBrightness, + XExceptionIdentifier, +} from 'x-framework-core'; +import { + XAvatarStatus, + XAvatarUploadAction, + XAvatarUploadActionModel, + XAvatarUploadActionButtonName, + XAvatarUploadContainerComponent, +} from './x-avatar-upload.typings'; +import { map } from 'rxjs/operators'; +import { Subject, Subscription } from 'rxjs'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import { XButtonType } from '../x-button/x-button.typings'; +import { XThumbnailType } from '../x-thumbnail/x-thumbnail.typings'; +import { XSlotName, XSlotLayout } from '../x-slotter/x-slotter.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-avatar-upload', + templateUrl: './x-avatar-upload.component.html', + styleUrls: ['./x-avatar-upload.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XAvatarUploadComponent extends XAvatarUploadContainerComponent { + // + //#region Props ... + // + private SELECTED_AVATAR: File; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly IconNames = Object.assign({}, XIconNames); + readonly SlotLayout = Object.assign({}, XSlotLayout); + readonly ButtonTypes = Object.assign({}, XButtonType); + readonly ThumbnailTypes = Object.assign({}, XThumbnailType); + readonly ColorNames = Object.assign({}, XColorWithBrightness); + readonly ActionButtonNames = Object.assign({}, XAvatarUploadActionButtonName); + + // + selectedFileWidth = 0; + selectedFileHeight = 0; + + // + @ViewChild('fileInput', { static: false }) + private fileInputRef!: ElementRef; + + set avatar(v: File) { + // + this.SELECTED_AVATAR = v; + + // + if (!v) { + return; + } + + // + getImageDimensions(v).then((dimension) => { + if (dimension) { + // + this.selectedFileWidth = dimension.width; + this.selectedFileHeight = dimension.height; + + // + this.detectChanges(); + } + }); + } + + get avatar() { + return this.SELECTED_AVATAR; + } + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Action Provider ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Task Handlers ... + //#endregion + + // + //#region UI Providers ... + getActions() { + return this.getArrayValue(this.actions).pipe( + map((actions) => sort(actions, (a1, a2) => a1.index - a2.index)) + ); + } + + isCroppableImage() { + return ( + this.avatar && + this.selectedFileWidth > 0 && + this.selectedFileHeight > 0 && + this.selectedFileWidth >= this.config.minAllowedImageSize && + this.selectedFileHeight >= this.config.minAllowedImageSize + ); + } + //#endregion + + // + //#region UI Handlers ... + // + handleFilesSelected() { + // + if (!this.fileInputRef) { + return; + } + + // + const fileInput = this.fileInputRef.nativeElement; + if (!fileInput || !fileInput.files) { + return; + } + + // + const files = getArrayOf(fileInput.files); + + // + if (!files || files.length === 0) { + return; + } + + // + const file = files[0]; + if (!file) { + return; + } + + // + fileInput.value = null; + + // + this.handleFileDropped(files); + } + + // + async handleFileDropped(files: File | File[]) { + // + if (!files) { + return; + } + + // + this.loading = true; + + // + let file: File; + if (!isArray(files) && files instanceof File) { + file = files as File; + } else { + // + const filesArr = files as File[]; + if (hasChild(filesArr)) { + file = filesArr[0]; + } + } + + // + this.loading = false; + + // + if (!file) { + return; + } + + // + const status = await this.getFileStatus(file); + if (status !== XAvatarStatus.OK) { + this.loading = false; + await this.handleFileStatus(status); + return; + } + + // + this.avatar = file; + this.avatarChange.emit(file); + + // + this.detectChanges(); + } + + // + async handleActionButtonClicked(name: XAvatarUploadActionButtonName) { + switch (name) { + // + case XAvatarUploadActionButtonName.Crop: + this.loadCropper(); + break; + + // + case XAvatarUploadActionButtonName.Clear: + await this.clear(); + break; + + // + case XAvatarUploadActionButtonName.Select: + this.select(); + break; + } + } + //#endregion + + // + //#region Actions ... + // + select() { + // + if (!this.fileInputRef || !this.fileInputRef.nativeElement) { + return; + } + + // + this.fileInputRef.nativeElement.click(); + } + + // + async clear(force = false) { + // + if (!this.avatar) { + return; + } + + // + if (force) { + this.clearImage(); + return; + } + + // + await this.managerService.dialogService.presentAreYouSureYesNoDialog(() => { + this.clearImage(); + }); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XAvatarUploadAction.Clear: + await this.clear(toBoolean(model.payload)); + break; + + // + case XAvatarUploadAction.Select: + this.select(); + break; + + // + case XAvatarUploadAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private loadCropper() { + this.imageCropperService.showCropper(this.avatar); + + // + const s = this.applyTakeUntilWrapper( + this.imageCropperService.onResult.asObservable() + ).subscribe((res) => { + // + if (res.result === XDialogResult.Ok && res.file) { + // + this.avatar = null; + setTimeout(() => { + // + this.avatar = res.file; + this.avatarChange.emit(res.file); + + // + this.detectChanges(); + }, 100); + } + + // + s.unsubscribe(); + }); + } + + private clearImage() { + // + if (!this.avatar) { + return; + } + + // + this.avatar = null; + this.selectedFileWidth = 0; + this.selectedFileHeight = 0; + this.avatarChange.emit(null); + + // + this.detectChanges(); + } + + private async getFileStatus(file: File) { + // + // Check Not Empty File ... + if (!file || file.size === 0) { + return XAvatarStatus.EMPTY; + } + + // + // Check File Extension Validation ... + const isExtensionIsOk = await this.isExtensionOk(file); + if (!isExtensionIsOk) { + return XAvatarStatus.NOT_ALLOWED_TYPE; + } + + // + // Check File Min Size Ok ... + const isMinAllowedFileSizeOk = await this.isMinAllowedFileSizeOk(file); + if (!isMinAllowedFileSizeOk) { + return XAvatarStatus.MIN_SIZE_NOT_REACHED; + } + + // + // Check File Max Size Ok ... + const isMaxAllowedFileSizeOk = await this.isMaxAllowedFileSizeOk(file); + if (!isMaxAllowedFileSizeOk) { + return XAvatarStatus.SIZE_EXCEEDED; + } + + // + // Everythings Ok ... + return XAvatarStatus.OK; + } + + private async handleFileStatus(status: XAvatarStatus) { + // + let identifier: XExceptionIdentifier = null; + switch (status) { + // + case XAvatarStatus.EMPTY: + identifier = await this.getValueAsync(this.emptyFileException); + break; + + // + case XAvatarStatus.NOT_ALLOWED_TYPE: + identifier = await this.getValueAsync(this.notAllowedFileException); + break; + + // + case XAvatarStatus.SIZE_EXCEEDED: + identifier = await this.getValueAsync(this.maxAllowedFileSizeException); + break; + + // + case XAvatarStatus.MIN_SIZE_NOT_REACHED: + identifier = await this.getValueAsync(this.minAllowedFileSizeException); + break; + } + + // + const exception = getErrorException(identifier); + if (exception) { + this.managerService.notificationService.presentErrorNotification({ + exception, + dissmissable: true, + }); + } + } + + // + //#region File Selection Policies ... + /** + * Check File Extension ... + */ + private async isExtensionOk(file: File) { + // + if (!file) { + return false; + } + + // + const allowedExtensionsType = await this.getValueAsync( + this.allowedExtensions + ); + if (!allowedExtensionsType) { + return true; + } + + // + const allowedExtensions = toArray(allowedExtensionsType); + if (!hasChild(allowedExtensions)) { + return true; + } + + // + const result = isExtensionOk(file, allowedExtensions); + return result; + } + + /** + * Check File Size less than or Equals Max Required Size ... + */ + private async isMaxAllowedFileSizeOk(file: File) { + // + const maxAllowedFileSize = await this.getValueAsync( + this.maxAllowedFileSize + ); + + // + const result = + maxAllowedFileSize && maxAllowedFileSize > 0 + ? file.size <= maxAllowedFileSize + : true; + + // + return result; + } + + /** + * Check File Size satisfied Min Required Size ... + */ + private async isMinAllowedFileSizeOk(file: File) { + // + const minAllowedFileSize = await this.getValueAsync( + this.minAllowedFileSize + ); + + // + const result = + minAllowedFileSize && minAllowedFileSize > 0 + ? file.size >= minAllowedFileSize + : true; + + // + return result; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-avatar-upload\x-avatar-upload.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XButtonModule } from '../x-button/x-button.module'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XAvatarUploadComponent } from './x-avatar-upload.component'; +import { XThumbnailModule } from '../x-thumbnail/x-thumbnail.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XFileDropAreaModule } from '../x-file-drop-area/x-file-drop-area.module'; + +@NgModule({ + declarations: [XAvatarUploadComponent], + imports: [ + XIconModule, + XCardModule, + CommonModule, + XButtonModule, + XSlotterModule, + XThumbnailModule, + XFileDropAreaModule, + XBaseComponentModule, + ], + exports: [ + XIconModule, + XCardModule, + XButtonModule, + XSlotterModule, + XThumbnailModule, + XFileDropAreaModule, + XBaseComponentModule, + XAvatarUploadComponent, + ], +}) +export class XAvatarUploadModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-avatar-upload\x-avatar-upload.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XColor, + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XCardFileFileDropContainer, + IXCardFileFileDropContainer, + XCardFileFileDropContainerComponent, +} from '../classes/i-x-file.container'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XIconIdentifier, XIconNames } from '../x-icon/x-icon.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XImageCropperService } from '../x-image-cropper/x-image-cropper.service'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XAvatarUploadAction { + // + Clear = 'clear', + Select = 'select', + Refresh = 'refresh', +} + +export type XAvatarUploadActionIdentifier = XAvatarUploadAction | string; + +export interface XAvatarUploadActionModel + extends XBaseActionModel {} + +export enum XAvatarStatus { + OK = 'ok', + EMPTY = 'empty', + SIZE_EXCEEDED = 'size_exceeded', + NOT_ALLOWED_TYPE = 'not_allowed_type', + MIN_SIZE_NOT_REACHED = 'min_size_not_reached', +} + +export type XAvatarStatusIdentifier = XAvatarStatus | string; + +export enum XAvatarUploadActionButtonName { + Crop, + Clear, + Select, +} + +export interface XAvatarUploadActionButton { + index?: number; + name: XAvatarUploadActionButtonName; + icon?: XStandardType; + color?: XStandardType; + tooltip?: XStandardType; +} + +export const defaultAvatarUploadActionButtons: XAvatarUploadActionButton[] = [ + // + // Select ... + { + index: 0, + color: XColor.Success, + icon: XIconNames.touch, + tooltip: XResourceIDs.select, + name: XAvatarUploadActionButtonName.Select, + }, + // + // Crop ... + { + index: 1, + icon: XIconNames.crop, + color: XColor.Tertiary, + tooltip: XResourceIDs.crop, + name: XAvatarUploadActionButtonName.Crop, + }, + // + // Clear ... + { + index: 2, + color: XColor.Warning, + icon: XIconNames.clear, + tooltip: XResourceIDs.clear, + name: XAvatarUploadActionButtonName.Clear, + }, +]; + +// +//#region Inside Card Container ... +export interface IXAvatarUploadContainer extends IXCardFileFileDropContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + placeHolderAvatar: XStandardType; + + // + actions: XStandardType>; + + // + avatarChange: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XAvatarUploadContainer + extends XCardFileFileDropContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly placeHolderAvatar: XStandardType; + + // + readonly actions: XStandardType> = + defaultAvatarUploadActionButtons; + + // + readonly avatarChange: EventEmitter = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XAvatarUploadContainerComponent + extends XCardFileFileDropContainerComponent + implements IXAvatarUploadContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Input() + placeHolderAvatar: XStandardType = + this.propertyProvider.placeHolderAvatar; + + // + @Input() + actions: XStandardType> = + this.propertyProvider.actions; + + // + @Output() + avatarChange: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + public imageCropperService: XImageCropperService, + protected propertyProvider: XAvatarUploadContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXAvatarUploadContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + placeHolderAvatar: this.getValue( + container ? container.placeHolderAvatar : this.placeHolderAvatar + ), + actions: this.getValue( + container ? container.actions : this.actions + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + avatarChange: this.avatarChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-button\x-button.component.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ getResourceValue(title) | async }} + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-button\x-button.component.scss + +:host ::ng-deep .x-button-icon-wrapper { + vertical-align: middle; + display: inline-grid; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-button\x-button.component.ts + +import { + nameof, + isNullOrUndefined, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XButtonType, + XButtonAction, + XButtonActionModel, + XButtonIconPosition, + XButtonContainerComponent, +} from './x-button.typings'; +import { concatMap, map, switchMap } from 'rxjs/operators'; +import { of, Observable, Subscription, Subject } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { XSlotLayout, XSlotName } from '../x-slotter/x-slotter.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-button', + templateUrl: './x-button.component.html', + styleUrls: ['./x-button.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XButtonComponent extends XButtonContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly XSlotNames = Object.assign({}, XSlotName); + readonly XButtonTypes = Object.assign({}, XButtonType); + readonly XSlotLayouts = Object.assign({}, XSlotLayout); + readonly XButtonIconPositions = Object.assign({}, XButtonIconPosition); + //#endregion + + // + //#region LifeCycle ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Action Provider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region is IconPositionChanged ... + const isIconPositionChanged = changeKeys.includes( + nameof('iconPosition') + ); + if (isIconPositionChanged && isNullOrUndefined(this.iconPosition)) { + // + this.iconPosition = XButtonIconPosition.Start; + this.detectChanges(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + hasIcon() { + return this.notNullOrUndefinedValue(this.icon); + } + + hasTitle() { + return this.notNullOrUndefinedValue(this.title); + } + + useSlot() { + return this.hasIcon().pipe( + concatMap((hasIcon) => + this.hasTitle().pipe(map((hasTitle) => hasIcon && hasTitle)) + ) + ); + } + + isVerticalLayout() { + return this.useSlot().pipe( + concatMap((useSlot) => { + // + if (!useSlot) { + return of(false); + } + + // + return this.getValue(this.iconPosition).pipe( + map( + (iPos) => + iPos === this.XButtonIconPositions.Above || + iPos === this.XButtonIconPositions.Below + ) + ); + }) + ); + } + + iconSlotName(): Observable { + return this.getValue(this.iconPosition).pipe( + map((iPos) => { + // + switch (iPos) { + // + case this.XButtonIconPositions.Start: + return this.XSlotNames.START; + + // + case this.XButtonIconPositions.End: + return this.XSlotNames.END; + + // + case this.XButtonIconPositions.Above: + return this.XSlotNames.TOP; + + // + case this.XButtonIconPositions.Below: + return this.XSlotNames.BOTTOM; + + // + default: + return this.XSlotNames.START; + } + }) + ); + } + + titleSlotName(): Observable { + return this.iconSlotName().pipe( + map((iSlot) => { + // + switch (iSlot) { + // + case this.XSlotNames.START: + return this.XSlotNames.END; + + // + case this.XSlotNames.END: + return this.XSlotNames.START; + + // + case this.XSlotNames.TOP: + return this.XSlotNames.BOTTOM; + + // + case this.XSlotNames.BOTTOM: + return this.XSlotNames.TOP; + + // + default: + return this.XSlotNames.CENTER; + } + }) + ); + } + + getButtonColor() { + return this.hasIcon().pipe( + switchMap((hasIcon) => + this.getValue(hasIcon ? XColorWithBrightness.Transparent : this.color) + ) + ); + } + //#endregion + + // + //#region UI Handlers ... + handleButtonClicked(event?: Event) { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + this.clicked.emit(event); + } + //#endregion + + // + //#region Actions ... + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XButtonAction.Click: + this.clicked.emit(); + break; + + // + case XButtonAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-button\x-button.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XButtonComponent } from './x-button.component'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XButtonComponent], + imports: [CommonModule, XIconModule, XSlotterModule, XBaseComponentModule], + exports: [ + CommonModule, + XIconModule, + XSlotterModule, + XButtonComponent, + XBaseComponentModule, + ], +}) +export class XButtonModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-button\x-button.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Renderer2, + Component, + Injectable, + ElementRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { XBaseContainer } from './../classes/i-x-base.container'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XButtonType { + Raised = 'raised', + Icon = 'icon', +} + +export type XButtonTypeIdentifier = XButtonType | string; + +export enum XButtonIconPosition { + Above = 'above', + Start = 'strat', + Below = 'below', + End = 'end', +} + +export type XButtonIconPositionIdentifier = XButtonIconPosition | string; + +export enum XButtonAction { + Click = 'click', + Refresh = 'refresh', +} + +export type XButtonActionIdentifier = XButtonAction | string; + +export interface XButtonActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXButtonContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + type: XStandardType; + title: XStandardType; + icon: XStandardType; + iconPosition: XStandardType; + + // + color: XStandardType; + foregroundColor: XStandardType; + iconColor: XStandardType; + + // + clicked: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XButtonContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly type: XStandardType = XButtonType.Raised; + readonly title: XStandardType; + readonly icon: XStandardType; + readonly iconPosition: XStandardType = + XButtonIconPosition.Start; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + readonly iconColor: XStandardType = + XColorWithBrightness.Light; + + // + readonly clicked: EventEmitter = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XButtonContainerComponent + extends XBaseContainerComponent + implements IXButtonContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + type: XStandardType = this.propertyProvider.type; + + @Input() + title: XStandardType = this.propertyProvider.title; + + @Input() + icon: XStandardType = this.propertyProvider.icon; + + @Input() + iconPosition: XStandardType = + this.propertyProvider.iconPosition; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + @Input() + iconColor: XStandardType = this.propertyProvider.iconColor; + + // + @Output() + clicked: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XButtonContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXButtonContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + + // + type: this.getValue(container ? container.type : this.type), + title: this.getValue(container ? container.title : this.title), + icon: this.getValue(container ? container.icon : this.icon), + iconPosition: this.getValue( + container ? container.iconPosition : this.iconPosition + ), + + // + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + iconColor: this.getValue( + container ? container.iconColor : this.iconColor + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + clicked: this.clicked, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-card\x-card.component.html + + + + + +
+ + + + +
+ + + + {{ getResourceValue(title) | async }} + + + + + {{ getResourceValue(subTitle) | async }} + + + +
+ + + + + + + + + + + + + + + + + + +
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-card\x-card.component.scss + +// +@import '../theme/theme.scss'; + +// +:host::ng-deep.x-card.no-text { + .mat-card-header-text { + display: none; + visibility: hidden; + } +} + +// +.x-card { + @extend .p-0; + width: inherit; + height: var(--height, inherit); +} + +.x-card.page-card { + @include push-auto(); + @include applyWidthHeight(); + @include m-tb($d-l); + @include animation('fadeInUp'); + + &.mobile { + box-sizing: border-box; + width: $d-100p; + max-width: $d-100p; + min-width: $d-100p; + @include m(0); + margin-bottom: $d-l; + } +} + +.x-header, +.x-content, +.x-actions, +.x-footer { + @include p($d-md); +} + +.x-footer { + margin: 0px !important; +} + +.x-image { + width: 100% !important; + @extend .m-0; +} + +.x-actions { + margin: 0 !important; +} + +.x-header:empty, +.x-footer:empty, +.x-actions:empty { + display: none; + visibility: hidden; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-card\x-card.component.ts + +import { + XCardAction, + XCardActionModel, + XCardContainerComponent, +} from './x-card.typings'; +import { Subject, Subscription } from 'rxjs'; +import { concatMap, map } from 'rxjs/operators'; +import { nameof, isNullOrEmptyString } from 'x-framework-core'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-card', + templateUrl: './x-card.component.html', + styleUrls: ['./x-card.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XCardComponent extends XCardContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Action Provider Changed ... + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + isNoTextCard() { + return this.getResourceValue(this.title).pipe( + concatMap((title) => + this.getResourceValue(this.subTitle).pipe( + map((subTitle) => { + return { + title, + subTitle, + }; + }), + map((object) => { + return ( + isNullOrEmptyString(object.title) && + isNullOrEmptyString(object.subTitle) + ); + }) + ) + ) + ); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.actionProvider.asObservable() + ).subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XCardAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-card\x-card.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardComponent } from './x-card.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [ + XCardComponent + ], + imports: [CommonModule, XBaseComponentModule], + exports: [XBaseComponentModule, XCardComponent], +}) +export class XCardModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-card\x-card.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseComponentContainer, + XBaseContainerComponent, + IXBaseComponentContainer, + XBaseComponentContainerComponent, +} from '../classes/i-x-base.container'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XCardAction { + Refresh = 'refresh', +} + +export type XCardActionIdentifier = XCardAction | string; + +export interface XCardActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXCardContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + showHeader: XStandardType; + title: XStandardType; + subTitle: XStandardType; + avatarUrl: XStandardType; + avatarTemplate: TemplateRef; + + // + imageUrl: XStandardType; + imageDescription: XStandardType; + + // + isInPage: XStandardType; + + // + showContent: XStandardType; + showActions: XStandardType; + showFooter: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XCardContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly showHeader: XStandardType = false; + readonly title: XStandardType; + readonly subTitle: XStandardType; + readonly avatarUrl: XStandardType; + readonly avatarTemplate: TemplateRef; + + // + readonly imageUrl: XStandardType; + readonly imageDescription: XStandardType; + + // + readonly isInPage: XStandardType = false; + + // + readonly showContent: XStandardType = true; + readonly showActions: XStandardType = false; + readonly showFooter: XStandardType = false; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XCardContainerComponent + extends XBaseContainerComponent + implements IXCardContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Input() + showHeader: XStandardType = this.propertyProvider.showHeader; + + @Input() + title: XStandardType = this.propertyProvider.title; + + @Input() + subTitle: XStandardType = this.propertyProvider.subTitle; + + @Input() + avatarUrl: XStandardType = this.propertyProvider.avatarUrl; + + @Input() + avatarTemplate: TemplateRef = this.propertyProvider.avatarTemplate; + + // + @Input() + imageUrl: XStandardType = this.propertyProvider.imageUrl; + + @Input() + imageDescription: XStandardType = + this.propertyProvider.imageDescription; + + // + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + // + @Input() + showContent: XStandardType = this.propertyProvider.showContent; + + @Input() + showActions: XStandardType = this.propertyProvider.showActions; + + @Input() + showFooter: XStandardType = this.propertyProvider.showFooter; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XCardContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXCardContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + + // + showHeader: this.getValue( + container ? container.showHeader : this.showHeader + ), + title: this.getValue(container ? container.title : this.title), + subTitle: this.getValue(container ? container.subTitle : this.subTitle), + avatarUrl: this.getValue( + container ? container.avatarUrl : this.avatarUrl + ), + avatarTemplate: this.getValue( + container ? container.avatarTemplate : this.avatarTemplate + ), + + // + imageUrl: this.getValue(container ? container.imageUrl : this.imageUrl), + imageDescription: this.getValue( + container ? container.imageDescription : this.imageDescription + ), + + // + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + + // + showContent: this.getValue( + container ? container.showContent : this.showContent + ), + showActions: this.getValue( + container ? container.showActions : this.showActions + ), + showFooter: this.getValue( + container ? container.showFooter : this.showFooter + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentCardContainer = { + [TKey in keyof Pick< + IXCardContainer, + | 'actionProvider' + | 'cssClass' + | 'color' + | 'foregroundColor' + | 'title' + | 'subTitle' + | 'showFooter' + | 'showActions' + > as `card${Capitalize}`]: IXCardContainer[TKey]; +}; + +export interface IXComponentCardContainer + extends TXComponentCardContainer, + IXBaseComponentContainer { + isInPage: XStandardType; + wrapWithCard: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XComponentCardContainer + extends XBaseComponentContainer + implements Readonly +{ + // + //#region Props ... + // + readonly isInPage: XStandardType = false; + readonly wrapWithCard: XStandardType = false; + + // + readonly cardActionProvider: Subject = + this.propertyProvider.actionProvider; + readonly cardCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly cardColor: XStandardType = + this.propertyProvider.color; + readonly cardForegroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + readonly cardTitle: XStandardType = + this.propertyProvider.title; + readonly cardSubTitle: XStandardType = + this.propertyProvider.subTitle; + + // + readonly cardShowFooter: XStandardType = + this.propertyProvider.showFooter; + readonly cardShowActions: XStandardType = + this.propertyProvider.showActions; + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XCardContainer) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XComponentCardContainerComponent + extends XBaseComponentContainerComponent + implements IXComponentCardContainer +{ + // + //#region Props ... + // + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + // + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + + // + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + // + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + // + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XComponentCardContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXComponentCardContainer + ): Observable { + return forkJoin({ + // + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardCssClass: this.getValue( + container ? container.cardCssClass : this.cardCssClass + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-chart\x-chart.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + + +
+
+
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-chart\x-chart.component.scss + +:host { + display: block; + width: 100%; + height: 100%; +} + +.x-chart-wrapper { + width: 100%; + height: 100%; + min-height: 300px; /* ارتفاع پیش‌فرض برای جلوگیری از فروپاشی layout */ +} + +.x-chart-container { + width: 100%; + height: 100%; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-chart\x-chart.component.ts + +import { + Component, + ViewChild, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XChartAction, + XChartOptions, + XChartSeriesType, + XChartContainerComponent, +} from './x-chart.typings'; +import { Subscription } from 'rxjs'; +import ApexCharts from 'apexcharts'; +import { isNullOrUndefined, nameof } from 'x-framework-core'; + +@Component({ + selector: 'x-chart', + templateUrl: './x-chart.component.html', + styleUrls: ['./x-chart.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XChartComponent extends XChartContainerComponent { + // + //#region Props ... + // + private CHART_CONTAINER: ElementRef | undefined; + + @ViewChild('chartContainer', { static: false }) + set chartContainer(el: ElementRef | undefined) { + // + if (isNullOrUndefined(el)) { + // + this.destroyComponent(); + return; + } + + // + this.CHART_CONTAINER = el; + this.initializeComponent(); + } + + get chartContainer() { + return this.CHART_CONTAINER; + } + + // + private chartInstance: ApexCharts = null; + + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + //#endregion + + // + //#region LifeCycles ... + async afterViewInit() { + super.afterViewInit(); + + // + await this.initializeComponent(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + // Auto Update ... + const isAutoUpdateChanged = changeKeys.includes( + nameof('autoUpdate'), + ); + if (isAutoUpdateChanged) { + // + if (isNullOrUndefined(this.autoUpdate)) { + this.autoUpdate = this.propertyProvider.autoUpdate; + } + } + + // + // Action Provider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider'), + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + + // + // Check Auto Update ... + var autoUpdate = await this.getValueAsync(this.autoUpdate); + if ( + autoUpdate && + !isNullOrUndefined(this.chartInstance) && + !isNullOrUndefined(this.chartContainer) && + !isNullOrUndefined(this.chartContainer.nativeElement) + ) { + // + // Options Changed ... + const isOptionsChanged = changeKeys.includes( + nameof('options'), + ); + if (isOptionsChanged) { + // + if (isNullOrUndefined(this.options)) { + this.options = this.propertyProvider.options; + } + + // + const options = await this.getValueAsync(this.options); + await this.chartInstance.updateOptions(options); + } + + // + // Series Changed ... + const isSeriesChanged = changeKeys.includes( + nameof('series'), + ); + if (isSeriesChanged) { + // + if (isNullOrUndefined(this.series)) { + this.series = this.propertyProvider.series; + } + + // + const series = await this.getValueAsync(this.series); + await this.chartInstance.updateSeries(series); + } + } + + // + this.detectChanges(); + } + + onDestroy() { + super.onDestroy(); + + // + this.destroyComponent(); + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Private ... + private async initializeComponent() { + // + if (isNullOrUndefined(this.CHART_CONTAINER)) { + return; + } + + // + // Reading Requirement Properties ... + const chartId = await this.getValueAsync(this.uuid); + const series = await this.getValueAsync(this.series); + const options = await this.getValueAsync(this.options); + + // + // Validate Options ... + if (isNullOrUndefined(options)) { + return; + } + + // + const computedStyle = getComputedStyle(document.documentElement); + const primaryColor = + computedStyle.getPropertyValue('--x-color-primary').trim() || '#008ffb'; + const textColor = + computedStyle.getPropertyValue('--x-color-dark-contrast').trim() || + '#373d3f'; + const gridColor = + computedStyle.getPropertyValue('--x-color-light-shade').trim() || + '#e0e0e0'; + + // + const finalOptions: XChartOptions = { + ...options, + series: series || options.series, + chart: { + ...options.chart, + id: chartId, + type: options.chart?.type || 'line', + foreColor: textColor, + events: { + ...options.chart?.events, + mounted: (chartContext, config) => { + this.chartRendered.emit(); + options.chart?.events?.mounted?.(chartContext, config); + }, + dataPointSelection: (event, chartContext, config) => { + this.dataPointSelected.emit({ event, chartContext, config }); + options.chart?.events?.dataPointSelection?.( + event, + chartContext, + config, + ); + }, + zoomed: (chartContext, { xaxis, yaxis }) => { + this.zoomed.emit({ xaxis, yaxis }); + options.chart?.events?.zoomed?.(chartContext, { xaxis, yaxis }); + }, + }, + }, + theme: { + ...options.theme, + palette: options.theme?.palette || 'palette1', + }, + colors: options.colors || [primaryColor], + grid: { + ...options.grid, + borderColor: gridColor, + }, + }; + + // + this.chartInstance = new ApexCharts( + this.chartContainer.nativeElement, + finalOptions, + ); + + // + await this.chartInstance.render(); + } + + private destroyComponent() { + // + if (isNullOrUndefined(this.chartInstance)) { + return; + } + + // + this.chartInstance.destroy(); + this.chartInstance = null; + } + + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = this.propertyProvider.actionProvider; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XChartAction.Refresh: + this.detectChanges(); + break; + + // + case XChartAction.Destroy: + this.onDestroy(); + break; + + // + case XChartAction.UpdateSeries: + // + if ( + !isNullOrUndefined(model.payload) || + !isNullOrUndefined(this.chartInstance) + ) { + // + this.series = await this.getValueAsync( + model.payload as XChartSeriesType, + ); + await this.chartInstance.updateSeries(this.series); + this.detectChanges(); + } + break; + + // + case XChartAction.UpdateOptions: + // + if ( + !isNullOrUndefined(model.payload) || + !isNullOrUndefined(this.chartInstance) + ) { + // + this.options = await this.getValueAsync( + model.payload as XChartOptions, + ); + await this.chartInstance.updateOptions(this.options); + this.detectChanges(); + } + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-chart\x-chart.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XChartComponent } from './x-chart.component'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + providers: [], + entryComponents: [], + declarations: [XChartComponent], + imports: [ + XCardModule, + XIconModule, + CommonModule, + XSlotterModule, + XBaseComponentModule, + ], + exports: [ + XCardModule, + XIconModule, + CommonModule, + XSlotterModule, + XChartComponent, + XBaseComponentModule, + ], +}) +export class XChartModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-chart\x-chart.typings.ts + +import { + Input, + Inject, + NgZone, + Output, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, +} from 'x-framework-core'; +import { + XCardActionModel, + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region Chart ... +export type XChartAnnotationStyle = { + background?: string; + color?: string; + fontFamily?: string; + fontWeight?: string | number; + fontSize?: string; + cssClass?: string; + padding?: { + left?: number; + right?: number; + top?: number; + bottom?: number; + }; +}; + +export type XChartAnnotationLabel = { + borderColor?: string; + borderWidth?: number; + borderRadius?: number; + text?: string | string[]; + textAnchor?: string; + offsetX?: number; + offsetY?: number; + style?: XChartAnnotationStyle; + position?: string; + orientation?: string; + mouseEnter?: (annotation: XChartAnnotationLabel, e: MouseEvent) => void; + mouseLeave?: (annotation: XChartAnnotationLabel, e: MouseEvent) => void; + click?: (annotation: XChartAnnotationLabel, e: MouseEvent) => void; +}; + +export type XCahrtAxisBaseAnnotations = { + id?: number | string; + strokeDashArray?: number; + fillColor?: string; + borderColor?: string; + borderWidth?: number; + opacity?: number; + offsetX?: number; + offsetY?: number; + label?: XChartAnnotationLabel; + draggable?: boolean; +}; + +export type XChartYAxisAnnotations = XCahrtAxisBaseAnnotations & { + y?: null | number | string; + y2?: null | number | string; + width?: number | string; + yAxisIndex?: number; +}; + +export type XChartXAxisAnnotations = XCahrtAxisBaseAnnotations & { + x?: null | number | string; + x2?: null | number | string; +}; + +export type XChartPointAnnotations = { + id?: number | string; + x?: number | string; + y?: null | number; + yAxisIndex?: number; + seriesIndex?: number; + /** + * Ink Layer (#7): make this point annotation draggable. Overrides + * `chart.ink.enabled`. Requires the `ink` feature. + */ + draggable?: boolean; + mouseEnter?: (annotation: XChartPointAnnotations, e: MouseEvent) => void; + mouseLeave?: (annotation: XChartPointAnnotations, e: MouseEvent) => void; + click?: (annotation: XChartPointAnnotations, e: MouseEvent) => void; + marker?: { + size?: number; + fillColor?: string; + strokeColor?: string; + strokeWidth?: number; + shape?: string; + offsetX?: number; + offsetY?: number; + cssClass?: string; + }; + label?: XChartAnnotationLabel; + image?: { + path?: string; + width?: number; + height?: number; + offsetX?: number; + offsetY?: number; + }; + /** + * Show a hover tooltip over the annotation marker, like a regular data + * point. Useful for surfacing more detail than fits in the label. + */ + tooltip?: { + enabled?: boolean; + /** + * Static tooltip content (HTML allowed; an array is joined with line + * breaks). Falls back to `label.text` when omitted. + */ + text?: string | string[]; + /** + * Returns the tooltip markup (HTML). Takes precedence over `text`. + */ + formatter?: (opts: { + annotation: XChartPointAnnotations; + seriesIndex?: number; + id?: number | string; + w: any; + }) => string; + /** + * Tooltip theme. Falls back to the global `tooltip.theme`. + */ + theme?: 'light' | 'dark'; + offsetX?: number; + offsetY?: number; + }; + /** + * Render arbitrary SVG markup at the annotation's position. Deprecated in + * favor of `image`/`marker`, but still supported. + */ + customSVG?: { + SVG?: string; + cssClass?: string; + offsetX?: number; + offsetY?: number; + }; +}; + +export type XChartTextAnnotations = { + x?: number; + y?: number; + text?: string; + textAnchor?: string; + foreColor?: string; + fontSize?: string | number; + fontFamily?: undefined | string; + fontWeight?: string | number; + /** CSS selector for the parent element the text is appended to. */ + appendTo?: string; + backgroundColor?: string; + borderColor?: string; + borderRadius?: number; + borderWidth?: number; + paddingLeft?: number; + paddingRight?: number; + paddingTop?: number; + paddingBottom?: number; +}; + +export type XChartImageAnnotations = { + path?: string; + x?: number; + y?: number; + width?: number; + height?: number; +}; + +export type XChartAnnotations = { + texts?: XChartTextAnnotations[]; + yaxis?: XChartYAxisAnnotations[]; + xaxis?: XChartXAxisAnnotations[]; + points?: XChartPointAnnotations[]; + images?: XChartImageAnnotations[]; +}; + +export type XChartContext = { + config: XChartOptions; + globals: any; + [key: string]: any; +}; + +export type XChartColorFormatterOpts = { + value: number; + seriesIndex: number; + dataPointIndex: number; + w: XChartContext; +}; + +export type XChartFormatterOpts = { + seriesIndex: number; + dataPointIndex: number; + series?: any[][]; + w: XChartContext; + [key: string]: any; +}; + +export type XChartDropShadow = { + enabled?: boolean; + top?: number; + left?: number; + blur?: number; + opacity?: number; + /** + * Shadow color. A single string applies to all series; an array applies + * per-series (only respected by `chart.dropShadow`). + */ + color?: string | string[]; +}; + +export type XChartDataLabels = { + enabled?: boolean; + enabledOnSeries?: undefined | number[]; + textAnchor?: 'start' | 'middle' | 'end'; + distributed?: boolean; + /** + * Horizontal offset of the label. Pass a function to vary the offset per + * data point, e.g. to separate labels that would otherwise overlap. + * The function must be pure, as it may be called more than once per label. + */ + offsetX?: number | ((opts: XChartFormatterOpts) => number); + /** + * Vertical offset of the label. Pass a function to vary the offset per + * data point, e.g. to separate labels that would otherwise overlap. + * The function must be pure, as it may be called more than once per label. + */ + offsetY?: number | ((opts: XChartFormatterOpts) => number); + style?: { + fontSize?: string; + fontFamily?: string; + fontWeight?: string | number; + colors?: string[]; + }; + background?: { + enabled?: boolean; + foreColor?: string; + backgroundColor?: string; + borderRadius?: number; + padding?: number; + opacity?: number; + borderWidth?: number; + borderColor?: string; + dropShadow?: XChartDropShadow; + }; + dropShadow?: XChartDropShadow; + /** + * Ride data labels to their new position on a data-change update (e.g. a bar + * chart race) instead of snapping. Off by default. Bar/column only; speed and + * easing follow chart.animations.dynamicAnimation. + */ + animate?: { + enabled?: boolean; + }; + /** + * Count the numeric value up/down from its previous value on update, like + * countUp.js. Off by default. The formatter runs each frame so number + * formatting is preserved. Bar/column only. + */ + countUp?: { + enabled?: boolean; + }; + formatter?( + val: string | number | number[], + opts?: XChartFormatterOpts, + ): string | number | (string | number)[]; +}; + +export type XChartHierarchyNode = { + /** The node's label. `name` is accepted as an alias. */ + x?: string | number; + name?: string; + /** The node's value. `value` is accepted as an alias. */ + y?: number | null; + value?: number | null; + color?: string; + fillColor?: string; + /** A second metric driving colour rather than size (`treemap`). */ + colorValue?: number; + meta?: unknown; + children?: XChartHierarchyNode[]; + /** + * The `id` of a `drilldown.series` entry, read as another level by the + * sunburst (and by the treemap with + * `plotOptions.treemap.nested.drilldownAsLevels`). + */ + drilldown?: string | number; +}; + +export type XChartParsing = { + x?: string; + y?: string | string[]; + z?: string; +}; + +export type XChartColorStop = { + offset: number; + color: string; + opacity: number; +}; + +export type XChartFill = { + colors?: string[]; + opacity?: number | number[]; + type?: string | string[]; + gradient?: { + shade?: string; + type?: string; + shadeIntensity?: number; + gradientToColors?: string[]; + inverseColors?: boolean; + opacityFrom?: number | number[]; + opacityTo?: number | number[]; + stops?: number[]; + colorStops?: XChartColorStop[][] | XChartColorStop[]; + }; + image?: { + src?: string | string[]; + width?: number; + height?: number; + }; + pattern?: { + style?: string | string[]; + width?: number; + height?: number; + strokeWidth?: number; + }; +}; + +export type XChartAxisChartSeries = { + name?: string; + type?: string; + color?: string; + group?: string; + hidden?: boolean; + zIndex?: number; + parsing?: XChartParsing; + data: + | (number | null)[] + | { + /** + * A category label, a timestamp, or a `Date`. On a `datetime` axis all + * three keep millisecond resolution. + */ + x: string | number | Date; + /** + * A plain value for most charts. For `candlestick`/`boxPlot`, the + * summary array (`[O,H,L,C]` / `[min,Q1,median,Q3,max]`). For `violin`, an + * object carrying the precomputed density profile (`[value, weight]` pairs) + * plus the raw observations rendered as jitter. For a `scatter` strip plot + * (`plotOptions.scatter.jitter`), the array of observations in this category. + */ + y: + | number + | null + | number[] + | { density: [number, number][]; points?: number[] }; + /** + * Optional raw observations for a `boxPlot` data point, rendered as jitter + * dots when `plotOptions.boxPlot.points.show` is enabled. + */ + points?: number[]; + fill?: XChartFill; + fillColor?: string; + strokeColor?: string; + meta?: unknown; + /** + * A second metric that drives this point's COLOUR, independent of `y` + * which drives its size (`treemap`). See + * `plotOptions.treemap.colorScale.colorValue` to read a different key. + */ + colorValue?: number; + /** + * Nested hierarchy: this point is a branch containing these children, + * to any depth (`treemap`, `sunburst`). A branch normally omits `y` and + * takes the sum of its children instead. + */ + children?: XChartHierarchyNode[]; + /** + * Drilldown target: the `id` of a `drilldown.series` entry. Clicking this + * point drills into that level. Requires the Drilldown feature. + */ + drilldown?: string | number; + goals?: { + name?: string; + value: number; + strokeHeight?: number; + strokeWidth?: number; + strokeColor?: string; + strokeDashArray?: number; + strokeLineCap?: 'butt' | 'square' | 'round'; + }[]; + barHeightOffset?: number; + columnWidthOffset?: number; + }[] + | [number, number | null][] + | [number, (number | null)[]][] + | number[][] + // A `children` hierarchy for the partition charts, where a branch carries no + // value of its own. Listed before the catch-all so authors get completion on + // the node shape instead of falling through to `Record`. + | XChartHierarchyNode[] + | Record[]; +}[]; + +export type XChartNonAxisChartSeries = number[] | XChartAxisChartSeries; + +export type XChartGradientLegend = { + enabled?: boolean; + /** + * Strip length for horizontal placements (top/bottom). Accepts a number + * (pixels) or percentage string (e.g. `'70%'`, resolved against the chart's + * SVG width). Default `'70%'`. + */ + width?: number | string; + /** + * Strip length for vertical placements (left/right). Accepts a number + * (pixels) or percentage string (e.g. `'70%'`, resolved against the chart's + * SVG height). Default `'70%'`. + */ + height?: number | string; + /** Strip thickness (short axis) in pixels. Default 12. */ + thickness?: number; + /** + * Strip alignment within the legend area. + * - top/bottom: 'start' = left, 'center', 'end' = right + * - left/right: 'start' = top, 'center', 'end' = bottom + * Default `'center'`. + */ + align?: 'start' | 'center' | 'end'; + /** + * Number of color stops sampled from the shade function when no explicit + * `ranges` (or continuous scale) supply their own. Default 16. + */ + stops?: number; + /** Show min/max labels at the ends of the strip. Default true. */ + showLabels?: boolean; + /** Show a value tooltip next to the arrow on mark hover. Default true. */ + showHoverValue?: boolean; + labelStyle?: { + fontSize?: string; + fontFamily?: string; + colors?: string; + }; + arrow?: { + size?: number; + color?: string; + }; + /** Formatter for min/max labels and the hover value tooltip. */ + formatter?(value: number): string; +}; + +export type XChartTreemapLevel = { + /** Inset between a parent's edge and the children inside it. Default 4. */ + padding?: number; + /** Container fill. Defaults to a neutral tint that deepens with each level. */ + fill?: string; + fillOpacity?: number; + borderColor?: string; + borderWidth?: number; + /** Falls back to `plotOptions.treemap.borderRadius`. */ + borderRadius?: number; + hover?: { + /** Outline the container on hover. Default true. */ + show?: boolean; + color?: string; + width?: number; + }; + header?: { + show?: boolean; + /** Height of the strip reserved at the top of the container. Default 22. */ + height?: number; + /** + * Skip the strip on tiles narrower than this, where no name could be read + * anyway. Default 40. + */ + minWidth?: number; + align?: 'left' | 'center' | 'right'; + offsetX?: number; + offsetY?: number; + /** Append the branch's aggregate to its name. Default false. */ + showValue?: boolean; + formatter?( + name: string, + opts: { + value: number; + depth: number; + seriesIndex: number; + node: any; + w: any; + }, + ): string; + style?: { + fontSize?: string; + fontFamily?: string; + fontWeight?: number | string; + color?: string; + background?: string; + cssClass?: string; + }; + }; +}; + +export type XChartBreadcrumb = { + show?: boolean; + position?: 'top-left' | 'top-right'; + separator?: string; + /** Label of the leftmost crumb, the "everything" level. Default 'All'. */ + rootLabel?: string; + offsetX?: number; + offsetY?: number; + formatter?( + label: string, + opts: { index: number; depth: number; data?: any }, + ): string; +}; + +export interface XChartUnitObject { + /** + * Stable identity. The datum's own `id`/`name` when the per-unit object form + * supplies one, so a provider can address a specific unit ("Texas", + * "employee 41") rather than a positional slot; otherwise + * `":"`. + */ + id: string; + /** Global draw order across every category. */ + index: number; + /** Category this mark belongs to. */ + seriesIndex: number; + /** Index within its category. */ + dataPointIndex: number; + /** Category label. */ + label: string; + /** The datum's value, when the per-unit object form supplies one. */ + value?: number; + /** The raw per-unit datum, when supplied. */ + datum?: any; + /** The radius the engine would use, so a size-aware provider need not redo it. */ + r: number; +} + +export interface XChartUnitPosition { + /** Must match an `ApexUnitObject.id`; unknown ids are ignored. */ + id: string; + x: number; + y: number; + /** Overrides the engine's radius for this mark. */ + r?: number; +} + +export type XChartUnitLayout = ( + objects: XChartUnitObject[], + rect: { x: number; y: number; width: number; height: number }, +) => XChartUnitPosition[]; + +export type XChartPlotOptions = { + line?: { + isSlopeChart?: boolean; + colors?: { + threshold?: number; + colorAboveThreshold?: string; + colorBelowThreshold?: string; + }; + }; + area?: { + fillTo?: 'origin' | 'end'; + }; + bar?: { + horizontal?: boolean; + columnWidth?: string | number; + barHeight?: string | number; + distributed?: boolean; + borderRadius?: number; + borderRadiusApplication?: 'around' | 'end'; + borderRadiusWhenStacked?: 'all' | 'last'; + hideZeroBarsWhenGrouped?: boolean; + rangeBarOverlap?: boolean; + rangeBarGroupRows?: boolean; + isDumbbell?: boolean; + dumbbellColors?: string[][]; + isFunnel?: boolean; + isFunnel3d?: boolean; + colors?: { + ranges?: { + from?: number; + to?: number; + color?: string; + }[]; + backgroundBarColors?: string[]; + backgroundBarOpacity?: number; + backgroundBarRadius?: number; + }; + dataLabels?: { + maxItems?: number; + hideOverflowingLabels?: boolean; + position?: string; + orientation?: 'horizontal' | 'vertical'; + total?: { + enabled?: boolean; + formatter?(val?: string, opts?: XChartFormatterOpts): string; + offsetX?: number; + offsetY?: number; + style?: { + color?: string; + fontSize?: string; + fontFamily?: string; + fontWeight?: number | string; + }; + }; + }; + }; + bubble?: { + zScaling?: boolean; + minBubbleRadius?: number; + maxBubbleRadius?: number; + }; + scatter?: { + /** + * Spread overlapping points apart ("jitter"). Two uses, one engine: + * - Strip plot: supply data as `{ x: 'Category', y: [v1, v2, ...] }`. Each + * category becomes a band and the values scatter horizontally within it. + * - Overplotting: ordinary `{ x, y }` points get a small random offset so + * dense clusters fan out. The underlying data (and tooltip values) stay + * exact; only the drawn position moves. + * Offsets are in axis units and deterministic (stable across re-renders). + */ + jitter?: { + enabled?: boolean; + /** Max ± horizontal offset, in x-axis units (1 = one category step). */ + x?: number; + /** Max ± vertical offset, in y-axis units. */ + y?: number; + /** Single series: colour each band differently (by its position). */ + distributed?: boolean; + /** Per-band cap; values beyond this are stride-thinned. */ + maxPoints?: number; + }; + }; + candlestick?: { + type?: string; + colors?: { + upward?: string | string[]; + downward?: string | string[]; + }; + wick?: { + useFillColor?: boolean; + }; + }; + boxPlot?: { + colors?: { + upper?: string | string[]; + lower?: string | string[]; + }; + /** + * Where the whiskers reach when the summary is DERIVED from raw + * observations: a datum supplying `points` instead of a five-number `y`, + * which requires `import 'apexcharts/features/stats'`. A precomputed + * summary is drawn exactly as given and ignores this. + * + * `'minmax'` (default) reaches the extremes, so nothing is hidden. + * `'tukey'` stops at the last observation within 1.5 * IQR of each + * quartile; points beyond the fence then fall outside the whisker, so pair + * it with `points.show` or they become invisible. + */ + whiskers?: 'minmax' | 'tukey'; + /** + * Individual observations ("jitter") overlaid on each box. Inert unless a + * data point supplies a `points: number[]` array; `show` is false by + * default so existing boxPlot charts are unchanged. + * + * `points` is also the sample the five-number summary is derived from when + * a datum has no `y` (see `whiskers`), so the observations live in one + * place whether the library summarises them or you do. + */ + points?: { + show?: boolean; + shape?: 'circle' | 'square'; + /** Marker radius in pixels. */ + size?: number; + /** 0..1 fraction of the box half-width to scatter within. */ + jitter?: number; + /** Cap per box; observations beyond this are stride-thinned. */ + maxPoints?: number; + opacity?: number; + /** + * Dot fill colour. Defaults to 'series-dark' (a darker shade of the + * series colour). Use 'series' for the series colour, or any literal + * colour string. + */ + fillColor?: string; + /** Colour of the outline around each dot. Defaults to '#fff'. */ + strokeColor?: string; + /** Width of the dot's outline in pixels. Defaults to 1; 0 disables it. */ + strokeWidth?: number; + /** + * Colour each dot by its value along a colour ramp (overrides fillColor). + * Points are bucketed into `steps` shades to keep rendering performant. + */ + colorScale?: { + colors: string[]; + min?: number; + max?: number; + steps?: number; + }; + }; + }; + violin?: { + /** + * Multiplies the density-derived half-width. 1 maps the density's own + * maxWeight to half the category slot. + */ + bandwidthScale?: number; + /** + * Kernel density estimation, used only when the density is DERIVED from raw + * observations: a datum supplying `points`, or a flat number array as `y`, + * which requires `import 'apexcharts/features/stats'`. A precomputed + * density profile is drawn exactly as given. + */ + kde?: { + /** + * Kernel width in value units. Unset uses Silverman's rule of thumb. + * This is the statistical parameter; `bandwidthScale` above only scales + * the drawn width. + */ + bandwidth?: number; + /** Density samples per violin (default 64). */ + resolution?: number; + }; + /** + * 'individual' (default): each violin is scaled to its own peak density, so + * all violins reach the full slot width. 'group': all violins share the + * densest violin's scale, keeping widths proportional to density across + * categories. + */ + normalize?: 'individual' | 'group'; + /** Individual observations ("jitter") overlaid on the violin shape. */ + points?: { + show?: boolean; + shape?: 'circle' | 'square'; + /** Marker radius in pixels. */ + size?: number; + /** 0..1 fraction of the half-width to scatter within. */ + jitter?: number; + /** Clamp jitter to the density width at each value so points stay inside. */ + constrainToViolin?: boolean; + /** Cap per violin; observations beyond this are stride-thinned. */ + maxPoints?: number; + opacity?: number; + /** + * Dot fill colour. Defaults to 'series-dark' (a darker shade of each + * violin's own colour). Use 'series' for the violin's colour as-is, or + * any literal colour string (e.g. '#fff'). + */ + fillColor?: string; + /** Colour of the ring/outline around each dot. Defaults to '#fff'. */ + strokeColor?: string; + /** Width of the dot's outline in pixels. Defaults to 1; 0 disables it. */ + strokeWidth?: number; + /** + * Colour each dot by its value along a colour ramp (overrides fillColor). + * Points are bucketed into `steps` shades to keep rendering performant. + */ + colorScale?: { + /** Hex colour stops, low → high (a sequential colour ramp). */ + colors: string[]; + /** Value mapped to the first stop. Defaults to the data minimum. */ + min?: number; + /** Value mapped to the last stop. Defaults to the data maximum. */ + max?: number; + /** Number of shade buckets. Defaults to 24. */ + steps?: number; + }; + }; + }; + /** + * `chart.type: 'histogram'`. The series carry raw observations (a flat + * number array, or `{ y }` objects) and are binned into one column per bin; + * all series share one set of edges so overlaid distributions stay + * comparable. + * + * Requires the optional stats feature. Import from `apexcharts/histogram`, + * or add `import 'apexcharts/features/stats'` alongside `apexcharts/bar`. + * The default `apexcharts` bundle already includes it. Without it the chart + * warns and draws nothing, rather than rendering one bar per observation. + */ + histogram?: { + /** + * How the bin width is chosen: a rule name, or a fixed bin count. + * `'auto'` takes the narrower of Freedman-Diaconis and Sturges, falling + * back to Sturges when the IQR is 0. + */ + bins?: 'auto' | 'fd' | 'sturges' | 'scott' | 'rice' | 'sqrt' | number; + /** + * Explicit bin width in value units. Wins over `bins`, for when the + * boundaries carry meaning (decades, 5-minute buckets) rather than being + * a statistical choice. + */ + binWidth?: number; + /** `[min, max]` to bin over instead of the data's own extent. */ + range?: number[]; + /** + * y units: observations per bin, percent of the series total, or + * `count / (n * binWidth)` so the total area is 1. + */ + normalize?: 'count' | 'relative' | 'density'; + /** Running total across bins, i.e. a cumulative distribution. */ + cumulative?: boolean; + /** + * With more than one series, draw every distribution across the full bin + * so they overlay, instead of dividing the bin between them. Defaults to + * `true`: all series already share one set of edges, and comparing two + * shapes is the reason to put them on one axis. Set `false` for + * side-by-side bars. + * + * An overlay also softens the fill and drops the bin separator stroke, so + * the overlapping region reads. Both remain overridable. + */ + overlap?: boolean; + }; + heatmap?: { + radius?: number; + enableShades?: boolean; + shadeIntensity?: number; + reverseNegativeShade?: boolean; + distributed?: boolean; + useFillColorAsStroke?: boolean; + colorScale?: { + ranges?: { + from?: number; + to?: number; + color?: string; + foreColor?: string; + name?: string; + }[]; + inverse?: boolean; + min?: number; + max?: number; + /** + * When enabled, replaces the default categorical legend with a + * continuous color gradient strip and a hover indicator arrow that + * tracks the currently hovered mark's value along the spectrum. + * Follows `legend.position` (top / right / bottom / left); the arrow + * orientation flips to point at the strip from the chart-facing side. + */ + gradientLegend?: XChartGradientLegend; + }; + }; + funnel?: { + /** + * 'rectangle' (default) preserves the existing centered-rectangle funnel + * geometry. 'trapezoid' produces continuous sloped sides between + * consecutive stages (each stage's bottom width matches the next stage's + * top width). + */ + shape?: 'rectangle' | 'trapezoid'; + /** + * For `shape: 'trapezoid'` only — last stage's bottom edge: + * 'flat' (default, parallel sides) or 'taper' (taper to a point). + */ + lastShape?: 'flat' | 'taper'; + }; + treemap?: { + enableShades?: boolean; + shadeIntensity?: number; + distributed?: boolean; + reverseNegativeShade?: boolean; + useFillColorAsStroke?: boolean; + dataLabels?: { + format?: 'scale' | 'truncate'; + /** + * Skip a tile's label when it would render below this size in px. + * + * With `format: 'scale'` the font size follows the tile's area, so a + * dense treemap asks for a lot of text only a few pixels tall. Each such + * label still has to be built and measured against the DOM, which on a + * large chart dominates the render. Default 4, below the smallest label + * any bundled sample draws. Set 0 to label every tile regardless. + */ + minFontSize?: number; + }; + borderRadius?: number; + colorScale?: { + inverse?: boolean; + ranges?: { + from?: number; + to?: number; + color?: string; + foreColor?: string; + name?: string; + }[]; + min?: number; + max?: number; + /** + * Colour a tile by a SECOND metric, independent of the value that sizes + * it: area is how big something is, colour is how it did. Reads + * `datum.colorValue` by default; pass a key name to read a different + * property, or an accessor to compute one. + */ + colorValue?: + | string + | (( + datum: any, + opts: { seriesIndex: number; dataPointIndex: number; w: any }, + ) => number); + /** + * Continuous interpolation between colour stops, for the metric above. + * Active as soon as any datum carries a colour metric; `enabled: false` + * opts out and `true` forces it on. `ranges` is unaffected and still + * applies wherever it is set. + */ + gradient?: { + enabled?: boolean; + /** Domain low. Defaults to the extent of the colour metric. */ + min?: number; + /** Domain high. Defaults to the extent of the colour metric. */ + max?: number; + /** + * The value the middle colour is pinned to. Defaults to 0 when the + * domain straddles zero (a diverging metric), otherwise none. Pass + * `null` to force a plain sequential ramp. + */ + midpoint?: number | null; + /** + * With a midpoint, balance the domain around it so equal moves in + * either direction read as equally saturated. Default true. + */ + symmetric?: boolean; + /** Low -> mid -> high. Two colours make a sequential ramp. */ + colors?: string[]; + /** Explicit stops; overrides `colors` and `midpoint`. */ + stops?: { value: number; color: string }[]; + }; + /** + * Continuous colour legend for the metric above: a gradient strip with + * end labels and a hover indicator, in place of the categorical legend. + */ + gradientLegend?: XChartGradientLegend; + }; + /** + * Arbitrary-depth treemap. A datum may carry `children` to any depth; + * every branch is drawn as a real container with a header strip and its + * children inset below it. + */ + nested?: { + /** + * Parent containers appear on their own as soon as the data is nested. + * `false` forces the flat two-level layout. + */ + enabled?: boolean; + /** + * Read `drilldown: ''` ids as extra levels instead of as a click + * target for the drilldown feature. Default false, because on a treemap + * that id has always meant "descend on click". + */ + drilldownAsLevels?: boolean; + }; + /** + * How a branch is drawn once the data is nested. Per-level overrides go in + * `levels`. + */ + parents?: XChartTreemapLevel & { + /** `'auto'` (default): on when the data carries `children`. */ + show?: boolean | 'auto'; + tooltip?: { + formatter?(opts: { + name: string; + value: number; + depth: number; + leafCount: number; + percentOfParent: number; + percentOfTotal: number; + node: any; + w: any; + }): string; + }; + }; + /** + * Per-depth overrides of `parents`, indexed from the outermost group + * actually drawn (0 = the series, or the first authored level when a + * single series is unwrapped). + */ + levels?: XChartTreemapLevel[]; + /** + * Click a group to fill the canvas with it; a breadcrumb goes back. + * + * Ignored when the drilldown feature is active on the same chart: both + * navigate the hierarchy, and drilldown owns the click there. + */ + zoom?: { + enabled?: boolean; + /** + * Overrides `drilldown.breadcrumb` for this chart only, so a zoomed + * treemap and a drilled-in chart present the same affordance without + * importing the drilldown feature. + */ + breadcrumb?: XChartBreadcrumb; + }; + seriesTitle?: { + show?: boolean; + offsetY?: number; + offsetX?: number; + borderColor?: string; + borderWidth?: number; + borderRadius?: number; + style?: { + background?: string; + color?: string; + fontSize?: string; + fontFamily?: string; + fontWeight?: number | string; + cssClass?: string; + padding?: { + left?: number; + right?: number; + top?: number; + bottom?: number; + }; + }; + }; + }; + unit?: { + /** + * 'grouped' (default): each category is its own cluster, laid out in a row. + * 'packed': one blob; categories are coloured and (with sortByGroup) ordered + * smallest-first so the minority group nests in the centre. + * 'columns': each category is a vertical bar built from stacked dots (a unit + * / waffle column) whose height encodes the count. + * 'grid': one lattice of cells filled in category order - a waffle / + * part-to-whole square "pie" (`chart.type: 'waffle'` presets this layout). + * 'scatter': beeswarm - each unit placed on a real numeric X value axis by + * its per-unit value, laned by category on Y (draws its own axis + lanes). + * 'arc': parliament / hemicycle - seats in concentric arced rows, filled in + * category order so each category is a contiguous wedge (see `arc`). + * 'custom': positions come from `positions`. + */ + layout?: + | 'grouped' + | 'packed' + | 'columns' + | 'grid' + | 'scatter' + | 'arc' + | 'custom'; + /** + * `layout: 'custom'` only. The layout provider: either a function returning + * plot-pixel positions, or the name of one registered with + * `ApexCharts.registerUnitLayout`. + * + * A layout is objects in, positions out. It knows nothing about animation, + * because the engine already tweens position, radius and colour and already + * keeps each mark's identity across a relayout, so an arrangement the + * built-in layouts cannot express needs no new transition code. + * + * A mark whose id the provider omits animates out; ids matching no mark are + * ignored. + */ + positions?: string | XChartUnitLayout; + /** + * How dots are matched between renders on an update (which previous dot a + * new dot tweens from). + * 'group' (default): keyed per category, so a dot stays in its group and + * category-level enters/exits fade in and out. + * 'flow': keyed by global draw order, so the anonymous crowd migrates (and + * recolours) across a regroup - the circles-to-bars transition. + * 'identity': keyed by each datum's `id`/`name`, so a SPECIFIC unit migrates + * across any regroup or relayout keeping its colour and size. Requires the + * per-unit object form with unique ids/names. + */ + transition?: 'group' | 'flow' | 'identity'; + /** Mark shape for each unit. `'image'` renders an icon (isotype pictogram). */ + shape?: 'circle' | 'square' | 'image'; + /** Icon used when `shape: 'image'`. */ + image?: { + /** Icon URL or data URI. */ + src?: string; + width?: number; + height?: number; + /** + * Recolour a monochrome icon to the category colour (or a per-unit + * `fillColor`) so the pictogram matches the legend. Leave off (default) + * for multi-colour icons that should keep their own colours. + */ + tint?: boolean; + }; + /** Dot radius in px, or 'auto' to size dots so the largest cluster fits. */ + size?: number | 'auto'; + /** + * The 'columns' layout can size its dots independently of `size` (which the + * circle layouts / storyboard beats often pin to a constant so dots do not + * resize while migrating). + */ + columns?: { + /** + * 'inherit' (default) uses `size`; 'auto' sizes dots to fill the plot + * height; a number pins a columns-only size. Circle / square only (image + * icons keep their intrinsic size). + */ + size?: 'inherit' | 'auto' | number; + }; + /** + * The 'grid' (waffle) layout: one lattice of cells filled in category order. + */ + grid?: { + /** Cells per row. Defaults to 10. */ + columns?: number; + /** + * Fixed cell budget (e.g. 100 for a percentage waffle); largest-remainder + * allocates the cells to categories. Leave undefined for one cell per unit + * (respects unitValue / maxUnits). + */ + total?: number; + /** First row of the fill: 'bottom' (default) or 'top'. */ + fillFrom?: 'bottom' | 'top'; + /** + * Small multiples: render ONE mini-waffle per category in a trellis + * instead of a single shared lattice. Each tile has `total` cells + * (default 100) and fills value/`max` of them; the rest show as a faint + * `trackColor` backdrop, and each tile carries its own label. + */ + split?: boolean; + /** Small-multiple tiles per row; undefined = auto (near-square). */ + tileColumns?: number; + /** + * Small-multiple value -> filled-cell denominator; undefined = the largest + * count (leader fills its tile). Set to 100 for true "of 100" percentage tiles. + */ + max?: number; + /** Small-multiple empty ("track") cell colour; undefined = neutral grey. */ + trackColor?: string; + }; + /** + * The 'scatter' layout places units on real value axes (needs the object-form + * data). `y:'lanes'` (default) is a beeswarm (X value axis, Y category lane); + * `y:'value'` is a 2D value-value scatter (each datum's `x`/`y` on two numeric + * axes, category = colour). `sizeRange` turns dots into bubbles. + */ + scatter?: { + /** 'lanes' (beeswarm, default) or 'value' (2D value-value scatter). */ + y?: 'lanes' | 'value'; + /** 'swarm' (anti-overlap pack, default) or 'jitter' (random lane spread). */ + spread?: 'swarm' | 'jitter'; + /** + * Beeswarm orientation (1D `y:'lanes'` mode only). 'horizontal' (default): + * value on X, category lanes stacked on Y. 'vertical': value on Y, + * category lanes as columns across X. The value-axis config keys + * (`xMin`/`xMax`/`xTitle`/`xFormatter`/`tickAmount`) describe the value + * axis in both orientations. + */ + orientation?: 'horizontal' | 'vertical'; + /** Approximate number of value-axis ticks. Defaults to 5. */ + tickAmount?: number; + /** Fixed X-axis min / max; undefined = derived (nice-numbered) from data. */ + xMin?: number; + xMax?: number; + /** X-axis title drawn under the tick labels. */ + xTitle?: string; + /** X tick-label formatter, `(value) => string`. */ + xFormatter?: (value: number) => string; + /** Approximate number of Y-axis ticks (2D mode). Defaults to 5. */ + yTickAmount?: number; + /** Fixed Y-axis min / max (2D mode); undefined = nice-numbered from data. */ + yMin?: number; + yMax?: number; + /** Y-axis title (2D mode), drawn rotated at the left. */ + yTitle?: string; + /** Y tick-label formatter, `(value) => string`. */ + yFormatter?: (value: number) => string; + /** Datum key holding the bubble size value. Defaults to 'z'. */ + sizeField?: string; + /** `[minRadius, maxRadius]` in px: turns dots into area-scaled bubbles. */ + sizeRange?: [number, number]; + /** Left-gutter width reserved for lane (category) labels (lanes mode). */ + laneLabelWidth?: number; + /** Draw the faint gridlines. Defaults to true. */ + gridlines?: boolean; + }; + /** + * Opt-in bubble sizing: scale each dot's radius by its per-unit value + * (requires the object-form data, `series: [{ data: [{ value }] }]`). + * Circle shape only; the lattice is spaced for the largest bubble so dots + * never overlap. Ignored when there are no per-unit values. + */ + sizeByValue?: { + enabled?: boolean; + /** Radius (px) for the largest value, or 'auto' to fit it to the plot. */ + maxRadius?: number | 'auto'; + /** Radius (px) for the smallest value; defaults to ~35% of maxRadius. */ + minRadius?: number; + /** 'area' (bubble area proportional to value) or 'linear'. */ + scale?: 'area' | 'linear'; + }; + /** Packing gap factor between spiral shells (1 = dots touch). */ + spacing?: number; + /** + * How marks move between layouts on an update, and where entering marks + * come from. + */ + gather?: { + /** + * 'spring' settles each mark on a damped spring, so a gather interrupted + * by the next update carries the marks' velocity into it instead of + * restarting them from a standstill. 'tween' runs the fixed-duration + * `easing` below. 'auto' (the default) is spring, unless `easing` was set + * to something other than the default. + */ + motion?: 'auto' | 'spring' | 'tween'; + /** + * Spring character (`motion: 'spring'` only): 'crisp' (default), + * 'gentle' (softer, for large reflows) or 'snappy' (faster, a hint of + * settle). Scaled by `chart.animations.speed`. + */ + spring?: 'crisp' | 'gentle' | 'snappy'; + /** Tween curve: 'outCubic' (default: decelerate and stop), 'inOutCubic' (weighted start), or 'outBack' (overshoot + settle). Setting this implies `motion: 'tween'`. */ + easing?: 'outCubic' | 'inOutCubic' | 'outBack'; + /** Overshoot strength for `easing: 'outBack'`. Defaults to 1.70158 (~10% overshoot). */ + overshoot?: number; + /** + * Where an ENTERING mark animates from (fresh mount, or a category + * appearing): 'burst' (default) flies out from the cluster centre, + * 'fade' materialises in place, 'rise' fades in while drifting gently + * up into its slot. + */ + enter?: 'burst' | 'fade' | 'rise'; + }; + /** + * Options for `layout: 'arc'` (parliament / hemicycle). Angles use the + * radialBar convention: 0 = top, clockwise. The default sweep is a top + * semicircle; a full circle is `startAngle: 0, endAngle: 360`. + */ + arc?: { + /** Sweep start angle in degrees (0 = top, clockwise). Default -90. */ + startAngle?: number; + /** Sweep end angle in degrees. Default 90 (a top semicircle). */ + endAngle?: number; + /** Donut hole: inner radius as a fraction of the outer radius. Default 0.4. */ + innerRadiusRatio?: number; + /** Number of concentric seat rows, or 'auto' to size dots as large as fit. */ + rows?: number | 'auto'; + }; + /** Corner radius for shape:'square'. */ + borderRadius?: number; + /** 1 dot represents this many units of value (waffle scaling). */ + unitValue?: number; + /** Safety cap on total dots; counts scale down proportionally above it. */ + maxUnits?: number; + /** Packed layout: order categories smallest-first (minority centred). */ + sortByGroup?: boolean; + clusterLabels?: { + show?: boolean; + /** Label placement relative to the cluster/bar. Defaults to 'top'. A 'bottom' label is always straight (the curved arc rides the top crown only). */ + position?: 'top' | 'bottom'; + curved?: boolean; + fontSize?: string; + fontFamily?: string; + fontWeight?: number | string; + /** Defaults to the cluster's own colour when undefined. */ + color?: string; + offsetY?: number; + /** Return "\n"-separated text to split an outer label over several lines. */ + formatter?( + name: string, + opts: { seriesIndex: number; value: number; percent: number; w: any }, + ): string; + /** + * Outer (name) labels, as pie/donut draw them: the label sits in the margin + * beside the shape and a leader line joins it to the colour band it names, + * so the crowd can be read without a legend. + * + * `layout: 'custom'` only, and best on a silhouette whose categories stack + * vertically (the default row ordering): those alternate down the left and + * right gutters. A column-ordered shape sends each label to the side its own + * band sits on. The margin is taken off both sides so the shape stays + * centred, so turning this on makes the silhouette a little smaller. + */ + external?: { + show?: boolean; + connector?: { + show?: boolean; + width?: number; + /** Defaults to the band's own colour when undefined. */ + color?: string; + /** Air between the band's outermost dot and the leader line's bend. */ + gap?: number; + /** Length of the run out to the label. */ + length?: number; + }; + offsetX?: number; + offsetY?: number; + }; + }; + /** Per-unit (per-dot) tooltip. */ + tooltip?: { + /** + * Return the tooltip body for a single hovered dot. The dot's category is + * `seriesIndex` and its index within that category is `dataPointIndex`, so + * the formatter can index into per-unit data. Return a string or HTML. + * Defaults to `"# of "`. + */ + formatter?(opts: { + seriesName: string; + seriesIndex: number; + dataPointIndex: number; + /** Number of dots drawn for this category (after unitValue + maxUnits). */ + count: number; + /** Raw category value (before unitValue scaling). */ + value: number; + unitValue: number; + /** + * This dot's own datum when the per-unit object form was supplied + * (`series: [{ name, data: [...] }]`); otherwise undefined. + */ + datum: any; + color: string; + w: any; + }): string; + }; + }; + pie?: { + startAngle?: number; + endAngle?: number; + customScale?: number; + offsetX?: number; + offsetY?: number; + expandOnClick?: boolean; + /** + * How far a clicked slice slides out of the pie (px), measured along its + * own mid-angle. The slice is translated, not redrawn at a bigger radius, + * so its shape is unchanged and a gap opens between it and the rest of the + * pie. Defaults to 10. Ignored for polarArea, and in a drilldown pie/donut + * where a slice click navigates instead. Set 0 to keep the slice in place + * on click. + */ + expandOffset?: number; + /** + * Hover outline: a translucent band traced just outside the rim of the + * hovered slice, so the slice keeps its own colour instead of being + * lightened. Takes the place of the `states.hover` filter for pie, donut + * and polarArea, and is skipped when `states.hover.filter.type` is + * `'none'`. + */ + hoverOutline?: { + show?: boolean; + /** Band thickness in px. Defaults to 8. */ + size?: number; + /** + * Extra clearance between the slice rim and the band, in px, on top of + * the slice stroke (the band always starts at the outer edge of the + * stroke, never under it). Defaults to 0, since a stroke is normally + * present and already reads as the separation. + */ + gap?: number; + /** Band opacity over the slice colour. Defaults to 0.3. */ + opacity?: number; + /** Band colour. Defaults to the hovered slice's colour. */ + color?: string; + }; + /** + * Rounds the corners of each slice (in px). Applies to pie, donut and + * polarArea. Defaults to 0 (sharp corners). The value is clamped per + * slice so corner fillets never cross on thin or narrow slices. + */ + borderRadius?: number; + /** + * Gap between adjacent slices (in px). Applies to pie, donut and + * polarArea. Defaults to 0 (slices touch). Each slice is inset + * symmetrically, so its mid-angle (data label and hit region) is kept. + */ + spacing?: number; + dataLabels?: { + offset?: number; + minAngleToShowLabel?: number; + /** + * External (outer) labels: render the category/series name outside the + * slice, joined by a leader (connector) line, so the chart is readable + * without the legend. The percentage keeps rendering inside the slice. + * Applies to pie and donut only (ignored for polarArea, where the radial + * length already encodes the value). + */ + external?: { + show?: boolean; + offsetX?: number; + offsetY?: number; + fontSize?: string; + fontFamily?: string; + fontWeight?: string | number; + color?: string; + /** + * Return a string for a single-line label, or an array of strings to + * stack multiple lines (e.g. `[name, percent + '%']`). + */ + formatter?( + name: string, + opts: { + seriesIndex: number; + percent: number; + value: number; + w: XChartContext; + }, + ): string | string[]; + /** Leader line from the slice edge to the label. */ + connector?: { + show?: boolean; + width?: number; + color?: string; + length?: number; + gap?: number; + }; + }; + }; + donut?: { + size?: string; + background?: string; + labels?: { + show?: boolean; + name?: { + show?: boolean; + fontSize?: string; + fontFamily?: string; + fontWeight?: string | number; + color?: string; + offsetY?: number; + formatter?(val: string): string; + }; + value?: { + show?: boolean; + fontSize?: string; + fontFamily?: string; + fontWeight?: string | number; + color?: string; + offsetY?: number; + formatter?(val: number | string): string; + }; + total?: { + show?: boolean; + showAlways?: boolean; + fontFamily?: string; + fontWeight?: string | number; + fontSize?: string; + label?: string; + color?: string; + formatter?(w: XChartContext): string; + }; + }; + }; + }; + polarArea?: { + rings?: { + strokeWidth?: number; + strokeColor?: string; + }; + spokes?: { + strokeWidth?: number; + connectorColors?: string | string[]; + }; + }; + /** + * Sunburst / nested pie-donut (hierarchical radial). Rings go from the centre + * hole outward, one per hierarchy level; each child arc is nested inside its + * parent's angular wedge. Accepts a native `children` hierarchy or an existing + * `drilldown` config (adapter). + */ + sunburst?: { + offsetX?: number; + offsetY?: number; + startAngle?: number; + endAngle?: number; + /** Centre hole radius as a % of the max radius (e.g. '15%'). */ + innerSize?: string; + /** Corner rounding of each arc (px), same semantics as pie borderRadius. */ + borderRadius?: number; + /** Gap between adjacent arcs (px), same semantics as pie spacing. */ + spacing?: number; + /** Draw a shallow branch's leaf to the rim ('extend') or stop it ('stop'). */ + leaf?: 'extend' | 'stop'; + /** Angular partition of a parent's wedge among its children. */ + partition?: 'normalize' | 'strict'; + /** Per-depth lightening of the parent colour (0 = same, 1 = white). */ + tint?: number; + /** Click a wedge to zoom into its branch (breadcrumb to go back). Default true. */ + zoomOnClick?: boolean; + dataLabels?: { + show?: boolean; + /** Hide the label on any arc narrower than this (degrees). */ + minAngleToShow?: number; + style?: { + fontSize?: string; + fontFamily?: string; + fontWeight?: string | number; + colors?: string[]; + }; + }; + }; + radar?: { + size?: number; + offsetX?: number; + offsetY?: number; + polygons?: { + strokeColors?: string | string[]; + strokeWidth?: number | number[] | string | string[]; + connectorColors?: string | string[]; + fill?: { + colors?: string[]; + }; + }; + }; + radialBar?: { + inverseOrder?: boolean; + startAngle?: number; + endAngle?: number; + offsetX?: number; + offsetY?: number; + /** + * Gauge sub-shape. 'arc' (default) renders the existing filled value-arc + * gauge; 'needle' replaces the value-arc with a rotating pointer/needle. + * Bands and ticks are independent and work for both shapes. + */ + shape?: 'arc' | 'needle'; + /** + * Value-to-angle mapping (gauge). Defaults: min: 0, max: 100. Override + * for gauges with a custom domain (e.g. min: 0, max: 240 speedometer). + */ + min?: number; + max?: number; + /** + * Threshold bands rendered as colored arc segments along the gauge arc. + * Each band spans [`from`, `to`] in the gauge's `min..max` domain and is + * filled with `color`. + */ + bands?: Array<{ + from: number; + to: number; + color: string; + label?: string; + }>; + bandsStyle?: { + strokeWidth?: string; + gap?: number; + hideTrackWhenPresent?: boolean; + linecap?: 'butt' | 'round' | 'square'; + }; + ticks?: { + show?: boolean; + major?: { + count?: number; + length?: number; + width?: number; + color?: string; + placement?: 'inside' | 'outside'; + }; + minor?: { + count?: number; + length?: number; + width?: number; + color?: string; + placement?: 'inside' | 'outside'; + }; + labels?: { + show?: boolean; + offset?: number; + fontSize?: string; + fontFamily?: string; + fontWeight?: string | number; + color?: string; + formatter?: (value: number) => string; + }; + }; + needle?: { + color?: string; + length?: string | number; + baseWidth?: number; + tipWidth?: number; + /** + * When true, also render the filled value-arc alongside the needle. + * Default false preserves needle-only behavior. + */ + showValueArc?: boolean; + /** + * px offset from the geometric arc center on Y. Positive values push + * the needle base down (toward the chord midpoint of a ∩-shape + * gauge); negative pushes up. The needle rotates around this shifted + * point. + */ + offsetY?: number; + animation?: { + enabled?: boolean; + duration?: number; + easing?: string; + }; + }; + hollow?: { + margin?: number; + size?: string; + background?: string; + image?: string; + imageWidth?: number; + imageHeight?: number; + imageOffsetX?: number; + imageOffsetY?: number; + imageClipped?: boolean; + position?: 'front' | 'back'; + /** + * Optional stroke color around the hollow ring. Combined with + * `strokeDasharray` this produces a dashed indicator circle around + * the value text. + */ + stroke?: string; + strokeWidth?: number; + strokeDasharray?: string | number; + dropShadow?: XChartDropShadow; + }; + track?: { + show?: boolean; + startAngle?: number; + endAngle?: number; + background?: string | string[]; + strokeWidth?: string; + opacity?: number; + margin?: number; + dropShadow?: XChartDropShadow; + }; + dataLabels?: { + show?: boolean; + name?: { + show?: boolean; + fontFamily?: string; + fontWeight?: string | number; + fontSize?: string; + color?: string; + offsetY?: number; + formatter?(seriesName: string): string; + }; + value?: { + show?: boolean; + fontFamily?: string; + fontSize?: string; + fontWeight?: string | number; + color?: string; + offsetY?: number; + formatter?(val: number): string; + }; + total?: { + show?: boolean; + label?: string; + color?: string; + fontFamily?: string; + fontWeight?: string | number; + fontSize?: string; + formatter?(w: XChartContext): string; + }; + }; + barLabels?: { + enabled?: boolean; + offsetX?: number; + offsetY?: number; + useSeriesColors?: boolean; + fontFamily?: string; + fontWeight?: string | number; + fontSize?: string; + formatter?: (barName: string, opts?: XChartFormatterOpts) => string; + onClick?: (barName: string, opts?: XChartFormatterOpts) => void; + }; + }; +}; + +export type XChartXAxis = { + type?: 'category' | 'datetime' | 'numeric'; + /** + * X-axis category labels. Pass a flat array for a single row of labels, + * or a 2-D array (`[group, label][]`) to render grouped category axes. + */ + categories?: Array | Array>; + overwriteCategories?: number[] | string[] | undefined; + offsetX?: number; + offsetY?: number; + sorted?: boolean; + labels?: { + show?: boolean; + rotate?: number; + rotateAlways?: boolean; + hideOverlappingLabels?: boolean; + showDuplicates?: boolean; + trim?: boolean; + minHeight?: number; + maxHeight?: number; + style?: { + colors?: string | string[]; + fontSize?: string; + fontFamily?: string; + fontWeight?: string | number; + cssClass?: string; + }; + offsetX?: number; + offsetY?: number; + format?: string; + formatter?( + value: string | number, + timestamp?: number, + opts?: XChartFormatterOpts, + ): string | string[]; + datetimeUTC?: boolean; + datetimeFormatter?: { + year?: string; + month?: string; + day?: string; + hour?: string; + minute?: string; + second?: string; + }; + }; + group?: { + groups?: { title: string; cols: number }[]; + style?: { + colors?: string | string[]; + fontSize?: string; + fontFamily?: string; + fontWeight?: string | number; + cssClass?: string; + }; + }; + axisBorder?: { + show?: boolean; + color?: string; + width?: string | number; + height?: number; + offsetX?: number; + offsetY?: number; + }; + axisTicks?: { + show?: boolean; + borderType?: 'solid' | 'dotted' | 'dashed'; + color?: string; + height?: number; + offsetX?: number; + offsetY?: number; + }; + tickPlacement?: string; + tickAmount?: number | 'dataPoints'; + stepSize?: number; + min?: number; + max?: number; + range?: number; + floating?: boolean; + decimalsInFloat?: number; + position?: string; + title?: { + text?: string; + offsetX?: number; + offsetY?: number; + style?: { + color?: string; + fontFamily?: string; + fontWeight?: string | number; + fontSize?: string; + cssClass?: string; + }; + }; + crosshairs?: { + show?: boolean; + width?: number | string; + position?: string; + opacity?: number; + stroke?: { + color?: string; + width?: number; + dashArray?: number; + }; + fill?: { + type?: string; + color?: string; + gradient?: { + colorFrom?: string; + colorTo?: string; + stops?: number[]; + opacityFrom?: number; + opacityTo?: number; + }; + }; + dropShadow?: XChartDropShadow; + }; + tooltip?: { + enabled?: boolean; + offsetY?: number; + formatter?(value: string | number, opts?: XChartFormatterOpts): string; + style?: { + fontSize?: string; + fontFamily?: string; + }; + }; +}; + +export type XChartYAxis = { + show?: boolean; + showAlways?: boolean; + showForNullSeries?: boolean; + seriesName?: string | string[]; + opposite?: boolean; + reversed?: boolean; + logarithmic?: boolean; + logBase?: number; + tickAmount?: number; + stepSize?: number; + forceNiceScale?: boolean; + alignZero?: boolean; + min?: number | ((min: number) => number); + max?: number | ((max: number) => number); + floating?: boolean; + decimalsInFloat?: number; + labels?: { + show?: boolean; + showDuplicates?: boolean; + minWidth?: number; + maxWidth?: number; + offsetX?: number; + offsetY?: number; + rotate?: number; + align?: 'left' | 'center' | 'right'; + padding?: number; + style?: { + colors?: string | string[]; + fontSize?: string; + fontWeight?: string | number; + fontFamily?: string; + cssClass?: string; + }; + formatter?(val: number, opts?: XChartFormatterOpts): string | string[]; + }; + axisBorder?: { + show?: boolean; + color?: string; + width?: number; + offsetX?: number; + offsetY?: number; + }; + axisTicks?: { + show?: boolean; + color?: string; + width?: number; + offsetX?: number; + offsetY?: number; + }; + title?: { + text?: string; + rotate?: number; + offsetX?: number; + offsetY?: number; + style?: { + color?: string; + fontSize?: string; + fontWeight?: string | number; + fontFamily?: string; + cssClass?: string; + }; + }; + crosshairs?: { + show?: boolean; + position?: string; + stroke?: { + color?: string; + width?: number; + dashArray?: number; + }; + }; + tooltip?: { + enabled?: boolean; + offsetX?: number; + }; +}; + +export type XChartLegendFormatterOpts = { + seriesIndex: number; + w: XChartContext; +}; + +export type XChartMarkerShapeOptions = + | 'circle' + | 'square' + | 'rect' + | 'line' + | 'cross' + | 'plus' + | 'star' + | 'sparkle' + | 'diamond' + | 'triangle'; + +export type XChartMarkerShape = + | XChartMarkerShapeOptions + | XChartMarkerShapeOptions[]; + +export type XChartDiscretePoint = { + seriesIndex?: number; + dataPointIndex?: number; + fillColor?: string; + strokeColor?: string; + size?: number; + shape?: XChartMarkerShape; +}; + +export type XChartMarkers = { + size?: number | number[]; + colors?: string | string[]; + strokeColors?: string | string[]; + strokeWidth?: number | number[]; + strokeOpacity?: number | number[]; + strokeDashArray?: number | number[]; + fillOpacity?: number | number[]; + discrete?: XChartDiscretePoint[]; + shape?: XChartMarkerShape; + offsetX?: number; + offsetY?: number; + showNullDataPoints?: boolean; + onClick?(e?: MouseEvent): void; + onDblClick?(e?: MouseEvent): void; + hover?: { + size?: number; + sizeOffset?: number; + }; +}; + +export type XChartLegend = { + show?: boolean; + showForSingleSeries?: boolean; + showForNullSeries?: boolean; + showForZeroSeries?: boolean; + floating?: boolean; + inverseOrder?: boolean; + position?: 'top' | 'right' | 'bottom' | 'left'; + horizontalAlign?: 'left' | 'center' | 'right'; + fontSize?: string; + fontFamily?: string; + fontWeight?: string | number; + width?: number; + height?: number; + offsetX?: number; + offsetY?: number; + formatter?(legendName: string, opts?: XChartLegendFormatterOpts): string; + tooltipHoverFormatter?( + legendName: string, + opts?: XChartLegendFormatterOpts, + ): string; + customLegendItems?: string[]; + clusterGroupedSeries?: boolean; + clusterGroupedSeriesOrientation?: 'vertical' | 'horizontal'; + labels?: { + colors?: string | string[]; + useSeriesColors?: boolean; + }; + markers?: { + size?: number; + strokeWidth?: number; + fillColors?: string[]; + shape?: XChartMarkerShape; + offsetX?: number; + offsetY?: number; + customHTML?(): string; + onClick?(e: MouseEvent): void; + }; + itemMargin?: { + horizontal?: number; + vertical?: number; + }; + onItemClick?: { + toggleDataSeries?: boolean; + }; + onItemHover?: { + highlightDataSeries?: boolean; + }; +}; + +export type XChartEasing = + | 'linear' + | 'easeInSine' + | 'easeOutSine' + | 'easeInOutSine' + | 'easeInQuad' + | 'easeOutQuad' + | 'easeInOutQuad' + | 'easeInCubic' + | 'easeOutCubic' + | 'easeInOutCubic' + | 'easeOutBack' + | 'easeInOutBack' + | (string & {}) + | [number, number, number, number] + | ((t: number) => number); + +export type XChartEventOpts = { + seriesIndex: number; + dataPointIndex: number; + w: XChartContext; + [key: string]: any; +}; + +export type XChartLocale = { + name?: string; + options?: { + months?: string[]; + shortMonths?: string[]; + days?: string[]; + shortDays?: string[]; + toolbar?: { + download?: string; + selection?: string; + selectionZoom?: string; + zoomIn?: string; + zoomOut?: string; + pan?: string; + reset?: string; + measure?: string; + menu?: string; + exportToSVG?: string; + exportToPNG?: string; + exportToCSV?: string; + }; + }; +}; + +export interface XChartStoryboardBeatInfo { + index: number; + key: string | null; + el: Element; + direction: 'up' | 'down'; +} + +export interface XChartDrilldownSeries { + /** Unique id referenced by a data point's `drilldown` field. */ + id: string | number; + /** Display name used by the breadcrumb and as the (single-series) child series name. */ + name?: string; + /** Child data points for a single-series level. Use this OR `series`. */ + data?: any[]; + /** Full multi-series array for a grouped/stacked drilldown level. Use this OR `data`. */ + series?: XChartAxisChartSeries; + /** Optional chart-type override applied when this level is shown. */ + chart?: Pick; + plotOptions?: XChartPlotOptions; + xaxis?: XChartXAxis; + yaxis?: XChartYAxis | XChartYAxis[]; + colors?: Array string)>; + /** Optional fill override (e.g. a pattern fill to visually distinguish drilled levels). */ + fill?: XChartFill; + /** Optional legend override (e.g. show a legend when a level is a pie/donut). */ + legend?: XChartLegend; +} + +export interface XChartDrilldownEvent { + /** The level id navigated away from. */ + from: string | number; + /** The level id navigated to (`'root'` at the top). */ + to: string | number; + /** The clicked data point (drill-down only). */ + point?: any; + seriesIndex?: number; + dataPointIndex?: number; +} + +export interface XChartDrilldownContext { + /** The requested level id, i.e. the clicked point's `drilldown` value. */ + id: string | number | null; + point: any; + seriesIndex?: number; + dataPointIndex?: number; +} + +export interface XChartDrilldown { + /** Master switch. When false the feature stays inert even if imported. */ + enabled?: boolean; + /** Inline child levels referenced by data-point `drilldown` ids. */ + series?: XChartDrilldownSeries[]; + breadcrumb?: + | false + | { + show?: boolean; + position?: 'top-left' | 'top-right'; + separator?: string; + rootLabel?: string; + offsetX?: number; + offsetY?: number; + formatter?( + label: string, + opts: { index: number; depth: number }, + ): string; + }; + animation?: { + enabled?: boolean; + /** + * Anchor the drill transition at the clicked point: the child unfolds + * outward from it (and settles back on drill-up) instead of the chart + * simply re-rendering. A gentle scale layered on the SVG. Opt-in. + * Defaults to false. + */ + zoomFromPoint?: boolean; + /** Base transition duration in ms when `zoomFromPoint` is true. Default 260. */ + speed?: number; + }; + /** + * The dot marking a drillable point on a line/area chart drawn without + * markers. A bar, slice, tile or cell is already a visible, clickable mark; + * a line point is not, so without this nothing would show that a point can + * be opened. Only drillable points get one. Set `show: false` to supply your + * own affordance. Omitted colours inherit the series marker defaults. + */ + marker?: { + /** Default true. */ + show?: boolean; + /** Radius in px. Default 6. */ + size?: number; + /** Defaults to the series marker shape. */ + shape?: 'circle' | 'square' | 'rect'; + /** Defaults to the series colour. */ + fillColor?: string; + /** Default '#fff'. */ + strokeColor?: string; + }; + /** + * Async resolver called when a drillable point has no inline `series` match. + * + * Failure never changes state: a throw, a rejection, or a resolved value + * without a `data` array leaves the chart where it was and fires + * `drillDownError`. A second click while one is in flight is ignored rather + * than starting a second request. + */ + onDrillDown?( + ctx: XChartDrilldownContext, + ): XChartDrilldownSeries | Promise; + /** + * Overlay shown while an async level resolves. `text` is optional; with + * none, the spinner shows alone and carries "Loading" as its accessible + * name, so the default ships no user-visible English. + */ + loading?: + | false + | { + show?: boolean; + text?: string; + }; + /** + * Cache levels resolved by `onDrillDown`, keyed by id, so drilling back down + * a branch does not re-fetch. Default true. Clear it with the drilldown + * module's `clearCache()` when the underlying data changes. + */ + cache?: boolean; +} + +export type XChartForecastDataPoints = { + count?: number; + fillOpacity?: number; + strokeWidth?: undefined | number; + dashArray?: number; +}; + +export type XChartGrid = { + show?: boolean; + borderColor?: string; + strokeDashArray?: number; + position?: 'front' | 'back'; + xaxis?: { + lines?: { + show?: boolean; + offsetX?: number; + offsetY?: number; + }; + }; + yaxis?: { + lines?: { + show?: boolean; + offsetX?: number; + offsetY?: number; + }; + }; + row?: { + colors?: string[]; + opacity?: number; + }; + column?: { + colors?: string[]; + opacity?: number; + }; + padding?: { + top?: number; + right?: number; + bottom?: number; + left?: number; + }; +}; + +export type XChartNoData = { + text?: string; + align?: 'left' | 'right' | 'center'; + verticalAlign?: 'top' | 'middle' | 'bottom'; + offsetX?: number; + offsetY?: number; + style?: { + color?: string; + fontSize?: string; + fontFamily?: string; + }; +}; + +export interface XChartPluginActivation { + name: string; + options?: Record; + order?: number; +} + +export type XChartResponsive = { + breakpoint?: number; + options?: XChartOptions; +}; + +export type XChartStates = { + hover?: { + filter?: { + type?: 'none' | 'lighten' | 'darken'; + /** + * Blend strength toward white (lighten) or black (darken), from 0 to 1. + * Higher means a stronger effect. The shift is proportional to the base + * color's head-room, so already-light colors are lightened only slightly + * (and already-dark colors darkened only slightly) and never wash out. + * @default 0.15 + */ + value?: number; + }; + }; + active?: { + allowMultipleDataPointsSelection?: boolean; + filter?: { + type?: 'none' | 'lighten' | 'darken'; + /** + * Blend strength toward white (lighten) or black (darken), from 0 to 1. + * Higher means a stronger effect. + * @default 0.35 + */ + value?: number; + }; + }; +}; + +export type XChartStroke = { + show?: boolean; + curve?: + | 'smooth' + | 'straight' + | 'stepline' + | 'linestep' + | 'monotoneCubic' + | ('smooth' | 'straight' | 'stepline' | 'linestep' | 'monotoneCubic')[]; + lineCap?: 'butt' | 'square' | 'round'; + colors?: string[]; + width?: number | number[]; + dashArray?: number | number[]; + fill?: XChartFill; +}; + +export type XChartTitleSubtitle = { + text?: string; + align?: 'left' | 'center' | 'right'; + margin?: number; + offsetX?: number; + offsetY?: number; + floating?: boolean; + style?: { + fontSize?: string; + fontFamily?: string; + fontWeight?: string | number; + color?: string; + }; +}; + +export type XChartTheme = { + /** '' (the default) inherits / auto-resolves; 'light' | 'dark' force a mode. */ + mode?: 'light' | 'dark' | ''; + palette?: string; + /** + * Facet (#13): read `--apx-*` CSS design tokens from the cascade + * (`--apx-accent`, `--apx-fore`, `--apx-grid`, `--apx-surface`, + * `--apx-series-1..N`). They top the resolution chain, below explicit config. + * true (default) reads any present (absence is a no-op); false disables. + * Tokens are re-read on each render; use `chart.refreshTokens()` after a + * runtime CSS change that does not itself trigger a render. + */ + tokens?: boolean; + /** + * Facet (#13): 'os' follows the operating system's `prefers-color-scheme` + * (light/dark) and `prefers-contrast` reactively, with no JS. SSR-safe. + */ + follow?: 'os' | false; + /** Facet (#13): a theme registered via `ApexCharts.registerTheme(name, def)`. */ + name?: string; + monochrome?: { + enabled?: boolean; + color?: string; + shadeTo?: 'light' | 'dark'; + shadeIntensity?: number; + }; + accessibility?: { + colorBlindMode?: + | 'deuteranopia' + | 'protanopia' + | 'tritanopia' + | 'highContrast' + | ''; + }; +}; + +export type XChartTooltipCustomOpts = { + series: number[][]; + seriesIndex: number; + dataPointIndex: number; + y1?: number; + y2?: number; + w: XChartContext; +}; + +export type XChartTooltipY = { + title?: { + formatter?(seriesName: string, opts?: XChartFormatterOpts): string; + }; + formatter?(val: number, opts?: XChartFormatterOpts): string; +}; + +export type XChartTooltip = { + enabled?: boolean; + enabledOnSeries?: undefined | number[]; + shared?: boolean; + followCursor?: boolean; + intersect?: boolean; + inverseOrder?: boolean; + arrow?: boolean; + custom?: + | (( + opts: XChartTooltipCustomOpts, + ) => string | number | Element | { nodeName: string }) + | Array< + ( + opts: XChartTooltipCustomOpts, + ) => string | number | Element | { nodeName: string } + >; + fillSeriesColor?: boolean; + theme?: 'light' | 'dark'; + cssClass?: string; + hideEmptySeries?: boolean; + style?: { + fontSize?: string; + fontFamily?: string; + background?: string; + }; + onDatasetHover?: { + highlightDataSeries?: boolean; + }; + x?: { + show?: boolean; + format?: string; + formatter?(val: string | number, opts?: XChartFormatterOpts): string; + }; + y?: XChartTooltipY | XChartTooltipY[]; + z?: { + title?: string; + formatter?(val: number): string; + }; + marker?: { + show?: boolean; + fillColors?: string[]; + }; + items?: { + display?: string; + }; + fixed?: { + enabled?: boolean; + position?: string; // topRight; topLeft; bottomRight; bottomLeft + offsetX?: number; + offsetY?: number; + }; +}; + +export interface XChartOptions { + annotations?: XChartAnnotations; + chart?: XChart; + /** + * Series colors. Each entry is either a CSS color string (hex, rgb, hsl, + * named) or a function returning one per-datapoint. The list cycles when + * there are more series than colors. + */ + colors?: Array string)>; + dataLabels?: XChartDataLabels; + /** Opt-in drilldown navigation. Requires `import 'apexcharts/features/drilldown'`. */ + drilldown?: XChartDrilldown; + fill?: XChartFill; + forecastDataPoints?: XChartForecastDataPoints; + grid?: XChartGrid; + labels?: string[]; + legend?: XChartLegend; + markers?: XChartMarkers; + noData?: XChartNoData; + /** Weave (#1) plugin activation list. Requires `import 'apexcharts/features/weave'`. */ + plugins?: XChartPluginActivation[]; + plotOptions?: XChartPlotOptions; + responsive?: XChartResponsive[]; + parsing?: XChartParsing; + series?: XChartSeriesType; + states?: XChartStates; + stroke?: XChartStroke; + subtitle?: XChartTitleSubtitle; + theme?: XChartTheme; + title?: XChartTitleSubtitle; + tooltip?: XChartTooltip; + xaxis?: XChartXAxis; + yaxis?: XChartYAxis | XChartYAxis[]; +} + +export type XChart = { + width?: string | number; + height?: string | number; + type?: + | 'line' + | 'area' + | 'bar' + | 'pie' + | 'donut' + | 'radialBar' + | 'scatter' + | 'bubble' + | 'heatmap' + | 'candlestick' + | 'boxPlot' + | 'violin' + | 'histogram' + | 'radar' + | 'polarArea' + | 'rangeBar' + | 'rangeArea' + | 'treemap' + | 'unit' + | 'waffle' + | 'sunburst' + | 'funnel' + | 'pyramid' + | 'gauge'; + /** + * Internal — populated when `type` is a first-class alias (`'funnel'`, + * `'pyramid'`, `'gauge'`, `'waffle'`, `'histogram'`). The original requested + * type is preserved here while `type` is normalized to the underlying + * renderer (`'bar'`, `'radialBar'` or `'unit'`). Read-only for consumers. + */ + requestedType?: 'funnel' | 'pyramid' | 'gauge' | 'waffle' | 'histogram'; + foreColor?: string; + fontFamily?: string; + background?: string; + offsetX?: number; + offsetY?: number; + dropShadow?: XChartDropShadow & { + enabledOnSeries?: undefined | number[]; + }; + nonce?: string; + events?: { + animationEnd?(chart: ApexCharts, options?: XChartEventOpts): void; + beforeMount?(chart: ApexCharts, options?: XChartEventOpts): void; + mounted?(chart: ApexCharts, options?: XChartEventOpts): void; + updated?(chart: ApexCharts, options?: XChartEventOpts): void; + mouseMove?( + e: MouseEvent, + chart?: ApexCharts, + options?: XChartEventOpts, + ): void; + mouseLeave?( + e: MouseEvent, + chart?: ApexCharts, + options?: XChartEventOpts, + ): void; + click?(e: MouseEvent, chart?: ApexCharts, options?: XChartEventOpts): void; + xAxisLabelClick?( + e: MouseEvent, + chart?: ApexCharts, + options?: XChartEventOpts, + ): void; + legendClick?( + chart: ApexCharts, + seriesIndex?: number, + options?: XChartEventOpts, + ): void; + markerClick?( + e: MouseEvent, + chart?: ApexCharts, + options?: XChartEventOpts, + ): void; + selection?( + chart: ApexCharts, + options?: { + xaxis?: { min: number; max: number }; + yaxis?: { min: number; max: number }; + }, + ): void; + dataPointSelection?( + e: MouseEvent, + chart?: ApexCharts, + options?: XChartEventOpts, + ): void; + dataPointMouseEnter?( + e: MouseEvent, + chart?: ApexCharts, + options?: XChartEventOpts, + ): void; + dataPointMouseLeave?( + e: MouseEvent, + chart?: ApexCharts, + options?: XChartEventOpts, + ): void; + beforeZoom?( + chart: ApexCharts, + options?: { xaxis: { min: number; max: number } }, + ): boolean | void; + beforeResetZoom?( + chart: ApexCharts, + options?: XChartEventOpts, + ): boolean | void; + zoomed?( + chart: ApexCharts, + options?: { + xaxis: { min: number; max: number }; + yaxis?: { min: number; max: number }[]; + }, + ): void; + scrolled?( + chart: ApexCharts, + options?: { xaxis: { min: number; max: number } }, + ): void; + brushScrolled?( + chart: ApexCharts, + options?: { + xaxis: { min: number; max: number }; + yaxis?: { min: number; max: number }[]; + }, + ): void; + /** + * Linked Views (#4): fired on the source chart when a brush range drives a + * crossfilter across the group. + */ + crossFilter?( + chart: ApexCharts, + options?: { xaxis: { min: number; max: number }; sourceChartID?: string }, + ): void; + /** + * Linked Views (#4) FILTER mode: fired on the source chart when a click + * toggles a crossfilter bucket. `options` carries the coordinator state + * (active filters, filtered/total counts), the source chartID, and the key. + */ + filterChange?( + chart: ApexCharts, + options?: { + filters: Record; + filteredCount: number; + total: number; + sourceChartID?: string; + key?: any; + }, + ): void; + /** + * Ink Layer (#7): fired after an annotation is dragged or resized. `options` + * carries the annotation type ('point' | 'xaxis' | 'yaxis'), id/index, and + * the new data coordinates (x/y, plus x2/y2 for range annotations). + */ + annotationDragged?( + chart: ApexCharts, + options?: { + type?: 'point' | 'xaxis' | 'yaxis'; + id?: string; + index: number; + x: any; + y: any; + x2?: any; + y2?: any; + }, + ): void; + /** + * Ink Layer (#7): fired after a point annotation's label is edited inline. + * `options` carries the annotation id/index and the new label text. + */ + annotationEdited?( + chart: ApexCharts, + options?: { + type?: 'point' | 'xaxis' | 'yaxis'; + id?: string; + index: number; + text: string; + }, + ): void; + /** + * Ink Layer (#7): fired after an annotation is created by clicking the + * plot in create mode or from the context menu (note or dashed line). + * `options` carries the new annotation type/id/index and its x and/or y. + */ + annotationCreated?( + chart: ApexCharts, + options?: { + type?: 'point' | 'xaxis' | 'yaxis'; + id?: string; + index: number; + x?: any; + y?: any; + }, + ): void; + /** + * Ink Layer (#7): fired after an annotation is restyled from the floating + * note editor (accent color, bold, font size, marker size/shape). `options` + * carries the annotation type/id/index and its current label + marker config. + */ + annotationStyled?( + chart: ApexCharts, + options?: { + type?: 'point' | 'xaxis' | 'yaxis'; + id?: string; + index: number; + label?: any; + marker?: any; + }, + ): void; + /** + * Ink Layer (#7): fired after an annotation is deleted from the floating + * note editor. `options` carries the annotation type/id and the index it + * occupied before removal. + */ + annotationDeleted?( + chart: ApexCharts, + options?: { + type?: 'point' | 'xaxis' | 'yaxis'; + id?: string; + index: number; + }, + ): void; + /** + * Measure ruler (#18): fired when a measure ruler is drawn. Requires the + * `measure` feature. `options` carries the endpoints and the deltas. + */ + measured?( + chart: ApexCharts, + options?: { + from: { x: any; y: any }; + to: { x: any; y: any }; + dx: number; + dy: number; + percentChange: number; + slope: number; + }, + ): void; + /** + * Storyboard: fired when scrolling (or goTo) activates a beat. Requires + * the `storyboard` feature and an active chart.storyboard.bind(). + */ + beatChange?(chart: ApexCharts, options?: XChartStoryboardBeatInfo): void; + keyDown?( + e: KeyboardEvent, + chart?: ApexCharts, + options?: XChartEventOpts, + ): void; + keyUp?( + e: KeyboardEvent, + chart?: ApexCharts, + options?: XChartEventOpts, + ): void; + /** Fired before a drill-down transition begins. Requires the Drilldown feature. */ + drillDownStart?( + info: ApexCharts.ApexDrilldownEvent, + chart?: ApexCharts, + options?: XChartEventOpts, + ): void; + /** Fired after a drill-down transition completes. Requires the Drilldown feature. */ + drillDownEnd?( + info: ApexCharts.ApexDrilldownEvent, + chart?: ApexCharts, + options?: XChartEventOpts, + ): void; + /** Fired after navigating back up a drilldown level. Requires the Drilldown feature. */ + drillUp?( + info: ApexCharts.ApexDrilldownEvent, + chart?: ApexCharts, + options?: XChartEventOpts, + ): void; + /** Fired when an async onDrillDown resolver throws or rejects. Requires the Drilldown feature. */ + drillDownError?( + info: { id: string | number | null; error: any }, + chart?: ApexCharts, + options?: XChartEventOpts, + ): void; + }; + brush?: { + enabled?: boolean; + autoScaleYaxis?: boolean; + target?: string; + targets?: string[]; + }; + /** + * Linked Views (#4): crossfilter / linked highlighting. Requires the `link` + * feature (`import 'apexcharts/features/link'`). Two modes: + * + * HIGHLIGHT (P1): `enabled` with no `dimension`. Charts sharing a + * `chart.group` form a set; brushing a range (needs `chart.selection.enabled`) + * on any member dims every member's marks whose x is outside the range, in + * place (no re-render). + * + * FILTER (P2): set `dimension` (its presence selects this path). Each chart + * declares a dimension + reduction over a shared record set registered with + * `ApexCharts.crossfilter({ id, records })`; clicking a bucket re-aggregates + * every other participating chart over the filtered subset. + */ + link?: { + /** @default false */ + enabled?: boolean; + /** Highlight mode (P1) label; filter mode is selected by `dimension`. @default 'highlight' */ + mode?: 'highlight' | 'filter'; + /** Opacity applied to dimmed (unselected / out-of-range) marks. @default 0.2 */ + dimOpacity?: number; + /** FILTER mode: crossfilter coordinator id (defaults to `chart.group`). */ + id?: string; + /** + * FILTER mode: `(row) => key`. Its presence selects filter mode. For a + * heatmap (matrix) dimension it returns `[xKey, yKey]`. + */ + dimension?: (row: any) => any; + /** FILTER mode: reduction over a bucket's rows. @default 'count' */ + reduce?: + | 'count' + | { sum?: string; avg?: string; min?: string; max?: string } + | ((rows: any[]) => number); + /** + * FILTER mode: bucket kind. Else inferred: `bins` present => 'range', a + * heatmap chart => 'matrix' (2D), otherwise 'category'. + */ + type?: 'category' | 'range' | 'matrix'; + /** FILTER mode (range dims): binning spec. */ + bins?: { width?: number; count?: number; thresholds?: number[] }; + /** FILTER mode (category dims): key ordering. @default 'first-seen' */ + order?: 'first-seen' | 'asc' | 'desc' | ((a: any, b: any) => number); + /** FILTER mode (axis charts): the derived series name. @default 'Count' */ + seriesName?: string; + }; + /** + * Ink Layer (#7): direct-manipulation annotations. When enabled, every point + * annotation is draggable (unless it sets `draggable:false`); or opt in per + * annotation with `annotations.points[].draggable`. Clicking an ink-managed + * annotation opens a floating editor card anchored to it: rename inline, + * recolor via accent swatches, toggle bold, step the font size, size/reshape + * the marker, or delete the note. Axis-line annotations get separate Label + * and Line color rows, so restyling the label chip never touches the stroke. + * Requires the `ink` feature (`import 'apexcharts/features/ink'`). Fires the + * `annotationDragged`, `annotationEdited`, `annotationStyled` and + * `annotationDeleted` events. + */ + ink?: { + /** @default false */ + enabled?: boolean; + /** + * Show a minimal "add note" tool palette; clicking it arms create mode (the + * next plot click drops an editable, draggable annotation). @default false + */ + palette?: boolean; + /** + * Snap a dragged point / axis-line annotation to the nearest gridline + * (numeric x + linear y). @default false + */ + snap?: boolean; + /** + * Accent swatches offered by the floating note editor. Defaults to a + * built-in 6-color palette when omitted. + */ + noteColors?: string[]; + }; + /** + * Measure ruler (#18): a measure/delta ruler. Requires the `measure` + * feature (`import 'apexcharts/features/measure'`). Hold `key` and drag + * A->B on the plot, or call `chart.startMeasure()`, to read + * dx/dy/%change/slope in data space; on release the ruler pins as a + * data-anchored overlay that re-projects on zoom/resize. Fires `measured`. + */ + measure?: { + /** @default false */ + enabled?: boolean; + /** + * 'span': finance-style vertical band between two x-positions with a + * change/%/range readout, endpoints snapped to the first series. 'free': + * a diagonal ruler between two arbitrary points. @default 'span' + */ + mode?: 'span' | 'free'; + /** Key held to arm a drag when not in sticky mode. @default 'm' */ + key?: string; + /** Pin the ruler as a data-anchored overlay on release. @default true */ + pinOnRelease?: boolean; + /** + * Semantic colors. Every element also has a stable CSS class and a + * direction class (apexcharts-measure-up|down|flat) for stylesheet theming. + */ + colors?: { up?: string; down?: string; neutral?: string; guide?: string }; + /** Span mode: draw the shaded band between the two x-positions. @default true */ + band?: boolean; + /** Span mode: draw the vertical dashed reference lines. @default true */ + guides?: boolean; + /** Draw the endpoint dots on the series line. @default true */ + markers?: boolean; + /** Value formatters for the readout. */ + format?: { + x?: (x: number) => string; + y?: (y: number) => string; + percent?: (pct: number) => string; + }; + /** + * Full readout override. Receives the measure info and returns a string or + * an array of lines. Overrides the default readout text. + */ + label?: (info: { + from: { x: any; y: any }; + to: { x: any; y: any }; + dx: number; + dy: number; + percentChange: number; + slope: number; + mode: 'span' | 'free'; + }) => string | string[]; + }; + /** + * Radial Actions (#chrome): right-click / long-press context menu. Requires + * the `contextMenu` feature (`import 'apexcharts/features/context-menu'`). + * Each action receives the clicked data coordinates, so verbs act at that + * point rather than chart-wide. 'measure' is shown only when the measure tool + * is enabled. When the ink feature is bundled, 'annotate' drops an + * ink-managed note that opens its floating editor (rename, restyle, delete), + * and 'xline' / 'yline' drop ink-managed dashed lines the same way ('xline' + * vertical at the clicked x, 'yline' horizontal at the clicked y). + */ + contextMenu?: { + /** @default false */ + enabled?: boolean; + /** + * Ordered menu items: built-in ids and/or custom entries. @default + * ['annotate','xline','yline','measure'] + */ + items?: Array< + | 'annotate' + | 'xline' + | 'yline' + | 'measure' + | { + id?: string; + label?: string; + icon?: string; + onClick?: ( + chart: ApexCharts, + context: { + x: any; + y: any; + seriesIndex: number | null; + dataPointIndex: number | null; + clientX: number; + clientY: number; + }, + ) => void; + } + >; + /** Override the built-in item labels. */ + labels?: { + annotate?: string; + xline?: string; + yline?: string; + measure?: string; + }; + /** Text of the annotation dropped by the built-in 'annotate' item. @default 'Note' */ + noteText?: string; + /** + * Shared styling for the built-in 'xline' ("Annotate here", vertical at + * the clicked x) and 'yline' ("Mark this level", horizontal at the + * clicked y) items. Lines only, never a range rectangle. With the ink + * feature bundled the line opens the floating editor, whose Label and + * Line color rows restyle the chip and the stroke independently, and is + * draggable and undoable, like the note. + */ + line?: { + /** Label drawn on the line. @default '' (no label) */ + text?: string; + /** @default 4 */ + strokeDashArray?: number; + /** Line color; omit to keep the annotation default. */ + color?: string; + }; + }; + id?: string; + injectStyleSheet?: boolean; + group?: string; + /** + * Per-chart license key for the gated premium features (storyboard, link / + * crossfilter, ink, measure, contextMenu, perspectives, history). Overrides + * ApexCharts.setLicense() and window.Apex.license for this chart. Without a + * valid key those features still work but show an "APEXCHARTS" trial + * watermark. Shared across the ApexCharts family. + */ + license?: string; + locales?: XChartLocale[]; + defaultLocale?: string; + perspectives?: { + serializeOptions?: string[]; + }; + history?: { + enabled?: boolean; + maxDepth?: number; + coalesceMs?: number; + keyboard?: boolean; + }; + /** Strata (#2) series renderer. Requires `import 'apexcharts/features/renderer-canvas'` for non-SVG. */ + renderer?: 'svg' | 'canvas' | 'auto'; + rendererThreshold?: number; + layers?: { + series?: 'svg' | 'canvas' | 'auto'; + grid?: 'svg'; + annotations?: 'svg'; + dataLabels?: 'svg'; + }; + parentHeightOffset?: number; + redrawOnParentResize?: boolean; + redrawOnWindowResize?: boolean | ((...args: any[]) => boolean); + sparkline?: { + enabled?: boolean; + }; + stacked?: boolean; + stackType?: 'normal' | '100%'; + stackOnlyBar?: boolean; + /** + * Real-time streaming mode. When enabled, appendData() bounds memory + * automatically: each series is trimmed to `maxPoints` (when set) or to the + * visible `xaxis.range` window plus a small off-screen runway. The + * constant-velocity scroll animation for windowed updates needs no opt-in. + */ + streaming?: { + enabled?: boolean; + /** Maximum points kept per series by appendData(). Unset: derived from + * `xaxis.range` when that is set; otherwise no trimming occurs. */ + maxPoints?: number; + }; + toolbar?: { + show?: boolean; + offsetX?: number; + offsetY?: number; + tools?: { + download?: boolean | string; + selection?: boolean | string; + zoom?: boolean | string; + zoomin?: boolean | string; + zoomout?: boolean | string; + pan?: boolean | string; + reset?: boolean | string; + /** + * Measure ruler toggle. Shown only when `chart.measure.enabled` is true + * and the `measure` feature is bundled. `false` keeps the ruler + * key-driven only; a string supplies a custom SVG icon. + */ + measure?: boolean | string; + customIcons?: { + icon?: string; + title?: string; + index?: number; + class?: string; + click?( + chart: ApexCharts, + options?: XChartEventOpts, + e?: MouseEvent, + ): void; + }[]; + }; + export?: { + csv?: { + filename?: undefined | string; + columnDelimiter?: string; + headerCategory?: string; + headerValue?: string; + categoryFormatter?(value?: string | number): string; + valueFormatter?(value?: string | number): string; + }; + svg?: { + filename?: undefined | string; + }; + png?: { + filename?: undefined | string; + }; + width?: number; + scale?: number; + /** + * Inline the `@font-face` rules for the fonts the chart actually uses + * into the exported SVG/PNG as base64 data URIs. + * + * An exported SVG is a standalone document and cannot reach the page's + * `@font-face` rules, so without this a custom font is replaced by a + * generic fallback in the export. Cross-origin font files that deny CORS + * are skipped and fall back as before. + * + * @default true + */ + embedFonts?: boolean; + }; + autoSelected?: 'zoom' | 'selection' | 'pan' | 'measure'; + }; + zoom?: { + enabled?: boolean; + type?: 'x' | 'y' | 'xy'; + autoScaleYaxis?: boolean; + /** + * Cursor-anchored zoom on mouse wheel / trackpad. `'auto'` enables it only + * when the toolbar's reset button is present, so an unintended scroll-zoom + * is always undoable; `true` forces it on even with the toolbar hidden. + * Requires `enabled: true`. + * @default 'auto' + */ + allowMouseWheelZoom?: boolean | 'auto'; + /** + * Momentum: enable two-finger pinch-zoom on touch devices. Zooms the x-axis + * around the pinch centroid, frame-by-frame. `'auto'` enables it only when + * the toolbar's reset button is present; `true` forces it on even with the + * toolbar hidden. Requires `enabled: true`. + * @default 'auto' + */ + pinch?: boolean | 'auto'; + zoomedArea?: { + fill?: { + color?: string; + opacity?: number; + }; + stroke?: { + color?: string; + opacity?: number; + width?: number; + }; + }; + }; + /** + * Momentum: kinetic panning on touch. A one-finger pan released with velocity + * keeps gliding and decelerates, clamping at the data edges. + */ + pan?: { + /** @default true */ + inertia?: boolean; + /** Velocity decay applied each animation frame (0-1). @default 0.92 */ + friction?: number; + }; + selection?: { + enabled?: boolean; + type?: string; + fill?: { + color?: string; + opacity?: number; + }; + stroke?: { + width?: number; + color?: string; + opacity?: number; + dashArray?: number; + }; + xaxis?: { + min?: number; + max?: number; + }; + yaxis?: { + min?: number; + max?: number; + }; + }; + animations?: { + /** + * Master switch. Each chart type gets a tailored initial-mount animation + * by default (line/area pen-stroke draw, bar grow, scatter pop, heatmap + * diagonal wave, treemap largest-first cascade, pie/donut/gauge sweep). + * Set false to render charts without any animation. + */ + enabled?: boolean; + /** Animation duration in ms (default 800). */ + speed?: number; + /** + * Cadence (#6): easing for the generic tweens. See `ApexEasing` for the + * complete built-in curve list and the accepted forms; register custom + * names with `ApexCharts.registerEasing`. + * @default 'easeInOutSine' + */ + easing?: XChartEasing; + /** + * Drives per-element stagger across all chart types. When enabled, bars, + * heatmap cells, scatter points, and treemap tiles reveal in sequence; + * line/area markers fade in progressively as the line draws. + */ + animateGradually?: { + enabled?: boolean; + /** Requested stagger step in ms; auto-capped per chart so total + * stagger ≤ ~half the animation speed. */ + delay?: number; + }; + /** Data-change (updateSeries) animation. Independent from initial mount. */ + dynamicAnimation?: { + enabled?: boolean; + speed?: number; + /** + * Easing for data-change morphs only (same accepted forms as + * `animations.easing`; see `ApexEasing`). Unset: inherits the + * chart-wide easing, except detected streaming scrolls (appendData or + * a shifted fixed-length window under `xaxis.range`) which default to + * 'linear' so the window slides at constant velocity. + */ + easing?: XChartEasing; + }; + /** + * Cross-type morph (updateOptions changing chart.type). Requires the + * optional `apexcharts/features/morph` feature to be registered; without + * that import these settings have no effect. Supported pairs include + * bar ↔ pie/donut/radialBar/polarArea (and the trivial pie↔donut↔polarArea + * cases). Falls back to instant snap when types or data shape are + * incompatible. + */ + chartTypeMorph?: { + enabled?: boolean; + speed?: number; + }; + /** + * When true (default), honors the OS-level prefers-reduced-motion media + * query — all initial-mount animations are skipped and the chart renders + * instantly. Set to false to override (e.g. for QA / demo screens). + */ + respectReducedMotion?: boolean; + /** + * Above this many data points (default 1000), the per-element morph + + * stagger — which spins up one JS-driven animation timeline per path — is + * replaced by a single GPU-composited opacity fade of the whole series. + * Keeps initial render and zoom transitions smooth on large datasets + * (e.g. thousands of candlesticks/bars). Set to 0 to always animate + * per-element regardless of dataset size. + */ + largeDatasetThreshold?: number; + }; + accessibility?: { + enabled?: boolean; + description?: string; + announcements?: { + enabled?: boolean; + }; + keyboard?: { + enabled?: boolean; + navigation?: { + enabled?: boolean; + wrapAround?: boolean; + }; + }; + }; + dataReducer?: { + enabled?: boolean; + algorithm?: 'lttb'; + targetPoints?: number; + threshold?: number; + }; +}; +//#endregion + +export enum XChartAction { + Refresh = 'refresh', + Destroy = 'destroy', + UpdateSeries = 'update_series', + UpdateOptions = 'update_options', +} + +export type XChartActionIdentifier = XChartAction | string; + +export interface XChartActionModel extends XBaseActionModel {} + +export type XChartSeriesType = XChartAxisChartSeries | XChartNonAxisChartSeries; +export interface IXChartContainer extends IXComponentCardContainer { + // + // Commons ... + actionProvider: Subject; + cssClass: XStandardType>; + + // + // Chart Props ... + autoUpdate: XStandardType; + options: XStandardType; + series: XStandardType; + + // + // Events ... + chartRendered: EventEmitter; + dataPointSelected: EventEmitter; + zoomed: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XChartContainer + extends XComponentCardContainer + implements Readonly +{ + // + readonly chartId: XStandardType; + readonly options: XStandardType; + readonly autoUpdate: XStandardType = true; + readonly cssClass: XStandardType>; + readonly actionProvider = new Subject(); + readonly series: XStandardType; + + // + readonly zoomed = new EventEmitter(); + readonly chartRendered = new EventEmitter(); + readonly dataPointSelected = new EventEmitter(); +} + +@Injectable() +@Component({ template: `` }) +export abstract class XChartContainerComponent + extends XComponentCardContainerComponent + implements IXChartContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + options: XStandardType = this.propertyProvider.options; + + @Input() + series: XStandardType = + this.propertyProvider.series; + + @Input() + autoUpdate: XStandardType = this.propertyProvider.autoUpdate; + + // + // Events ... + @Output() + chartRendered: EventEmitter = new EventEmitter(); + + @Output() + dataPointSelected: EventEmitter = new EventEmitter(); + + @Output() + zoomed: EventEmitter = new EventEmitter(); + + // + //#region ComponentCard Props ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XChartContainer, + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider, + ); + } + //#endregionّ + + // + //#region Abstract ... + public extractProps( + container?: IXChartContainer, + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass, + ), + + // + options: this.getValue( + container ? container.options : this.options, + ), + series: this.getValue(container ? container.series : this.series), + autoUpdate: this.getValue( + container ? container.autoUpdate : this.autoUpdate, + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }), + ); + }), + ) + .pipe( + map((info) => ({ + ...info.data, + ...info.superData, + actionProvider: container + ? container.actionProvider + : this.actionProvider, + chartRendered: this.chartRendered, + dataPointSelected: this.dataPointSelected, + zoomed: this.zoomed, + })), + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color\x-color.component.html + +
+ +
+ {{ + item.key + }} +
+ + +
+ + + +
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color\x-color.component.scss + +:host { + --size: 32px; +} + +.x-color { + direction: ltr !important; + width: 100%; + display: flex; +} + +.x-color-title { + flex: 1 1 auto; + + margin: { + top: auto; + bottom: auto; + } + + max-width: 230px; +} + +.x-spacer { + flex: 1 1 auto; +} + +.x-color-value-button { + background-color: transparent; +} + +.x-color-value { + opacity: 0.7; + width: var(--size); + border-radius: 50%; + height: var(--size); + border-style: var(--border-style); + border-color: var(--border-color); + border-width: var(--border-width); +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color\x-color.component.ts + +import { + nameof, + parseXColor, + isNullOrEmptyString, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XColorAction, + XColorCssVars, + XColorActionModel, + XColorContainerComponent, +} from './x-color.typings'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + selector: 'x-color', + templateUrl: './x-color.component.html', + styleUrls: ['./x-color.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XColorComponent extends XColorContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.applyDefaults(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Size Changed ... + const isSizeChanged = changeKeys.includes(nameof('size')); + if (isSizeChanged) { + await this.applySize(); + } + //#endregion + + // + //#region borderWidth Changed ... + const isBorderWidthChanged = changeKeys.includes( + nameof('borderWidth') + ); + if (isBorderWidthChanged) { + await this.applyBorderWith(); + } + //#endregion + + // + //#region borderStyle Changed ... + const isBorderStyleChanged = changeKeys.includes( + nameof('borderStyle') + ); + if (isBorderStyleChanged) { + await this.applyBorderStyle(); + } + //#endregion + + // + //#region borderColor Changed ... + const isBorderColorChanged = changeKeys.includes( + nameof('borderColor') + ); + if (isBorderColorChanged) { + await this.applyBorderColor(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Handlers ... + async handleCopyToClipboard(value: string) { + this.managerService.saveToClipboard(value); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XColorAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + //#region Appliers ... + private async applyDefaults() { + // + await this.applySize(); + await this.applyBorderWith(); + await this.applyBorderColor(); + await this.applyBorderStyle(); + } + + private async applySize() { + // + let size = await this.getValueAsync(this.size); + size = !size || size === 0 ? 32 : size; + + // + this.setCssVar(XColorCssVars.Size, `${size}px`); + this.detectChanges(); + } + + private async applyBorderWith() { + // + let borderWidth = await this.getValueAsync(this.borderWidth); + + // + this.setCssVar(XColorCssVars.BorderWidth, `${borderWidth}px`); + this.detectChanges(); + } + + private async applyBorderStyle() { + // + let borderStyle = await this.getValueAsync(this.borderStyle); + + // + this.setCssVar(XColorCssVars.BorderStyle, `${borderStyle}`); + this.detectChanges(); + } + + private async applyBorderColor() { + // + const color = await this.getValueAsync(this.borderColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XColorCssVars.BorderColor, `var(${bgColor})`); + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color\x-color.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XColorComponent } from './x-color.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XColorComponent], + imports: [CommonModule, XBaseComponentModule], + exports: [CommonModule, XColorComponent, XBaseComponentModule], +}) +export class XColorModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color\x-color.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { + XBorderStyle, + XStandardType, + XColorKeyValue, + XOneOrManyType, + XColorIdentifier, + XColorWithBrightness, + XBorderStyleIdentifier, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XColorAction { + Refresh = 'refresh', + SetSelectedColor = 'set_selected_color', +} + +export type XColorActionIdentifier = XColorAction | string; + +export interface XColorActionModel + extends XBaseActionModel {} + +export enum XColorCssVars { + Size = 'size', + BorderWidth = 'border-width', + BorderColor = 'border-color', + BorderStyle = 'border-style', +} + +// +//#region Container ... +export interface IXColorContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + showLabel: XStandardType; + labelColor: XStandardType; + + // + size: XStandardType; + value: XStandardType; + + // + borderWidth: XStandardType; + borderStyle: XStandardType; + borderColor: XStandardType; +} + +@Injectable({ + providedIn: 'root' +}) +export class XColorContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly showLabel: XStandardType = true; + readonly labelColor: XStandardType = + XColorWithBrightness.Dark; + + // + readonly size: XStandardType = 32; + + // + readonly value: XStandardType; + + // + readonly borderWidth: XStandardType = 1; + readonly borderStyle: XStandardType = + XBorderStyle.Solid; + readonly borderColor: XStandardType = + XColorWithBrightness.LightShade; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XColorContainerComponent + extends XBaseContainerComponent + implements IXColorContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + showLabel: XStandardType = this.propertyProvider.showLabel; + + @Input() + labelColor: XStandardType = + this.propertyProvider.labelColor; + + // + @Input() + size: XStandardType = this.propertyProvider.size; + + @Input() + value: XStandardType = this.propertyProvider.value; + + // + @Input() + borderWidth: XStandardType = this.propertyProvider.borderWidth; + + @Input() + borderStyle: XStandardType = + this.propertyProvider.borderStyle; + + @Input() + borderColor: XStandardType = + this.propertyProvider.borderColor; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XColorContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXColorContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + showLabel: this.getValue( + container ? container.showLabel : this.showLabel + ), + labelColor: this.getValue( + container ? container.labelColor : this.labelColor + ), + + // + size: this.getValue(container ? container.size : this.size), + + // + value: this.getValue(container ? container.value : this.value), + + // + borderWidth: this.getValue( + container ? container.borderWidth : this.borderWidth + ), + borderStyle: this.getValue( + container ? container.borderStyle : this.borderStyle + ), + borderColor: this.getValue( + container ? container.borderColor : this.borderColor + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + + +
+ +
+ +
+ +
+
+ + +
+ +
+ +
+ + +
+
+ + +
+ + +
+ +
+ +
+
+ + +
+ +
+
+
+
+ + +
+ + +
+ + {{ hexValue }} + + +
+ +
+
+ + +
+ + {{ rgbaValue }} + + +
+ +
+
+ + +
+ + {{ hslaValue }} + + +
+ +
+
+
+
+
+ + +
+
+ +
+ +
+ + +
+ +
+
+
+ + +
+
+ + + {{ getResourceValue(presetColorsLable) | async }} + + +
+
+
+
+
+
+ + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker.component.scss + +:host { + display: block; + width: inherit; + --cursor-color: val(--x-color-dark); +} + +:host ::ng-deep .x-on-top { + z-index: 10000; +} + +@mixin boder-radius($radius) { + -moz-border-radius: $radius; + -webkit-border-radius: $radius; + border-radius: $radius; + -khtml-border-radius: $radius; +} + +.x-force-ltr { + direction: ltr !important; +} + +.x-color-picker { + // + margin: 0; + font-size: 11px; + + // + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + + // + height: auto; + width: inherit; + cursor: default; + min-width: 200px; + + // + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.x-color-palette { + // + width: 100%; + border: none; + cursor: pointer; + min-height: 130px; + margin-bottom: 8px; + background-size: 100% 100%; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACCCAYAAABSD7T3AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AIWDwksPWR6lgAAIABJREFUeNrtnVuT47gRrAHN+P//Or/61Y5wONZ7mZ1u3XAeLMjJZGZVgdKsfc5xR3S0RIIUW+CHzCpc2McYo7XGv3ex7UiZd57rjyzzv+v+33X/R/+3r/f7vR386Y+TvKNcf/wdhTLPcv9qU2wZd74uth0t1821jkIZLPcsI/6nWa4XvutquU0Z85mnx80S/ZzgpnLnOtHNt7/ofx1TKXcSNzN/7qbMQ3ju7rNQmMYYd/4s2j9aa+P+gGaMcZrb1M/tdrvf7/d2v99P9/t93O/3cbvdxu12G9frdVwul3E+n8c///nP+2+//Xb66aefxl//+tfx5z//2YK5Al2rgvf4UsbpdGrB52bAvArXpuzjmiqAVSGz5eDmGYXzhbAZmCrnmzddpUU+8Y1dAOYeXCtDUwVwV7YCGH6uAmyMcZ9l5vkUaBPGMUZ7/J5w/792/fvv9Xq93263dr/fTxPECeME8nK5jM/Pz/HTTz/dv337dvrll1/GP/7xj/G3v/1t/OUvfwkVswongjdOp9PzH3U3D3zmWGnZVXn4jCqs7wC2BKP4/8tAzkZsoWx6XrqeHZymvp4ABCBJhTQwKfDT8gzrZCIqi5AhiACjBfEB2rP8/X63MM7f6/V6v9/v7Xa7bYC83W7jcrlsVHIq5ffv30+//fbb+OWXX8ZPP/00/v73v4+ff/75JSvbeu+bL2WMMaFbAlpBNM85QX+ct6qoSqkPAwuQlBVKqGNFSUOAA3Bmu7gC5hNOd15nSwvAOUW7C4giUCV8Sgn5L9hNFIqTsp0GxI0ysioyjAjkY/tGJVEpz+fz+OWXX+7fv38//f777+Pbt2/j119/HT///PP49ddfx8fHRwrmTjV779EXu2px2xhjwtdJZQcAWQIPLPISsMJaSwiD8gzIKrwSyATE5j5nAbR5c1dBUwBlsEWW0h6LqiYsqFPAQxCyRZ3wOSARxmlXMX5k64pQfvv27f75+dk+Pj5OHx8f4/v37+Pbt2/jt99+G9++fRsfHx/jcrmUFLO31gYDWblxRIs/TqfT7ousxJsAxXA2Gc7TA9XdgfdoHbFsj76X2+1WArgI1ageGwA3qupqoHsmcbI6Fu93quggFa9d7LeDtgKfAFHBJ+NEByIkcJ5KervdTmhhGcgJJSZ5vn//fj+fz+18Pp8+Pz/H5+fnmGD+/vvv4/v37+Pj42N8fn6O2+1Ws7JjjP6wraMI5E4RZ8x2vV5TSwkquotV7/d7Tz6HFWsD/qNcdw0CQ3q/321c686TwDVIdbuy73zNldhSHb8I2klZznm+InBS4U6n0302aBFsLhHDAKJVJVglfI9jhvu53W53sLANYNxAiDA6MCeUHx8f9+v12i6XS7tcLqcZW57P5yeY8/fz83Ocz+fnsSmYUyknWEG85WBst9stzSLyMdfr9Qi08iY15UZ0LlDGLhR3o5zK2j7OPUTD0E+nU3tk7Xb/16NFbhloAMuY1zjLUOO3BKeIDe+Z8s3/J4gFo4TM5jPmuRg28foUKKVSwo16TgA5npywcWLHgYl/Pz8/73/605/ab7/91m63W7tcLie0sZj4mao5gTyfz88E0f1+j8EcYzwTPEG2cqjyfHNF0M8fuqEiaOVnRzZZQNh5fwQyHg/HDGfJo89Q1zb/quu5XC6773I2XKfTqd/v9+d3wuqWva/YTdUdEV3fhIv/Viyps6YE3x3r43K5bJQS66zaxVGFsvd+//j4aF+/fm3fv39vt9utff36tf3+++/tdrudvn37ZuNLBaaCMgUzC+rZRiFowxUuJI8YMqcCp9Opq5vagaYU6lGJA1XQqejchw6Cj0Gw5nYBrGw01A2O206n04BGouNNyTfp/FwElhUey6nXrIKw7QQWddxuN2ldL5fL839gSPF8ahu/JvBO48CPSuqMf8Vp9/P53L58+dLu93s7n8/tfr8/39/v9/b5+TkhPJ3P56mQ436/j+/fv+/iSgbzer0+AZx/5+88bv6OMda6S5z6kd21fYC9dxv7cIJJ2d9AOS30fPMzyHiTM8B4DF6XUlYHp4KQW3W+1t77MNB1vGHxWq7Xa7vf78+y5/N5A+H1et29xuP5dbYtyaRu4AksbPq6936fjRzXRxBbPr/b+b18+fKljTHaBBBfn8/n0/1+H1++fBnn8zm0sB8fH5u4cr5GuBhMVk0EEn9RsctgVhM+ixlJtMA23R8B6yysAstBOgFXIKKCMIgToMqNEu2fYMH7ztc732dQKkCj1ytAZtY0Kx8pIr8GGJ+AT3V+2Hirhl++fBmXy2Wz73w+b17P8p+fn8/tUwGVleVkTyUb68DkfayWY4zxNRihU4EpLJPZVrK+u7J4/mgfKqeLW9X2REWlItL1diynbDDb3+jXgYjQqn0rrxWc+NkILP7F7xIbMvx7vV53x40xnlbWJF12ZSag/N0pW6t+ZzmOMzHjajKwDfond78zYTdfq18up97zr2q8v3IioBprRtBl0EZ9og5WBRGOdOHjIjXF7UotFbgOWnXzIJyzYvjG5IYgsmMOxHkz8OsMSrVNWeq5T8DaOcbEv1Od5rbs9aO7YvMet63EkF++fMExq+MRl4/L5bLZN/+ez+fnZ6KazuMqXSQVO5spJXflHAIzes/xJseckRJiDMog9d6VfRrqXMr6KpVV27jRwJacGovOAM1zMdQMnwK1AubK63kdCChvI1C7g0z9nf/D+Xze2Vj8H7Gx4P9duQlsYCrqyN8XqG3Hm/10Oj3jw/n+crlstuM+jPmmxT2dTuPz83Pzt2pn1XsEHX/bnPaVqVmh0xwOt0o6XLLAHePUU203wHfcrspCwmV3TryB5s0Mseeg97x/BwzCjBlbB+pRAPla0BVQuT6V6QHdBlj3d0KG147b+DqxQeUymDO43W4dQar+TIjwmAd0z8/h65vf0/yLv3Pb5XLpru/ydDo9s7ET0I+Pj6dKK9VUEIeKWQWPAOrJ8LKd4vE+t91Y3e7UFlWatg2VwJnb+HPmtvm/sfK59/OaWF3x/eP1UPHvA5DDYDpYXfb0drv1V2DkBkxtw/tEWVVlXWdC9pFYs5/jfh9dS/16vW7s6lTG+TfqsxSJHxkXXq/Xdr1eu4LsfD6P3vsT3N77DkL+zPm5jSdKL4zR3AxQd6rHkLkYlSowsrq7znzu6wSwdsMJOXmA5fBcjxtgMGBYHlr5zokhtsMCTgXLQOW4XC6dEyEMprL8mAQzXRgduix2yZzorxkYsDn3hB1VeMLGsXsVtgl2pW8S3svk0vw7R4hNaHvv4cACl5HFzwIH0Kc6zu4XjDPR/jpAVxWzO1Xk2DDb3vTcxeGU1iWZHkmIDWziWKvirCJ4Dravs6IJ/GG6cTqWdXDy+fArQDVVkLqkVjAoZIITdmmIqXwqa95N3+MGYoZQdRVNO53Y1xRkhO16vY7eu507Ca9lJnbGpxOemQhSw/AQsmmp5zU9BiU8G6wvX76M6/U6Pj4+do0Bz4CpgiknTUeDqwlKBmg3u4OVjrZ1A+rAcgaejWq6eJCvCYFDONSwOgHX4EQRw8lxbzDOdEK6gZ3Hk1b+8g2o1JFtKXyv/fEdTXuWjWXdAZiBp6ADeDrCFiim7B6ZFneeI7Gvm/PMkUDX67W7xI8b0D7/v8dA9qfN5oaCf74WZjH0mf1cmfY1Y0JUFmVrTWu8uzkNcLtEj7u5FXBTkfC6GOA5q8YMxO8KVvF6sAVGdcrUbsKODcQKkLMOMdmlxum642YrPm26AlhZW1YB1R+rrGswE8TaYAWeUMxdf+WjwSvZ2Ef3ytOyfn5+PpVPAaqOn43MtNBqvmjjxbjM4lZjZY4gqNMI5ktaW/sYKNwS+9lFQzGihmMCKPa7+Z0V6Eb0GRmobtpX8JljWu5FMLN5ja6hG9kwQgZqf5+1NH5UxzkFReCdWhJ8XdlGUkxO7HRlYRm4mVO43W7ter12TPJEw/rmEN3L5SKHIWZg9mz+pUoKOYq5bJTJdX2gme1UcxMZQFaEQIlHct32M+Y1BzGkGuzfiyAN9z+ugplZ1symCrDCYYkGxDTpI9RzBy0rHyeDUC1nWaeUaD9n4xkNyYMBDZtzZ3B++fJlY21XFDOcARJlabOyiS3uCpLI9jrZjCDkaVvcCCjwognKShWdzXZWlZMvVTgD8LpqlCLrqgbcB+qYwrgKYpT0ccCqbKyCValkEabn/FynogCrPKfqf51xJ7sGB2ZXcZmxoSOztjx300DZi7a0/2AIR0UlBag9SuDw6KcAzlaB7vHZvWpjK90dyrq6bKyDUZQbR0B05biLQkHIcSUmgIK+SwuqgHCnoio2RQU1yj+BnBy9pphVKLGyC7ZzFK1pxWK+E8IhVCWLN/uLtnUU4ayoYLoaANz8FdtaSvY4pV0BEW2ls61czqllBKpTyKgMAhrZ1cdc1RROtPmvWNkdcKZ7ZKxaWjiPLJMpp7OZKxA+rqG/oJLjxf0pnJlqLoDZo3gyU0mKGys2taKecj/d1C+rJSplBqlTyAqgR+D8KjKlmRL2gtUcAdCtsL+ijCNT1oqqqkH2OHEbG5sDFnUg5Aa+yLou2VU1ptj1S2ZQqv1ORZN9IWzRfgaRBxKoBE8UWyqlJFtrIc0AxNjSjed99CTY/XDfSzCz5M0IZoVEsWnPFNTsl8ooVC1TzbGgqFZNDSgVwKK+1sGDMKqxZCWGVMDysiEr1jVSQJUYwj5iHOlThdHt44SQg9CN+nl8D90NMIgAdgr46JqRiR9I8vRdFvbr17m/yxUMKjNLMiVUADwu2CWGhhi+F55TWM9M9cogzms1dnM4uOF/LAEYWdcqnM7yFmyq3IfwmOROd7Y1iFWtOjoY8To41mTV5IysgFFuRzsbWFGbNIIJCDv1dOo4lZG7jWBwRFtVTKuWyeCByJKOan8oZ3ep9XddNl0tDuaywLz9cXPYeDAA0SpkBO9sbVcTOVWldPv4uyzEkzxHtjvonHoSkFEWNoo1d8DhcQputd2ppNon4BzoAiJ1hBFQg0dVtdbGHHDQWushmNEQukLM2QO1G2Y8bgTXqFhcBJj7EjPgcPts8US8qPpPB/dXznOh5Z438tzH5ec6QgrOKrRRfKmysBmUDB+PhYabMlVPER+GCSITTzr7am2tArH3bgcEzPJm+cr5jJ4NnHNFDVrFXcI5Le9k5Jnw+bedbV+FfRzZIHaOOaOsLY0/7UGs58DjrGwKMIMFIGzOEW1/jGsdAtCN6hEAI4hBe9YXeRROBSVPAVPAqvIM5bx5hVKWAMP6zBRy3iescridVdFBinBxXDnG2GRY2XbCvp1lhvGtO9Bxu5h908XQu42lnSArMFdizMim8uwRCxPGnnOS8lwpnbOiDqTAjsrRN/PcoAScCbaACqVM40ylnjjTBs+bwWlAG23/UKbdkiwKWIQPGzWaczpoSlxPEj822cNWkpS7FyzsDrqpfgpG3jahw2vgbaSQAxuLWZYt7JzyNe8JoZpNAcvDFOdw0wqYT9AK1rZz/DdbSlLPp0ryIxgQJlK9AZlEq7IOXpohg9PIhrCng88JsOxiV4ZWAYfg4sikx/8ky2Z9l862uqwrfscIH8+ugTmVGyiddeVYUgEMn4GZzg14EwIsh9sx2cKKiWXReuOE5gzGOQgdlRKVVdlevqb279Xq0Qnsts2VDaBO0coezsruWtHApu6sKG4IBhN0aGU2kLrMKGRTN3HmbCDwKV14zvkMEDG4QfZVspVlaNU2mhc5TEZ3N1h/zqTheuLpW05ZWTGVjb3dbnNmxKZBnN8JqidaVLKAOyARNLS+MB54Z2+VaqoMLKroVBlngefnTPAcoHNWCSvlfA8CI0HEmBNBnBlXyMrzU7A7WVm94PPqQ2gmqKx+WDGsnvilmcSOBJqOK1nYyAIzuAyesq3UdSK3KfWcYKD95HmfYOU3qser2CtYEUA+FpfqdNvgPBZUBhDrGONRVlQsh8rLcaUCykHG0OOUwTlLBrsh5soEMGezi1E4HRVt1icp5wZEFXdibCkG8Y8vX75sbO4E0iom9z+hjSiOfy3DhpXItpVhE+UGQdvoWjtChmrGHf4YAzKgBNnGtuJxFCeGdhUAfQLLK8kBYAP6gvFJZajMG3Xkycy8KuC0q4Eyymwtwdxdv2M0mIBtK0LKnf640j00Auq4gUkdWGlhs22qJc6dZCsL19oxnlTJG4SYVRIGpD8TPFBuM6OElbS1pldid4mGAyN6ZIupbC5bXJN9fdpbThSxLUaI8IG1XIYBxW3Tjs6KQosKcxfxcQmdnwRGM10GnFcCy2XYunLMyAkdgk4mePiczsLygthcBut6goOqS7YVFXADLjaosB6s6ofcZWAZSIRYqSUkizYwttYab3vUOQ9w2HRxIIg8WwRVeE68xi4UtL3zRphxplzwuZrcqYCq1I3jPI5dnJIygEohMbPqVJSzrwzxBJTs5zN+ReUSgxikPQVF3JVBeNQxbHENrEMNvEdFZVV9lH9+ORGEsNZQpyTNc4C3AG7XF4ngzq+DrO2zbuaaOXgdaFcdkEotoSFBVX2qJ0C8OWZeG4KGlpghA0XfTOPCqV2qqwQ26QWfF2PMLhI2w1lVAa2aPsYd0za25MQRwgcZN6uQDCi+ZxiD4XEM2kZxOT41FnZnaRlcpZouzlRqqdbQVWopQoSB58RV50lBNrHi/AwXS5LrwDVlpY3Fc3ByiYGc52Trist6kOXdwInAQtJpp5QchyaquYOV7Su+fxVMaV3dc0RE2S6mUY0gLt2pMcYqrKIQ9w2l1gpQUMtQYcmmbt5DTNxdhnUCjQqtbK9SUSzvrC0mmhhE1e2FS2+oxypy/ZASutkmtjx3vcBC24PX65nbqkBCRhfjS9kIYPnee8cMagVOhI/3T1fAmdtAWZsCswTJCkQVNa0qWKSKPOpHAUhD9DrbVcyoYkwqhvh17vYAayXLQyKGYdxlUDFp494rBXRjYgO17DDYetNIUj/ezp6S0lnlpEwsWmJMkOwsKXeZKEAjIHn0EQJISaRBcO6UMINz7p/bEjjnw4ft+xmDvksxX4G2rIris7qaeKwAFMP2Oi7n4criuZwtpSUwpfLxSnORSrIqusc5ZFaXysqRWjiZ2DyAWEIL35tVSoQElFACjOeGGSE7AHEQgdo/LSvCOgGBvkxsmDbvlS3Fp5vhaB2TAGqRKrKKMrhLVpaGzEVjZ0OQxDhaCTA+QyRR1d15aQzrJntL3RibsipjG6jlgL4yqbS0sNYg1e84vhbBVrElK64CUcWYXDfKxhpIuxiVJZUxsbMy/uRBKTNRQ4kQ3LdRYLS0rJjRPlTPqY6gdJsEDc+aQXAn+HgsNUCbRuF0Oj0zwnA7bWDkbhO5Ens00qeQhS1laBMl5M/cAaxsLF8rKyql+Tf7ELLEGu/ixiimdCvo0TjfpjKwaggen4eh5v7LokLKbLuyvHhcZG8dhGrEDx7Hg93ZppJF7qBqO3iVveXEDQNInzeoe8Yq6ePaZBZ2JviM3W2UAGotekRCAGq4EkF1X3DOnR11yRsBL1tRa0PVcZiNFXZ2c34FskvomInQQ6lzpJoZbJxk43NwKJFBquJSsrByHydxKOnTxQASBmS3j+JMnsHSla3Ec6K9VWoJVn9zfjwOM7hqYAAqJQwE2a3nA48J2QGegRkpZNivSY+ys3EkKd4oJIwsvIHl3cWgLt5k4NH6OmtLWdpurOkwEMupYc7eMtDRhOcI2ui5JhVIzXzLyto/GAPuZoyo8wkoduVgJglCt7OhGbgID4Mq4si+63zUS1FuFFXFlqyaj2emHlLMcBqYu0FMuR28BbB7lOxRMSiCQXFhCKuwkhZ+pYDiGSgbsKKV8MiSRsuHSIWM9rklRiIlZZuqXjsQK8ooYJMgq3JKWVkhHbhsVxFUzthOWPkYijcbx54IKsSdT+uLr3crGKyoYgFiGR9iBk4kfloUX+JIlQRQqabmpgnhqtpQpb6RVQ1WH5DnrS4hEoGZqaerQ2dhFbz8XePxShmDbo70eISjoorO2vK8SJXI4SUmEU4zWKDzUDtWTYw7xXlbSTEj4FRg7zKnKoGRALv0Gs9Tgc1BpCywGZRQAtqVz2xrBcAMzEpfZwFSa2G5W0QBFjSMapWAEFa3HcGN7CxDzECyIkJ97qwrqWNTWVo876PPsjPkj2wvgroM5lLZKMETKVql/CvnWVFiFa/SzJUQwkoZsr67Y6vlSRV3/2tmNTOY3vnaxYwMuoPKqdzR1w7IqHymlPxaAThfU7Ko2ZXYj4AYJHL+kNdKwRQYESTRa5fsUZ/rVC1TMTyWVyYoqNtuzaHsMyv2tvoarxdfqwYgU1axFo/cnql1FGsqK+uAROV8BX4GU8WcZTATi2q7Qcyi0O0V+GhWBMNRUkn8H1SsWVE5By3Gi0ECqUeJoBfAtDa4amkdXG37AGP5Ggeb84p7UazpoKRzdFzeQ8HkoHGxprKy/Hpm5t12p47J6xTYDEz7uINEXSuxYXvFskYAc+ySxH9sf5ftKzU6IbwVBcUGg5e5FMCEXSErZR0wGayV19woM9guPjTqJdVTqR4uE4nJnLldWVkECCZLd2VLF+xtamex7IpiriSDUpvrpn9lrwGMCHyppMH+ps6LILsuFGUj1XEOXiqbqSHPUKnClpWV68kqtURVNDY4TNaocykoYeTU5ngGEQa/S1DnnE4AeXMcKjHPAmFVjCBENaeyLVNHfr3px8xUstJ94hIpfH4HKE/eDaArK6lSyVVFbdt1gxTIVk3pppVlFXi4pEhVBTObquohU85MLXn1iahvUkHJjSCMc01tLFveVVBx0DodM6jftCu7DOtIzYxrc0qp1JGP2ayYFz2Gb6HvMrO8cnGtV6Gjm3uImSfD2GpWK6uowbZGMxFKQCo1pOMtcMXFpRst+hXGoAomF3sSTBGgTglbBKWwsQ3tZqaYSp0Z1CimRDWFcCJUPYJ00BI5FkKYNoifuQxmN88SWVXWLMaUqqqgC0BmQJR6sk3u9NCf6jYLXxAfqsYEgVLAhRY2AtgtflZNFmFyhxdrLkAdWlk4D88M2ixHyepIdhMHrG/iR1ZGtq0MGpbDbRPYOXeSY1M6Ny4ZstvGSktK+XbFPATj2D371saPEsAMXhXrsZ0km/XStkhhMyBfsa6uXFZe2VCe+YMr1+GKgwrQyNYq1VRrB+EizAow6NsdNKcyVEkYeM73ys6q4kAHp6BiFklTkIrVC5oYV7uzwOGCz4UJ0Stq2lWMJy4wtb+RetL6tZFicnJmBw5UjCvXXMZVJX2MQkbf+XN5EWd78Vz8/JEsMZTBiKNzsm1inLRUQ74H4NidaqI68j5sAFgxcRveC7ieLJXfQYxjZZ2CsiWFewZXJmBIlZ1tdtrX4hSuateKso/RZOtOKW2nmq1oTzeK6dRWAWu2NRVb4hq0SXm1GvtugHrbr5IXqmSktg5CuDE2MSlPwsY5kNE2Wp3AqiZbWVLAxiBF+2iBZbuNj6MB6rsMLC7FyasaYDyo7KkoPyEtw3pEMXfPvxAJi2jAQQgjrz0rLIZSWZlIoNhwd5xK4AR9mYNjWAaLrnuImJeBVN9zBORObVvbr+mTTfFSEJLSRnHo7hEJoIi8MFqjxmvgmF5URZz4zLFgZZ8Ctu2X7ggVccKm9gVxIsOHqxXgNMKnFWZYnf1dBnOhayXq17QwFlWW09eNKyVJFmXqaONGA5aCegMbJ3UUkGY1ic3nKWgjq8qfVYGQG1gRt6rs62a6HiqqUOqdesK5NmX4nGofJoiE1d0dF9lVVkvT1/kEEaaCoYOwFpcVcoLM+7669PxC9rWqktH0sWUYld0VCpuBZ/stVRcGgy9WX2+U1Qthi9SzAqSxzZsy+OiFzBYnySGV6Gku44rD8BCOZBV3BvD5+AKRHNwMEsB6EzHnJpkTAeiUlEGkcECeB6GDZTp5YEJTlvdrknxYjTllMkfNtXwDjM7uVjK5JXUUn43rrqpK2jytaxHW0M5G8DC8rtHMYs7KSgduVQMGTYFqFvVS6rkD3sDJ46afdYFwoq11AOKCBLhvwoUgc8IGANycR6knZrdJPdsuxnyjfd3FovTlRMdEdtOl5CMV5EHsXQBis7TOwvIDZaGj2Vnpbh7cpK63VwYEMLwqbjzyl699sawFFkF1yqjUU31HfC6sW1ZFVFuXVXVgz9keEaw0ys1lWfm+azQAQSWA+hKYVfsZjPncAcUB9oIayy/UZXRNckDGji77GsWbvBo6tPrWPqOyVkBUq+INeqpzNdYs/u0ifh5qmpqIW+33JVSUcwY70KL4U9lYdU6ljtSls7lmfi9g3YzeQfVkaGFaV3ODCnaD2N8wsEDFklE3RzM3ZghdYkWHsszq70FIecnKkVkt8ezMzRq9bkGuKojRLBVSod3Y1yPqKgYW7JRQTPVyy5xIYLjOgxgT52RKJUY1dOrIiRd4futQx/A5AcSmEjz0vFWrkLzvbWAu9HOWbGgxFk1VNTpnBKk6TgwisI/HcxYXP1uAWO72ULFlBTq+aSu2VTUs6hrxM2CF+hEor1VIA9ZmFUaab1lSSgZsVs4sxzHlVLoJHr9H4DhONTkI1XC0/wiY2NoWAG5RlnHFnq6oLccpQddMuJ/O17JVA5OHLi0BqCztq7Y1++ucCd98qLI8MIHBV/cKjxQTme3hFBS3MyCqnDsuym2o80HjvFFTtrURmNaGJsmVahImjTsUXKtQZTAVs7Mvv8/+fzUrZAXcLJ6M4koe6XP0b6SmWWNDzyUpQ8bl+LtWx4tuqZ36cRYV3yuVxPNwvIiqiQCSmu7srgTzR6nkyhpCarXwFy1vGd5iP2cY06lFr5Njhhg1Y6+NB28ftbK83s8rf7kLJbKwDFPbLg25a0AdZJEiqr5phixKMDlRUtcssq1hriLqGoH+zeNgVm9OemjsETV8JdF0NHnkIFxWY1OB4Yrp7rtWJ7NgAAAPXklEQVQ3oNs5nplyVf8u2FoLu1JrHveaZWQjqAkshtFa2gzsSG3Zpkbvg3HafF9slPPlldjFlK80Gysm8Mr4MPhneNWENPGjAIpmilTPATdTRTXlCBYHYAQuPwA36xIpWtGN4q3Y2MhiGsUpuSSnlEJRD8PorC7CFYVw+F51qThgabxsTxWzCGY0ZSsb3lfqAy0OPNjNy8xiQQKsHYFQ2HBZVvVbBuq3m1oWKajqaonsM6uZUr6CjXWNZ0l5E3h3jURma6kP3MJIiy1Lm+kahQq41N2iZja5sjtlLYNZHZrH6qUGm4vMbDp6Rw2CFmvuyFkrBcCyMtFqBaECmsHoK9BZ2LA/lJcRqSaDqnaWbrZdGaz3DLgIvBln4woGztbyJGqslwxkhhHrTjTYFXCtOoKS8uLdofVdAbOylGU6nlYpXWZts4nXBq6WxJitMNokHUJnbnJplQm+aGpY2a5GMV2QD1hRubBPFKdumf5OHkLHz0F9luE5kjBjRa0nFE5CUGqHw32MmjZ6xkgINVnSnZ1VZStK2qKlRaLlQgK7uTq7JFXJwM+3SOEKyhZNI+tJ0I5qMYy9k2qJD7dVWdqKXa0CKNR0Ccjg+B2IYu2fcBZJZkMFgM11r0X92wilghFGgzVnexlqB7xL9mS29SiYUVY2nXOZjNBRsyDsQPRWW5hrZ4XcdC4HVWRbjgJr4sFofK5SzjQ7rhI1UebdPdEbj6sqIvTZQZ5va08rABsAW0UxeWytAk7A2KJ9ZpxzCioB24XFtYAeXYxr6anSqhLgppEqWbGwLunTgrV+IjWlL29ljaAl4EQMGsErp4apeZiquwRXLXAqOCeru32mmydc6oWTSWpFAGdzeTB8RTHVMEtlM90CbbQCYhPjq3egYr1FGdYIQjiuDGZ5zZ/AzobKGOyLxti6c4Rwtv2anyWlLICnlLhxJRXt6A5ebDBWFNONbxWZ2d02mnu4S9YECpeppV1zSWRBWxHYzVIv1CXSouwqqX3jBBBDZdYQbpTQW4ZQlS8r5kH4suSRmg2++3JN10x1PaAmEkmtYlEdeGpJEM6kOuCqCR22oSujj5IV2HdT0zj5prLKTjXFAPjdQlyq7xIBxAQP5yMczG4VxAKw0n6ilZ2QBce2pLulkuxxqnoIzFfgqyqjil9S1VNwBrFmeyeops8yOjZUybZdfS8CuaTIJumzs5tODaNtLpFDQ/PcJGweLhmeL1nB0KqiUDScsiUVD89Di3HtrKtSULw3RLiygZD+7sF8JTObgYsrGvDNUFRGl1iy0Ll1YkUc2aJYMog920I8qW6YDCg1Mqk0JHJFKXkbgbRreI+qpYNOZHrVcDUba7pjsphSJNtK6upgRNAVoOS0mugBeN4bIZgHhuPZ/s1ENaX6KsVr+YNrh1Nb7ipR0PE5zbNRegCbrHRUw6Yf07dLBJl1f8KB9as2V1nNqAsl62LBBhehwalerkHmB1JFIEZKSEusdl5JQj1nJlHXSCF342gJ9CYGrXelknJIXqVP8sD+qtplCR3XH2qfKq0ygMp+KnVkKxNlZ8m2YkIlVMiCnXUwl7qznBKSvQz3m3Pt6oQbXO5b5FixCh/fHxUQW/AEcK6zCNqKQnL9sywqmKuwvqSYzT/aPVNNpVyhvRW21aqciCsjdWvBwILUvh5VyCzbWoC1pJjJ680CWsl+udKB6T5RwG1mlohnlpbg47iz5U9ha0FGtmRLFYBtO99y97Ap0z+ZDTAog6kSLZsMHg/IFkkgp6CpvU2U0cYVSdnmkjwBdOmXbxTWNWzuIbipMioVxEckZEoahSOiy2M3K0jcC1LhVDwaqG0ZvkcWqCnrG4GIxykrqlbWdw6LQyBaZR8HmLRIhQWsHswD42ZXVLNkf9l+FlW0HVQ2lwFsC/Z1FdzlQR0KaPfo+Fdfu+/dwVRICu1CGR7AEIiAhc+AZUF0kOBaPxmUqg4i64vQnU4nFDYJ9Nz+1fVXveH9qmr+kPILx8oKcRV/BFbxbE0JMT0kSD4w6L/lNY8ocsqagVdU3A3MjxhxcGuqzsPH4irpaow1q6OyrVjvp9Npc59E91LldboYVzJWdimWfAW2SNEKcDaX2FmBLLA/uKxlmhh613Is1URQApbKfttwxL02q6Onx5pQxSbPojAg+v5hAnN6LHVRDXIsvKtRjiS0qJUyZTAXVbAK82ElFJWaQdVoqUC1Unt7BVaTQudM6SuqexjQJN4+0icaxv/utbKv83ETbT8H8gjcOKxOJmbUa6OOVXht3dFY6rHv9XoNzFLceEA1o8+pKm0LAHPHZ2rYKjFq0hfZFixsqHJgD3eD5n+U0kb1mFjXkn2lvMSSOsNE/CdIAKF0Sytq6urOHUN5gwg4GZosgbmggM5ucra2qrS2Ig1cbiBBcxYzgzUDNLCvL8GbZXNp6ORy3LmS+Kk83zRIAK6A1ioKa2I9NapIuiUFdfC9766PFZUtqUr6KbWk+zZU1a/ZrIXEztrjTOfz7hwKziCeXIaraHtbZIMz+2pGgazCmw4qWAFvEdhodYp0Xq0pV7G1YWYWbO4qhGq42+Z8BYtrLWvluNPpZAeaFFS1vubPgbgxsqcpnAaszBovKaFoDQ8BGtjfUOl4NAG2nmQV04feJgumvX2fsrQEWZghL0JnVdYkn3DOZIeRN86RqPWCmsvGVqEMRnwxQAxwS8EMYo3IzmY2+BCcLp4MKiuyuhImamlbZFcNoNl7tp+RHd18ZjQIRKyXdFRhN98/hyKqwXWNo7O1wiaXoHN108REZZWEq6grnIfjzeg8jdRf1XEL4kkXa5bBjKxoKaljBjeHlVxQ4GaycpW4lDOAKtnTxHAtOfzOtZwHAM7sqVXkV6yu6kap1nHkXKqWF/4XHqjenNKqBjpR3l1ch3Ejg1+EsgdQhsdG0B4FM9sWAVWpuAyiwTPleZxt9VyZVS2qXfReWqTAilpr9ApoWTjxymit7NwV4JTriZyOA9B0k7HFfULourmKYHVnRQvqGL5HMHdqFcR2qWpmcK6eTwx2dipWrviDilr+fKWq3OWRWdHKwA4eu8wjchbeRzFilqjjZN3ufCpfkJ0/scVpnYk6L0PI77lxdWCZ87WiWm7B/AGquQSnujGKsB8CJmiJq8q1pKIVWyqOiTK66r18BN8r74/AE71fdC3yPS2MxdOpnE1tlVxD9JmVOoggN+r4PjAXVFPa3Eg5jVJGFVUGNolH20GVrUB7BOySWq6WqYQdWR92pcFMYMwckbSgCKCqD67DiiWu1g8MQC9ByfcFqW1L+jL714qNCuznoSxt0da2gtWN1G8F0BK0NN0nuimelUF9dIdAfjO44UT3CjQLoUeLHJFTO3gmpRuIIOvwBQCbqNeo3qtZ9iF6xVK13GRlo4zqimq+CGdTiR1uRY8oqgE02hZBa79kZXPMquxRHKla2saZWN4mRqZUj0vLCKhkjKnqOQHNuSZVJoKvAqS1wpEquvWDC1B2ypwrCPsRMEPVTODMLJMDv6qeKXwi2JYV5Sq4qKyvgGsHCLiuj2jR59V8gMqSJ2FJZRXEHVRHj3sFPrct6OpqlW1GpatQdt0GvwfM6n63InsGVFhJGaBqgqqIV6IsXllZgySPq4R3bnt3wi5cv+cN2yqQLW1T95KYVsWWtKk4cB9W53WQQflQYR6Wl4HaJZjvVE0D5yvq+RKgZCs5qdBEP5sD94cAvQLlSgNaSMAtHx88BuNQ41zdFsX30zKbcs0MLD/ihkpQzl0wiTqKLTfbKmCmyYICnK0IbaieC4CG9iSyLQ7cIMGQwau6TKoq60Apl3WN40LZpca1CKKK9VQyyIEn8w0F8F6CL2h8o3ixGwC7s7EWzCOqmcApYxYD4jsAzVS0sl2t98pA7vrKophCVSonbYpgH6mvSn24pTBV4sdtV3BtMq5k82y+IADvUJ0uAlkCVTxIaPm+UNu/qkV4F1TzHXCGrXIAqItBKypqK99VtAOVs64O4ObX7pHLVCpYHcRmwvLR7TvYAKBBN58LGVzDuFz+hQbWgncQyCZAk+VbsPSouf93261iZgmfCpwRbAvqmSqriU2PwhjaoOyYqtIegVXViTsmyta6bGySpY3gyRrpIyAeaWDDxtpsXwKyalMDKNP7YBXMqEskUsi2uC8FNAPxAKTVfT1o6VzM0E0jF+1rWcUuHvdyg7vgoFplX8HpvHpMCOMRUPHzZkInsqlFKNX/EIO52E0SxSzOwob2VmRLW5D1XIU0rbgM1AzWgyC7fe8G7xUAK/taEBat7luqtyP7EmsaJQOj5F+mrnZfCuYCfBUAWwShyd6pMY/vAHG1UqOYpbI/gy5T0CMKm+UO3gFuC85dgfDVeguPDfITrIBLsLrcgdh3CFgFZjaKJ4Iv3F8ANEqvuxR1tVKOgLoCa1jxboBAkj6v7j/icFbA7f4rfRnQDLRViG13i0vqBQrYVqBbADZT0ZpiHoSzvQpopKIFS3sE1HfBWlHXd0H7LnArqvougMtljHBgZnh3Eoz/BKjLML4Z2Aq0+hEJr9jaVUBbvNzCIUiroC7AWmmFw4o5AK3MtB5VypZMSFgs05JyGVwlwBqsEGAAa2ZU1CjUexXGsE4rKriilBvFzOKKo3AuAroE6QFQU3u8YpNXwS5k+1TZt5UrwouN4KiUEw+k3ZWDp1RXHNRqXb21Ts39945yZSg3VnZFNQ9CF3XeZyr5DgBXKiwCMa2MxeTDYXgP1Fsf9QNKZc0k81RJk3r6EQ3rCmBVyLL75EjZ1pIVDHoFtiOAHoB0BdTVylqBsKKKS+AeBXJVLY+CXASuGvO/Auq7GuEjDfGKg1oKa1z/dmmi9I9SUGNhl0AtfulHAawoYrnSkmNXAVuGEhrEVXvUF+A5Ct2PqNOjDetyna4CmeUolmeXLN4Aq7C5Sj10Q7yjgl+t6CNxSRHmI5X+CpwreYB3Qfdqna4q21KdBuc4GoZsn49ZOOiVinwHqK9WzjvgeweEh2AU5+vtxZ9Cd9Wqkh49V18E5oj6vVyn0RStAyGIO5edXRKd5B0VGVXq2yr3xYp+5Ut+C4QJ4P1N339pQMjRejj4vb/Dcr6rQc3O/0rjmtZpeYCBiCHfCemRbNhbK/pNUPc3wfKy5f2D7OlL3/uPhve/oU4T0F8f+VNM2vyoiv0jK+KHQfdHq+0bncz4oz73/+Y6LbKw1o/5B7eOf1Rl/0du9B9tn/9bvrf/j+v0h6ttn2tp/r/4819y4/zv5391uvzzfwDifz6phT1MPgAAAABJRU5ErkJggg=='); +} + +.x-cursor { + cursor: default; + width: 16px; + height: 16px; + border-width: 2px; + position: relative; + border-style: solid; + @include boder-radius(50%); + border-color: var(--cursor-color); +} + +.x-info-box { + // + display: flex; + margin-bottom: 8px; + + // + .x-start { + margin: { + top: auto; + bottom: auto; + } + + position: relative; + } + + // + .x-margin { + width: 2px; + height: auto; + + margin: { + left: 4px; + right: 4px; + } + } + + // + .x-end { + flex: 1 1 auto; + } +} + +.x-selected-color { + top: 0; + left: 0; + width: 40px; + height: 40px; + position: absolute; + @include boder-radius(50%); +} + +.x-selected-color-background { + width: 40px; + height: 40px; + @include boder-radius(50%); + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAh0lEQVRYR+2W0QlAMQgD60zdfwOdqa8TmI/wQMr5K0I5bZLIzLOa2nt37VVVbd+dDx5obgCC3KBLwJ2ff4PnVidkf+ucIhw80HQaCLo3DMH3CRK3iFsmAWVl6hPNDwt8EvNE5q+YuEXcMgkonVM6SdyCoEvAnZ8v1Hjx817MilmxSUB5rdLJDycZgUAZUch/AAAAAElFTkSuQmCC'); +} + +.x-color-hue { + width: 100%; + height: 16px; + border: none; + cursor: pointer; + margin-bottom: 8px; + background-size: 100% 100%; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAAAQCAYAAAD06IYnAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AIWDwkUFWbCCAAAAFxJREFUaN7t0kEKg0AQAME2x83/n2qu5qCgD1iDhCoYdpnbQC9bbY1qVO/jvc6k3ad91s7/7F1/csgPrujuQ17BDYSFsBAWwgJhISyEBcJCWAgLhIWwEBYIi2f7Ar/1TCgFH2X9AAAAAElFTkSuQmCC'); +} + +.x-color-alpha { + width: 100%; + height: 16px; + border: none; + cursor: pointer; + background-size: 100% 100%; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAAAQCAYAAAD06IYnAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AIWDwYQlZMa3gAAAWVJREFUaN7tmEGO6jAQRCsOArHgBpyAJYGjcGocxAm4A2IHpmoWE0eBH+ezmFlNvU06shJ3W6VEelWMUQAIIF9f6qZpimsA1LYtS2uF51/u27YVAFZVRUkEoGHdPV/sIcbIEIIkUdI/9Xa7neyv61+SWFUVAVCSct00TWn2fv6u3+Ecfd3tXzy/0+nEUu+SPjo/kqzrmiQpScN6v98XewfA8/lMkiLJ2WxGSUopcT6fM6U0NX9/frfbjev1WtfrlZfLhYfDQQHG/AIOlnGwjINlHCxjHCzjYJm/TJWdCwquJXseFFzGwDNNeiKMOJTO8xQdDQaeB29+K9efeLaBo9J7vdvtJj1RjFFjfiv7qv95tjx/7leSQgh93e1ffMeIp6O+YQjho/N791t1XVOSSI7N//K+4/GoxWLBx+PB5/Op5XLJ+/3OlJJWqxU3m83ovv5iGf8KjYNlHCxjHCzjYBkHy5gf5gusvQU7U37jTAAAAABJRU5ErkJggg=='); +} + +.x-color-value { + margin-bottom: 8px; +} + +.x-hex, +.x-rgba, +.x-hsla { + display: flex; + margin-bottom: 8px; +} + +.x-color-text { + display: block; + flex: 1 1 auto; + margin: { + top: auto; + bottom: auto; + } +} + +.x-color-input { + // + width: 100%; + background-color: transparent !important; + + // + border: { + top: unset; + left: unset; + right: unset; + } + + &:focus { + outline: unset; + } +} + +.x-preset-colors-wrapper { + * { + text-align: start; + } +} + +.x-preset-label { + display: block; +} + +.x-preset-colors-box { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.x-preset-color { + width: 18px; + height: 18px; + cursor: pointer; + display: inline-block; + margin: 6px 6px 8px 6px; + @include boder-radius(25%); + border: #a9a9a9 solid 1px; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker.component.ts + +import { + Component, + ViewChild, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + rgbString, + XHSVAColor, + hslaToHsva, + hsvaToHsla, + rgbaString, + rgbaToHsva, + hsvaToRgba, + rgbaToHex1, + hslaString, + parseXColor, + stringToHsva, + toNormalString, + denormalizeRGBA, + isNullOrEmptyString, + XColorWithBrightness, + XSliderValueEventModel, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XSliderPosition, + XSliderDimension, + XColorPickerAction, + XColorPickerCssVar, + XColorPickerActionModel, + XColorPickerOutputFormat, + DEFAULT_COLOR_PICKER_COLOR, + XColorPickerContainerComponent, +} from './x-color-picker.typings'; +import { Subscription, Subject } from 'rxjs'; + +@Component({ + selector: 'x-color-picker', + templateUrl: './x-color-picker.component.html', + styleUrls: ['./x-color-picker.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XColorPickerComponent extends XColorPickerContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private LAST_OUTPUT_COLOR: string; + + // + readonly ColorFormats = Object.assign({}, XColorPickerOutputFormat); + + // + hexValue: string; + hslaValue: string; + rgbaValue: string; + + // + hsva: XHSVAColor = { + h: 0, + s: 0, + v: 0, + a: 1, + }; + hueSliderColor: string; + slider: XSliderPosition; + alphaSliderColor: string; + sliderDimMax: XSliderDimension; + + // + inputColor: string; + + // + //#region View Childs ... + // + //#region ColorPicker ViewChild ... + private COLOR_PICKER: ElementRef; + + @ViewChild('colorPicker', { static: false }) + set colorPicker(v: ElementRef) { + // + this.COLOR_PICKER = v; + this.initializeComponent(); + } + + get colorPicker() { + return this.COLOR_PICKER; + } + //#endregion + + // + //#region PaletteSlider ViewChild ... + private PALETTE_SLIDER: ElementRef; + + @ViewChild('paletteSlider', { static: false }) + set paletteSlider(v: ElementRef) { + // + this.PALETTE_SLIDER = v; + this.initializeComponent(); + } + + get paletteSlider() { + return this.PALETTE_SLIDER; + } + //#endregion + + // + //#region HueSlider ViewChild ... + private HUE_SLIDER: ElementRef; + + @ViewChild('hueSlider', { static: false }) + set hueSlider(v: ElementRef) { + // + this.HUE_SLIDER = v; + this.initializeComponent(); + } + + get hueSlider() { + return this.HUE_SLIDER; + } + //#endregion + + // + //#region AlphaSlider ViewChild ... + private ALPHA_SLIDER: ElementRef; + + @ViewChild('alphaSlider', { static: false }) + set alphaSlider(v: ElementRef) { + // + this.ALPHA_SLIDER = v; + this.initializeComponent(); + } + + get alphaSlider() { + return this.ALPHA_SLIDER; + } + //#endregion + //#endregion + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.applyColors(); + await this.initializeComponent(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Colors Changed ... + const isCursorColorChanged = changeKeys.includes( + nameof('cursorColor') + ); + if (isCursorColorChanged) { + await this.applyColors(); + } + //#endregion + + // + //#region SelectedColor Changed ... + const isSelectedColorChanged = changeKeys.includes( + nameof('selectedColor') + ); + if (isSelectedColorChanged) { + // + const selectedColor = await this.getValueAsync(this.selectedColor); + if (isNullOrEmptyString(selectedColor)) { + this.LAST_OUTPUT_COLOR = DEFAULT_COLOR_PICKER_COLOR; + } else { + this.LAST_OUTPUT_COLOR = selectedColor; + } + + // + await this.initializeComponent(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + async afterViewInit() { + super.afterViewInit(); + } + + async onResize() { + super.onResize(); + + // + await this.initializeComponent(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + //#endregion + + // + //#region UI Handlers ... + async handleSaveSelectedColorInClipboard(color: string) { + // + if (isNullOrEmptyString(color)) { + return; + } + + // + const isSaved = this.managerService.saveToClipboard(color); + if (!isSaved) { + return; + } + + // + await this.managerService.notificationService.presentDefaultSuccessNotification(); + } + + async handleClearSelectedColor() { + // + this.selectedColor = DEFAULT_COLOR_PICKER_COLOR; + this.LAST_OUTPUT_COLOR = DEFAULT_COLOR_PICKER_COLOR; + + // + this.detectChanges(); + this.colorSelected.emit(''); + } + + async handleInputColor(event?: any) { + // + if (isNullOrEmptyString(this.inputColor)) { + return; + } + + // + const color = toNormalString(this.inputColor); + this.selectedColor = color; + await this.initializeComponent(); + } + //#endregion + + // + //#region Actions ... + async setR(model: XSliderValueEventModel) { + // + const rgba = hsvaToRgba(this.hsva); + rgba.r = model.v / model.rg; + this.hsva = rgbaToHsva(rgba); + + // + await this.update(); + } + + async setG(model: XSliderValueEventModel) { + // + const rgba = hsvaToRgba(this.hsva); + rgba.g = model.v / model.rg; + this.hsva = rgbaToHsva(rgba); + + // + await this.update(); + } + + async setB(model: XSliderValueEventModel) { + // + const rgba = hsvaToRgba(this.hsva); + rgba.b = model.v / model.rg; + this.hsva = rgbaToHsva(rgba); + + // + await this.update(); + } + + async setHue(model: XSliderValueEventModel) { + // + this.hsva.h = model.v / model.rg; + + // + await this.update(); + } + + async setHexColor(model: string) { + // + let hsva = stringToHsva(model); + if (!hsva) { + hsva = stringToHsva(model); + } + + // + if (hsva) { + // + this.hsva = hsva; + await this.update(); + } + } + + async setAlpha(model: XSliderValueEventModel) { + // + this.hsva.a = model.v / model.rg; + + // + await this.update(); + } + + async setSaturation(model: XSliderValueEventModel) { + // + const hsla = hsvaToHsla(this.hsva); + hsla.s = model.v / model.rg; + this.hsva = hslaToHsva(hsla); + + // + await this.update(); + } + + async setLightness(model: XSliderValueEventModel) { + // + const hsla = hsvaToHsla(this.hsva); + hsla.l = model.v / model.rg; + this.hsva = hslaToHsva(hsla); + + // + await this.update(); + } + + async setSaturationAndBrightness(model: XSliderValueEventModel) { + // + this.hsva.s = model.s / model.rgX; + this.hsva.v = model.v / model.rgY; + + // + await this.update(); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XColorPickerAction.Refresh: + this.detectChanges(); + break; + + // + case XColorPickerAction.SetSelectedColor: + // + if (model.payload) { + // + this.selectedColor = model.payload as string; + await this.initializeComponent(); + } + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async applyColors() { + // + let cursorColor = await this.getValueAsync(this.cursorColor); + if (!cursorColor) { + cursorColor = XColorWithBrightness.Dark; + } + + // + const colorDescriptor = parseXColor(cursorColor); + if (!colorDescriptor) { + this.detectChanges(); + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XColorPickerCssVar.CursorColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + private async initializeComponent() { + // + if (!this.colorPicker) { + return; + } + + // + const colorPickerElement = this.colorPicker.nativeElement as HTMLElement; + if (!colorPickerElement) { + return; + } + + // + let paletteWidth = 0; + let paletteHeight = 0; + let hueOffsetWith = 0; + let alphaOffsetWith = 0; + + // + //#region Check Palette ... + const hasPaletteChannel = await this.getValueAsync(this.showPaletteChannel); + if (hasPaletteChannel) { + // + if (!this.paletteSlider) { + return; + } + + // + const paletteElement = this.paletteSlider.nativeElement as HTMLElement; + if (!paletteElement) { + return; + } + + // + paletteWidth = paletteElement.offsetWidth; + if (paletteWidth === 0) { + setTimeout(() => this.initializeComponent(), 100); + return; + } + + // + paletteHeight = paletteElement.offsetHeight; + if (paletteHeight === 0) { + setTimeout(() => this.initializeComponent(), 100); + return; + } + } + //#endregion + + // + //#region Check Hue ... + const hasHueChannel = await this.getValueAsync(this.showHueChannel); + if (hasHueChannel) { + // + if (!this.hueSlider) { + return; + } + + // + const hueElement = this.hueSlider.nativeElement as HTMLElement; + if (!hueElement) { + return; + } + + // + hueOffsetWith = hueElement.offsetWidth; + if (hueOffsetWith === 0) { + setTimeout(() => this.initializeComponent(), 100); + return; + } + } + //#endregion + + // + //#region Check Alpha ... + const hasAlphaChannel = await this.getValueAsync(this.showAlphaChannel); + if (hasAlphaChannel) { + // + if (!this.alphaSlider) { + return; + } + + // + const alphaElement = this.alphaSlider.nativeElement as HTMLElement; + if (!alphaElement) { + return; + } + + // + alphaOffsetWith = alphaElement.offsetWidth; + if (alphaOffsetWith === 0) { + setTimeout(() => this.initializeComponent(), 100); + return; + } + } + //#endregion + + // + this.sliderDimMax = { + h: hueOffsetWith, + s: paletteWidth, + v: paletteHeight, + a: alphaOffsetWith, + }; + + // + this.slider = { + h: 0, + s: 0, + v: 0, + a: 0, + }; + + // + let selectedColor = await this.getValueAsync(this.selectedColor); + if (isNullOrEmptyString(selectedColor)) { + selectedColor = DEFAULT_COLOR_PICKER_COLOR; + } + + // + let hsva = stringToHsva(selectedColor); + if (hsva) { + // + this.hsva = hsva; + + // + await this.update(); + } + + // + this.detectChanges(); + } + + private async getSelectedColor() { + // + const rgba = denormalizeRGBA(hsvaToRgba(this.hsva)); + + // + let outputFormat = await this.getValueAsync(this.outputFormat); + if (isNullOrEmptyString(outputFormat)) { + outputFormat = XColorPickerOutputFormat.XHEX; + } + + // + let result = ''; + switch (outputFormat) { + // + case XColorPickerOutputFormat.XHEX: + result = rgbaToHex1(rgba, true); + break; + + // + case XColorPickerOutputFormat.XHSLA: + // + const hsla = hsvaToHsla(this.hsva); + result = hslaString(hsla); + break; + + // + case XColorPickerOutputFormat.XRGBA: + result = rgbaString(rgba); + break; + } + + // + return result; + } + + private async update() { + // + const rgba = denormalizeRGBA(hsvaToRgba(this.hsva)); + const hsla = hsvaToHsla(this.hsva); + + // + const hHsva: XHSVAColor = { + h: this.hsva.h, + s: 1, + v: 1, + a: 1, + }; + const hueRgba = denormalizeRGBA(hsvaToRgba(hHsva)); + + // + this.hexValue = rgbaToHex1(rgba, true); + this.rgbaValue = rgbaString(rgba); + this.hslaValue = hslaString(hsla); + + // + this.alphaSliderColor = rgbString(rgba); + this.hueSliderColor = rgbString(hueRgba); + + // + this.selectedColor = await this.getSelectedColor(); + const selectedColor = await this.getValueAsync(this.selectedColor); + + // + this.slider = { + h: this.hsva.h * this.sliderDimMax.h - 8, + s: this.hsva.s * this.sliderDimMax.s - 8, + v: (1 - this.hsva.v) * this.sliderDimMax.v - 8, + a: this.hsva.a * this.sliderDimMax.a - 8, + }; + + // + if ( + !isNullOrEmptyString(this.LAST_OUTPUT_COLOR) && + this.LAST_OUTPUT_COLOR !== selectedColor + ) { + // + this.LAST_OUTPUT_COLOR = selectedColor; + this.colorSelected.emit(selectedColor); + } + + // + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XColorPickerComponent } from './x-color-picker.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XColorTextDirective } from './directives/x-color-text.directive'; +import { XColorPickerDialogService } from './x-color-picker-dialog/x-color-picker-dialog.service'; +import { XColorPickerDialogComponent } from './x-color-picker-dialog/x-color-picker-dialog.component'; + +@NgModule({ + providers: [XColorPickerDialogService], + entryComponents: [XColorPickerDialogComponent], + declarations: [ + XColorTextDirective, + XColorPickerComponent, + XColorPickerDialogComponent, + ], + imports: [ + XCardModule, + XIconModule, + CommonModule, + XSlotterModule, + XBaseComponentModule, + ], + exports: [ + XCardModule, + XIconModule, + CommonModule, + XSlotterModule, + XColorTextDirective, + XBaseComponentModule, + XColorPickerComponent, + XColorPickerDialogComponent, + ], +}) +export class XColorPickerModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XHSVAColor, + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { map, concatMap } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XColorPickerAction { + Refresh = 'refresh', + SetSelectedColor = 'set_selected_color', +} + +export type XColorPickerActionIdentifier = XColorPickerAction | string; + +export interface XColorPickerActionModel + extends XBaseActionModel {} + +export interface XSliderPosition extends Required {} + +export interface XSliderDimension extends Required {} + +export enum XColorPickerOutputFormat { + XHEX = 'hex', + XRGBA = 'rgba', + XHSLA = 'hsla', +} + +export type XColorPickerOutputFormatIdentifier = + | XColorPickerOutputFormat + | string; + +export enum XColorPickerCssVar { + CursorColor = 'cursor-color', +} + +export const DEFAULT_COLOR_PICKER_COLOR = '#ffffffff'; + +// +//#region Container ... +export interface IXColorPickerContainer extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + colorSelected: EventEmitter; + + // + //#region Colors ... + selectedColor: XStandardType; + color: XStandardType; + foregroundColor: XStandardType; + copyToClipboardIconColor: XStandardType; + colorInputIconColor: XStandardType; + clearColorIconColor: XStandardType; + colorTextColor: XStandardType; + presetColorsLableColor: XStandardType; + cursorColor: XStandardType; + //#endregion + + // + //#region Labels ... + colorInputIconLabel: XStandardType; + copyToClipboardIconLabel: XStandardType; + clearColorIconLabel: XStandardType; + //#endregion + + // + //#region PresetColors ... + presetColorsLable: XStandardType; + presetColors: XStandardType>; + //#endregion + + // + //#region Represent Color Value ... + representColorFormats: XStandardType< + XOneOrManyType + >; + outputFormat: XStandardType; + //#endregion + + // + //#region Show Parts Props ... + showColorInput: XStandardType; + showCopyToClipboard: XStandardType; + showSelectedColorPreview: XStandardType; + showPaletteChannel: XStandardType; + showHueChannel: XStandardType; + showAlphaChannel: XStandardType; + showColorValue: XStandardType; + showPresetColors: XStandardType; + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +export class XColorPickerContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly colorSelected: EventEmitter = new EventEmitter(); + + // + //#region Colors ... + readonly selectedColor: XStandardType = DEFAULT_COLOR_PICKER_COLOR; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly copyToClipboardIconColor: XStandardType = + XColorWithBrightness.TertiaryShade; + + readonly colorInputIconColor: XStandardType = + XColorWithBrightness.SecondaryShade; + + readonly clearColorIconColor: XStandardType = + XColorWithBrightness.Danger; + + readonly colorTextColor: XStandardType = + XColorWithBrightness.Dark; + + readonly presetColorsLableColor: XStandardType = + XColorWithBrightness.Dark; + + readonly cursorColor: XStandardType = + XColorWithBrightness.Dark; + //#endregion + + // + //#region Labels ... + readonly colorInputIconLabel: XStandardType = + XResourceIDs.input; + + readonly copyToClipboardIconLabel: XStandardType = + XResourceIDs.copy_to_clipboard; + + readonly clearColorIconLabel: XStandardType = + XResourceIDs.clear; + //#endregion + + // + //#region PresetColors ... + readonly presetColorsLable: XStandardType; + readonly presetColors: XStandardType>; + //#endregion + + // + //#region Represent Color Value ... + readonly representColorFormats: XStandardType< + XOneOrManyType + > = [XColorPickerOutputFormat.XHEX]; + + readonly outputFormat: XStandardType = + XColorPickerOutputFormat.XHEX; + //#endregion + + // + //#region Show Parts Props ... + readonly showColorInput: XStandardType = true; + readonly showCopyToClipboard: XStandardType = false; + readonly showSelectedColorPreview: XStandardType = true; + readonly showPaletteChannel: XStandardType = true; + readonly showHueChannel: XStandardType = true; + readonly showAlphaChannel: XStandardType = true; + readonly showColorValue: XStandardType = true; + readonly showPresetColors: XStandardType = true; + //#endregion + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XColorPickerContainerComponent + extends XComponentCardContainerComponent + implements IXColorPickerContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Output() + colorSelected: EventEmitter = new EventEmitter(); + + // + //#region Colors ... + @Input() + selectedColor: XStandardType = this.propertyProvider.selectedColor; + + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + @Input() + copyToClipboardIconColor: XStandardType = + this.propertyProvider.copyToClipboardIconColor; + + @Input() + colorInputIconColor: XStandardType = + this.propertyProvider.colorInputIconColor; + + @Input() + clearColorIconColor: XStandardType = + this.propertyProvider.clearColorIconColor; + + @Input() + colorTextColor: XStandardType = + this.propertyProvider.colorTextColor; + + @Input() + presetColorsLableColor: XStandardType = + this.propertyProvider.presetColorsLableColor; + + @Input() + cursorColor: XStandardType = + this.propertyProvider.cursorColor; + //#endregion + + // + //#region Labels ... + @Input() + colorInputIconLabel: XStandardType = + this.propertyProvider.colorInputIconLabel; + + @Input() + copyToClipboardIconLabel: XStandardType = + this.propertyProvider.copyToClipboardIconLabel; + + @Input() + clearColorIconLabel: XStandardType = + this.propertyProvider.clearColorIconLabel; + //#endregion + + // + //#region PresetColors ... + @Input() + presetColorsLable: XStandardType = + this.propertyProvider.presetColorsLable; + + @Input() + presetColors: XStandardType> = + this.propertyProvider.presetColors; + //#endregion + + // + //#region Represent Color Value ... + @Input() + representColorFormats: XStandardType< + XOneOrManyType + > = this.propertyProvider.representColorFormats; + + @Input() + outputFormat: XStandardType = + this.propertyProvider.outputFormat; + //#endregion + + // + //#region Show Parts Props ... + @Input() + showColorInput: XStandardType = this.propertyProvider.showColorInput; + + @Input() + showCopyToClipboard: XStandardType = + this.propertyProvider.showCopyToClipboard; + + @Input() + showSelectedColorPreview: XStandardType = + this.propertyProvider.showSelectedColorPreview; + + @Input() + showPaletteChannel: XStandardType = + this.propertyProvider.showPaletteChannel; + + @Input() + showHueChannel: XStandardType = this.propertyProvider.showHueChannel; + + @Input() + showAlphaChannel: XStandardType = + this.propertyProvider.showAlphaChannel; + + @Input() + showColorValue: XStandardType = this.propertyProvider.showColorValue; + + @Input() + showPresetColors: XStandardType = + this.propertyProvider.showPresetColors; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XColorPickerContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXColorPickerContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + //#region Colors ... + selectedColor: this.getValue( + container ? container.selectedColor : this.selectedColor + ), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + copyToClipboardIconColor: this.getValue( + container + ? container.copyToClipboardIconColor + : this.copyToClipboardIconColor + ), + colorInputIconColor: this.getValue( + container + ? container.colorInputIconColor + : this.colorInputIconColor + ), + clearColorIconColor: this.getValue( + container + ? container.clearColorIconColor + : this.clearColorIconColor + ), + colorTextColor: this.getValue( + container ? container.colorTextColor : this.colorTextColor + ), + presetColorsLableColor: this.getValue( + container + ? container.presetColorsLableColor + : this.presetColorsLableColor + ), + cursorColor: this.getValue( + container ? container.cursorColor : this.cursorColor + ), + //#endregion + + // + //#region Labels ... + colorInputIconLabel: this.getValue( + container + ? container.colorInputIconLabel + : this.colorInputIconLabel + ), + copyToClipboardIconLabel: this.getValue( + container + ? container.copyToClipboardIconLabel + : this.copyToClipboardIconLabel + ), + clearColorIconLabel: this.getValue( + container + ? container.clearColorIconLabel + : this.clearColorIconLabel + ), + //#endregion + + // + //#region PresetColors ... + presetColorsLable: this.getValue( + container ? container.presetColorsLable : this.presetColorsLable + ), + presetColors: this.getValue( + container ? container.presetColors : this.presetColors + ), + //#endregion + + // + //#region Represent Color Value ... + representColorFormats: this.getValue( + container + ? container.representColorFormats + : this.representColorFormats + ), + outputFormat: this.getValue( + container ? container.outputFormat : this.outputFormat + ), + //#endregion + + // + //#region Show Parts Props ... + showColorInput: this.getValue( + container ? container.showColorInput : this.showColorInput + ), + showCopyToClipboard: this.getValue( + container + ? container.showCopyToClipboard + : this.showCopyToClipboard + ), + showSelectedColorPreview: this.getValue( + container + ? container.showSelectedColorPreview + : this.showSelectedColorPreview + ), + showPaletteChannel: this.getValue( + container ? container.showPaletteChannel : this.showPaletteChannel + ), + showHueChannel: this.getValue( + container ? container.showHueChannel : this.showHueChannel + ), + showAlphaChannel: this.getValue( + container ? container.showAlphaChannel : this.showAlphaChannel + ), + showColorValue: this.getValue( + container ? container.showColorValue : this.showColorValue + ), + showPresetColors: this.getValue( + container ? container.showPresetColors : this.showPresetColors + ), + //#endregion + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + colorSelected: this.colorSelected, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\directives\x-color-text.directive.ts + +import { + Input, + Output, + Directive, + EventEmitter, + HostListener, +} from '@angular/core'; + +@Directive({ + selector: '[xColorText]', +}) +export class XColorTextDirective { + // + @Input() + text: any; + + @Input() + rg: number; + + // + @Output() + newValue = new EventEmitter(); + + // + @HostListener('input', ['$event.target.value']) + changeInput(value: string) { + // + if (this.rg === undefined) { + this.newValue.emit(value); + } else { + // + const numeric = parseFloat(value); + + // + if (!isNaN(numeric) && numeric >= 0 && numeric <= this.rg) { + this.newValue.emit({ v: numeric, rg: this.rg }); + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker-dialog\x-color-picker-dialog.component.html + + + + +
+ + + +
+ +
+
+ + + + +

+ {{ getTitle() | async }} +

+
+
+ + + +
+ +
+
+
+
+
+ + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker-dialog\x-color-picker-dialog.component.scss + +::ng-deep.fullscreen-dialog { + // + width: 80vw !important; + max-width: 80vw !important; + + // + height: 80vh !important; + max-height: 80vh !important; + + // + &.mobile { + // + width: 100vw !important; + max-width: 100vw !important; + + // + height: 100vh !important; + max-height: 100vh !important; + } + + // + .mat-dialog-container { + padding: 0 !important; + } +} + +x-icon { + --size: 24px; +} + +.toolbar-area { + margin: 8px; +} + +.toolbar-item { + width: auto; + height: 48px; +} + +.toolbar-button { + width: 32px; + margin: { + left: 4px; + right: 4px; + } +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker-dialog\x-color-picker-dialog.component.ts + +import { + Inject, + NgZone, + Renderer2, + Component, + ElementRef, + ChangeDetectorRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XColor, + hasChild, + isFunction, + XResourceIDs, + XDialogResult, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XColorPickerActionModel, + XColorPickerOutputFormat, +} from '../x-color-picker.typings'; +import { Subject, of } from 'rxjs'; +import { concatMap, map } from 'rxjs/operators'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XBasePage } from '../../classes/x-base-page'; +import { XManagerService } from 'x-framework-services'; +import { XIconNames } from '../../x-icon/x-icon.typings'; +import { XSlotName } from '../../x-slotter/x-slotter.typings'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { XColorPickerDialogConfig } from './x-color-picker-dialog.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from './../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from './../../config/x-framework-components.config'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-color-picker-dialog', + templateUrl: './x-color-picker-dialog.component.html', + styleUrls: ['./x-color-picker-dialog.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XColorPickerDialogComponent extends XBasePage { + // + //#region Props ... + // + selectIcon = XIconNames.checked; + backIcon = XIconNames.arrow_back; + SlotNames = Object.assign({}, XSlotName); + + // + isMobileUi$ = this.managerService.isMobileUi$; + isNotMobile$ = this.managerService.isNotMobileUi$; + + // + actionProvider = new Subject(); + + // + selectedColor: string; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + @Inject(MAT_DIALOG_DATA) public data: any, + public dialogRef: MatDialogRef + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + this.preventBack = true; + } + + async afterViewInit() { + super.afterViewInit(); + } + //#endregion + + // + //#region UI Providers ... + getData() { + return this.getValue(this.data as XColorPickerDialogConfig); + } + + hasData() { + return !isNullOrUndefined(this.getData()); + } + + canSelect() { + return !isNullOrEmptyString(this.selectedColor); + } + + getTitle() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config.title)) + ); + } + + /** + * retrieve selectedColor value ... + */ + getSelectedColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.selectedColor)) + ); + } + + // + //#region Colors ... + /** + * retrieve color value ... + */ + getColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.color)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Light)) + ); + } + + /** + * determines is Foureground color or not ... + */ + isForegroundColor() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.foregroundColor)) + ); + } + + /** + * retrieve cursorColor value ... + */ + getCursorColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.cursorColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Dark)) + ); + } + + /** + * retrieve colorTextColor value ... + */ + getColorTextColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.colorTextColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Dark)) + ); + } + + /** + * retrieve clearColorIconColor value ... + */ + getClearColorIconColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.clearColorIconColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Danger)) + ); + } + + /** + * retrieve colorInputIconColor value ... + */ + getColorInputIconColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.colorInputIconColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Secondary)) + ); + } + + /** + * retrieve presetColorsLableColor value ... + */ + getPresetColorsLableColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.presetColorsLableColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Dark)) + ); + } + + /** + * retrieve copyToClipboardIconColor value ... + */ + getCopyToClipboardIconColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.copyToClipboardIconColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Tertiary)) + ); + } + //#endregion + + // + //#region Labels ... + /** + * retrieve presetColorsLable value ... + */ + getPresetColorsLable() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config.presetColorsLable)), + map((value) => + !isNullOrEmptyString(value) ? value : XResourceIDs.preset_colors + ) + ); + } + + /** + * retrieve clearColorIconLabel value ... + */ + getClearColorIconLabel() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config.clearColorIconLabel)), + map((value) => (!isNullOrEmptyString(value) ? value : XResourceIDs.clear)) + ); + } + + /** + * retrieve colorInputIconLabel value ... + */ + getColorInputIconLabel() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config.colorInputIconLabel)), + map((value) => (!isNullOrEmptyString(value) ? value : XResourceIDs.input)) + ); + } + + /** + * retrieve copyToClipboardIconLabel value ... + */ + getCopyToClipboardIconLabel() { + return this.getData().pipe( + concatMap((config) => + this.getResourceValue(config.copyToClipboardIconLabel) + ), + map((value) => + !isNullOrEmptyString(value) ? value : XResourceIDs.copy_to_clipboard + ) + ); + } + //#endregion + + // + //#region Preset Colors ... + getPresetColors() { + return this.getData().pipe( + concatMap((config) => this.getArrayValue(config.presetColors)) + ); + } + //#endregion + + // + //#region Represent Format ... + /** + * retrieve representColorFormats value ... + */ + getRepresentColorFormats() { + return this.getData().pipe( + concatMap((config) => this.getArrayValue(config.representColorFormats)), + map((formats) => + hasChild(formats) ? formats : [XColorPickerOutputFormat.XHEX] + ) + ); + } + + /** + * retrieve outputFormat value ... + */ + getOutputFormat() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.outputFormat)), + map((format) => + !isNullOrEmptyString(format) ? format : XColorPickerOutputFormat.XHEX + ) + ); + } + //#endregion + + // + //#region Show Parts ... + /** + * determines showCopyToClipboard or not ... + */ + showCopyToClipboard() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showCopyToClipboard)) + ); + } + + /** + * determines showColorInput or not ... + */ + showColorInput() { + return this.getData().pipe( + concatMap((config) => + !isNullOrUndefined(config.showColorInput) + ? this.getOptionalValue(config.showColorInput) + : of(true) + ) + ); + } + + /** + * determines showSelectedColorPreview or not ... + */ + showSelectedColorPreview() { + return this.getData().pipe( + concatMap((config) => + !isNullOrUndefined(config.showSelectedColorPreview) + ? this.getOptionalValue(config.showSelectedColorPreview) + : of(true) + ) + ); + } + + /** + * determines showPaletteChannel or not ... + */ + showPaletteChannel() { + return this.getData().pipe( + concatMap((config) => + !isNullOrUndefined(config.showPaletteChannel) + ? this.getOptionalValue(config.showPaletteChannel) + : of(true) + ) + ); + } + + /** + * determines showHueChannel or not ... + */ + showHueChannel() { + return this.getData().pipe( + concatMap((config) => + !isNullOrUndefined(config.showHueChannel) + ? this.getOptionalValue(config.showHueChannel) + : of(true) + ) + ); + } + + /** + * determines showAlphaChannel or not ... + */ + showAlphaChannel() { + return this.getData().pipe( + concatMap((config) => + !isNullOrUndefined(config.showAlphaChannel) + ? this.getOptionalValue(config.showAlphaChannel) + : of(true) + ) + ); + } + + /** + * determines showColorValue or not ... + */ + showColorValue() { + return this.getData().pipe( + concatMap((config) => + !isNullOrUndefined(config.showColorValue) + ? this.getOptionalValue(config.showColorValue) + : of(true) + ) + ); + } + + /** + * determines showPresetColors or not ... + */ + showPresetColors() { + return this.getData().pipe( + concatMap((config) => + config.showPresetColors + ? this.getOptionalValue(config.showPresetColors) + : of(null) + ) + ); + } + //#endregion + + // + /** + * retrieve css classes ... + */ + getCssClass() { + return this.getData().pipe( + concatMap((config) => this.getArrayValue(config.cssClass)) + ); + } + + // + //#region + /** + * retireve cardColor value ... + */ + getCardColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.cardColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Light)) + ); + } + + /** + * determines is cardForegroundColor or not ... + */ + isCardForegroundColor() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.cardForegroundColor)) + ); + } + + /** + * retrieve cardTitle value ... + */ + getCardTitle() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config.cardTitle)) + ); + } + + /** + * retrieve cardSubTitle value ... + */ + getCardSubTitle() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config.cardSubTitle)) + ); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + async handleBack() { + this.dialogRef.close(); + } + + async handleColorSelected(event: string) { + // + this.selectedColor = event; + + // + const config = await this.getValueAsync(this.getData()); + if (!config || !config.colorSelected || !isFunction(config.colorSelected)) { + return; + } + + // + config.colorSelected.emit(event); + + // + this.detectChanges(); + } + + async handleSelect() { + // + if (!this.canSelect()) { + return; + } + + // + this.dialogRef.close({ + result: XDialogResult.Ok, + payload: this.selectedColor, + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker-dialog\x-color-picker-dialog.service.ts + +import { + XResourceIDs, + XBaseService, + XDialogResult, + isNullOrUndefined, +} from 'x-framework-core'; +import { + MatDialog, + MatDialogRef, + MatDialogConfig, +} from '@angular/material/dialog'; +import { + XColorPickerDialogResult, + XColorPickerDialogConfig, +} from './x-color-picker-dialog.typings'; +import { XManagerService } from 'x-framework-services'; +import { Injectable, Inject, Output, EventEmitter } from '@angular/core'; +import { XColorPickerDialogComponent } from './x-color-picker-dialog.component'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from './../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from './../../config/x-framework-components.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XColorPickerDialogService extends XBaseService { + // + //#region Props ... + private MOBILE_CLASS_NAME = 'mobile'; + private DIALOG_REF: MatDialogRef; + + // + isMobile$ = this.managerService.isMobileUi$; + + // tslint:disable-next-line:no-output-on-prefix + @Output() + onResult = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + private dialog: MatDialog, + private managerService: XManagerService + ) { + super(config); + } + //#endregion + + // + //#region Actions ... + /** + * Open Select Map Dialog ... + */ + async openDialog(config?: XColorPickerDialogConfig) { + // + // Prevent Double Bubble ... + if (this.DIALOG_REF) { + return; + } + + // + // Prepare Config ... + if (!config) { + config = {}; + } + + // + // Set Default Title ... + if (config && !config.title) { + config.title = XResourceIDs.color_picker_dialog_title; + } + + // + // Prepare Dialog Config ... + const data: MatDialogConfig = { + data: config, + }; + + // + data.panelClass = 'fullscreen-dialog'; + + // + // Show Cropper inside a Dialog ... + this.DIALOG_REF = this.dialog.open(XColorPickerDialogComponent, data); + this.DIALOG_REF.disableClose = true; + + // + // Attach Mobile Class ... + this.isMobile$.subscribe((isMobile) => { + // + if (isMobile) { + if (this.DIALOG_REF) { + this.DIALOG_REF.addPanelClass(this.MOBILE_CLASS_NAME); + } + } else { + if (this.DIALOG_REF) { + this.DIALOG_REF.removePanelClass(this.MOBILE_CLASS_NAME); + } + } + }); + + // + // Handle Backdrop ... + this.DIALOG_REF.backdropClick().subscribe(() => { + // + // Cancel Dialog ... + this.onResult.emit({ result: XDialogResult.Cancel }); + + // + this.closeDialog(); + }); + + // + // Handle Dialog Result ... + this.DIALOG_REF.afterClosed().subscribe( + (model: XColorPickerDialogResult) => { + // + if (isNullOrUndefined(model) || model.result !== XDialogResult.Ok) { + // + // Cancel Dialog ... + this.onResult.emit({ result: XDialogResult.Cancel }); + } else { + // + // Handle Results ... + this.onResult.emit(model); + } + + // + this.closeDialog(); + } + ); + } + + /** + * Close Select Map Dialog ... + */ + closeDialog() { + // + // Prevent Go Forward ... + if (!this.DIALOG_REF) { + return; + } + + // + // Close Dialog ... + this.DIALOG_REF.close(); + this.DIALOG_REF = null; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker-dialog\x-color-picker-dialog.typings.ts + +import { XDialogResult } from 'x-framework-core'; +import { IXColorPickerContainer } from '../x-color-picker.typings'; +import { XBaseDialogConfig } from '../../classes/i-x-base-dialog.container'; + +export interface XColorPickerDialogResult { + payload?: string; + result: XDialogResult; +} + +export interface XColorPickerDialogConfig + extends XBaseDialogConfig, + Partial {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-counter\x-counter.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + +

+ {{ displayMessage }} +

+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-counter\x-counter.component.scss + +.display-message-container { + margin: 2px; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-counter\x-counter.component.ts + +import { + XCounterTick, + XCounterType, + XCounterState, + XCounterAction, + XCounterActionModel, + XCounterTypeIdentifier, + XCounterContainerComponent, +} from './x-counter.typings'; +import { nameof } from 'x-framework-core'; +import { Subscription, interval, Subject } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + selector: 'x-counter', + templateUrl: './x-counter.component.html', + styleUrls: ['./x-counter.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XCounterComponent extends XCounterContainerComponent { + // + //#region Props ... + private StepCount: number; + private IsCounting: boolean; + private TYPE: XCounterTypeIdentifier; + private TimerSubscription: Subscription; + + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + displayMessage = ''; + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Handle Values ... + const isStopValueChanged = changeKeys.includes( + nameof('stopValue') + ); + const isStartValueChanged = changeKeys.includes( + nameof('startValue') + ); + const isCurrentValueChanged = changeKeys.includes( + nameof('currentValue') + ); + if (isStopValueChanged || isStartValueChanged || isCurrentValueChanged) { + this.setBoundaries(this.startValue, this.stopValue, this.currentValue); + } + //#endregion + + // + //#region Handle Type Changed ... + const isTypeChanged = changeKeys.includes( + nameof('type') + ); + if (isTypeChanged) { + await this.prepareType(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + async onInit() { + super.onInit(); + + // + await this.prepareType(); + + // + // Try to Show Start Value ... + this.updateDisplayMessage(); + + // + await this.handleAutoStart(); + } + + onDestroy() { + super.onDestroy(); + + // + if (this.TimerSubscription) { + this.TimerSubscription.unsubscribe(); + this.TimerSubscription = null; + } + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + //#endregion + + // + //#region UI Handlers ... + //#endregion + + // + //#region Actions ... + setBoundaries(start: number, stop: number, current: number) { + // + this.startValue = start; + this.stopValue = stop; + this.currentValue = current; + } + + async start() { + // + const isUpdated = await this.updateValues(); + + // + if (isUpdated && !this.TimerSubscription && this.getStepsCount() > 0) { + this.startCounter(); + } + } + + pause() { + // + if (this.TimerSubscription) { + this.IsCounting = false; + this.TimerSubscription.unsubscribe(); + this.TimerSubscription = null; + } + } + + cancel() { + this.currentValue = this.stopValue; + } + + getCounterState(): XCounterState { + // + if ( + this.TYPE === XCounterType.CountDown && + this.currentValue === this.startValue + ) { + return XCounterState.Start; + } + + // + if ( + this.TYPE === XCounterType.CountUp && + this.currentValue === this.stopValue + ) { + return XCounterState.Start; + } + + // + if (this.isLastStep()) { + return XCounterState.Stop; + } + + // + if ( + this.TYPE === XCounterType.CountDown && + this.currentValue > this.startValue + ) { + return XCounterState.Tick; + } + + // + if ( + this.TYPE === XCounterType.CountUp && + this.currentValue < this.startValue + ) { + return XCounterState.Tick; + } + + // + return XCounterState.Stop; + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XCounterAction.START: + await this.start(); + break; + + // + case XCounterAction.PAUSE: + this.pause(); + break; + + // + case XCounterAction.CANCEL: + this.cancel(); + break; + + // + case XCounterAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private startCounter() { + // + this.StepCount = this.getStepsCount(); + + // + if (!this.IsCounting && this.StepCount && this.StepCount > 0) { + // + this.IsCounting = true; + this.TimerSubscription = interval(this.delay).subscribe(() => { + // + this.updateDisplayMessage(); + + // + const eventModel = this.getTickModel(); + + // + if (eventModel.state === XCounterState.Start) { + this.counterStart.emit(eventModel); + } + + // + if (eventModel.state === XCounterState.Stop) { + this.counterStop.emit(eventModel); + } + + // + this.counterTtick.emit(eventModel); + + // + if (this.isLastStep()) { + this.pause(); + } else if (this.TYPE === XCounterType.CountDown) { + this.currentValue--; + } else if (this.TYPE === XCounterType.CountUp) { + this.currentValue++; + } + }); + } + } + + private getTickModel(): XCounterTick { + return { + type: this.TYPE, + start: this.startValue, + stop: this.stopValue, + current: this.currentValue, + delay: this.delay, + state: this.getCounterState(), + } as XCounterTick; + } + + private getStepsCount() { + // + let count = -1; + + // + switch (this.TYPE) { + case XCounterType.CountDown: + count = this.startValue - this.stopValue; + break; + + case XCounterType.CountUp: + count = this.stopValue - this.startValue; + break; + } + + // + if (isNaN(count)) { + count = -1; + } + + // + return count; + } + + private isLastStep() { + // + switch (this.TYPE) { + // + case XCounterType.CountDown: + return this.currentValue <= this.stopValue; + + // + default: + case XCounterType.CountUp: + return this.currentValue >= this.startValue; + } + } + + private updateValues() { + return new Promise((res) => { + // + switch (this.TYPE) { + case XCounterType.CountDown: + // + if (this.startValue < this.stopValue) { + res(false); + return; + } + + // + if (!this.currentValue) { + this.currentValue = this.startValue; + } + break; + + case XCounterType.CountUp: + if (this.startValue > this.stopValue) { + res(false); + return; + } + + // + if (!this.currentValue) { + this.currentValue = this.stopValue; + } + break; + } + + // + res(true); + }); + } + + private updateDisplayMessage() { + // + this.displayMessage = this.displayPattern( + this.stopValue, + this.startValue, + this.currentValue + ); + } + + // + private async prepareType() { + this.TYPE = await this.getValueAsync(this.type); + } + + // + private async handleAutoStart() { + // + const isAutoStart = await this.getValueAsync(this.autoStart); + if (!isAutoStart) { + return; + } + + // + await this.start(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-counter\x-counter.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XCounterComponent } from './x-counter.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XCounterComponent], + imports: [XCardModule, CommonModule, XBaseComponentModule], + exports: [XCardModule, CommonModule, XCounterComponent, XBaseComponentModule], +}) +export class XCounterModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-counter\x-counter.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XOneOrManyType, XStandardType } from 'x-framework-core'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XCounterType { + CountUp = 'count_up', + CountDown = 'count_down', +} + +export type XCounterTypeIdentifier = XCounterType | string; + +export enum XCounterState { + Start, + Tick, + Stop, +} + +export interface XCounterTick { + type: XCounterType; + start: number; + stop: number; + current: number; + delay: number; + state: XCounterState; +} + +export const defaultStringPatternMaker = ( + max: number, + min: number, + current: number +) => { + return `${current}`; +}; + +export enum XCounterAction { + START = 'start', + PAUSE = 'pause', + CANCEL = 'cancel', + + // + Refresh = 'refresh', +} + +export type XCounterActionIdentifier = XCounterAction | string; + +export interface XCounterActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXCounterContainer extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + type: XStandardType; + autoStart: XStandardType; + stopValue: number; + startValue: number; + currentValue: number; + delay: number; + + // + displayPattern: (min: number, max: number, current: number) => string; + + // + counterStart: EventEmitter; + counterTtick: EventEmitter; + counterStop: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XCounterContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly type: XStandardType = XCounterType.CountDown; + readonly autoStart: XStandardType = false; + readonly stopValue: number; + readonly startValue: number; + readonly currentValue: number; + readonly delay: number = 1000; + + // + readonly displayPattern: ( + min: number, + max: number, + current: number + ) => string = defaultStringPatternMaker; + + // + readonly counterStart: EventEmitter = + new EventEmitter(); + readonly counterTtick: EventEmitter = + new EventEmitter(); + readonly counterStop: EventEmitter = + new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XCounterContainerComponent + extends XComponentCardContainerComponent + implements IXCounterContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + type: XStandardType = this.propertyProvider.type; + + @Input() + autoStart: XStandardType = false; + + @Input() + stopValue: number = this.propertyProvider.stopValue; + + @Input() + startValue: number = this.propertyProvider.startValue; + + @Input() + currentValue: number = this.propertyProvider.currentValue; + + @Input() + delay = this.propertyProvider.delay; + + @Input() + displayPattern: (min: number, max: number, current: number) => string = + this.propertyProvider.displayPattern; + + // + @Output() + counterStart: EventEmitter = new EventEmitter(); + + @Output() + counterTtick: EventEmitter = new EventEmitter(); + + @Output() + counterStop: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XCounterContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXCounterContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + type: this.getValue(container ? container.type : this.type), + autoStart: this.getValue( + container ? container.autoStart : this.autoStart + ), + stopValue: this.getValue( + container ? container.stopValue : this.stopValue + ), + startValue: this.getValue( + container ? container.startValue : this.startValue + ), + currentValue: this.getValue( + container ? container.currentValue : this.currentValue + ), + delay: this.getValue(container ? container.delay : this.delay), + displayPattern: this.getValue( + container ? container.displayPattern : this.displayPattern + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + counterStart: this.counterStart, + counterTtick: this.counterTtick, + counterStop: this.counterStop, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-pager\x-data-pager.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + +
+ + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + {{ + preparedPaginatorOptions.pageInfoProvider( + managerService.currentLocale, + currentPage, + pageSize, + totalPages, + totalItems + ) + }} + + + + +
+ + +
+ + {{ applyLocale(pageSize) }} + +
+
+
+ + + + + + + + + + + + + +
+
+ + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-pager\x-data-pager.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-pager\x-data-pager.component.ts + +import { + nameof, + XLocale, + toNumber, + hasChild, + applyLocale, + XModalButtonRole, + XPickerColumnOption, + XBaseDataPagerQuery, +} from 'x-framework-core'; +import { + XDataPagerAction, + XDataPagerActionModel, + XDataPagerPresentType, + XDataPagerPaginatorOptions, + XDataPagerPaginatorPosition, + XDataPagerContainerComponent, +} from './x-data-pager.typings'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ViewChild } from '@angular/core'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import { ChangeDetectionStrategy } from '@angular/core'; +import { MatPaginator } from '@angular/material/paginator'; + +@Component({ + selector: 'x-data-pager', + templateUrl: './x-data-pager.component.html', + styleUrls: ['./x-data-pager.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XDataPagerComponent extends XDataPagerContainerComponent { + // + //#region Props ... + // + private MODAL: any; + private PAGE_SIZE: number; + private TOTAL_PAGES: number; + private TOTAL_ITEMS: number; + private CURRENT_PAGE: number; + private PAGINATOR: MatPaginator; + private PAGINATOR_PAGE_SUBSCRIPTION: Subscription; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private PAGINATOR_OPTIONS: XDataPagerPaginatorOptions; + + // + readonly DataPagerPresetTypes = Object.assign({}, XDataPagerPresentType); + readonly DataPagerPaginatorPositions = Object.assign( + {}, + XDataPagerPaginatorPosition + ); + + // + showPageNumberInput: boolean; + + // + @ViewChild(MatPaginator, { static: false }) + set paginator(v: MatPaginator) { + if (!v) { + return; + } + + // + this.PAGINATOR = v; + this.setPaginatorData(); + } + + get paginator() { + return this.PAGINATOR; + } + + get pageSize() { + return this.PAGE_SIZE; + } + + get currentPage() { + return this.CURRENT_PAGE; + } + + get totalPages() { + return this.TOTAL_PAGES; + } + + get totalItems() { + return this.TOTAL_ITEMS; + } + + get preparedPaginatorOptions() { + return this.PAGINATOR_OPTIONS; + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + await this.preparePaginatorOptions(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region PaginatorOptionChanged ... + const isPaginatorOptionsChanged = changeKeys.includes( + nameof('paginatorOptions') + ); + if (isPaginatorOptionsChanged) { + await this.preparePaginatorOptions(); + } + //#endregion + + // + //#region Query Changed ... + const isTaskChanged = changeKeys.includes( + nameof('task') + ); + const isQueryChanged = changeKeys.includes( + nameof('query') + ); + if (isQueryChanged || isTaskChanged) { + await this.handleQueryChanged(); + } + //#endregion + + // + //#region Result Changed ... + const isResultChanged = changeKeys.includes( + nameof('result') + ); + if (isResultChanged) { + await this.handleResultChanged(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Handlers ... + async handlePageChange(event: CustomEvent | any) { + // + const selectedPage = (event.detail.value as number) || undefined; + if (!selectedPage) { + return; + } + + // + await this.toPage(selectedPage); + } + + async handlePageSizeChange(event?: any) { + // + if (!this.preparedPaginatorOptions || !this.pageSize) { + return; + } + + // + const pageSizes = await this.getValueAsync( + this.preparedPaginatorOptions.pageSizes + ); + if (!hasChild(pageSizes)) { + return; + } + + // + const options: XPickerColumnOption[] = pageSizes.map((ps) => { + // + const opt: XPickerColumnOption = { + text: this.applyLocale(ps.toString()), + value: ps, + disabled: ps === this.pageSize, + }; + + // + return opt; + }); + + // + if (this.MODAL) { + await this.managerService.dialogService.pickerController.dismiss(); + this.MODAL = null; + } + + // + this.MODAL = await this.managerService.dialogService.presentPicker({ + buttons: [ + { + text: this.resourceProvider(this.ResourceIDs.accept), + role: XModalButtonRole.Selected, + handler: (value) => { + if (!value) { + return; + } + + // + const pageSize = value.pageSize.value; + this.changePageSize(pageSize); + }, + }, + { + text: this.resourceProvider(this.ResourceIDs.cancel), + role: XModalButtonRole.Cancel, + }, + ], + columns: [{ name: 'pageSize', options }], + }); + + // + await this.MODAL.onWillDismiss(); + this.MODAL = null; + } + //#endregion + + // + //#region Actions ... + // + //#region Pager Actions ... + async firstPage() { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + this.paginator.firstPage(); + } + + async lastPage() { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + this.paginator.lastPage(); + } + + async nextPage() { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + this.paginator.nextPage(); + } + + async previousPage() { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + this.paginator.previousPage(); + } + + async toPage(index: number) { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + index = index === 0 ? 1 : index; + + // + this.paginator.pageIndex = index - 1; + this.paginator.page.next({ + pageIndex: index - 1, + pageSize: this.pageSize, + length: this.totalItems, + }); + } + + async changePageSize(size: number) { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + if (!size || !this.paginator) { + return; + } + + // + this.paginator.pageSize = size; + this.paginator.page.next({ + pageIndex: 0, + pageSize: size, + length: this.totalItems, + }); + } + //#endregion + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XDataPagerAction.Refresh: + this.detectChanges(); + break; + + // + case XDataPagerAction.GoFirst: + await this.firstPage(); + break; + + // + case XDataPagerAction.GoLast: + await this.lastPage(); + break; + + // + case XDataPagerAction.GoNext: + await this.nextPage(); + break; + + // + case XDataPagerAction.GoPrev: + await this.previousPage(); + break; + + // + case XDataPagerAction.ToPage: + if (model.payload) { + await this.toPage(model.payload); + } + break; + + // + case XDataPagerAction.ChangePageSize: + if (model.payload) { + await this.changePageSize(model.payload); + } + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + private async handleQueryChanged() { + // + this.result = null; + + // + if (!this.query || !this.task) { + return; + } + + // + const mTask = this.task(this.query); + + // + this.taskReady.emit(mTask); + } + + private async handleResultChanged() { + // + if (!this.result) { + return; + } + + // + //#region Extract Required Options ... + this.CURRENT_PAGE = this.query.pageNumber; + this.PAGE_SIZE = this.query.rowsInPage; + this.TOTAL_PAGES = this.calculatePageCount( + this.result.count, + this.query.rowsInPage + ); + this.TOTAL_ITEMS = this.result.count; + //#endregion + + // + if (!this.preparedPaginatorOptions) { + await this.preparePaginatorOptions(); + } + + // + await this.setPaginatorData(); + } + + private async setPaginatorData() { + // + if (!this.PAGINATOR) { + return; + } + + // + this.PAGINATOR.pageSize = this.PAGE_SIZE; + this.PAGINATOR.pageIndex = this.CURRENT_PAGE - 1; + this.PAGINATOR.length = this.TOTAL_ITEMS; + + // + // Prepare Paginator Options ... + await this.preparePaginatorOptions(); + await this.registerPaginatorHandlers(); + } + + private async preparePaginatorOptions() { + // + if (!this.result) { + return; + } + + // + //#region Preparing Options ... + // + const defaultOptions = this.defaultPaginatorOptions(); + const options = await this.getValueAsync(this.paginatorOptions); + const preparedOptions: XDataPagerPaginatorOptions = { + ...defaultOptions, + ...options, + pageSize: this.query.rowsInPage, + }; + + // + if (options) { + // + if (!options.first) { + preparedOptions.first = undefined; + } else { + // + const first = options.first; + + // + if (first.disabled) { + const firstDisabled = await this.getValueAsync(first.disabled); + preparedOptions.first.disabled = + defaultOptions.first.disabled && firstDisabled; + } else { + preparedOptions.first.disabled = defaultOptions.first.disabled; + } + + // + if (!first.icon) { + preparedOptions.first.icon = defaultOptions.first.icon; + } + + // + if (!first.title) { + preparedOptions.first.title = defaultOptions.first.title; + } + } + + // + if (!options.last) { + preparedOptions.last = undefined; + } else { + // + const last = options.last; + + // + if (last.disabled) { + const lastDisabled = await this.getValueAsync(last.disabled); + preparedOptions.last.disabled = + defaultOptions.last.disabled && lastDisabled; + } else { + preparedOptions.last.disabled = defaultOptions.last.disabled; + } + + // + if (!last.icon) { + preparedOptions.last.icon = defaultOptions.last.icon; + } + + // + if (!last.title) { + preparedOptions.last.title = defaultOptions.last.title; + } + } + + // + if (!options.next) { + preparedOptions.next = undefined; + } else { + // + const next = options.next; + + // + if (next.disabled) { + const nextDisabled = await this.getValueAsync(next.disabled); + preparedOptions.next.disabled = + defaultOptions.next.disabled && nextDisabled; + } else { + preparedOptions.next.disabled = defaultOptions.next.disabled; + } + + // + if (!next.icon) { + preparedOptions.next.icon = defaultOptions.next.icon; + } + + // + if (!next.title) { + preparedOptions.next.title = defaultOptions.next.title; + } + } + + // + if (!options.prev) { + preparedOptions.prev = undefined; + } else { + // + const prev = options.prev; + + // + if (prev.disabled) { + const prevDisabled = await this.getValueAsync(prev.disabled); + preparedOptions.prev.disabled = + defaultOptions.prev.disabled && prevDisabled; + } else { + preparedOptions.prev.disabled = defaultOptions.prev.disabled; + } + + // + if (!prev.icon) { + preparedOptions.prev.icon = defaultOptions.prev.icon; + } + + // + if (!prev.title) { + preparedOptions.prev.title = defaultOptions.prev.title; + } + } + } + + // + this.PAGINATOR_OPTIONS = preparedOptions; + + // + this.detectChanges(); + //#endregion + } + + private async registerPaginatorHandlers() { + // + //#region PageHandler Actions ... + // + if (!this.PAGINATOR) { + return; + } + + // + // Unsubscribe last ... + if (this.PAGINATOR_PAGE_SUBSCRIPTION) { + this.PAGINATOR_PAGE_SUBSCRIPTION.unsubscribe(); + this.PAGINATOR_PAGE_SUBSCRIPTION = null; + } + + // + // Subscribe New ... + this.PAGINATOR_PAGE_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.paginator.page.asObservable() + ).subscribe(async (res) => { + // + // Prepare New Query ... + const query: XBaseDataPagerQuery = { + ...this.query, + // + pageNumber: res.pageIndex + 1, + rowsInPage: res.pageSize, + }; + + // + // Notify Query Changed ... + this.queryChange.emit(query); + }); + //#endregion + } + + private defaultPaginatorOptions() { + // + const options: XDataPagerPaginatorOptions = { + pageSizes: this.config.pageSizes, + pageSize: this.config.defaultPageSize, + first: { + title: 'first', + icon: XIconNames.first, + disabled: !this.paginator || !this.paginator.hasPreviousPage(), + }, + last: { + title: 'last', + icon: XIconNames.last, + disabled: !this.paginator || !this.paginator.hasNextPage(), + }, + prev: { + title: 'previous', + icon: XIconNames.previous, + disabled: !this.paginator || !this.paginator.hasPreviousPage(), + }, + next: { + title: 'next', + icon: XIconNames.next, + disabled: !this.paginator || !this.paginator.hasNextPage(), + }, + pageInfoProvider: ( + locale: XLocale, + currentPage: number, + pageSize: number, + totalPages: number, + totalItems: number + ) => { + // + const info = `${currentPage}/${totalPages}`; + return applyLocale(info, locale); + }, + }; + + // + return options; + } + + private calculatePageCount(itemsCount: number, pageSize: number) { + // + // Data Correction ... + pageSize = toNumber(pageSize); + itemsCount = toNumber(itemsCount); + + // + // Result ... + return Math.floor((itemsCount + (pageSize - 1)) / pageSize); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-pager\x-data-pager.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XEmptyModule } from '../x-empty/x-empty.module'; +import { XDataPagerComponent } from './x-data-pager.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XDataPagerComponent], + imports: [ + XCardModule, + XIconModule, + XEmptyModule, + CommonModule, + XBaseComponentModule, + ], + exports: [ + XIconModule, + XCardModule, + XEmptyModule, + CommonModule, + XDataPagerComponent, + XBaseComponentModule, + ], +}) +export class XDataPagerModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-pager\x-data-pager.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XBaseDataPagerQuery, + XColorWithBrightness, + XBaseDataPagerQueryResult, +} from 'x-framework-core'; +import { + XCardEmptyContainer, + XCardEmptyContainerComponent, +} from '../classes/i-x-card-empty.container'; +import { map, concatMap } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XTablePaginatorOptions } from '../x-table/x-table.typings'; +import { IXCardEmptyContainer } from '../classes/i-x-card-empty.container'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XDataPagerPaginatorPosition { + Above = 'above', + Below = 'below', +} + +export type XDataPagerPaginatorPositionIdentifier = + | XDataPagerPaginatorPosition + | string; + +// tslint:disable-next-line:no-empty-interface +export interface XDataPagerPaginatorOptions extends XTablePaginatorOptions {} + +export enum XDataPagerPresentType { + Item = 'item', + Page = 'page', +} + +export type XDataPagerPresentTypeIdentifier = XDataPagerPresentType | string; + +export enum XDataPagerAction { + GoFirst = 'go_first', + GoLast = 'go_last', + GoNext = 'go_next', + GoPrev = 'go_prev', + ToPage = 'to_page', + ChangePageSize = 'change_page_size', + Refresh = 'refresh', +} + +export type XDataPagerActionIdentifier = XDataPagerAction | string; + +export interface XDataPagerActionModel + extends XBaseActionModel {} + +// +//#region Inside Card/Empty Container ... +export interface IXDataPagerContainer extends IXCardEmptyContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + pageChanged: EventEmitter; + type: XStandardType; + itemTemplate: TemplateRef; + + // + result: XBaseDataPagerQueryResult; + task: ( + query: XBaseDataPagerQuery + ) => Observable>; + taskReady: EventEmitter>>; + query: XBaseDataPagerQuery; + queryChange: EventEmitter; + + // + showPaginator: XStandardType; + paginatorPosition: XStandardType; + paginatorColor: XStandardType; + paginatorOptions: XStandardType; + + // + emptyListTemplate: TemplateRef; +} + +@Injectable({ + providedIn: 'root', +}) +export class XDataPagerContainer + extends XCardEmptyContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider = new Subject(); + readonly cssClass: XStandardType>; + + // + readonly pageChanged: EventEmitter = new EventEmitter(); + readonly type: XStandardType = + XDataPagerPresentType.Item; + readonly itemTemplate: TemplateRef; + + // + readonly result: XBaseDataPagerQueryResult; + readonly task: ( + query: XBaseDataPagerQuery + ) => Observable>; + readonly taskReady: EventEmitter>> = + new EventEmitter>>(); + readonly query: XBaseDataPagerQuery; + readonly queryChange: EventEmitter = + new EventEmitter(); + + // + readonly showPaginator: XStandardType; + readonly paginatorPosition: XStandardType = + XDataPagerPaginatorPosition.Below; + readonly paginatorColor: XStandardType = + XColorWithBrightness.Light; + readonly paginatorOptions: XStandardType; + + // + readonly emptyListTemplate: TemplateRef; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XDataPagerContainerComponent + extends XCardEmptyContainerComponent + implements IXDataPagerContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Output() + pageChanged: EventEmitter = new EventEmitter(); + + @Input() + type: XStandardType = + this.propertyProvider.type; + + @Input() + itemTemplate: TemplateRef = this.propertyProvider.itemTemplate; + + // + @Input() + result: XBaseDataPagerQueryResult = this.propertyProvider.result; + + @Input() + task: ( + query: XBaseDataPagerQuery + ) => Observable> = this.propertyProvider.task; + + @Output() + taskReady: EventEmitter>> = + new EventEmitter(); + + @Input() + query: XBaseDataPagerQuery = this.propertyProvider.query; + + @Output() + queryChange: EventEmitter = new EventEmitter(); + + // + @Input() + showPaginator: XStandardType = this.propertyProvider.showPaginator; + + @Input() + paginatorPosition: XStandardType = + this.propertyProvider.paginatorPosition; + + @Input() + paginatorColor: XStandardType = + this.propertyProvider.paginatorColor; + + @Input() + paginatorOptions: XStandardType = + this.propertyProvider.paginatorOptions; + + // + @Input() + emptyListTemplate: TemplateRef = this.propertyProvider.emptyListTemplate; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XDataPagerContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXDataPagerContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + type: this.getValue(container ? container.type : this.type), + itemTemplate: this.getValue( + container ? container.itemTemplate : this.itemTemplate + ), + result: this.getValue(container ? container.result : this.result), + task: this.getValue(container ? container.task : this.task), + query: this.getValue(container ? container.query : this.query), + showPaginator: this.getValue( + container ? container.showPaginator : this.showPaginator + ), + paginatorPosition: this.getValue( + container ? container.paginatorPosition : this.paginatorPosition + ), + paginatorColor: this.getValue( + container ? container.paginatorColor : this.paginatorColor + ), + paginatorOptions: this.getValue( + container ? container.paginatorOptions : this.paginatorOptions + ), + + // + emptyListTemplate: this.getValue( + container ? container.emptyListTemplate : this.emptyListTemplate + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + pageChanged: this.pageChanged, + taskReady: this.taskReady, + queryChange: this.queryChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-scroller\x-data-scroller.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + +
+ + + +
+
+ + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-scroller\x-data-scroller.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-scroller\x-data-scroller.component.ts + +import { + XDataScrollerAction, + XDataScrollerActionModel, + XDataScrollerContainerComponent, +} from './x-data-scroller.typings'; +import { Component } from '@angular/core'; +import { of, Subject, Subscription } from 'rxjs'; +import { ChangeDetectionStrategy } from '@angular/core'; +import { nameof, hasChild, XBaseDataPagerQuery } from 'x-framework-core'; + +@Component({ + selector: 'x-data-scroller', + templateUrl: './x-data-scroller.component.html', + styleUrls: ['./x-data-scroller.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XDataScrollerComponent extends XDataScrollerContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + canNext: boolean = true; + items: any[] = []; + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Query Changed ... + const isTaskChanged = changeKeys.includes( + nameof('task') + ); + const isQueryChanged = changeKeys.includes( + nameof('query') + ); + if (isQueryChanged || isTaskChanged) { + await this.handleQueryChanged(); + } + //#endregion + + // + //#region Result Changed ... + const isResultChanged = changeKeys.includes( + nameof('result') + ); + if (isResultChanged) { + await this.handleResultChanged(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Handlers ... + //#endregion + + // + //#region Actions ... + async refreshItems() { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + this.items = []; + this.result = null; + this.canNext = true; + this.notifyCanNext(); + + // + const query: XBaseDataPagerQuery = { + pageNumber: 1, + rowsInPage: this.query.rowsInPage, + }; + + // + // Notify Query Changed ... + this.queryChange.emit(query); + } + + async loadMore() { + // + if (!this.canNext || this.uiDisabled || this.loading) { + this.notifyCanNext(); + return; + } + + // + // Prepare New Query ... + const query: XBaseDataPagerQuery = { + ...this.query, + // + pageNumber: this.query.pageNumber + 1, + rowsInPage: this.query.rowsInPage, + }; + + // + // Notify Query Changed ... + this.queryChange.emit(query); + } + + async clearItems() { + // + this.items = []; + this.result = null; + this.canNext = true; + this.notifyCanNext(); + + // + this.detectChanges(); + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.notifyCanNext(); + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XDataScrollerAction.Refresh: + this.detectChanges(); + break; + + // + case XDataScrollerAction.LoadMore: + await this.loadMore(); + break; + + // + case XDataScrollerAction.RefreshItems: + await this.refreshItems(); + break; + + // + case XDataScrollerAction.ClearItems: + await this.clearItems(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + private async handleQueryChanged() { + // + this.result = null; + + // + if (!this.query || !this.task) { + return; + } + + // + const mTask = this.task(this.query); + + // + this.taskReady.emit(mTask); + } + + private async handleResultChanged() { + // + if (!this.result || !hasChild(this.result.items)) { + return; + } + + // + of(...this.result.items).subscribe({ + next: item => { + setTimeout(() => { + // + this.items.push(item); + this.detectChanges(); + }, 100); + }, + error: (err) => {}, + complete: () => { + this.canNext = this.items.length < this.result.count; + this.notifyCanNext(); + } + }) + } + + private notifyCanNext() { + // + if (!this.actionProvider) { + return; + } + + // + this.actionProvider.next({ + action: XDataScrollerAction.CanNext, + payload: this.canNext, + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-scroller\x-data-scroller.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XEmptyModule } from '../x-empty/x-empty.module'; +import { XDataScrollerComponent } from './x-data-scroller.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XDataScrollerComponent], + imports: [XCardModule, CommonModule, XEmptyModule, XBaseComponentModule], + exports: [ + XCardModule, + CommonModule, + XEmptyModule, + XBaseComponentModule, + XDataScrollerComponent, + ], +}) +export class XDataScrollerModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-scroller\x-data-scroller.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XBaseDataPagerQuery, + XBaseDataPagerQueryResult, +} from 'x-framework-core'; +import { + IXCardEmptyContainer, + XCardEmptyContainer, + XCardEmptyContainerComponent, +} from '../classes/i-x-card-empty.container'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XDataScrollerAction { + Refresh = 'refresh', + RefreshItems = 'refresh_items', + LoadMore = 'load_more', + ClearItems = 'clear_items', + CanNext = 'can_next', +} + +export type XDataScrollerActionIdentifier = XDataScrollerAction | string; + +export interface XDataScrollerActionModel + extends XBaseActionModel {} + +// +//#region Inside Card/Empty Container ... +export interface IXDataScrollerContainer extends IXCardEmptyContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + result: XBaseDataPagerQueryResult; + task: (query: XBaseDataPagerQuery) => Observable; + taskReady: EventEmitter>; + query: XBaseDataPagerQuery; + queryChange: EventEmitter; + + // + emptyListTemplate: TemplateRef; +} + +@Injectable({ + providedIn: 'root', +}) +export class XDataScrollerContainer + extends XCardEmptyContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider = new Subject(); + readonly cssClass: XStandardType>; + + // + readonly result: XBaseDataPagerQueryResult; + readonly task: (query: XBaseDataPagerQuery) => Observable; + readonly taskReady = new EventEmitter>(); + readonly query: XBaseDataPagerQuery; + readonly queryChange = new EventEmitter(); + + // + readonly emptyListTemplate: TemplateRef; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XDataScrollerContainerComponent + extends XCardEmptyContainerComponent + implements IXDataScrollerContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + result: XBaseDataPagerQueryResult = this.propertyProvider.result; + + @Input() + task: (query: XBaseDataPagerQuery) => Observable = + this.propertyProvider.task; + + @Output() + taskReady: EventEmitter> = new EventEmitter(); + + @Input() + query: XBaseDataPagerQuery = this.propertyProvider.query; + + @Output() + queryChange: EventEmitter = new EventEmitter(); + + // + @Input() + emptyListTemplate: TemplateRef; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XDataScrollerContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXDataScrollerContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + result: this.getValue(container ? container.result : this.result), + task: this.getValue(container ? container.task : this.task), + query: this.getValue(container ? container.query : this.query), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + emptyListTemplate: container + ? container.emptyListTemplate + : this.emptyListTemplate, + + // + taskReady: this.taskReady, + queryChange: this.queryChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + + +
+ + +
+ + + +
+ +
+ + {{ getTitle(key) | async }} + +
+ + + +
+ + + + {{ getContent(key, preparedModel[key]) | async }} + + + + + + + + + + + +
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+
+ + + +
+ + + + + +
+ {{ getTitle(key) | async }} +
+ + +
+ + +
+
+
+
+
+
+
+ + + + + + + + + + + + + + + {{ value }} + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list.component.scss + +:host ::ng-deep .x-description-list { + &.isLegacy { + .x-card { + .x-description-list-title { + padding: { + left: 0; + right: 0; + } + } + + .x-description-list-description:not(.full-width) { + padding: { + left: 0; + right: 0; + } + } + } + + .x-description-list-title { + padding: { + left: 4px; + right: 4px; + } + } + + .x-description-list-description:not(.full-width) { + padding: { + left: 4px; + right: 4px; + } + } + + dl { + margin: { + top: 0; + bottom: 0; + } + } + + dl.inline dd { + display: inline; + margin: 0; + } + + dl.inline dd::after { + white-space: pre; + content: "\a"; + } + + dl.inline dt { + display: inline-block; + min-width: 100px; + } + + dt { + margin-top: 8px; + margin-bottom: 8px; + } + + dt::after { + content: ": "; + } + + dd { + text-align: center; + + // + &.full-width { + margin-inline-end: 0 !important; + margin-inline-start: 0 !important; + } + } + } + + &.isNotLegacy { + dd { + width: 100%; + display: inline; + margin-inline-start: 8px; + } + + .x-description-list-item-container { + width: 100%; + } + + .x-title-span::after { + content: ": "; + } + + x-slotter { + width: 100%; + display: inline-block; + } + + .x-title-span, + .x-decription-span { + max-width: calc(90%); + } + + .x-slotter { + .x-start-top-slot { + flex: 1 1 auto !important; + } + + .x-end-bottom-slot { + flex: 1 1 0% !important; + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list.component.ts + +import { + Component, + QueryList, + ContentChildren, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + tap, + map, + filter, + mergeMap, + concatMap, + switchMap, +} from 'rxjs/operators'; +import { + keys, + nameof, + isDate, + isArray, + toArray, + isObject, + isNumber, + isString, + toXString, + isFunction, + isSameObject, + XModelProperty, + XOneOrManyType, + toNormalString, + getPropertyValue, + setPropertyValue, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XDescriptionListAction, + XDescriptionListActionModel, + XDescriptionListContainerComponent, +} from './x-description-list.typings'; +import { XSlotLayout, XSlotName } from '../x-slotter/x-slotter.typings'; +import { of, from, Subject, forkJoin, Observable, Subscription } from 'rxjs'; +import { XDescriptionListItemComponent } from './x-description-list-item/x-description-list-item.component'; + +@Component({ + selector: 'x-description-list', + templateUrl: './x-description-list.component.html', + styleUrls: ['./x-description-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XDescriptionListComponent< + T = any +> extends XDescriptionListContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private keyPrefix = ''; + private MODEL: T; + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + + // + get prefix() { + return this.keyPrefix; + } + + get preparedModel() { + return this.MODEL; + } + + // + @ContentChildren(XDescriptionListItemComponent) + items: QueryList; + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.prepareModel(); + } + + async afterViewInit() { + super.afterViewInit(); + + // + // prepare KeyPrefix ... + const keyPrefix = this.element.nativeElement + ? (this.element.nativeElement as HTMLElement).getAttribute('keyPrefix') + : ''; + if (!isNullOrEmptyString(keyPrefix)) { + this.keyPrefix += `${keyPrefix}`; + } + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Model Changed ... + const isModelChanged = changeKeys.includes( + nameof('model') + ); + + // + if (isModelChanged) { + await this.prepareModel(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + keys(model: T) { + // + let modelKeys = keys(model); + + // + if (!this.hasChild(this.sortOrderModelKeys)) { + return modelKeys; + } + + // + const sortOrderArray = toArray(this.sortOrderModelKeys) as string[]; + const result: string[] = [ + ...sortOrderArray.filter((k) => modelKeys.includes(k)), + ...modelKeys.filter((k) => !sortOrderArray.includes(k)), + ]; + + // + return result; + } + + canShowKey(key: string) { + // + return forkJoin({ + key: this.getValue(key), + model: this.getValue(this.model), + keyPrefix: this.getValue(this.keyPrefix), + hiddenModelKeys: this.getArrayValue(this.hiddenModelKeys), + hideOptionalValues: this.getValue(this.hideOptionalValues), + availableModelKeys: this.getArrayValue(this.availableModelKeys), + }).pipe( + map((provider) => { + // + const value = provider.model[provider.key]; + + // + const modelHasValue = + !isNullOrUndefined(provider.model) && + !isNullOrUndefined(value) && + !isNullOrEmptyString(toXString(provider.model[provider.key])); + const hasChildHiddens = this.hasChild(provider.hiddenModelKeys); + const hasChildAvailables = this.hasChild(provider.availableModelKeys); + + // + if (!hasChildAvailables && !hasChildHiddens) { + // + if (!modelHasValue && provider.hideOptionalValues) { + return false; + } + + // + return true; + } + + // + const isInHiddens = provider.hiddenModelKeys.includes( + provider.key as any + ); + const isInAvailables = provider.availableModelKeys.includes( + provider.key as any + ); + + // + if ( + (!hasChildAvailables && !isInHiddens) || + (isInAvailables && !hasChildHiddens) || + (isInAvailables && !isInHiddens) || + (!isInAvailables && + !isInHiddens && + isNumber(provider.key) && + !isNullOrEmptyString(provider.keyPrefix)) + ) { + // + if (!modelHasValue && provider.hideOptionalValues) { + return false; + } + + // + return true; + } + + // + return false; + }) + ); + } + + canShowLabelKey(key: string) { + return this.getArrayValue(this.hiddenLabelModelKeys).pipe( + map((hiddenLabelsModelKeys) => { + // + const hasChildHiddenLabels = this.hasChild(hiddenLabelsModelKeys); + + // + if (!hasChildHiddenLabels) { + return true; + } + + // + const isInHiddens = hiddenLabelsModelKeys.includes(key as any); + return hasChildHiddenLabels && !isInHiddens; + }) + ); + } + + getTitle(key: string): Observable { + return this.getValue(this.modelKeyProvider).pipe( + concatMap((provider) => { + return isNullOrUndefined(provider) || !isFunction(provider) + ? this.getValue(key) + : this.getValue(provider(key as any)); + }) + ); + } + + hasContentItem(key: string) { + return of(this.items).pipe( + concatMap((items) => { + return isNullOrUndefined(items) + ? of(false) + : this.getArrayValue(items.toArray()).pipe( + switchMap((items) => + from(items).pipe( + mergeMap((item) => + this.getValue(item.name).pipe( + map((name) => ({ name, item })) + ) + ), + filter( + (itemData) => + toNormalString(itemData.name) === toNormalString(key) + ), + map((dataObject) => !isNullOrUndefined(dataObject)) + ) + ) + ); + }) + ); + } + + getContentItem( + key: string, + value: any + ): Observable { + return this.hasContentItem(key).pipe( + concatMap((isExists) => { + return !isExists + ? of(null) + : of(this.items).pipe( + concatMap((items) => { + return isNullOrUndefined(items) + ? of(null) + : this.getArrayValue(items.toArray()).pipe( + switchMap((items) => + from(items).pipe( + mergeMap((item) => + this.getValue(item.name).pipe( + map((name) => ({ name, item })) + ) + ), + filter( + (itemData) => + toNormalString(itemData.name) === + toNormalString(key) + ), + tap((dataObject) => (dataObject.item.value = value)), + map((dataObject) => dataObject.item) + ) + ) + ); + }) + ); + }) + ); + } + + getContent(key: string, value: any): Observable { + return this.getValue(this.modelValueProvider).pipe( + concatMap((provider) => { + return isNullOrUndefined(provider) || !isFunction(provider) + ? this.getValue(value) + : this.getValue(provider(key as any, value)); + }) + ); + } + + canDescribe(key: string, value: any) { + return of(value).pipe( + map( + (eVal) => + isDate(eVal) || + isFunction(eVal) || + (!isArray(eVal) && !isObject(eVal)) + ) + ); + } + + getDescriptionTitleClasses(key: string) { + return [`x-description-${key}-title`]; + } + + getDescriptionContentClasses(key: string) { + return [`x-description-${key}-title`]; + } + + // + //#region Neste ... + getKeyPrefix(key: string) { + // + const result = isNullOrEmptyString(this.keyPrefix) + ? `${key}` + : `${this.keyPrefix}.${key}`; + + // + return result; + } + + getHiddenModelKeys(key: string) { + return this.prepareNestedCollection(key, this.hiddenModelKeys); + } + + getSortOrderModelKeys(key: string) { + return this.prepareNestedCollection(key, this.sortOrderModelKeys); + } + + getAvailableModelKeys(key: string) { + return this.prepareNestedCollection(key, this.availableModelKeys); + } + + getHiddenLabelModelKeys(key: string) { + return this.prepareNestedCollection(key, this.hiddenLabelModelKeys); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + //#endregion + + // + //#region Actions ... + async setModelProperty(property: XModelProperty) { + // + if (!property || isNullOrEmptyString(property.key)) { + return; + } + + // + const model = this.MODEL; + const nodeValue = getPropertyValue(model, property.prefix); + const modelKeys = isNullOrUndefined(nodeValue) ? [] : keys(nodeValue); + if (!this.hasChild(modelKeys)) { + return; + } + + // + const isExists = modelKeys.includes(property.key as string); + if (!isExists) { + return; + } + + // + // normalize value ... + const normalValue = isString(property.value) + ? !isNullOrEmptyString(property.value) && + !isNullOrUndefined(property.value) + ? property.value.toString().trim() + : undefined + : isNullOrUndefined(property.value) + ? undefined + : property.value; + + // + if (nodeValue[property.key] === normalValue) { + return; + } + + // + const setterPropertyPath = isNullOrEmptyString(property.prefix) + ? `${property.key as string}` + : `${property.prefix}.${property.key as string}`; + + // + setPropertyValue(model, setterPropertyPath, normalValue); + + // + this.MODEL = { + ...model, + }; + this.detectChanges(); + this.onModelChanged.emit(model); + } + + async getModelProperty(key: keyof T | string) { + // + if (isNullOrEmptyString(key)) { + return null; + } + + // + const model = await this.getValueAsync(this.model); + const modelKeys = await this.getValueAsync(this.keys(model)); + if (!this.hasChild(modelKeys)) { + return null; + } + + // + const isExists = modelKeys.includes(key); + if (!isExists) { + return null; + } + + // + const result = model[key as any]; + return result; + } + + async getModelViewProperty(key: keyof T | string) { + // + if (isNullOrEmptyString(key)) { + return null; + } + + // + const model = await this.getValueAsync(this.model); + const modelKeys = await this.getValueAsync(this.keys(model)); + if (!this.hasChild(modelKeys)) { + return null; + } + + // + const isExists = modelKeys.includes(key); + if (!isExists) { + return null; + } + + // + const result = await this.getValueAsync( + this.getContent(key, model[key as any]) + ); + return result; + } + + async setModel(model: T) { + // + if (!model || isSameObject(model as any, this.MODEL as any)) { + return; + } + + // + this.model = { + ...model, + }; + this.MODEL = { + ...model, + }; + + // + await this.prepareModel(); + this.onModelChanged.emit(model); + } + + async getModel() { + // + const result = await this.getValueAsync(this.model); + return result; + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + let modelProperty: XModelProperty = null; + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XDescriptionListAction.Refresh: + this.detectChanges(); + break; + + // + case XDescriptionListAction.ClearProperty: + // + modelProperty = model.payload as XModelProperty; + if ( + !model.payload || + isNullOrUndefined(model) || + isNullOrUndefined(model.payload) || + isNullOrEmptyString(model.action) + ) { + return; + } + + // + modelProperty.value = null; + + // + await this.setModelProperty(modelProperty); + break; + + // + case XDescriptionListAction.SetProperty: + // + modelProperty = model.payload as XModelProperty; + if ( + !model.payload || + isNullOrUndefined(model) || + isNullOrUndefined(model.payload) || + isNullOrEmptyString(model.action) + ) { + return; + } + + // + await this.setModelProperty(modelProperty); + break; + + // + case XDescriptionListAction.SetModel: + // + const resetModel = model.payload as T; + if (!model || !model.payload || !resetModel) { + return; + } + + // + await this.setModel(resetModel); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async prepareModel() { + // + this.MODEL = null; + this.detectChanges(); + + // + if (!this.model) { + return; + } + + // + this.MODEL = await this.getValueAsync(this.model); + this.detectChanges(); + } + + private prepareNestedCollection( + key: string, + collection: XOneOrManyType + ) { + // + return forkJoin({ + key: this.getValue(key), + keyPrefix: this.getValue(this.getKeyPrefix(key)), + model: this.getValue(this.preparedModel[key]), + collection: this.getArrayValue(collection), + }).pipe( + map((provider) => { + // + const allAvailableCollectionItems = provider.collection; + + // + const keySelector = isNullOrUndefined(provider.keyPrefix) + ? `${provider.key}.` + : `${provider.keyPrefix}.`; + + // + const canFilter = + !isNumber(provider.key) && + !provider.keyPrefix.includes(`${provider.key}.`); + const result = canFilter + ? allAvailableCollectionItems + .filter((k) => k.includes(`${provider.key}.`)) + .map((k) => k.replace(`${provider.key}.`, '')) + : allAvailableCollectionItems; + + // + return result; + }) + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XDescriptionListComponent } from './x-description-list.component'; +import { XDescriptionListItemComponent } from './x-description-list-item/x-description-list-item.component'; + +@NgModule({ + declarations: [XDescriptionListComponent, XDescriptionListItemComponent], + imports: [XCardModule, CommonModule, XSlotterModule, XBaseComponentModule], + exports: [ + XCardModule, + CommonModule, + XSlotterModule, + XBaseComponentModule, + XDescriptionListComponent, + XDescriptionListItemComponent, + ], +}) +export class XDescriptionListModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XModelFieldsType, + XModelKeyProvider, + XModelValueProvider, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XDescriptionListAction { + // + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', + + // + ClearProperty = 'clear_property', + SetProperty = 'set_property', + + // + SetModel = 'set_model', + + // + Refresh = 'refresh', +} + +export type XDescriptionListActionIdentifier = XDescriptionListAction | string; + +export interface XDescriptionListActionModel + extends XBaseActionModel {} + +// +//#region Inside Card Container ... +export interface IXDescriptionListContainer + extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + isLegacy: XStandardType; + showLabels: XStandardType; + color: XStandardType; + foregroundColor: XStandardType; + labelColor: XStandardType; + valueColor: XStandardType; + propertyActionTemplate: TemplateRef; + onModelChanged: EventEmitter; + + // + model: XStandardType; + + // + //#region Model Providers ... + hideOptionalValues: XStandardType; + availableModelKeys: XModelFieldsType; + hiddenModelKeys: XModelFieldsType; + hiddenLabelModelKeys: XModelFieldsType; + sortOrderModelKeys: XModelFieldsType; + modelKeyProvider: XModelKeyProvider; + modelValueProvider: XModelValueProvider; + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +export class XDescriptionListContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly isLegacy: XStandardType = true; + readonly showLabels: XStandardType = false; + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + readonly labelColor: XStandardType = + XColorWithBrightness.TertiaryShade; + readonly valueColor: XStandardType = + XColorWithBrightness.Dark; + readonly propertyActionTemplate: TemplateRef; + + // + readonly onModelChanged: EventEmitter; + + // + readonly model: XStandardType; + + // + //#region Model Providers ... + readonly hideOptionalValues: XStandardType; + readonly availableModelKeys: XModelFieldsType; + readonly hiddenModelKeys: XModelFieldsType; + readonly hiddenLabelModelKeys: XModelFieldsType; + readonly sortOrderModelKeys: XModelFieldsType; + readonly modelKeyProvider: XModelKeyProvider; + readonly modelValueProvider: XModelValueProvider; + //#endregion + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XDescriptionListContainerComponent + extends XComponentCardContainerComponent + implements IXDescriptionListContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + isLegacy: XStandardType = this.propertyProvider.isLegacy; + + @Input() + showLabels: XStandardType = this.propertyProvider.showLabels; + + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + @Input() + labelColor: XStandardType = + this.propertyProvider.labelColor; + + @Input() + valueColor: XStandardType = + this.propertyProvider.valueColor; + + @Input() + propertyActionTemplate: TemplateRef = + this.propertyProvider.propertyActionTemplate; + + @Output() + onModelChanged: EventEmitter = new EventEmitter(); + + // + @Input() + model: XStandardType = this.propertyProvider.model; + + // + //#region Model Providers ... + @Input() + hideOptionalValues: XStandardType = + this.propertyProvider.hideOptionalValues; + + @Input() + availableModelKeys: XModelFieldsType = + this.propertyProvider.availableModelKeys; + + @Input() + hiddenModelKeys: XModelFieldsType = this.propertyProvider.hiddenModelKeys; + + @Input() + hiddenLabelModelKeys: XModelFieldsType = + this.propertyProvider.hiddenLabelModelKeys; + + @Input() + sortOrderModelKeys: XModelFieldsType = + this.propertyProvider.sortOrderModelKeys; + + @Input() + modelKeyProvider: XModelKeyProvider = + this.propertyProvider.modelKeyProvider; + + @Input() + modelValueProvider: XModelValueProvider = + this.propertyProvider.modelValueProvider; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XDescriptionListContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXDescriptionListContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + isLegacy: this.getValue( + container ? container.isLegacy : this.isLegacy + ), + showLabels: this.getValue( + container ? container.showLabels : this.showLabels + ), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + labelColor: this.getValue( + container ? container.labelColor : this.labelColor + ), + valueColor: this.getValue( + container ? container.valueColor : this.valueColor + ), + propertyActionTemplate: this.getValue( + container + ? container.propertyActionTemplate + : this.propertyActionTemplate + ), + model: this.getValue(container ? container.model : this.model), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + //#region Model Providers ... + hideOptionalValues: container + ? container.hideOptionalValues + : this.hideOptionalValues, + availableModelKeys: container + ? container.availableModelKeys + : this.availableModelKeys, + hiddenModelKeys: container + ? container.hiddenModelKeys + : this.hiddenModelKeys, + hiddenLabelModelKeys: container + ? container.hiddenLabelModelKeys + : this.hiddenLabelModelKeys, + sortOrderModelKeys: container + ? container.sortOrderModelKeys + : this.sortOrderModelKeys, + modelKeyProvider: container + ? container.modelKeyProvider + : this.modelKeyProvider, + modelValueProvider: container + ? container.modelValueProvider + : this.modelValueProvider, + //#endregion + + // + onModelChanged: this.onModelChanged, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list-item\x-description-list-item.component.html + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list-item\x-description-list-item.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list-item\x-description-list-item.component.ts + +import { nameof } from 'x-framework-core'; +import { + XDescriptionListItemAction, + XDescriptionListItemActionModel, + XDescriptionListItemContainerComponent, +} from './x-description-list-item.typings'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ViewChild, TemplateRef } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-description-list-item', + templateUrl: './x-description-list-item.component.html', + styleUrls: ['./x-description-list-item.component.scss'], +}) +export class XDescriptionListItemComponent extends XDescriptionListItemContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + @ViewChild('content', { static: false }) + content: TemplateRef; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XDescriptionListItemAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list-item\x-description-list-item.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../../classes/i-x-base.container'; +import { map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XOneOrManyType, XStandardType } from 'x-framework-core'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +export enum XDescriptionListItemAction { + Refresh = 'refresh', +} +export type XDescriptionListItemActionIdentifier = + | XDescriptionListItemAction + | string; + +export interface XDescriptionListItemActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXDescriptionListItemContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + name: XStandardType; + value: XStandardType; +} + +@Injectable({ + providedIn: 'root' +}) +export class XDescriptionListItemContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly name: XStandardType; + readonly value: XStandardType; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XDescriptionListItemContainerComponent + extends XBaseContainerComponent + implements IXDescriptionListItemContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + name: XStandardType = this.propertyProvider.name; + + @Input() + value: XStandardType = this.propertyProvider.value; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XDescriptionListItemContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXDescriptionListItemContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + name: this.getValue(container ? container.name : this.name), + value: this.getValue(container ? container.value : this.value), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-empty\x-empty.component.html + +
+

+ {{ getResourceValue(message) | async }} +

+ + + +
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-empty\x-empty.component.scss + +:host { +--padding: 6px; +} + +.x-empty { + padding: var(--padding); +} + +.x-empty-title { + padding-bottom: 6px; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-empty\x-empty.component.ts + +import { + XEmptyAction, + XEmptyActionModel, + XEmptyContainerComponent, +} from './x-empty.typings'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; +import { XButtonType } from '../x-button/x-button.typings'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-empty', + templateUrl: './x-empty.component.html', + styleUrls: ['./x-empty.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XEmptyComponent extends XEmptyContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly ButtonTypes = Object.assign({}, XButtonType); + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Action Provider Changed ... + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XEmptyAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-empty\x-empty.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XEmptyComponent } from './x-empty.component'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XButtonModule } from '../x-button/x-button.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XEmptyComponent], + imports: [XIconModule, XButtonModule, CommonModule, XBaseComponentModule], + exports: [XIconModule, XButtonModule, XEmptyComponent, XBaseComponentModule], +}) +export class XEmptyModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-empty\x-empty.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XBaseContainer, + IXBaseContainer, + XBaseComponentContainer, + XBaseContainerComponent, + IXBaseComponentContainer, + XBaseComponentContainerComponent, +} from '../classes/i-x-base.container'; +import { map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XIconIdentifier, XIconNames } from '../x-icon/x-icon.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XEmptyAction { + Refresh = 'refresh', +} + +export type XEmptyActionIdentifier = XEmptyAction | string; + +export interface XEmptyActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXEmptyContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + message: XStandardType; + messageColor: XStandardType; + foregroundMessageColor: XStandardType; + + // + hasRefresh: XStandardType; + + // + refreshButtonColor: XStandardType; + foregroundRefreshButtonColor: XStandardType; + + // + refreshIcon: XStandardType; + + // + refresh: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XEmptyContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly messageColor: XStandardType = + XColorWithBrightness.Dark; + readonly foregroundMessageColor: XStandardType = true; + + // + readonly hasRefresh: XStandardType = true; + + // + readonly refreshButtonColor: XStandardType = + XColorWithBrightness.Dark; + readonly foregroundRefreshButtonColor: XStandardType = true; + readonly message: XStandardType = XResourceIDs.no_item; + + // + readonly refreshIcon: XStandardType = XIconNames.refresh; + + // + readonly refresh: EventEmitter = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XEmptyContainerComponent + extends XBaseContainerComponent + implements IXEmptyContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Input() + message: XStandardType = this.propertyProvider.message; + + @Input() + messageColor: XStandardType = + this.propertyProvider.messageColor; + + @Input() + foregroundMessageColor: XStandardType = + this.propertyProvider.foregroundMessageColor; + + // + @Input() + hasRefresh: XStandardType = this.propertyProvider.hasRefresh; + + // + @Input() + refreshButtonColor: XStandardType = + this.propertyProvider.refreshButtonColor; + + @Input() + foregroundRefreshButtonColor: XStandardType = + this.propertyProvider.foregroundRefreshButtonColor; + + // + @Input() + refreshIcon: XStandardType = + this.propertyProvider.refreshIcon; + + @Output() + refresh: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XEmptyContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXEmptyContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + + // + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + + // + messageColor: this.getValue( + container ? container.messageColor : this.messageColor + ), + foregroundMessageColor: this.getValue( + container + ? container.foregroundMessageColor + : this.foregroundMessageColor + ), + + // + hasRefresh: this.getValue( + container ? container.hasRefresh : this.hasRefresh + ), + refreshButtonColor: this.getValue( + container ? container.refreshButtonColor : this.refreshButtonColor + ), + foregroundRefreshButtonColor: this.getValue( + container + ? container.foregroundRefreshButtonColor + : this.foregroundRefreshButtonColor + ), + refreshIcon: this.getValue( + container ? container.refreshIcon : this.refreshIcon + ), + + // + message: this.getValue(container ? container.message : this.message), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + refresh: this.refresh, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentEmptyContainer = { + [TKey in keyof IXEmptyContainer as `empty${Capitalize< + string & TKey + >}`]: IXEmptyContainer[TKey]; +}; + +export interface IXComponentEmptyContainer + extends TXComponentEmptyContainer, + IXBaseComponentContainer { + showEmpty: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XComponentEmptyContainer + extends XBaseComponentContainer + implements Readonly +{ + // + //#region Props ... + // + readonly showEmpty: XStandardType = false; + + // + readonly emptyActionProvider: Subject = + this.propertyProvider.actionProvider; + readonly emptyCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly emptyColor: XStandardType = + this.propertyProvider.color; + readonly emptyForegroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + readonly emptyMessage: XStandardType = + this.propertyProvider.message; + readonly emptyMessageColor: XStandardType = + this.propertyProvider.messageColor; + readonly emptyForegroundMessageColor: XStandardType = + this.propertyProvider.foregroundMessageColor; + + // + readonly emptyHasRefresh: XStandardType = + this.propertyProvider.hasRefresh; + + // + readonly emptyRefreshButtonColor: XStandardType = + this.propertyProvider.refreshButtonColor; + readonly emptyForegroundRefreshButtonColor: XStandardType = + this.propertyProvider.foregroundRefreshButtonColor; + + // + readonly emptyRefreshIcon: XStandardType = + this.propertyProvider.refreshIcon; + + // + readonly emptyRefresh: EventEmitter = this.propertyProvider.refresh; + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XEmptyContainer) { + super(); + } + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +@Component({ + template: ``, +}) +export abstract class XComponentEmptyContainerComponent + extends XBaseComponentContainerComponent + implements IXComponentEmptyContainer +{ + // + //#region Props ... + // + @Input() + showEmpty: XStandardType = this.propertyProvider.showEmpty; + + // + @Input() + emptyActionProvider: Subject = + this.propertyProvider.emptyActionProvider; + + @Input() + emptyCssClass: XStandardType> = + this.propertyProvider.emptyCssClass; + + // + @Input() + emptyColor: XStandardType = + this.propertyProvider.emptyColor; + + @Input() + emptyForegroundColor: XStandardType = + this.propertyProvider.emptyForegroundColor; + + // + @Input() + emptyMessage: XStandardType = + this.propertyProvider.emptyMessage; + + @Input() + emptyMessageColor: XStandardType = + this.propertyProvider.emptyMessageColor; + + @Input() + emptyForegroundMessageColor: XStandardType = + this.propertyProvider.emptyForegroundMessageColor; + + // + @Input() + emptyHasRefresh: XStandardType = + this.propertyProvider.emptyHasRefresh; + + // + @Input() + emptyRefreshButtonColor: XStandardType = + this.propertyProvider.emptyRefreshButtonColor; + + @Input() + emptyForegroundRefreshButtonColor: XStandardType = + this.propertyProvider.emptyForegroundRefreshButtonColor; + + // + @Input() + emptyRefreshIcon: XStandardType = + this.propertyProvider.emptyRefreshIcon; + + // + @Output() + emptyRefresh: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XComponentEmptyContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXComponentEmptyContainer + ): Observable { + return forkJoin({ + // + showEmpty: this.getValue( + container ? container.showEmpty : this.showEmpty + ), + emptyCssClass: this.getValue( + container ? container.emptyCssClass : this.emptyCssClass + ), + emptyColor: this.getValue( + container ? container.emptyColor : this.emptyColor + ), + emptyForegroundColor: this.getValue( + container ? container.emptyForegroundColor : this.emptyForegroundColor + ), + emptyMessage: this.getValue( + container ? container.emptyMessage : this.emptyMessage + ), + emptyMessageColor: this.getValue( + container ? container.emptyMessageColor : this.emptyMessageColor + ), + emptyForegroundMessageColor: this.getValue( + container + ? container.emptyForegroundMessageColor + : this.emptyForegroundMessageColor + ), + emptyHasRefresh: this.getValue( + container ? container.emptyHasRefresh : this.emptyHasRefresh + ), + emptyRefreshButtonColor: this.getValue( + container + ? container.emptyRefreshButtonColor + : this.emptyRefreshButtonColor + ), + emptyForegroundRefreshButtonColor: this.getValue( + container + ? container.emptyForegroundRefreshButtonColor + : this.emptyForegroundRefreshButtonColor + ), + emptyRefreshIcon: this.getValue( + container ? container.emptyRefreshIcon : this.emptyRefreshIcon + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + emptyActionProvider: container + ? container.emptyActionProvider + : this.emptyActionProvider, + + // + emptyRefresh: this.emptyRefresh, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-list\x-expandable-list.component.html + + + + + + + + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

{{ getResourceValue(item.title) | async }}

+
+ + + +
{{ getResourceValue(item.title) | async }}
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-list\x-expandable-list.component.scss + +:host ::ng-deep { + // + --divider-color: var(--x-color-dark); + + // + --child-color: var(--x-color-primary-shade-contrast); + --child-background-color: var(--x-color-primary-shade); + + // + --child-active-color: var(--x-color-dark-tint-contrast); + --child-active-background-color: var(--x-color-dark-tint); + + // + --highlight-color: var(--x-color-warning-contrast); + --highlight-background-color: var(--x-color-warning); + + // + --parent-color: var(--x-color-tertiary-tint-contrast); + --parent-background-color: var(--x-color-tertiary-tint); + + // + --parent-active-color: var(--x-color-dark-contrast); + --parent-active-background-color: var(--x-color-dark); +} + +:host ::ng-deep .invisible { + display: none; + visibility: hidden; +} + +ion-list, +ion-item, +ion-item-group { + width: 100%; + --background: transparent; + --inner-padding-end: 0px; + --inner-padding-start: 0px; + --color: var(--child-color); + --border-color: var(--divider-color); +} + +ion-item-divider { + width: 100%; + position: relative; + --border-color: var(--divider-color); +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; +} + +.item-parent { + // + width: 100%; + padding-top: 0; + padding-bottom: 0; + position: relative; + + // + color: var(--parent-color); + background-color: var(--parent-background-color); + + // + *, + ion-icon, + x-icon { + color: var(--parent-color) !important; + } +} + +.item-child { + padding-top: 0; + padding-bottom: 0; + display: block !important; + transition: opacity 0.2s ease-in; + color: var(--child-color) !important; + background-color: var(--child-background-color) !important; + + // + *, + ion-icon, + x-icon { + color: var(--child-color) !important; + } +} + +.item-child:hover, +.item-parent:hover { + color: var(--highlight-color) !important; + --color: var(--highlight-color) !important; + background-color: var(--highlight-background-color) !important; + + // + *, + ion-icon, + x-icon { + color: var(--highlight-color) !important; + } +} + +.item-parent.active-link, +.active-parent { + // + color: var(--parent-active-color) !important; + background-color: var(--parent-active-background-color) !important; + + // + *, + ion-icon, + x-icon { + color: var(--parent-active-color) !important; + } +} + +.item-child.active-link, +.active-child { + // + color: var(--child-active-color) !important; + background-color: var(--child-active-background-color) !important; + + // + *, + ion-icon, + x-icon { + color: var(--child-active-color) !important; + } +} + +ion-icon { + &.invisible { + visibility: hidden; + } +} + +.collapsed { + opacity: 0; + height: 0; +} + +.ion-hide { + display: none !important; +} + +x-card { + width: inherit; + height: inherit; +} + +.item-parent, +.expandable-item-parent { + // + cursor: pointer; + + // + min-height: 48px; + + // + &.active { + color: var(--parent-active-color); + background-color: var(--parent-active-background-color); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-list\x-expandable-list.component.ts + +import { + nameof, + toArray, + hasChild, + parseXColor, + XOneOrManyType, + toNormalString, + isNullOrEmptyString, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + findItemById, + findItemByRoute, + parentItemsById, + parentItemsByRoute, + containsItemsByRoute, +} from './x-expandable-list.tools'; +import { + XExpandableListItem, + XExpandableListAction, + XExpandableListCssVarKeys, + XExpandableListActionModel, + XExpandableListStartupState, + XExpandableListContainerComponent, +} from './x-expandable-list.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import { forkJoin, of, Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-expandable-list', + templateUrl: './x-expandable-list.component.html', + styleUrls: ['./x-expandable-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XExpandableListComponent extends XExpandableListContainerComponent { + // + //#region Props ... + // + private ACTIVATED_CHILD: XExpandableListItem; + private ACTIVATED_PARENTS: XExpandableListItem[]; + + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + //#endregion + + // + //#region LifeCycle ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Item Changed ... + const isItemsChanged = changeKeys.includes( + nameof('items') + ); + if (isItemsChanged) { + await this.handleStartupState(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Color Change Detectors ... + const isChildColorChanged = changeKeys.includes( + nameof('childColor') + ); + if (isChildColorChanged) { + await this.applyChildColor(); + } + + const isChildActiveColorChanged = changeKeys.includes( + nameof('childActiveColor') + ); + if (isChildActiveColorChanged) { + await this.applyChildActiveColor(); + } + + const isParentColorChanged = changeKeys.includes( + nameof('parentColor') + ); + if (isParentColorChanged) { + await this.applyParentColor(); + } + + const isParentActiveColorChanged = changeKeys.includes( + nameof('parentActiveColor') + ); + if (isParentActiveColorChanged) { + await this.applyParentActiveColor(); + } + + const isHighlightColorChanged = changeKeys.includes( + nameof('highlightColor') + ); + if (isHighlightColorChanged) { + await this.applyHighlightColor(); + } + + const isDividerColorChanged = changeKeys.includes( + nameof('dividerColor') + ); + if (isDividerColorChanged) { + await this.applyDividerColor(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Provider Actions ... + hasChildItems(item: XExpandableListItem) { + // + if (!item || !item.childs) { + return of(false); + } + + return this.getValue(item.childs).pipe(map((childs) => hasChild(childs))); + } + + getIconName(item: XExpandableListItem) { + return this.isExpanded(item).pipe( + map((isExpanded) => + isExpanded + ? XIconNames.arrow_dropup.toString() + : XIconNames.arrow_dropdown.toString() + ) + ); + } + + isParentActivated(item: XExpandableListItem) { + // + return this.getValue(item).pipe( + concatMap((pItem) => + this.getValue(this.supportRouting).pipe( + map((supportRouting) => { + return { item: pItem, supportRouting }; + }) + ) + ), + concatMap((dObject) => { + // + if (!dObject || !dObject.item) { + return this.getValue(false); + } + + // + return this.getArrayValue(dObject.item.href).pipe( + map((hrefArray) => this.managerService.mergeRoutes(hrefArray)), + concatMap((route) => { + // + if (!this.ACTIVATED_PARENTS || !hasChild(this.ACTIVATED_PARENTS)) { + return this.getValue(false); + } + + // + if (dObject.supportRouting) { + // + const routes$ = forkJoin( + this.ACTIVATED_PARENTS.map((i) => + this.getArrayValue(i.href).pipe( + map((href) => this.managerService.mergeRoutes(href)) + ) + ) + ); + + // + return routes$.pipe( + map( + (routes) => + routes.findIndex( + (r) => toNormalString(r) === toNormalString(route) + ) > -1 + ) + ); + } else { + // + const ids$ = forkJoin(this.ACTIVATED_PARENTS.map((i) => i.id)); + + // + return ids$.pipe( + map( + (ids) => + ids.findIndex( + (i) => + toNormalString(i) === toNormalString(dObject.item.id) + ) > -1 + ) + ); + } + }) + ); + }) + ); + } + + isChildActivated(item: XExpandableListItem) { + // + return this.getValue(item).pipe( + concatMap((pItem) => + this.getValue(this.supportRouting).pipe( + map((supportRouting) => { + return { item: pItem, supportRouting }; + }) + ) + ), + concatMap((dObject) => { + // + if (!dObject || !dObject.item || !this.ACTIVATED_CHILD) { + return this.getValue(false); + } + + // + return this.getArrayValue(this.ACTIVATED_CHILD.href) + .pipe(map((hrefArray) => this.managerService.mergeRoutes(hrefArray))) + .pipe( + concatMap((activatedRoute) => { + return this.getArrayValue(dObject.item.href) + .pipe( + map((hrefArray) => this.managerService.mergeRoutes(hrefArray)) + ) + .pipe( + map((itemRoute) => { + return { itemRoute, activatedRoute }; + }), + map((routeObject) => { + // + if (dObject.supportRouting) { + return ( + toNormalString(routeObject.itemRoute) === + toNormalString(routeObject.activatedRoute) + ); + } else { + return ( + toNormalString(this.ACTIVATED_CHILD.id) === + toNormalString(dObject.item.id) + ); + } + }) + ); + }) + ); + }) + ); + } + + isHidden(item: XExpandableListItem) { + return !item || !item.hidden ? of(false) : this.getValue(item.hidden); + } + + isDisabled(item: XExpandableListItem) { + return !item || !item.disabled ? of(false) : this.getValue(item.disabled); + } + + isExpanded(item: XExpandableListItem) { + return !item || !item.opened ? of(false) : this.getValue(item.opened); + } + //#endregion + + // + //#region UI Handlers ... + async handleItemSelectedAction( + item: XExpandableListItem, + parent: XExpandableListItem + ) { + // + // Check Component State ... + if (this.loading || this.uiDisabled) { + return; + } + + // + // Emit Event Handler ... + this.itemSelected.emit(item); + + // + const isSupportRouting = await this.getValueAsync(this.supportRouting); + if (isSupportRouting) { + // + const currUrl: string = this.managerService.getRoute(); + const itemHref = await this.getArrayValueAsync(item.href); + const itemUrl = this.managerService.mergeRoutes(itemHref); + + // + if (currUrl === itemUrl) { + // + await this.setActivatedParent(parent); + await this.setActivatedChild(item); + } else { + await this.handleItemRoutingActions(item); + } + } else { + // + await this.setActivatedParent(parent); + await this.setActivatedChild(item); + } + } + //#endregion + + // + //#region Actions ... + async toggle( + item: XExpandableListItem, + siblings: XExpandableListItem[] + ) { + // + const isExpanded = await this.toPromise(this.isExpanded(item)); + item.opened = !isExpanded; + + // + const isCloseSibling = await this.toPromise( + this.getValue(this.closeSiblings) + ); + + // + if (isCloseSibling) { + this.handleCloseSiblings(item, siblings); + } + + // + if (this.loading || this.uiDisabled) { + return; + } + + // + this.itemSelected.emit(item); + + // + await this.handleItemRoutingActions(item, true); + } + + async expand(item: XExpandableListItem) { + item.opened = true; + } + + async expandAll() { + // + const items = await this.getArrayValueAsync(this.items); + await this.handleChangeState(items, true); + } + + async collapse(item: XExpandableListItem) { + item.opened = false; + } + + async collapseAll() { + // + const items = await this.getArrayValueAsync(this.items); + await this.handleChangeState(items, false); + } + //#endregion + + // + //#region Privates ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XExpandableListAction.Refresh: + this.detectChanges(); + break; + + // + case XExpandableListAction.ExpandAll: + await this.expandAll(); + break; + + // + case XExpandableListAction.CollapseAll: + await this.collapseAll(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + //#region Parent and Child Avtivated ... + private async handleApplyCurrentItems() { + // + if (this.uiDisabled || this.loading) { + setTimeout(async () => await this.handleApplyCurrentItems(), 100); + } + + // + const supportRouting = await this.getValueAsync(this.supportRouting); + if (supportRouting) { + await this.handleApplyCurrentItemsByRouting(); + } else { + await this.handleApplyCurrentItemsById(); + } + + // + this.detectChanges(); + } + + private async handleApplyCurrentItemsByRouting() { + // + const currentRoute = this.managerService.getRoute(); + if (isNullOrEmptyString(currentRoute)) { + return; + } + + // + const foundedItem = await findItemByRoute(this.items, currentRoute); + const parentItems = await parentItemsByRoute(this.items, currentRoute); + + // + if (!foundedItem || !hasChild(parentItems)) { + // + if (foundedItem) { + // + this.ACTIVATED_CHILD = foundedItem; + this.ACTIVATED_PARENTS = [foundedItem]; + } + + // + return; + } + + // + this.ACTIVATED_PARENTS = parentItems; + this.ACTIVATED_CHILD = foundedItem; + } + + private async handleApplyCurrentItemsById() { + // + const items = await this.getArrayValueAsync(this.items); + const currentId = this.ACTIVATED_CHILD + ? this.ACTIVATED_CHILD.id + : hasChild(items) + ? items[0].id + : ''; + if (isNullOrEmptyString(currentId)) { + return; + } + + // + const foundedItem = await findItemById(this.items, currentId); + const parentItems = await parentItemsById(this.items, currentId); + + // + if (!foundedItem || !hasChild(parentItems)) { + return; + } + + // + this.ACTIVATED_PARENTS = parentItems; + this.ACTIVATED_CHILD = foundedItem; + } + //#endregion + + private async handleItemRoutingActions( + item: XExpandableListItem, + isParent = false + ) { + // + if (!item || this.uiDisabled || this.loading) { + return; + } + + // + const hidden = await this.getValueAsync(item.hidden); + const disabled = await this.getValueAsync(item.disabled); + if (hidden || disabled) { + return; + } + + // + //#region Handle Routing ... + // + const href = await this.getValueAsync(item.href); + const route = this.managerService.mergeRoutes(href); + const supportRouting = await this.getValueAsync(this.supportRouting); + const replaceUrl = await this.getOptionalValueAsync(item.replaceUrl); + const disableParentRouting = await this.getValueAsync( + this.disableParentRouting + ); + + // + // When just a not Child Parent with Href provided ... + const childsArr = !item.childs + ? [] + : await this.getArrayValueAsync(item.childs); + if (isParent && !hasChild(childsArr)) { + isParent = false; + } + + // + if (isParent && disableParentRouting) { + return; + } + + // + if (supportRouting && hasChild(href) && !isNullOrEmptyString(route)) { + await this.managerService.navigateByUrlReplace(route, {}, replaceUrl); + } + //#endregion + } + + private handleCloseSiblings( + item: XExpandableListItem, + siblings: XOneOrManyType> + ) { + // + const pSiblings = toArray(siblings); + const others = pSiblings.filter((i) => item.id !== i.id); + others.forEach((i) => (i.opened = false)); + + // + this.detectChanges(); + } + + private async handleChangeState( + items: XOneOrManyType>, + expand: boolean + ) { + // + const pItems = toArray(items); + pItems.forEach((i) => { + // + i.opened = expand; + + // + this.getValueAsync(i.childs).then((childs) => { + if (hasChild(childs)) { + this.handleChangeState(childs, expand); + } + }); + + // + this.detectChanges(); + }); + } + + private async handleStartupState() { + // + const items = (await this.getValueAsync( + this.items + )) as XExpandableListItem[]; + + // + await this.applyStartupState(items); + await this.handleApplyCurrentItems(); + } + + private async setActivatedParent(v: XExpandableListItem) { + // + const isExists = hasChild(this.ACTIVATED_PARENTS) + ? this.ACTIVATED_PARENTS.findIndex( + (p) => toNormalString(p.id) === toNormalString(v.id) + ) > 1 + : false; + + // + if (isExists) { + return; + } + + // + this.ACTIVATED_PARENTS = await containsItemsByRoute(this.items, v.href); + + // + this.detectChanges(); + } + + private async setActivatedChild(v: XExpandableListItem) { + // + const isSamed = this.ACTIVATED_CHILD + ? v.id === this.ACTIVATED_CHILD.id + : false; + + // + if (isSamed) { + return; + } + + // + this.ACTIVATED_CHILD = v; + + // + this.detectChanges(); + } + + private async applyStartupState( + items: XOneOrManyType> + ) { + // + if (!hasChild(items)) { + return; + } + + // + const startupState = await this.getValueAsync(this.startupState); + const pItems = await this.getArrayValueAsync(items); + + // + pItems.forEach((i) => { + // + // Handle StartUp State for Items ... + i.opened = + startupState === XExpandableListStartupState.CollapseAll + ? false + : startupState === XExpandableListStartupState.ExpandAll + ? true + : false; + + // + if (i.childs) { + this.getValueAsync(i.childs).then((childs) => { + this.applyStartupState(childs); + }); + } + + // + this.detectChanges(); + }); + + // + this.detectChanges(); + } + + // + //#region Color Appliers ... + private async applyChildColor() { + // + const color = await this.getValueAsync(this.childColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XExpandableListCssVarKeys.ChildColor, `var(${fgColor})`); + this.setCssVar(XExpandableListCssVarKeys.ChildBgColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + private async applyChildActiveColor() { + // + const color = await this.getValueAsync(this.childActiveColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XExpandableListCssVarKeys.ChildActiveColor, + `var(${fgColor})` + ); + this.setCssVar( + XExpandableListCssVarKeys.ChildActiveBgColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + private async applyParentColor() { + // + const color = await this.getValueAsync(this.parentColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XExpandableListCssVarKeys.ParentColor, `var(${fgColor})`); + this.setCssVar(XExpandableListCssVarKeys.ParentBgColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + private async applyParentActiveColor() { + // + const color = await this.getValueAsync(this.parentActiveColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XExpandableListCssVarKeys.ParentActiveColor, + `var(${fgColor})` + ); + this.setCssVar( + XExpandableListCssVarKeys.ParentActiveBgColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + private async applyHighlightColor() { + // + const color = await this.getValueAsync(this.highlightColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XExpandableListCssVarKeys.HighlightColor, `var(${fgColor})`); + this.setCssVar( + XExpandableListCssVarKeys.HighlightBgColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + private async applyDividerColor() { + // + const color = await this.getValueAsync(this.dividerColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const colorVar = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(colorVar)) { + return; + } + + // + this.setCssVar(XExpandableListCssVarKeys.DividerColor, `var(${colorVar})`); + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-list\x-expandable-list.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XExpandableListComponent } from './x-expandable-list.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XExpandableListComponent], + imports: [CommonModule, XBaseComponentModule, XCardModule, XIconModule], + exports: [ + XCardModule, + XIconModule, + XBaseComponentModule, + XExpandableListComponent, + ], +}) +export class XExpandableListModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-list\x-expandable-list.tools.ts + +import { + toArray, + hasChild, + toPromise, + mergeRoutes, + XStandardType, + XOneOrManyType, + toNormalString, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { XExpandableListItem } from './x-expandable-list.typings'; + +export async function toFlatItems( + items: XStandardType>> +) { + // + const result: XExpandableListItem[] = []; + + // + if (!items) { + return result; + } + + // + const pItemsArray = await toPromise(items); + if (!hasChild(pItemsArray)) { + return result; + } + + // + const pItems = toArray(pItemsArray); + for (const item of pItems) { + // + const childsArray = await toPromise(item.childs); + const isParent = hasChild(childsArray); + + // + result.push(item); + + // + if (isParent) { + // + const childsRes = await toFlatItems(item.childs); + result.push(...childsRes); + } + } + + // + return result; +} + +// +//#region By Route ... +export async function findItemByRoute( + items: XStandardType>>, + route: XStandardType> +) { + // + let result: XExpandableListItem = null; + + // + if (!items || !route) { + return result; + } + + // + const routeArray = await toPromise(route); + if (!hasChild(routeArray)) { + return result; + } + + // + const pRoute = mergeRoutes(routeArray); + if (isNullOrEmptyString(pRoute)) { + return result; + } + + // + const pItems = await toFlatItems(items); + if (!hasChild(pItems)) { + return result; + } + + // + for (const item of pItems) { + // + const itemRouteArray = await toPromise(item.href); + if (hasChild(itemRouteArray)) { + // + const itemRoute = mergeRoutes(itemRouteArray); + if ( + !isNullOrEmptyString(itemRoute) && + toNormalString(itemRoute) === toNormalString(pRoute) + ) { + result = item; + } + } + } + + // + return result; +} + +export async function containsItemsByRoute( + items: XStandardType>>, + route: XStandardType> +) { + // + const result: XExpandableListItem[] = []; + + // + if (!items || !route) { + return result; + } + + // + const routeArray = await toPromise(route); + if (!hasChild(routeArray)) { + return result; + } + + // + const pRoute = mergeRoutes(routeArray); + if (isNullOrEmptyString(pRoute)) { + return result; + } + + // + const pItems = await toFlatItems(items); + if (!hasChild(pItems)) { + return result; + } + + // + for (const item of pItems) { + // + const itemRouteArray = await toPromise(item.href); + if (hasChild(itemRouteArray)) { + // + const itemRoute = mergeRoutes(itemRouteArray); + if ( + !isNullOrEmptyString(itemRoute) && + toNormalString(pRoute).includes(toNormalString(itemRoute)) + ) { + result.push(item); + } + } + } + + // + return result; +} + +export async function parentItemsByRoute( + items: XStandardType>>, + route: XStandardType> +) { + // + let result: XExpandableListItem[] = []; + + // + if (!items || !route) { + return result; + } + + // + const parentItems = await containsItemsByRoute(items, route); + const foundedItem = await findItemByRoute(items, route); + if (!hasChild(parentItems) || isNullOrUndefined(foundedItem)) { + return result; + } + + // + result = parentItems.filter( + (i) => toNormalString(i.id) !== toNormalString(foundedItem.id) + ); + + // + return result; +} +//#endregion + +// +//#region By Id ... +export async function findItemById( + items: XStandardType>>, + id: XStandardType +) { + // + let result: XExpandableListItem = null; + + // + if (!items || !id) { + return result; + } + + // + const pId = await toPromise(id); + if (isNullOrEmptyString(pId)) { + return result; + } + + // + const pItems = await toFlatItems(items); + if (!hasChild(pItems)) { + return result; + } + + // + for (const item of pItems) { + // + const itemRouteArray = await toPromise(item.href); + if (hasChild(itemRouteArray)) { + // + if ( + !isNullOrEmptyString(item.id) && + toNormalString(item.id) === toNormalString(pId) + ) { + result = item; + } + } + } + + // + return result; +} + +export async function containsItemsById( + items: XStandardType>>, + id: XStandardType +) { + // + const result: XExpandableListItem[] = []; + + // + if (!items || !id) { + return result; + } + + // + const pId = await toPromise(id); + if (isNullOrEmptyString(pId)) { + return result; + } + + // + const pItems = await toFlatItems(items); + if (!hasChild(pItems)) { + return result; + } + + // + for (const item of pItems) { + // + const isSameId = toNormalString(item.id) === toNormalString(pId); + if (isSameId) { + result.push(item); + } else { + // + const childsArr = await toPromise(item.childs); + const childs = toArray(childsArr); + const isParent = hasChild(childs); + + // + if (isParent) { + const pChildsRes = await containsItemsById(childsArr, id); + result.push(...pChildsRes); + } + } + } + + // + return result; +} + +export async function parentItemsById( + items: XStandardType>>, + id: XStandardType +) { + // + let result: XExpandableListItem[] = []; + + // + if (!items || !id) { + return result; + } + + // + const parentItems = await containsItemsById(items, id); + const foundedItem = await findItemById(items, id); + if (!hasChild(parentItems) || isNullOrUndefined(foundedItem)) { + return result; + } + + // + result = parentItems.filter( + (i) => toNormalString(i.id) !== toNormalString(foundedItem.id) + ); + + // + return result; +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-list\x-expandable-list.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export interface XExpandableListItem { + id: string; + data: XStandardType; + opened?: XStandardType; + hidden?: XStandardType; + disabled?: XStandardType; + replaceUrl?: XStandardType; + title: XStandardType; + href?: XStandardType>; + description?: XStandardType; + childs?: XStandardType>>; +} + +export enum XExpandableListStartupState { + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', +} + +export type XExpandableListStartupStateIdentifier = + | XExpandableListStartupState + | string; + +export enum XExpandableListAction { + // + Refresh = 'refresh', + + // + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', +} + +export type XExpandableListActionIdentifier = XExpandableListAction | string; + +export interface XExpandableListActionModel + extends XBaseActionModel {} + +export enum XExpandableListCssVarKeys { + // + ChildColor = 'child-color', + ChildBgColor = 'child-background-color', + + // + ChildActiveColor = 'child-active-color', + ChildActiveBgColor = 'child-active-background-color', + + // + ParentColor = 'parent-color', + ParentBgColor = 'parent-background-color', + + // + ParentActiveColor = 'parent-active-color', + ParentActiveBgColor = 'parent-active-background-color', + + // + HighlightColor = 'highlight-color', + HighlightBgColor = 'highlight-background-color', + + // + DividerColor = 'divider-color', +} + +// +//#region Inside Card Container ... +export interface IXExpandableListContainer extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + itemCssClass: XStandardType>; + + // + supportRouting: XStandardType; + disableParentRouting: XStandardType; + showParentDetailIcon: XStandardType; + showChildDetailIcon: XStandardType; + closeSiblings: XStandardType; + startupState: XStandardType; + items: XStandardType>>; + parentTemplate: TemplateRef; + childTemplate: TemplateRef; + + // + childColor: XStandardType; + childActiveColor: XStandardType; + parentColor: XStandardType; + parentActiveColor: XStandardType; + highlightColor: XStandardType; + dividerColor: XStandardType; + + // + itemSelected: EventEmitter>; +} + +@Injectable({ + providedIn: 'root', +}) +export class XExpandableListContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject; + readonly cssClass: XStandardType>; + + // + readonly itemCssClass: XStandardType>; + + // + readonly supportRouting: XStandardType = false; + readonly disableParentRouting: XStandardType = true; + readonly showParentDetailIcon: XStandardType = true; + readonly showChildDetailIcon: XStandardType = true; + readonly closeSiblings: XStandardType = false; + readonly startupState: XStandardType = + XExpandableListStartupState.CollapseAll; + readonly items: XStandardType>>; + readonly parentTemplate: TemplateRef; + readonly childTemplate: TemplateRef; + + // + readonly childColor: XStandardType = + XColorWithBrightness.PrimaryShade; + readonly childActiveColor: XStandardType = + XColorWithBrightness.Dark; + readonly parentColor: XStandardType = + XColorWithBrightness.TertiaryTint; + readonly parentActiveColor: XStandardType = + XColorWithBrightness.Dark; + readonly highlightColor: XStandardType = + XColorWithBrightness.Warning; + readonly dividerColor: XStandardType = + XColorWithBrightness.Dark; + + // + readonly itemSelected: EventEmitter> = + new EventEmitter>(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XExpandableListContainerComponent + extends XComponentCardContainerComponent + implements IXExpandableListContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + itemCssClass: XStandardType> = + this.propertyProvider.itemCssClass; + + // + @Input() + supportRouting: XStandardType = this.propertyProvider.supportRouting; + + @Input() + disableParentRouting: XStandardType = + this.propertyProvider.disableParentRouting; + + @Input() + showParentDetailIcon: XStandardType = + this.propertyProvider.showParentDetailIcon; + + @Input() + showChildDetailIcon: XStandardType = + this.propertyProvider.showChildDetailIcon; + + @Input() + closeSiblings: XStandardType = this.propertyProvider.closeSiblings; + + @Input() + startupState: XStandardType = + this.propertyProvider.startupState; + + @Input() + items: XStandardType>> = + this.propertyProvider.items; + + @Input() + parentTemplate: TemplateRef = this.propertyProvider.parentTemplate; + + @Input() + childTemplate: TemplateRef = this.propertyProvider.childTemplate; + + // + @Input() + childColor: XStandardType = + this.propertyProvider.childColor; + + @Input() + childActiveColor: XStandardType = + this.propertyProvider.childActiveColor; + + @Input() + parentColor: XStandardType = + this.propertyProvider.parentColor; + + @Input() + parentActiveColor: XStandardType = + this.propertyProvider.parentActiveColor; + + @Input() + highlightColor: XStandardType = + this.propertyProvider.highlightColor; + + @Input() + dividerColor: XStandardType = + this.propertyProvider.dividerColor; + + // + @Output() + itemSelected: EventEmitter> = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XExpandableListContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXExpandableListContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + itemCssClass: this.getValue( + container ? container.itemCssClass : this.itemCssClass + ), + + // + supportRouting: this.getValue( + container ? container.supportRouting : this.supportRouting + ), + disableParentRouting: this.getValue( + container + ? container.disableParentRouting + : this.disableParentRouting + ), + showParentDetailIcon: this.getValue( + container + ? container.showParentDetailIcon + : this.showParentDetailIcon + ), + showChildDetailIcon: this.getValue( + container + ? container.showChildDetailIcon + : this.showChildDetailIcon + ), + closeSiblings: this.getValue( + container ? container.closeSiblings : this.closeSiblings + ), + startupState: this.getValue( + container ? container.startupState : this.startupState + ), + items: this.getValue(container ? container.items : this.items), + parentTemplate: this.getValue( + container ? container.parentTemplate : this.parentTemplate + ), + childTemplate: this.getValue( + container ? container.childTemplate : this.childTemplate + ), + + // + childColor: this.getValue( + container ? container.childColor : this.childColor + ), + childActiveColor: this.getValue( + container ? container.childActiveColor : this.childActiveColor + ), + parentColor: this.getValue( + container ? container.parentColor : this.parentColor + ), + parentActiveColor: this.getValue( + container ? container.parentActiveColor : this.parentActiveColor + ), + highlightColor: this.getValue( + container ? container.highlightColor : this.highlightColor + ), + dividerColor: this.getValue( + container ? container.dividerColor : this.dividerColor + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + itemSelected: this.itemSelected, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-expandable-panel.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+ + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-expandable-panel.component.scss + +:host { + --panel-header-size: 48px; +} + +.x-expandable-panel-header { + height: var(--panel-header-size); +} + +.applyColors { + .mat-expansion-panel-header { + // + color: var(--header-foreground-color, var(--x-color-light-contrast)); + background-color: var(--header-background-color, var(--x-color-light)); + + * { + color: var(--header-foreground-color, var(--x-color-light-contrast)); + } + + .mat-expansion-indicator { + &::after { + color: var( + --header-foreground-color, + var(--x-color-light-contrast) + ) !important; + } + } + + &:focus, + &:hover, + &.mat-expanded { + background: unset; + color: var( + --header-highlight-foreground-color, + var(--x-color-light-contrast) + ); + background-color: var( + --header-highlight-background-color, + var(--x-color-light) + ); + + * { + color: var( + --header-highlight-foreground-color, + var(--x-color-light-contrast) + ); + } + + .mat-expansion-indicator { + &::after { + color: var( + --header-highlight-foreground-color, + var(--x-color-light-contrast) + ); + } + } + } + } +} + +:host ::ng-deep .mat-expansion-indicator { + &::after { + color: var( + --header-foreground-color, + var(--x-color-light-contrast) + ) !important; + } +} + +.x-panel-actions, +.x-panel-custom-actions-wrapper { + width: 100%; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-expandable-panel.component.ts + +import { + Component, + QueryList, + ContentChildren, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + parseXColor, + XStandardType, + XModalButtonRole, + isNullOrEmptyString, + XColorWithBrightness, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XExpandablePanelAction, + XExpandablePanelCssVars, + XExpandablePanelActionModel, + XExpandablePanelStartupState, + XExpandablePandelContainerComponent, +} from './x-expandable-panel.typings'; +import { Subject, Subscription } from 'rxjs'; +import { concatMap, map } from 'rxjs/operators'; +import { XButtonType } from '../x-button/x-button.typings'; +import { XPanelComponent } from './x-panel/x-panel.component'; +import { XNPRButtonType } from '../classes/i-x-next-prev-reset.container'; + +@Component({ + selector: 'x-expandable-panel', + templateUrl: './x-expandable-panel.component.html', + styleUrls: ['./x-expandable-panel.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XExpandablePanelComponent extends XExpandablePandelContainerComponent { + // + //#region Props ... + // + private IS_ACCORDION: boolean; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly NPRButtonTypes = Object.assign({}, XNPRButtonType); + + // + //#region Panels Props ... + // + private PANELS: QueryList; + + // + @ContentChildren(XPanelComponent) + set panels(p: QueryList) { + // + if (!p) { + return; + } + + // + this.PANELS = p; + this.detectChanges(); + } + + // + get panels() { + return this.PANELS; + } + //#endregion + //#endregion + + // + //#region LifeCycles ... + async afterViewInit() { + super.afterViewInit(); + + // + await this.handleStartupState(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + const isAccordionChanged = changeKeys.includes( + nameof('isAccordion') + ); + if (isAccordionChanged) { + this.IS_ACCORDION = await this.getValueAsync(this.isAccordion); + } + + // + const isHeaderColorChanged = changeKeys.includes( + nameof('headerColor') + ); + if (isHeaderColorChanged) { + await this.applyHeaderColor(); + } + + // + const isHeaderHighlighColorChanged = changeKeys.includes( + nameof('headerHighlightColor') + ); + if (isHeaderHighlighColorChanged) { + await this.applyHeaderHighlightColor(); + } + + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Register Handlers ... + registerInstanceHandlers() { + super.registerInstanceHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Provider ... + hasStepAction(panel: XPanelComponent, type: XNPRButtonType) { + // + switch (type) { + // + case this.NPRButtonTypes.Next: + return this.getOptionalValue(panel.hasNext); + + // + case this.NPRButtonTypes.Prev: + return this.getOptionalValue(panel.hasPrev); + + // + case this.NPRButtonTypes.Reset: + return this.getOptionalValue(panel.hasReset); + } + } + + hasPanels() { + return this.panels && this.panels.length > 0; + } + + hasActions(panel: XPanelComponent) { + return this.getValue(panel.hasNext).pipe( + concatMap((hasNext) => + this.getValue(panel.hasPrev).pipe( + map((hasPrev) => { + return { + hasNext, + hasPrev, + }; + }) + ) + ), + concatMap((hasNextPrev) => + this.getValue(panel.hasReset).pipe( + map((hasReset) => { + return { + ...hasNextPrev, + hasReset, + }; + }) + ) + ), + map((info) => info.hasNext || info.hasPrev || info.hasReset) + ); + } + + getIsAccordion() { + return this.IS_ACCORDION; + } + + getExpanded(panel: XPanelComponent) { + return this.getValue(panel.id).pipe( + concatMap((panelId) => + this.getValue(panel.expanded).pipe( + map((panelExpanded) => { + return { + panelId, + panelExpanded, + }; + }) + ) + ), + map((panelInfo) => { + return { + ...panelInfo, + isAccordion: this.IS_ACCORDION, + }; + }), + map((info) => + info.isAccordion ? this.selected === info.panelId : info.panelExpanded + ) + ); + } + + // + //#region Button Providers ... + getButtonIcon(panel: XPanelComponent, type: XNPRButtonType) { + return this.getActionButton(panel, type).pipe( + concatMap((actionButton) => this.getValue(actionButton.icon)) + ); + } + + getButtonType(panel: XPanelComponent, type: XNPRButtonType) { + return this.getActionButton(panel, type).pipe( + concatMap((actionButton) => this.getValue(actionButton.type)), + map((btnType) => + !isNullOrEmptyString(btnType) ? btnType : XButtonType.Raised + ) + ); + } + + getButtonTitle(panel: XPanelComponent, type: XNPRButtonType) { + return this.getActionButton(panel, type).pipe( + concatMap((actionButton) => this.getResourceValue(actionButton.title)) + ); + } + + getButtonColor(panel: XPanelComponent, type: XNPRButtonType) { + return this.getActionButton(panel, type).pipe( + concatMap((actionButton) => this.getValue(actionButton.color)), + map((btnColor) => + !isNullOrEmptyString(btnColor) ? btnColor : XColorWithBrightness.Primary + ) + ); + } + + getButtonDisabled(panel: XPanelComponent, type: XNPRButtonType) { + return this.getActionButtonDisabled(panel, type); + } + + getButtonCssClass(panel: XPanelComponent, type: XNPRButtonType) { + return this.getActionButton(panel, type).pipe( + concatMap((actionButton) => this.getArrayValue(actionButton.cssClass)) + ); + } + + getButtonIconPosition(panel: XPanelComponent, type: XNPRButtonType) { + return this.getActionButton(panel, type).pipe( + concatMap((actionButton) => this.getValue(actionButton.iconPosition)) + ); + } + + getButtonForegroundColor(panel: XPanelComponent, type: XNPRButtonType) { + return this.getActionButton(panel, type).pipe( + concatMap((actionButton) => + this.getOptionalValue(actionButton.foregroundColor) + ) + ); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + async handleNPRStep(panel: XPanelComponent, type: XNPRButtonType) { + // + switch (type) { + // + case this.NPRButtonTypes.Next: + await this.handleNextStep(panel); + break; + + // + case this.NPRButtonTypes.Prev: + await this.handlePrevStep(panel); + break; + + // + case this.NPRButtonTypes.Reset: + await this.handleReset(panel); + break; + } + } + + async handleNextStep(panel: XPanelComponent, force?: boolean) { + // + if ( + this.uiDisabled || + this.loading || + !panel || + panel.loading || + panel.uiDisabled || + !this.hasPanels() + ) { + return; + } + + // + let nextId: string; + const panelId = await this.getValueAsync(panel.id); + const panelIndex = await this.getPanelIndex(panelId); + if (panelIndex === -1) { + return; + } + if (panelIndex < this.panels.length - 1) { + nextId = await this.getValueAsync( + this.panels.toArray()[panelIndex + 1].id + ); + } + if (isNullOrEmptyString(nextId)) { + return; + } + + // + if (!force) { + // + const isUiBusy = this.uiDisabled || this.loading; + const canNextStep = await this.getValueAsync(this.canNext); + const disableNextStep = await this.getValueAsync(panel.disableNext); + + // + if (isUiBusy || disableNextStep || !canNextStep) { + return; + } + + // + const isStepNextConfitmedType = panel.nextConfirmer + ? panel.nextConfirmer() + : this.getValue(true); + const isStepNextConfirmed = await this.getValueAsync( + isStepNextConfitmedType + ); + if (!isStepNextConfirmed) { + return; + } + } + + // + this.selected = nextId; + this.detectChanges(); + this.selectedChange.emit(nextId); + this.onNext.emit(panelId); + this.detectChanges(); + } + + async handlePrevStep(panel: XPanelComponent, force?: boolean) { + // + if ( + this.uiDisabled || + this.loading || + !panel || + panel.loading || + panel.uiDisabled || + !this.hasPanels() + ) { + return; + } + + // + let prevId: string; + const panelId = await this.getValueAsync(panel.id); + const panelIndex = await this.getPanelIndex(panelId); + if (panelIndex === -1) { + return; + } + if (panelIndex > 0) { + prevId = await this.getValueAsync( + this.panels.toArray()[panelIndex - 1].id + ); + } + if (isNullOrEmptyString(prevId)) { + return; + } + + // + if (!force) { + const isUiBusy = this.uiDisabled || this.loading; + const canPrevStep = await this.getValueAsync(this.canPrev); + const disablePrevStep = await this.getValueAsync(panel.disablePrev); + if (isUiBusy || disablePrevStep || !canPrevStep) { + return; + } + + // + const isStepPrevConfitmedType = panel.prevConfirmer + ? panel.prevConfirmer() + : this.getValue(true); + const isStepPrevConfirmed = await this.getValueAsync( + isStepPrevConfitmedType + ); + if (!isStepPrevConfirmed) { + return; + } + } + + // + this.selected = prevId; + this.detectChanges(); + this.selectedChange.emit(prevId); + this.onPrev.emit(panelId); + this.detectChanges(); + } + + async handleReset(panel: XPanelComponent, force?: boolean) { + // + if ( + this.uiDisabled || + this.loading || + !panel || + panel.loading || + panel.uiDisabled || + !this.hasPanels() + ) { + return; + } + + // + const stepId = await this.getValueAsync(panel.id); + const firstId = await this.getValueAsync(this.panels.toArray()[0].id); + + // + if (!force) { + const isUiBusy = this.uiDisabled || this.loading; + const canResetStep = await this.getValueAsync(this.canReset); + const disableResetStep = await this.getValueAsync(panel.disableReset); + if (isUiBusy || disableResetStep || !canResetStep) { + return; + } + + // + const isConfirmedType = await this.resetConfirmer(); + const isConfirmed = await this.getValueAsync(isConfirmedType); + + // + if (!isConfirmed) { + return; + } + + // + const isStepResetConfitmedType = panel.resetConfirmer + ? panel.resetConfirmer() + : this.getValue(true); + const isStepResetConfirmed = await this.getValueAsync( + isStepResetConfitmedType + ); + if (!isStepResetConfirmed) { + return; + } + } + + // + this.selected = firstId; + this.detectChanges(); + this.selectedChange.emit(firstId); + this.onReset.emit(stepId); + this.detectChanges(); + } + + async handleToStep(panel: XPanelComponent) { + // + if ( + this.uiDisabled || + this.loading || + !panel || + panel.loading || + panel.uiDisabled || + !this.hasPanels() || + this.selected === (await this.getValueAsync(panel.id)) + ) { + return; + } + + // + const panelId = await this.getValueAsync(panel.id); + + // + this.selected = panelId; + this.detectChanges(); + this.selectedChange.emit(panelId); + this.detectChanges(); + } + + async handlePanelOpened(panel: XPanelComponent) { + // + const panelId = await this.getValueAsync(panel.id); + this.selected = panelId; + this.detectChanges(); + } + //#endregion + + // + //#region Actions ... + async next(force?: boolean) { + // + const panel = await this.getPanel(this.selected); + if (!panel) { + return; + } + + // + await this.handleNextStep(panel, force); + } + + async prev(force?: boolean) { + // + const panel = await this.getPanel(this.selected); + if (!panel) { + return; + } + + // + await this.handlePrevStep(panel, force); + } + + async reset(force?: boolean) { + // + const panel = await this.getPanel(this.selected); + if (!panel) { + return; + } + + // + await this.handleReset(panel, force); + } + + async to(id: XStandardType, force?: boolean) { + // + if (!id || !this.hasPanels()) { + return; + } + + // + const panelId = await this.getValueAsync(id); + if (!panelId) { + return; + } + + // + const panelIndex = await this.getPanelIndex(panelId); + if (panelIndex === -1) { + return; + } + + // + const panel = await this.getPanel(panelId); + if (!panel) { + return; + } + + // + const selectedIndex = await this.getPanelIndex(this.selected); + if (selectedIndex === -1 || selectedIndex === panelIndex) { + return; + } + + // + if (panelIndex > selectedIndex) { + // + // next confirm ... + // + if (!force) { + // + const isUiBusy = this.uiDisabled || this.loading; + const canNextStep = await this.getValueAsync(this.canNext); + const disableNextStep = await this.getValueAsync(panel.disableNext); + + // + if (isUiBusy || disableNextStep || !canNextStep) { + return; + } + + // + const isStepNextConfitmedType = panel.nextConfirmer + ? panel.nextConfirmer() + : this.getValue(true); + const isStepNextConfirmed = await this.getValueAsync( + isStepNextConfitmedType + ); + if (!isStepNextConfirmed) { + return; + } + } + + // + this.selected = panelId; + this.selectedChange.emit(panelId); + } else if (panelIndex < selectedIndex) { + // + // prev Confirm ... + // + if (!force) { + const isUiBusy = this.uiDisabled || this.loading; + const canPrevStep = await this.getValueAsync(this.canPrev); + const disablePrevStep = await this.getValueAsync(panel.disablePrev); + if (isUiBusy || disablePrevStep || !canPrevStep) { + return; + } + + // + const isStepPrevConfitmedType = panel.prevConfirmer + ? panel.prevConfirmer() + : this.getValue(true); + const isStepPrevConfirmed = await this.getValueAsync( + isStepPrevConfitmedType + ); + if (!isStepPrevConfirmed) { + return; + } + } + + // + this.selected = panelId; + this.selectedChange.emit(panelId); + } + } + + async expandAll() { + // + if (!this.hasChild(this.PANELS)) { + return; + } + + // + this.PANELS.toArray().forEach((panel) => { + // + if (panel) { + // + panel.expanded = true; + panel.detectChanges(); + } + }); + + // + this.detectChanges(); + } + + async collapseAll() { + // + if (!this.hasChild(this.PANELS)) { + return; + } + + // + this.PANELS.toArray().forEach((panel) => { + // + if (panel) { + // + panel.expanded = false; + panel.detectChanges(); + } + }); + + // + this.detectChanges(); + } + //#endregion + + // + //#region Private ... + private async handleStartupState() { + // + const state = await this.getValueAsync(this.startupState); + if (isNullOrEmptyString(state)) { + return; + } + + // + switch (state) { + // + case XExpandablePanelStartupState.CollapseAll: + await this.collapseAll(); + break; + + // + case XExpandablePanelAction.ExpandAll: + await this.expandAll(); + break; + } + } + + private defaultStepperResetConfirmer() { + // + return new Promise((resolver) => { + // + this.managerService.dialogService.presentAreYouSureYesNoDialog( + () => resolver(true), + XModalButtonRole.Destructive, + () => resolver(false) + ); + }); + } + + private getActionButton(panel: XPanelComponent, type: XNPRButtonType) { + // + switch (type) { + // + case this.NPRButtonTypes.Next: + return this.getValue(panel.nextButton); + + // + case this.NPRButtonTypes.Prev: + return this.getValue(panel.prevButton); + + // + case this.NPRButtonTypes.Reset: + return this.getValue(panel.resetButton); + } + } + + private getActionButtonDisabled( + panel: XPanelComponent, + type: XNPRButtonType + ) { + // + switch (type) { + // + case this.NPRButtonTypes.Next: + return this.getOptionalValue(panel.disableNext).pipe( + map((sDisable) => this.uiDisabled || panel.uiDisabled || sDisable) + ); + + // + case this.NPRButtonTypes.Prev: + return this.getOptionalValue(panel.disablePrev).pipe( + map((sDisable) => this.uiDisabled || panel.uiDisabled || sDisable) + ); + + // + case this.NPRButtonTypes.Reset: + return this.getOptionalValue(panel.disableReset).pipe( + map((sDisable) => this.uiDisabled || panel.uiDisabled || sDisable) + ); + } + } + + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XExpandablePanelAction.NEXT: + await this.next(model.force); + break; + + // + case XExpandablePanelAction.PREV: + await this.prev(model.force); + break; + + // + case XExpandablePanelAction.RESET: + await this.reset(model.force); + break; + + // + case XExpandablePanelAction.TO: + if (model.payload) { + await this.to(model.payload, model.force); + } + break; + + // + case XExpandablePanelAction.ExpandAll: + await this.expandAll(); + break; + + // + case XExpandablePanelAction.CollapseAll: + await this.collapseAll(); + break; + + // + case XExpandablePanelAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + + private async getPanel(id: string) { + // + if (!id || !this.hasPanels()) { + return null; + } + + // + return this.panels.toArray().find(async (p) => { + const pId = await this.getValueAsync(p.id); + return pId === id; + }); + } + + private async getPanelIndex(id: string) { + // + if (!id || !this.hasPanels()) { + return -1; + } + + // + let result = -1; + const panels = this.panels.toArray(); + if (!this.hasChild(panels)) { + return result; + } + + // + for (let i = 0; i < panels.length; i++) { + // + const panel = panels[i]; + const pId = await this.getValueAsync(panel.id); + if (pId === id) { + result = i; + } + } + + // + return result; + } + + // + //#region Color Appliers ... + private async applyHeaderColor() { + // + const color = await this.getValueAsync(this.headerColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XExpandablePanelCssVars.HeaderForegroundColor, + `var(${fgColor})` + ); + this.setCssVar( + XExpandablePanelCssVars.HeaderBackgroundColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + private async applyHeaderHighlightColor() { + // + const color = await this.getValueAsync(this.headerHighlightColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XExpandablePanelCssVars.HeaderHighlightForegroundColor, + `var(${fgColor})` + ); + this.setCssVar( + XExpandablePanelCssVars.HeaderHighlightBackgroundColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-expandable-panel.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XButtonModule } from '../x-button/x-button.module'; +import { XPanelComponent } from './x-panel/x-panel.component'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XExpandablePanelComponent } from './x-expandable-panel.component'; + +@NgModule({ + declarations: [XPanelComponent, XExpandablePanelComponent], + imports: [ + XIconModule, + XCardModule, + CommonModule, + XButtonModule, + XSlotterModule, + XBaseComponentModule, + ], + exports: [ + XIconModule, + XCardModule, + CommonModule, + XButtonModule, + XSlotterModule, + XPanelComponent, + XBaseComponentModule, + XExpandablePanelComponent, + ], +}) +export class XExpandablePanelModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-expandable-panel.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XModalButtonRole, + XColorIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { concatMap, map } from 'rxjs/operators'; +import { + XNextPrevResetHostContainer, + XCardNextPrevResetHostContainer, + IXCardNextPrevResetHostContainer, + XCardNextPrevResetHostContainerComponent, +} from '../classes/i-x-next-prev-reset.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XComponentCardContainer } from '../x-card/x-card.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XExpandablePanelStartupState { + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', +} + +export type XExpandablePanelStartupStateIdentifier = + | XExpandablePanelStartupState + | string; + +export enum XExpandablePanelAction { + NEXT = 'next', + PREV = 'prev', + RESET = 'reset', + TO = 'to', + + // + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', + + // + Refresh = 'refresh', +} + +export type XExpandablePanelActionIdentifier = XExpandablePanelAction | string; + +export interface XExpandablePanelActionModel + extends XBaseActionModel { + force?: boolean; +} + +export enum XExpandablePanelCssVars { + // + HeaderForegroundColor = 'header-foreground-color', + HeaderBackgroundColor = 'header-background-color', + + // + HeaderHighlightForegroundColor = 'header-highlight-foreground-color', + HeaderHighlightBackgroundColor = 'header-highlight-background-color', +} + +export type XExpandablePandelCssVarsIdentifier = + | XExpandablePanelCssVars + | string; + +// +//#region Inside Host Container ... +export interface IXExpandablePandelContainer + extends IXCardNextPrevResetHostContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + startupState: XStandardType; + isAccordion: XStandardType; + multiExpanded: XStandardType; + hideDescriptionOnSmallScreens: XStandardType; + + // + applyColors: XStandardType; + headerColor: XStandardType; + headerHighlightColor: XStandardType; + + // + resetConfirmer: () => XStandardType; + selected: string; + + // + selectedChange: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XExpandablePandelContainer + extends XCardNextPrevResetHostContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider = new Subject(); + readonly cssClass: XStandardType>; + + // + readonly startupState: XStandardType = + XExpandablePanelStartupState.ExpandAll; + readonly isAccordion: XStandardType; + readonly multiExpanded: XStandardType; + readonly hideDescriptionOnSmallScreens: XStandardType; + + // + readonly applyColors: XStandardType = true; + readonly headerColor: XStandardType = + XColorWithBrightness.Primary; + readonly headerHighlightColor: XStandardType = + XColorWithBrightness.Warning; + + // + readonly resetConfirmer: () => XStandardType = () => { + return new Promise((resolver) => { + // + this.managerService.dialogService.presentAreYouSureYesNoDialog( + () => resolver(true), + XModalButtonRole.Destructive, + () => resolver(false) + ); + }); + }; + readonly selected: string; + + // + readonly selectedChange: EventEmitter; + //#endregion + + // + //#region Constructor ... + constructor( + public managerService: XManagerService, + protected cardPropertyProvider: XComponentCardContainer, + protected nextPrevResetHostPropertyProvider: XNextPrevResetHostContainer + ) { + super(cardPropertyProvider, nextPrevResetHostPropertyProvider); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XExpandablePandelContainerComponent + extends XCardNextPrevResetHostContainerComponent + implements IXExpandablePandelContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + startupState: XStandardType = + this.propertyProvider.startupState; + + @Input() + isAccordion: XStandardType = this.propertyProvider.isAccordion; + + @Input() + multiExpanded: XStandardType = this.propertyProvider.multiExpanded; + + @Input() + hideDescriptionOnSmallScreens: XStandardType = + this.propertyProvider.hideDescriptionOnSmallScreens; + + // + @Input() + applyColors: XStandardType = this.propertyProvider.applyColors; + + @Input() + headerColor: XStandardType = + this.propertyProvider.headerColor; + + @Input() + headerHighlightColor: XStandardType = + this.propertyProvider.headerHighlightColor; + + // + @Input() + resetConfirmer: () => XStandardType = + this.propertyProvider.resetConfirmer; + + @Input() + selected: string = this.propertyProvider.selected; + + // + @Output() + selectedChange: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XExpandablePandelContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXExpandablePandelContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + startupState: this.getValue( + container ? container.startupState : this.startupState + ), + isAccordion: this.getValue( + container ? container.isAccordion : this.isAccordion + ), + multiExpanded: this.getValue( + container ? container.multiExpanded : this.multiExpanded + ), + hideDescriptionOnSmallScreens: this.getValue( + container + ? container.hideDescriptionOnSmallScreens + : this.hideDescriptionOnSmallScreens + ), + applyColors: this.getValue( + container ? container.applyColors : this.applyColors + ), + headerColor: this.getValue( + container ? container.headerColor : this.headerColor + ), + headerHighlightColor: this.getValue( + container + ? container.headerHighlightColor + : this.headerHighlightColor + ), + resetConfirmer: this.getValue( + container ? container.resetConfirmer : this.resetConfirmer + ), + selected: this.getValue( + container ? container.selected : this.selected + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + selectedChange: this.selectedChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-panel\x-panel.component.html + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-panel\x-panel.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-panel\x-panel.component.ts + +import { + Component, + ViewChild, + TemplateRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XPanelAction, + XPanelActionModel, + XPanelContainerComponent, +} from './x-panel.typings'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; + +@Component({ + selector: 'x-panel', + templateUrl: './x-panel.component.html', + styleUrls: ['./x-panel.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XPanelComponent extends XPanelContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + //#region ViewChilds ... + @ViewChild('title', { static: false }) + title!: TemplateRef; + + @ViewChild('description', { static: false }) + description!: TemplateRef; + + @ViewChild('content', { static: false }) + content!: TemplateRef; + + @ViewChild('actions', { static: false }) + actions!: TemplateRef; + //#endregion + //#endregion + + // + //#region LifeCycle ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Privates ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XPanelAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-panel\x-panel.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { + IXNextPrevResetContainer, + XNextPrevResetContainer, + XNextPrevResetContainerComponent, +} from '../../classes/i-x-next-prev-reset.container'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { XStandardType, XOneOrManyType, generateUuid } from 'x-framework-core'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +export enum XPanelAction { + Refresh = 'refresh', +} + +export type XPanelActionIdentifier = XPanelAction | string; + +export interface XPanelActionModel + extends XBaseActionModel {} + +// +//#region Inside Card Container ... +export interface IXPanelContainer extends IXNextPrevResetContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + id: XStandardType; + + // + hideToggle: XStandardType; + expanded: XStandardType; + hasCustomActions: XStandardType; +} + +@Injectable({ + providedIn: 'root' +}) +export class XPanelContainer + extends XNextPrevResetContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider = new Subject(); + readonly cssClass: XStandardType>; + + // + readonly id: XStandardType = generateUuid('XPanelComponent'); + + // + readonly hideToggle: XStandardType = false; + readonly expanded: XStandardType; + readonly hasCustomActions: XStandardType; + //#endregion +} + +@Injectable({ + providedIn: 'root' +}) +@Component({ + template: ` + + `, +}) +export abstract class XPanelContainerComponent + extends XNextPrevResetContainerComponent + implements IXPanelContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + id: XStandardType = this.propertyProvider.id; + + @Input() + hideToggle: XStandardType = this.propertyProvider.hideToggle; + + @Input() + expanded: XStandardType = this.propertyProvider.expanded; + + @Input() + hasCustomActions: XStandardType = + this.propertyProvider.hasCustomActions; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XPanelContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXPanelContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + id: this.getValue(container ? container.id : this.id), + hideToggle: this.getValue( + container ? container.hideToggle : this.hideToggle + ), + expanded: this.getValue( + container ? container.expanded : this.expanded + ), + hasCustomActions: this.getValue( + container ? container.hasCustomActions : this.hasCustomActions + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-fab\x-fab.component.html + +
+ + + + + + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-fab\x-fab.component.scss + +:host ::ng-deep .x-icon { + background-color: transparent !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-fab\x-fab.component.ts + +import { + XFabItem, + XFabButton, + XFabAction, + XFabButtonSide, + XFabActionModel, + XFabContainerComponent, +} from './x-fab.typings'; +import { + nameof, + hasChild, + isFunction, + isNullOrUndefined, +} from 'x-framework-core'; +import { IonFab } from '@ionic/angular'; +import { concatMap, map } from 'rxjs/operators'; +import { of, Observable, Subject, Subscription } from 'rxjs'; +import { Component, ViewChild, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-fab', + templateUrl: './x-fab.component.html', + styleUrls: ['./x-fab.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XFabComponent extends XFabContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + private FAB: IonFab; + private PARSED_ITEM: XFabItem; + private PARSED_CHILDS: XFabButton[] = []; + private HAS_TOP_SIDE_CHILDS: boolean; + private HAS_END_SIDE_CHILDS: boolean; + private HAS_START_SIDE_CHILDS: boolean; + private HAS_BOTTOM_SIDE_CHILDS: boolean; + + // + @ViewChild('fab', { static: false }) + set fab(v: IonFab) { + // + if (!v) { + return; + } + + // + this.FAB = v; + this.detectChanges(); + } + + get fab() { + return this.FAB; + } + + get parsedItem() { + return this.PARSED_ITEM; + } + + get parsedChilds() { + return this.PARSED_CHILDS; + } + + get hasTopSideChilds() { + return this.HAS_TOP_SIDE_CHILDS; + } + + get hasEndSideChilds() { + return this.HAS_END_SIDE_CHILDS; + } + + get hasStartSideChilds() { + return this.HAS_START_SIDE_CHILDS; + } + + get hasBottomSideChilds() { + return this.HAS_BOTTOM_SIDE_CHILDS; + } + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Items Changes ... + // + const isItemsChanged = changeKeys.includes(nameof('item')); + if (isItemsChanged) { + // + const item = await this.getValueAsync(this.item); + this.detectChanges(); + if (!isNullOrUndefined(item)) { + // + this.PARSED_ITEM = item; + this.detectChanges(); + + // + if (item.childs) { + // + const childs = await this.getValueAsync(item.childs); + if (!hasChild(childs)) { + return; + } + + // + // Sorting Childs ... + this.PARSED_CHILDS = childs.sort((a, b) => a.index - b.index); + this.detectChanges(); + + // + // Determine Sides ... + this.PARSED_CHILDS.forEach(async (ch) => { + // + const side = await this.getValueAsync(ch.side); + switch (side) { + case XFabButtonSide.TOP: + if (!this.HAS_TOP_SIDE_CHILDS) { + this.HAS_TOP_SIDE_CHILDS = true; + } + break; + + case XFabButtonSide.END: + if (!this.HAS_END_SIDE_CHILDS) { + this.HAS_END_SIDE_CHILDS = true; + } + break; + + case XFabButtonSide.START: + if (!this.HAS_START_SIDE_CHILDS) { + this.HAS_START_SIDE_CHILDS = true; + } + break; + + case XFabButtonSide.BOTTOM: + if (!this.HAS_BOTTOM_SIDE_CHILDS) { + this.HAS_BOTTOM_SIDE_CHILDS = true; + } + break; + } + + // + this.detectChanges(); + }); + } + } + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Provider ... + getFixedValue(items: XFabItem) { + return of(items).pipe( + concatMap((rItems) => { + // + if (!rItems) { + return of(''); + } + + // + const obs = this.getOptionalValue(rItems.fixed) as Observable; + return obs.pipe(map((res) => (res === true ? 'fixed' : ''))); + }) + ); + } + + // + //#region Fab Button Side Providers ... + isTopSideFab(child: XFabButton) { + return this.getValue(child.side).pipe( + map((side) => side === XFabButtonSide.TOP.toString()) + ); + } + + isEndSideFab(child: XFabButton) { + return this.getValue(child.side).pipe( + map((side) => side === XFabButtonSide.END.toString()) + ); + } + + isStartSideFab(child: XFabButton) { + return this.getValue(child.side).pipe( + map((side) => side === XFabButtonSide.START.toString()) + ); + } + + isBottomSideFab(child: XFabButton) { + return this.getValue(child.side).pipe( + map((side) => side === XFabButtonSide.BOTTOM.toString()) + ); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + async handleFabButtonClicked(item: XFabButton | XFabButton) { + // + if (!item) { + return; + } + + // + if (item.handler && isFunction(item.handler)) { + await this.getValueAsync(item.handler(item.id)); + } + } + //#endregion + + // + //#region Actions ... + async close() { + // + if (!this.fab) { + return; + } + + // + await this.fab.close(); + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.actionProvider.asObservable() + ).subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XFabAction.Refresh: + this.detectChanges(); + break; + + // + case XFabAction.CLOSE: + await this.close(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-fab\x-fab.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XFabComponent } from './x-fab.component'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XFabComponent], + imports: [CommonModule, XBaseComponentModule, XIconModule], + exports: [XBaseComponentModule, XIconModule, XFabComponent], +}) +export class XFabModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-fab\x-fab.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XRouterDirection, + XResourceIdentifier, + XRouterTargetIdentifier, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XFabVerticalSlot { + TOP = 'top', + CENTER = 'center', + BOTTOM = 'bottom', +} + +export type XFabVerticalSlotIdentifier = XFabVerticalSlot | string; + +export enum XFabHorizontalSlot { + START = 'start', + CENTER = 'center', + END = 'end', +} + +export type XFabHorizontalSlotIdentifier = XFabHorizontalSlot | string; + +export enum XFabButtonSide { + TOP = 'top', + END = 'end', + START = 'start', + BOTTOM = 'bottom', +} + +export type XFabButtonSideIdentifier = XFabButtonSide | string; + +export enum XFabButtonSize { + REGULAR = '', + MINI = 'small', +} + +export type XFabButtonSizeIdentifier = XFabButtonSize | string; + +export interface XFabButton { + // + id: string; + index: number; + + // + icon: XStandardType; + side: XStandardType; + size?: XStandardType; + + // + disabled?: XStandardType; + hidden?: XStandardType; + + // + color: XStandardType; + + // + tooltip?: XStandardType; + + // + href?: XStandardType; + download?: XStandardType; + routerDirection?: XStandardType; + routerTarget?: XStandardType; + + // + handler?: (id?: string) => XStandardType; +} + +type FAB_MAIN_BUTTON = Pick< + XFabButton, + | 'id' + | 'icon' + | 'size' + | 'color' + | 'hidden' + | 'handler' + | 'tooltip' + | 'disabled' + | 'href' + | 'download' + | 'routerDirection' + | 'routerTarget' +>; + +export interface XFabItem extends FAB_MAIN_BUTTON { + // + vertical: XStandardType; + horizontal: XStandardType; + + // + fixed?: XStandardType; + edged?: XStandardType; + + // + activated?: XStandardType; + + // + childs?: XStandardType; +} + +export enum XFabAction { + CLOSE = 'close', + + // + Refresh = 'refresh', +} + +export type XFabActionIdentifier = XFabAction | string; + +export interface XFabActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXFabContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + childsCssClass: XStandardType>; + item: XStandardType; +} + +@Injectable({ + providedIn: 'root' +}) +export class XFabContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly childsCssClass: XStandardType>; + readonly item: XStandardType = null; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XFabContainerComponent + extends XBaseContainerComponent + implements IXFabContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + childsCssClass: XStandardType> = + this.propertyProvider.childsCssClass; + + @Input() + item: XStandardType = this.propertyProvider.item; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XFabContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps(container?: IXFabContainer): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + childsCssClass: this.getValue( + container ? container.childsCssClass : this.childsCssClass + ), + item: this.getValue(container ? container.item : this.item), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-drop-area\x-file-drop-area.component.html + + + + + + + + +
+ +
+
+
+ + + +
+
+ +
+
+
+ + + +
+
+ +
+ + {{ getResourceValue(message) | async }} + +
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-drop-area\x-file-drop-area.component.scss + +:host { + width: inherit; + height: inherit; + + --label-color: var(--x-color-light-contrast); +} + +.x-wrapper { + cursor: grab; +} + +.x-wrapper:active { + cursor: grabbing; +} + +.no-drag { + cursor: unset; +} + +.x-droppable-container { + border-radius: 10px; +} + +.x-drop-area { + width: 100%; + height: auto; + + box-sizing: border-box; + + border-radius: 10px; + border: 2px dashed var(--label-color); + + text-align: center; + + padding: { + top: 16px; + bottom: 16px; + } +} + +.x-drag-drop-title { + color: var(--label-color); +} + +.x-icon { + margin: { + left: auto; + right: auto; + } + + margin-bottom: 16px; + + font-size: 8px; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-drop-area\x-file-drop-area.component.ts + +import { + nameof, + parseXColor, + isNullOrEmptyString, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XFileDropAreaAction, + XFileDropAreaCssVars, + XFileDropAreaActionModel, + XFileDropAreaContainerComponent, +} from './x-file-drop-area.typings'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-file-drop-area', + templateUrl: './x-file-drop-area.component.html', + styleUrls: ['./x-file-drop-area.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XFileDropAreaComponent extends XFileDropAreaContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Color Changed ... + const isColorChanged = changeKeys.includes( + nameof('color') + ); + if (isColorChanged) { + await this.applyColor(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Provider ... + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe((model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XFileDropAreaAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async applyColor() { + // + const color = await this.getValueAsync(this.color); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFileDropAreaCssVars.LabelColor, `var(${fgColor})`); + + // + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-drop-area\x-file-drop-area.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XListModule } from '../x-list/x-list.module'; +import { XFileDropAreaComponent } from './x-file-drop-area.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XFileDropAreaComponent], + imports: [ + XListModule, + XCardModule, + XIconModule, + CommonModule, + XBaseComponentModule, + ], + exports: [ + XCardModule, + XIconModule, + XListModule, + XBaseComponentModule, + XFileDropAreaComponent, + ], +}) +export class XFileDropAreaModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-drop-area\x-file-drop-area.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XCardActionModel, + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { + XBaseComponentContainer, + IXBaseComponentContainer, + XBaseComponentContainerComponent, +} from '../classes/i-x-base.container'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XIconIdentifier, XIconNames } from '../x-icon/x-icon.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XFileDropAreaAction { + Refresh = 'refresh', +} + +export type XFileDropAreaActionIdentifier = XFileDropAreaAction | string; + +export interface XFileDropAreaActionModel + extends XBaseActionModel {} + +export enum XFileDropAreaCssVars { + LabelColor = 'label-color', +} + +// +//#region Container ... +export interface IXFileDropAreaContainer extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + icon: XStandardType; + message: XStandardType; + + // + disableFileCheck: XStandardType; + + // + supportsMultipleFiles: XStandardType; + allowedExtensions: XStandardType>; + + // + dragDisabled: XStandardType; + contentTemplate: TemplateRef; + filesChangeEmiter: EventEmitter>; + + // + successColor: XStandardType; + errorColor: XStandardType; + accentColor: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XFileDropAreaContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider = new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + readonly icon: XStandardType = XIconNames.arrow_down; + readonly message: XStandardType = + XResourceIDs.drag_drop_here; + + // + readonly disableFileCheck: XStandardType = false; + + // + readonly allowedExtensions: XStandardType> = []; + readonly supportsMultipleFiles: XStandardType = true; + + // + readonly dragDisabled: XStandardType = false; + readonly contentTemplate: TemplateRef; + readonly filesChangeEmiter = new EventEmitter>(); + + // + readonly successColor: XStandardType = + XColorWithBrightness.Success; + readonly errorColor: XStandardType = + XColorWithBrightness.Danger; + readonly accentColor: XStandardType = + XColorWithBrightness.Secondary; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XFileDropAreaContainerComponent + extends XComponentCardContainerComponent + implements IXFileDropAreaContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Input() + icon: XStandardType = this.propertyProvider.icon; + + @Input() + message: XStandardType = this.propertyProvider.message; + + // + @Input() + disableFileCheck: XStandardType = + this.propertyProvider.disableFileCheck; + + // + @Input() + supportsMultipleFiles: XStandardType = + this.propertyProvider.supportsMultipleFiles; + + @Input() + allowedExtensions: XStandardType> = + this.propertyProvider.allowedExtensions; + + // + @Input() + dragDisabled: XStandardType = this.propertyProvider.dragDisabled; + + @Input() + contentTemplate: TemplateRef = this.propertyProvider.contentTemplate; + + @Output() + filesChangeEmiter: EventEmitter> = new EventEmitter(); + + // + @Input() + successColor: XStandardType = + this.propertyProvider.successColor; + + @Input() + errorColor: XStandardType = + this.propertyProvider.errorColor; + + @Input() + accentColor: XStandardType = + this.propertyProvider.accentColor; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XFileDropAreaContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXFileDropAreaContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + icon: this.getValue(container ? container.icon : this.icon), + message: this.getValue( + container ? container.message : this.message + ), + disableFileCheck: this.getValue( + container ? container.disableFileCheck : this.disableFileCheck + ), + supportsMultipleFiles: this.getValue( + container + ? container.supportsMultipleFiles + : this.supportsMultipleFiles + ), + allowedExtensions: this.getValue( + container ? container.allowedExtensions : this.allowedExtensions + ), + dragDisabled: this.getValue( + container ? container.dragDisabled : this.dragDisabled + ), + successColor: this.getValue( + container ? container.successColor : this.successColor + ), + errorColor: this.getValue( + container ? container.errorColor : this.errorColor + ), + accentColor: this.getValue( + container ? container.accentColor : this.accentColor + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + contentTemplate: container + ? container.contentTemplate + : this.contentTemplate, + + // + filesChangeEmiter: this.filesChangeEmiter, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentFileDropAreaContainer = { + [TKey in keyof IXFileDropAreaContainer as `fileDropArea${Capitalize< + string & TKey + >}`]: IXFileDropAreaContainer[TKey]; +}; + +export interface IXComponentFileDropAreaContainer + extends TXComponentFileDropAreaContainer, + IXBaseComponentContainer { + showFileDropArea: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XComponentFileDropAreaContainer + extends XBaseComponentContainer + implements Readonly +{ + // + //#region Props ... + // + readonly showFileDropArea: XStandardType = true; + + // + readonly fileDropAreaActionProvider: Subject = + this.propertyProvider.actionProvider; + + readonly fileDropAreaCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly fileDropAreaColor: XStandardType = + this.propertyProvider.color; + + readonly fileDropAreaForegroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + readonly fileDropAreaIcon: XStandardType = + this.propertyProvider.icon; + + readonly fileDropAreaMessage: XStandardType = + this.propertyProvider.message; + + // + readonly fileDropAreaDisableFileCheck: XStandardType = + this.propertyProvider.disableFileCheck; + + // + readonly fileDropAreaSupportsMultipleFiles: XStandardType = + this.propertyProvider.supportsMultipleFiles; + + readonly fileDropAreaAllowedExtensions: XStandardType< + XOneOrManyType + > = this.propertyProvider.allowedExtensions; + + // + readonly fileDropAreaDragDisabled: XStandardType = + this.propertyProvider.dragDisabled; + + readonly fileDropAreaContentTemplate: TemplateRef = + this.propertyProvider.contentTemplate; + + readonly fileDropAreaFilesChangeEmiter: EventEmitter> = + this.propertyProvider.filesChangeEmiter; + + // + readonly fileDropAreaSuccessColor: XStandardType = + this.propertyProvider.successColor; + + readonly fileDropAreaErrorColor: XStandardType = + this.propertyProvider.errorColor; + + readonly fileDropAreaAccentColor: XStandardType = + this.propertyProvider.accentColor; + + // + //#region ComponentsCard ... + readonly fileDropAreaIsInPage: XStandardType = false; + readonly fileDropAreaWrapWithCard: XStandardType = false; + readonly fileDropAreaCardShowFooter: XStandardType = false; + readonly fileDropAreaCardShowActions: XStandardType = false; + readonly fileDropAreaCardColor: XStandardType = + XColorWithBrightness.Light; + readonly fileDropAreaCardForegroundColor: XStandardType = false; + readonly fileDropAreaCardTitle: XStandardType; + readonly fileDropAreaCardActionProvider: Subject = + new Subject(); + readonly fileDropAreaCardSubTitle: XStandardType; + readonly fileDropAreaCardCssClass: XStandardType>; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XFileDropAreaContainer) { + super(); + } + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +@Component({ + template: ` + + `, +}) +export abstract class XComponentFileDropAreaContainerComponent + extends XBaseComponentContainerComponent + implements IXComponentFileDropAreaContainer +{ + // + //#region Props ... + // + @Input() + fileDropAreaActionProvider: Subject = + this.propertyProvider.fileDropAreaActionProvider; + + @Input() + fileDropAreaCssClass: XStandardType> = + this.propertyProvider.fileDropAreaCssClass; + + // + @Input() + fileDropAreaColor: XStandardType = + this.propertyProvider.fileDropAreaColor; + + @Input() + fileDropAreaForegroundColor: XStandardType = + this.propertyProvider.fileDropAreaForegroundColor; + + // + @Input() + fileDropAreaIcon: XStandardType = + this.propertyProvider.fileDropAreaIcon; + + @Input() + fileDropAreaMessage: XStandardType = + this.propertyProvider.fileDropAreaMessage; + + // + @Input() + fileDropAreaDisableFileCheck: XStandardType = + this.propertyProvider.fileDropAreaDisableFileCheck; + + // + @Input() + fileDropAreaSupportsMultipleFiles: XStandardType = + this.propertyProvider.fileDropAreaSupportsMultipleFiles; + + @Input() + fileDropAreaAllowedExtensions: XStandardType> = + this.propertyProvider.fileDropAreaAllowedExtensions; + + // + @Input() + fileDropAreaDragDisabled: XStandardType = + this.propertyProvider.fileDropAreaDragDisabled; + + @Input() + fileDropAreaContentTemplate: TemplateRef = + this.propertyProvider.fileDropAreaContentTemplate; + + @Output() + fileDropAreaFilesChangeEmiter: EventEmitter> = + new EventEmitter(); + + // + @Input() + fileDropAreaSuccessColor: XStandardType = + this.propertyProvider.fileDropAreaSuccessColor; + + @Input() + fileDropAreaErrorColor: XStandardType = + this.propertyProvider.fileDropAreaErrorColor; + + @Input() + fileDropAreaAccentColor: XStandardType = + this.propertyProvider.fileDropAreaAccentColor; + + // + @Input() + showFileDropArea: XStandardType = + this.propertyProvider.showFileDropArea; + + // + //#region ComponentsCard ... + @Input() + fileDropAreaIsInPage: XStandardType = + this.propertyProvider.fileDropAreaIsInPage; + + @Input() + fileDropAreaWrapWithCard: XStandardType = + this.propertyProvider.fileDropAreaWrapWithCard; + + @Input() + fileDropAreaCardShowFooter: XStandardType = + this.propertyProvider.fileDropAreaCardShowFooter; + + @Input() + fileDropAreaCardShowActions: XStandardType = + this.propertyProvider.fileDropAreaCardShowActions; + + @Input() + fileDropAreaCardColor: XStandardType = + this.propertyProvider.fileDropAreaCardColor; + + @Input() + fileDropAreaCardForegroundColor: XStandardType = + this.propertyProvider.fileDropAreaCardForegroundColor; + + @Input() + fileDropAreaCardTitle: XStandardType = + this.propertyProvider.fileDropAreaCardTitle; + + @Input() + fileDropAreaCardActionProvider: Subject = + this.propertyProvider.fileDropAreaCardActionProvider; + + @Input() + fileDropAreaCardSubTitle: XStandardType = + this.propertyProvider.fileDropAreaCardSubTitle; + + @Input() + fileDropAreaCardCssClass: XStandardType> = + this.propertyProvider.fileDropAreaCardCssClass; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XComponentFileDropAreaContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXComponentFileDropAreaContainer + ): Observable { + return forkJoin({ + // + fileDropAreaCssClass: this.getValue( + container ? container.fileDropAreaCssClass : this.fileDropAreaCssClass + ), + fileDropAreaColor: this.getValue( + container ? container.fileDropAreaColor : this.fileDropAreaColor + ), + fileDropAreaForegroundColor: this.getValue( + container + ? container.fileDropAreaForegroundColor + : this.fileDropAreaForegroundColor + ), + fileDropAreaIcon: this.getValue( + container ? container.fileDropAreaIcon : this.fileDropAreaIcon + ), + fileDropAreaMessage: this.getValue( + container ? container.fileDropAreaMessage : this.fileDropAreaMessage + ), + fileDropAreaDisableFileCheck: this.getValue( + container + ? container.fileDropAreaDisableFileCheck + : this.fileDropAreaDisableFileCheck + ), + fileDropAreaSupportsMultipleFiles: this.getValue( + container + ? container.fileDropAreaSupportsMultipleFiles + : this.fileDropAreaSupportsMultipleFiles + ), + fileDropAreaAllowedExtensions: this.getValue( + container + ? container.fileDropAreaAllowedExtensions + : this.fileDropAreaAllowedExtensions + ), + fileDropAreaDragDisabled: this.getValue( + container + ? container.fileDropAreaDragDisabled + : this.fileDropAreaDragDisabled + ), + fileDropAreaContentTemplate: this.getValue( + container + ? container.fileDropAreaContentTemplate + : this.fileDropAreaContentTemplate + ), + fileDropAreaSuccessColor: this.getValue( + container + ? container.fileDropAreaSuccessColor + : this.fileDropAreaSuccessColor + ), + fileDropAreaErrorColor: this.getValue( + container + ? container.fileDropAreaErrorColor + : this.fileDropAreaErrorColor + ), + fileDropAreaAccentColor: this.getValue( + container + ? container.fileDropAreaAccentColor + : this.fileDropAreaAccentColor + ), + showFileDropArea: this.getValue( + container ? container.showFileDropArea : this.showFileDropArea + ), + fileDropAreaIsInPage: this.getValue( + container ? container.fileDropAreaIsInPage : this.fileDropAreaIsInPage + ), + fileDropAreaWrapWithCard: this.getValue( + container + ? container.fileDropAreaWrapWithCard + : this.fileDropAreaWrapWithCard + ), + fileDropAreaCardShowFooter: this.getValue( + container + ? container.fileDropAreaCardShowFooter + : this.fileDropAreaCardShowFooter + ), + fileDropAreaCardShowActions: this.getValue( + container + ? container.fileDropAreaCardShowActions + : this.fileDropAreaCardShowActions + ), + fileDropAreaCardColor: this.getValue( + container ? container.fileDropAreaCardColor : this.fileDropAreaCardColor + ), + fileDropAreaCardForegroundColor: this.getValue( + container + ? container.fileDropAreaCardForegroundColor + : this.fileDropAreaCardForegroundColor + ), + fileDropAreaCardTitle: this.getValue( + container ? container.fileDropAreaCardTitle : this.fileDropAreaCardTitle + ), + fileDropAreaCardSubTitle: this.getValue( + container + ? container.fileDropAreaCardSubTitle + : this.fileDropAreaCardSubTitle + ), + fileDropAreaCardCssClass: this.getValue( + container + ? container.fileDropAreaCardCssClass + : this.fileDropAreaCardCssClass + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + fileDropAreaActionProvider: container + ? container.fileDropAreaActionProvider + : this.fileDropAreaActionProvider, + + // + fileDropAreaCardActionProvider: container + ? container.fileDropAreaCardActionProvider + : this.fileDropAreaCardActionProvider, + + // + fileDropAreaFilesChangeEmiter: this.fileDropAreaFilesChangeEmiter, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-upload\x-file-upload.component.html + + + + + + + + +
+ +
+
+
+ + + +
+ + + + + + + + + + + + +
+
+ + + +
+ +
+
+ + + + + + + + + +
+ +
+ {{ item.data.name }} +
+
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-upload\x-file-upload.component.scss + +:host { + --top-radius: 5px; + --padding: 6px; + --host-bg-color: var(--x-color-light); +} + +:host ::ng-deep .x-card { + box-shadow: unset !important; +} + +:host ::ng-deep .x-thumbnail, +:host ::ng-deep .x-item { + width: 32px !important; + height: 32px !important; + object-fit: contain !important; + object-position: center !important; +} + +:host ::ng-deep ion-searchbar { + padding-left: unset; + padding-right: unset; + padding-inline-end: 0 !important; + padding-inline-start: 0 !important; +} + +.x-file-item { + width: 100%; + text-align: start; +} + +.x-file-item, +.x-file-thumbnail { + height: 32px; +} + +.x-file-thumbnail { + width: 32px; +} + +.x-file-title, +.x-file-thumbnail { + margin: { + left: 4px; + right: 4px; + } +} + +.x-file-title { + text-align: start; + + margin: { + top: auto; + bottom: auto; + } +} + +.x-file-upload-file-drag-drop-wrapper { + padding: var(--padding); + background-color: var(--host-bg-color); + border-top-left-radius: var(--top-radius); + border-top-right-radius: var(--top-radius); +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-upload\x-file-upload.component.ts + +import { + ViewChild, + Component, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XColor, + nameof, + isFile, + isArray, + toArray, + isString, + hasChild, + XFileType, + XFileBlob, + getArrayOf, + isXFileBlob, + parseXColor, + getFileInfo, + isExtensionOk, + XOneOrManyType, + getErrorException, + isNullOrUndefined, + isNullOrEmptyString, + XExceptionIdentifier, + XBaseRangeRequestDto, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XFileStatus, + XFileUploadAction, + XFileUploadCssVars, + XFileUploadActionModel, + XFileUploadContainerComponent, +} from './x-file-upload.typings'; +import { map } from 'rxjs/operators'; +import { + XActionBarItem, + defaultActionBarAddItem, + DefaultActionBarItemIds, + defaultActionBarUploadItem, + defaultActionBarRemoveItem, + defaultActionBarSelectAllItem, + defaultActionBarDeSelectAllItem, + defaultActionBarInverseSelectionItem, +} from '../x-action-bar/x-action-bar.typings'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import { BehaviorSubject, Subject, Subscription } from 'rxjs'; +import { XListItem, XListAction } from '../x-list/x-list.typings'; +import { XThumbnailType } from '../x-thumbnail/x-thumbnail.typings'; +import { XFileDropAreaAction } from '../x-file-drop-area/x-file-drop-area.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-file-upload', + templateUrl: './x-file-upload.component.html', + styleUrls: ['./x-file-upload.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XFileUploadComponent extends XFileUploadContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + private addDisablerSubject = new BehaviorSubject(false); + private uploadDisablerSubject = new BehaviorSubject(true); + private removeDisablerSubject = new BehaviorSubject(true); + private selectAllDisablerSubject = new BehaviorSubject(true); + private deSelectAllDisablerSubject = new BehaviorSubject(true); + private inverseSelectionDisablerSubject = new BehaviorSubject(true); + + // + @ViewChild('fileInput', { static: false }) + private fileInputRef!: ElementRef; + + // + private inputFiles: File[] = []; + fileListItems: XListItem[] = []; + fileListItemsSubject: BehaviorSubject[]> = + new BehaviorSubject([]); + + // + get files() { + return this.inputFiles; + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.prepareActionBarActions(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + const isColorChanged = changeKeys.includes( + nameof('color') + ); + if (isColorChanged) { + await this.applyColor(); + } + + // + //#region ActionBar Actions ... + const isActionBarActionsChanged = changeKeys.includes( + nameof('actionBarActions') + ); + const isActionBarHideActionsChanged = changeKeys.includes( + nameof('actionBarHideActions') + ); + if (isActionBarActionsChanged || isActionBarHideActionsChanged) { + await this.prepareActionBarActions(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Register Handlers ... + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Ui Providers ... + async canAddFile() { + // + const isMaxFilesOk = await this.isMaxAllowedFilesOk( + this.inputFiles.length + 1 + ); + const isMaxSizeOk = await this.isMaxAllowedSizeOk(); + + // + const result = isMaxFilesOk && isMaxSizeOk; + if (result) { + this.addDisablerSubject.next(false); + } else { + this.addDisablerSubject.next(true); + } + + // + return result; + } + + getThumbnailType(item: any) { + return this.getValue(item).pipe( + map((it) => { + // + let result = XThumbnailType.GENERAL; + + // + let fileInfo; + if (isString(it)) { + fileInfo = getFileInfo(it); + } else if (!isFile(it) && isXFileBlob(fileInfo)) { + fileInfo = getFileInfo((it as XFileBlob).name); + } else { + fileInfo = getFileInfo((it as File).name); + } + + // + switch (fileInfo.type) { + case XFileType.Image: + case XFileType.CoverImage: + case XFileType.ProfileImage: + result = XThumbnailType.CUSTOM; + break; + } + + // + return result; + }) + ); + } + + getThumbnailIdentifier(item: any) { + return this.getValue(item).pipe( + map((it) => { + // + let result = ''; + if (!isFile(it)) { + result = !isNullOrUndefined(it.name) ? it.name : it; + } else { + result = it; + } + + // + return result; + }) + ); + } + //#endregion + + // + //#region Ui Handlers ... + async handleFileDropped(files: File | File[]) { + // + if (!files) { + return; + } + + // + this.loading = true; + + // + if (!isArray(files) && files instanceof File) { + await this.handleAddNewFile(files as File); + } else { + // + const filesArr = files as File[]; + for (const file of filesArr) { + await this.handleAddNewFile(file); + } + } + + // + this.loading = false; + this.fileChange.emit(this.inputFiles); + this.handleChangeDetection(); + } + + async handleAddNewFile(file: File) { + // + if (this.isExists(file)) { + return; + } + + // + const status = await this.getFileStatusForAdd(file); + + // + await this.prepareActionBarActionsSubjects(this.getSelectedFiles()); + if (status !== XFileStatus.OK) { + this.loading = false; + await this.handleFileStatusForAdd(status); + return; + } + + // + this.inputFiles.push(file); + const fileListItem = await this.getListItem(file); + this.fileListItems.push(fileListItem); + this.fileListItemsSubject.next(this.fileListItems); + this.handleChangeDetection(); + } + + async handleRemoveItems(request: XBaseRangeRequestDto) { + // + if (!request || !hasChild(request.items)) { + return; + } + + // + this.loading = true; + + // + const filteredFiles = this.inputFiles.filter( + (f) => !request.items.includes(f) + ); + this.inputFiles = []; + this.fileListItems = []; + + // + if (!hasChild(filteredFiles)) { + // + this.inputFiles = []; + this.fileListItems = []; + this.fileListItemsSubject.next([]); + } else { + await this.handleFileDropped(filteredFiles); + } + + // + this.loading = false; + this.fileChange.emit(this.inputFiles); + this.handleChangeDetection(); + } + + async handleFilesSelectionChanged(items: XListItem[]) { + await this.prepareActionBarActionsSubjects(items); + } + + async handleListOrderChanged() { + // + const listItems = await this.getArrayValueAsync(this.fileListItems); + this.inputFiles = [...listItems.map((li) => li.data)]; + + // + this.loading = false; + this.fileChange.emit(this.inputFiles); + this.handleChangeDetection(); + } + + handleFilesSelected() { + // + if (!this.fileInputRef) { + return; + } + + // + const fileInput = this.fileInputRef.nativeElement; + if (!fileInput || !fileInput.files) { + return; + } + + // + const files = getArrayOf(fileInput.files); + + // + if (!files || files.length === 0) { + return; + } + + // + const file = files[0]; + if (!file) { + return; + } + + // + fileInput.value = null; + + // + this.handleFileDropped(files); + } + //#endregion + + // + //#region Actions ... + selectFiles() { + // + const isBussy = this.uiDisabled || this.loading; + if (isBussy) { + return; + } + + // + if (!this.fileInputRef) { + return; + } + + // + const fileInput = this.fileInputRef.nativeElement; + if (!fileInput) { + return; + } + + // + fileInput.click(); + } + + isExists(file: File) { + // + const result = + file && hasChild(this.inputFiles) + ? this.inputFiles.findIndex( + (f) => + f.name === file.name && + f.size === file.size && + f.type === file.type && + f.lastModified === file.lastModified + ) > -1 + : false; + + // + return result; + } + + getSelectedFiles() { + return this.fileListItems.filter((i) => i.isSelected === true); + } + + /** + * sync files with list ... + * @param files + */ + async handleSyncFiles(files: Array) { + // + // first Remove List and Files ... + await this.handleRemoveItems({ + items: this.files, + }); + + // + // Drop New Files ... + await this.handleFileDropped(files); + } + //#endregion + + // + //#region Private ... + /** + * retrieve all exists files in list current size ... + */ + private currentFilesSize() { + // + let result = 0; + this.inputFiles.forEach((i) => (result += i.size)); + + // + return result; + } + + /** + * Check File Extension ... + */ + private async isExtensionOk(file: File) { + // + if (!file) { + return false; + } + + // + const allowedExtensionsType = await this.getValueAsync( + this.allowedExtensions + ); + if (!allowedExtensionsType) { + return true; + } + + // + const allowedExtensions = toArray(allowedExtensionsType); + if (!hasChild(allowedExtensions)) { + return true; + } + + // + const result = isExtensionOk(file, allowedExtensions); + return result; + } + + /** + * Check Number of Files is Ok after and before Upload ... + */ + private async isMaxAllowedFilesOk(num?: number) { + // + if (!num && !hasChild(this.inputFiles)) { + return true; + } + + // + if (!num) { + num = this.inputFiles.length; + } + + // + const maxAllowedFiles = await this.getValueAsync(this.maxAllowedFiles); + const result = + maxAllowedFiles && maxAllowedFiles > 0 ? num <= maxAllowedFiles : true; + + // + return result; + } + + /** + * Check all File Sizes after and before Upload ... + */ + private async isMaxAllowedSizeOk(file?: File) { + // + let currentSize = this.currentFilesSize(); + + // + if (file) { + currentSize += file.size; + } + + // + const maxAllowedSize = await this.getValueAsync(this.maxAllowedSize); + + // + const result = + maxAllowedSize && maxAllowedSize > 0 + ? currentSize <= maxAllowedSize + : true; + + // + return result; + } + + /** + * Check File Size satisfied Min Required Size ... + */ + private async isMinAllowedFileSizeOk(file: File) { + // + const minAllowedFileSize = await this.getValueAsync( + this.minAllowedFileSize + ); + + // + const result = + minAllowedFileSize && minAllowedFileSize > 0 + ? file.size >= minAllowedFileSize + : true; + + // + return result; + } + + /** + * Check File Size less than or Equals Max Required Size ... + */ + private async isMaxAllowedFileSizeOk(file: File) { + // + const maxAllowedFileSize = await this.getValueAsync( + this.maxAllowedFileSize + ); + + // + const result = + maxAllowedFileSize && maxAllowedFileSize > 0 + ? file.size <= maxAllowedFileSize + : true; + + // + return result; + } + + /** + * Check File Status for Add a File to List ... + */ + private async getFileStatusForAdd(file: File) { + // + // Check Not Empty File ... + if (!file || file.size === 0) { + return XFileStatus.EMPTY; + } + + // + // Check File Extension Validation ... + const isExtensionIsOk = await this.isExtensionOk(file); + if (!isExtensionIsOk) { + return XFileStatus.NOT_ALLOWED_TYPE; + } + + // + // Check File Min Size Ok ... + const isMinAllowedFileSizeOk = await this.isMinAllowedFileSizeOk(file); + if (!isMinAllowedFileSizeOk) { + return XFileStatus.MIN_SIZE_NOT_REACHED; + } + + // + // Check File Max Size Ok ... + const isMaxAllowedFileSizeOk = await this.isMaxAllowedFileSizeOk(file); + if (!isMaxAllowedFileSizeOk) { + return XFileStatus.SIZE_EXCEEDED; + } + + // + // Check Max Files Size Ok ... + const isMaxAllowedSizeOk = await this.isMaxAllowedSizeOk(file); + if (!isMaxAllowedSizeOk) { + return XFileStatus.MAX_ALLOWED_SIZE_REACHED; + } + + // + // Check Max Files Ok ... + const isMaxAllowedFilesOk = await this.isMaxAllowedFilesOk( + this.inputFiles.length + 1 + ); + if (!isMaxAllowedFilesOk) { + return XFileStatus.MAX_ALLOWED_FILES_REACHED; + } + + // + // Everythings Ok ... + return XFileStatus.OK; + } + + /** + * recieve file status and act based of it (notifiy when exception) ... + * + * @param status File Status for Add ... + */ + private async handleFileStatusForAdd(status: XFileStatus) { + // + let identifier: XExceptionIdentifier = null; + switch (status) { + // + case XFileStatus.EMPTY: + identifier = await this.getValueAsync(this.emptyFileException); + break; + + // + case XFileStatus.NOT_ALLOWED_TYPE: + identifier = await this.getValueAsync(this.notAllowedFileException); + break; + + // + case XFileStatus.SIZE_EXCEEDED: + identifier = await this.getValueAsync(this.maxAllowedFileSizeException); + break; + + // + case XFileStatus.MAX_ALLOWED_SIZE_REACHED: + identifier = await this.getValueAsync(this.maxAllowedSizeException); + break; + + // + case XFileStatus.MAX_ALLOWED_FILES_REACHED: + identifier = await this.getValueAsync(this.maxAllowedFilesException); + break; + + // + case XFileStatus.MIN_SIZE_NOT_REACHED: + identifier = await this.getValueAsync(this.minAllowedFileSizeException); + break; + } + + // + const exception = getErrorException(identifier); + if (exception) { + this.managerService.notificationService.presentErrorNotification({ + exception, + dissmissable: true, + }); + } + } + + private handleChangeDetection() { + // + if (this.listActionProvider) { + this.listActionProvider.next({ + action: XListAction.Refresh, + }); + } + + // + if (this.fileDropAreaActionProvider) { + this.fileDropAreaActionProvider.next({ + action: XFileDropAreaAction.Refresh, + }); + } + + // + this.detectChanges(); + } + + // + //#region ActionBar Preparations ... + private async prepareActionBarActions() { + // + //#region Prepare Default Actions ... + const hideActions = await this.getArrayValueAsync( + this.actionBarHideActions + ); + + // + const defaultActions: XOneOrManyType = []; + + // + // Add ... + const addId = DefaultActionBarItemIds.Add.toString(); + if (!hideActions.includes(addId)) { + defaultActions.push({ + // + ...defaultActionBarAddItem, + + // + index: 1, + disabled: this.addDisablerSubject.asObservable(), + + // + handler: () => { + this.selectFiles(); + }, + }); + } + + // + // Upload ... + const uploadId = DefaultActionBarItemIds.Upload.toString(); + if (!hideActions.includes(uploadId)) { + defaultActions.push({ + // + ...defaultActionBarUploadItem, + + // + index: 2, + disabled: this.uploadDisablerSubject.asObservable(), + }); + } + + // + // Remove ... + const removeId = DefaultActionBarItemIds.Remove.toString(); + if (!hideActions.includes(removeId)) { + defaultActions.push({ + // + ...defaultActionBarRemoveItem, + + // + index: 3, + disabled: this.removeDisablerSubject.asObservable(), + }); + } + + // + // Select All ... + const selectAllId = DefaultActionBarItemIds.SelectAll.toString(); + if (!hideActions.includes(selectAllId)) { + defaultActions.push({ + // + ...defaultActionBarSelectAllItem, + + // + index: 4, + disabled: this.selectAllDisablerSubject.asObservable(), + }); + } + + // + // Inverse Selection ... + const inverseSelectionId = + DefaultActionBarItemIds.InverseSelection.toString(); + if (!hideActions.includes(inverseSelectionId)) { + defaultActions.push({ + // + ...defaultActionBarInverseSelectionItem, + + // + index: 5, + disabled: this.inverseSelectionDisablerSubject.asObservable(), + }); + } + + // + // De Select All ... + const deselectAllId = DefaultActionBarItemIds.DeSelectAll.toString(); + if (!hideActions.includes(deselectAllId)) { + defaultActions.push({ + // + ...defaultActionBarDeSelectAllItem, + + // + index: 6, + disabled: this.deSelectAllDisablerSubject.asObservable(), + }); + } + //#endregion + + // + if (!this.actionBarActions) { + // + this.actionBarActions = defaultActions; + this.handleChangeDetection(); + return; + } + + // + const actions = await this.getArrayValueAsync(this.actionBarActions); + if (!hasChild(actions)) { + // + this.actionBarActions = defaultActions; + this.handleChangeDetection(); + return; + } + + // + this.actionBarActions = actions; + this.handleChangeDetection(); + } + + private async prepareActionBarActionsSubjects(items: XListItem[]) { + // + // Check Can Add ... + const canAdd = await this.canAddFile(); + if (canAdd) { + this.addDisablerSubject.next(false); + } else { + this.addDisablerSubject.next(true); + } + + // + // Check Select All ... + if (!hasChild(this.fileListItems)) { + // + this.addDisablerSubject.next(false); + this.uploadDisablerSubject.next(true); + this.removeDisablerSubject.next(true); + this.selectAllDisablerSubject.next(true); + this.deSelectAllDisablerSubject.next(true); + this.inverseSelectionDisablerSubject.next(true); + } else { + // + this.uploadDisablerSubject.next(false); + + // + // DeSelectAll ... + if (!hasChild(items)) { + // + this.removeDisablerSubject.next(true); + this.deSelectAllDisablerSubject.next(true); + this.inverseSelectionDisablerSubject.next(true); + } else { + // + this.removeDisablerSubject.next(false); + this.deSelectAllDisablerSubject.next(false); + this.inverseSelectionDisablerSubject.next(false); + } + + // + if (this.fileListItems.length === items.length) { + // + this.selectAllDisablerSubject.next(true); + this.inverseSelectionDisablerSubject.next(true); + } else { + this.selectAllDisablerSubject.next(false); + } + } + + // + this.handleChangeDetection(); + } + //#endregion + + // + //#region Handler Registerrers ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XFileUploadAction.SELECT_FILES: + this.selectFiles(); + break; + + // + case XFileUploadAction.CLEAR_LIST: + await this.handleRemoveItems({ + items: this.files, + }); + break; + + // + case XFileUploadAction.SYNC_LIST: + await this.handleSyncFiles(model.payload); + break; + + // + case XFileUploadAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + //#region List Handlers ... + private async getListItem(item: any) { + // + if (!item) { + return null; + } + + // + const result: XListItem = { + data: item, + disabled: this.uiDisabled, + slideOptions: [ + { + id: 'delete_file', + icon: XIconNames.remove, + title: this.ResourceIDs.delete, + color: XColor.Danger, + onlyIcon: true, + disabled: this.uiDisabled, + slot: 'end', + handler: async () => { + await this.confirmAndRemove(item); + }, + }, + ], + }; + + // + return result; + } + + private isValidDataSource() { + return hasChild(this.inputFiles); + } + + private addToDataSource(item: XListItem) { + this.fileListItems.push(item); + } + + private changeDataSourceItem(item: XListItem) { + // + const index = this.fileListItems.findIndex((i) => i.data === item.data); + if (index === -1) { + return; + } + + // + this.fileListItems[index] = item; + } + + private async clearListItems() { + this.fileListItems = null; + this.fileListItems = []; + } + + private async refreshListItems(forceClean = false) { + // + if (forceClean) { + await this.clearListItems(); + } + + // + if (!this.isValidDataSource()) { + return; + } + + // + for (const f of this.inputFiles) { + // + const fListItem = await this.getListItem(f); + + // + if (forceClean) { + this.addToDataSource(fListItem); + } else { + this.changeDataSourceItem(fListItem); + } + } + } + + private async confirmAndRemove(items: XOneOrManyType) { + // + if (!items) { + return; + } + + // + const extractedItems: File[] = !items + ? [] + : isArray(items) + ? (items as File[]) + : [items as File]; + const removeRequest: XBaseRangeRequestDto = { + items: [...extractedItems], + }; + + // + const confirmRemove = await this.getValueAsync(this.listConfirmRemove); + if (confirmRemove) { + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + () => { + this.handleRemoveItems(removeRequest); + } + ); + } else { + this.handleRemoveItems(removeRequest); + } + } + //#endregion + + // + //#region Color Appliers ... + private async applyColor() { + // + const color = await this.getValueAsync(this.color); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFileUploadCssVars.HostBgColor, `var(${bgColor})`); + + // + this.handleChangeDetection(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-upload\x-file-upload.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XListModule } from '../x-list/x-list.module'; +import { XFileUploadComponent } from './x-file-upload.component'; +import { XThumbnailModule } from '../x-thumbnail/x-thumbnail.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XFileDropAreaModule } from '../x-file-drop-area/x-file-drop-area.module'; + +@NgModule({ + declarations: [XFileUploadComponent], + imports: [ + XCardModule, + XListModule, + CommonModule, + XThumbnailModule, + XFileDropAreaModule, + XBaseComponentModule, + ], + exports: [ + XCardModule, + XListModule, + XThumbnailModule, + XFileDropAreaModule, + XBaseComponentModule, + XFileUploadComponent, + ], +}) +export class XFileUploadModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-upload\x-file-upload.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + Injectable, + ElementRef, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + hasChild, + XFileBlob, + XStandardType, + XOneOrManyType, + toNormalString, + XColorIdentifier, + fileToJsonString, + XResourceIdentifier, + XColorWithBrightness, + XExceptionIdentifier, +} from 'x-framework-core'; +import { + XListItem, + XListFilteringHandler, + XComponentListContainer, + IXComponentListContainer, +} from '../x-list/x-list.typings'; +import { + XCardActionModel, + XComponentCardContainer, + IXComponentCardContainer, +} from '../x-card/x-card.typings'; +import { + XEmptyActionModel, + XComponentEmptyContainer, + IXComponentEmptyContainer, +} from '../x-empty/x-empty.typings'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { + XActionBarItem, + XActionBarActionModel, + XActionBarActionIdentifier, + XActionBarActionFiredModel, + XComponentActionBarContainer, + IXComponentActionBarContainer, +} from '../x-action-bar/x-action-bar.typings'; +import { + XSearchBarActionModel, + XSearchBarTypeIdentifier, + XComponentSearchBarContainer, + IXComponentSearchBarContainer, +} from '../x-search-bar/x-search-bar.typings'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { + XFileDropAreaActionModel, + XComponentFileDropAreaContainer, + IXComponentFileDropAreaContainer, +} from '../x-file-drop-area/x-file-drop-area.typings'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { IXFileContainer, XFileContainer } from '../classes/i-x-file.container'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XFileStatus { + OK = 'ok', + EMPTY = 'empty', + SIZE_EXCEEDED = 'size_exceeded', + NOT_ALLOWED_TYPE = 'not_allowed_type', + MIN_SIZE_NOT_REACHED = 'min_size_not_reached', + MAX_ALLOWED_FILES_REACHED = 'max_allowed_files_reached', + MAX_ALLOWED_SIZE_REACHED = 'max_allowed_size_reached', +} + +export type XFileStatusIdentifier = XFileStatus | string; + +export enum XFileUploadAction { + SELECT_FILES = 'select_files', + CLEAR_LIST = 'clear_list', + SYNC_LIST = 'sync_list', + + // + Refresh = 'refresh', +} + +export type XFileUploadActionIdentifier = XFileUploadAction | string; + +export interface XFileUploadActionModel + extends XBaseActionModel {} + +export const defaultFileUploadListFilteringHandler: XListFilteringHandler = ( + query: string, + items: XListItem[] +) => { + // + if (!query || !hasChild(items)) { + return []; + } + + // + return items.filter((i) => + toNormalString(fileToJsonString(i.data)).includes(toNormalString(query)) + ); +}; + +export enum XFileUploadCssVars { + HostBgColor = 'host-bg-color', +} + +// +//#region Container ... +// +type TXFileUploadContainer = IXComponentCardContainer & + Partial & + Partial & + IXComponentActionBarContainer & + IXComponentSearchBarContainer & + IXComponentEmptyContainer & + IXFileContainer; + +export interface IXFileUploadContainer + extends IXBaseContainer, + TXFileUploadContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + fileChange: EventEmitter>; + + // + actionBarHideActions: XStandardType< + XOneOrManyType + >; +} + +@Injectable({ + providedIn: 'root', +}) +export class XFileUploadContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + //#region Self ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly fileChange: EventEmitter> = new EventEmitter< + XOneOrManyType + >(); + + readonly actionBarHideActions: XStandardType< + XOneOrManyType + >; + //#endregion + + // + //#region File Drop Props ... + // + readonly showFileDropArea: XStandardType = + this.fileDropAreaPropertyProvider.showFileDropArea; + + // + readonly fileDropAreaActionProvider: Subject = + this.fileDropAreaPropertyProvider.fileDropAreaActionProvider; + readonly fileDropAreaCssClass: XStandardType> = + this.fileDropAreaPropertyProvider.fileDropAreaCssClass; + + // + readonly fileDropAreaColor: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaColor; + readonly fileDropAreaForegroundColor: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaForegroundColor; + + // + readonly fileDropAreaIcon: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaIcon; + readonly fileDropAreaMessage: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaMessage; + + // + readonly fileDropAreaDisableFileCheck: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaDisableFileCheck; + + // + readonly fileDropAreaSupportsMultipleFiles: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaSupportsMultipleFiles; + readonly fileDropAreaAllowedExtensions: XStandardType< + XOneOrManyType + > = this.fileDropAreaPropertyProvider.fileDropAreaAllowedExtensions; + + // + readonly fileDropAreaDragDisabled: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaDragDisabled; + readonly fileDropAreaContentTemplate: TemplateRef = + this.fileDropAreaPropertyProvider.fileDropAreaContentTemplate; + readonly fileDropAreaFilesChangeEmiter: EventEmitter> = + this.fileDropAreaPropertyProvider.fileDropAreaFilesChangeEmiter; + + // + readonly fileDropAreaSuccessColor: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaSuccessColor; + readonly fileDropAreaErrorColor: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaErrorColor; + readonly fileDropAreaAccentColor: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaAccentColor; + //#endregion + + // + //#region Card Props ... + readonly isInPage: XStandardType = + this.cardPropertyProvider.isInPage; + readonly wrapWithCard: XStandardType = + this.cardPropertyProvider.wrapWithCard; + readonly cardActionProvider: Subject = + this.cardPropertyProvider.cardActionProvider; + readonly cardCssClass: XStandardType> = + this.cardPropertyProvider.cardCssClass; + readonly cardColor: XStandardType = + this.cardPropertyProvider.cardColor; + readonly cardForegroundColor: XStandardType = + this.cardPropertyProvider.cardForegroundColor; + readonly cardTitle: XStandardType = + this.cardPropertyProvider.cardTitle; + readonly cardSubTitle: XStandardType = + this.cardPropertyProvider.cardSubTitle; + readonly cardShowFooter: XStandardType = + this.cardPropertyProvider.cardShowFooter; + readonly cardShowActions: XStandardType = + this.cardPropertyProvider.cardShowActions; + //#endregion + + // + //#region List Props ... + readonly showList: XStandardType = + this.listPropertyProvider.showList; + readonly listConfirmRemove: XStandardType = + this.listPropertyProvider.listConfirmRemove; + readonly listActionProvider = this.listPropertyProvider.listActionProvider; + readonly listCssClass: XStandardType> = + this.listPropertyProvider.listCssClass; + readonly listShowToolbar: XStandardType = true; + readonly listStickyToolbar: XStandardType = + this.listPropertyProvider.listStickyToolbar; + readonly listEnableReordering: XStandardType = + this.listPropertyProvider.listEnableReordering; + readonly listShowDivider: XStandardType = + this.listPropertyProvider.listShowDivider; + readonly listFilteringHandler: XListFilteringHandler = + this.listPropertyProvider.listFilteringHandler; + //#endregion + + // + //#region ActionBar ... + // + readonly showActionBar: XStandardType = true; + + // + readonly actionBarActionProvider: Subject = + this.actionBarPropertyProvider.actionBarActionProvider; + readonly actionBarCssClass: XStandardType> = + this.actionBarPropertyProvider.actionBarCssClass; + + // + readonly actionBarColor: XStandardType = + this.actionBarPropertyProvider.actionBarColor; + readonly actionBarForegroundColor: XStandardType = + this.actionBarPropertyProvider.actionBarForegroundColor; + + // + readonly actionBarActions: XStandardType> = + this.actionBarPropertyProvider.actionBarActions; + + // + readonly actionBarActionFired: EventEmitter = + this.actionBarPropertyProvider.actionBarActionFired; + //#endregion + + // + //#region SearchBar ... + // + readonly showSearchBar: XStandardType = + this.searchBarPropertyProvider.showSearchBar; + + // + readonly searchBarActionProvider: Subject = + this.searchBarPropertyProvider.searchBarActionProvider; + readonly searchBarCssClass: XStandardType> = + this.searchBarPropertyProvider.searchBarCssClass; + + // + readonly searchBarColor: XStandardType = + this.searchBarPropertyProvider.searchBarColor; + readonly searchBarForegroundColor: XStandardType = + this.searchBarPropertyProvider.searchBarForegroundColor; + + // + readonly searchBarSearchInputColor: XStandardType = + this.searchBarPropertyProvider.searchBarSearchInputColor; + readonly searchBarForegroundSearchInputColor: XStandardType = + this.searchBarPropertyProvider.searchBarForegroundSearchInputColor; + + // + readonly searchBarType: XStandardType = + this.searchBarPropertyProvider.searchBarType; + readonly searchBarTitle: XStandardType = + this.searchBarPropertyProvider.searchBarTitle; + readonly searchBarValue: XStandardType = + this.searchBarPropertyProvider.searchBarValue; + readonly searchBarEnterKeyHint: XStandardType = + this.searchBarPropertyProvider.searchBarEnterKeyHint; + readonly searchBarKeyboardType: XStandardType = + this.searchBarPropertyProvider.searchBarKeyboardType; + readonly searchBarSpellcheck: XStandardType = + this.searchBarPropertyProvider.searchBarSpellcheck; + readonly searchBarDebounce: XStandardType = + this.searchBarPropertyProvider.searchBarDebounce; + + // + readonly searchBarShowCancelButton: XStandardType = + this.searchBarPropertyProvider.searchBarShowCancelButton; + readonly searchBarCancelButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonIcon; + readonly searchBarCancelButtonTitle: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonTitle; + readonly searchBarCancelButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonIconColor; + + // + readonly searchBarShowClearButton: XStandardType = + this.searchBarPropertyProvider.searchBarShowClearButton; + readonly searchBarClearButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarClearButtonIcon; + readonly searchBarClearButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarClearButtonIconColor; + + // + readonly searchBarSearchButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarSearchButtonIcon; + readonly searchBarSearchButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarSearchButtonIconColor; + + // + readonly searchBarQueryChange: EventEmitter = + this.searchBarPropertyProvider.searchBarQueryChange; + readonly searchBarOnEvent: EventEmitter = + this.searchBarPropertyProvider.searchBarOnEvent; + //#endregion + + // + //#region Empty Props ... + readonly showEmpty: XStandardType = + this.emptyPropertyProvider.showEmpty; + readonly emptyActionProvider: Subject = + this.emptyPropertyProvider.emptyActionProvider; + readonly emptyCssClass: XStandardType> = + this.emptyPropertyProvider.emptyCssClass; + readonly emptyColor: XStandardType = + this.emptyPropertyProvider.emptyColor; + readonly emptyForegroundColor: XStandardType = + this.emptyPropertyProvider.emptyForegroundColor; + readonly emptyMessage: XStandardType = + this.emptyPropertyProvider.emptyMessage; + readonly emptyMessageColor: XStandardType = + this.emptyPropertyProvider.emptyMessageColor; + readonly emptyForegroundMessageColor: XStandardType = + this.emptyPropertyProvider.emptyForegroundMessageColor; + readonly emptyHasRefresh: XStandardType = + this.emptyPropertyProvider.emptyHasRefresh; + readonly emptyRefreshButtonColor: XStandardType = + this.emptyPropertyProvider.emptyRefreshButtonColor; + readonly emptyForegroundRefreshButtonColor: XStandardType = + this.emptyPropertyProvider.emptyForegroundRefreshButtonColor; + readonly emptyRefreshIcon: XStandardType = + this.emptyPropertyProvider.emptyRefreshIcon; + readonly emptyRefresh: EventEmitter = + this.emptyPropertyProvider.emptyRefresh; + //#endregion + + // + //#region File Props ... + readonly emptyFileException: XStandardType = + this.filePropertyProvider.emptyFileException; + readonly notAllowedFileException: XStandardType = + this.filePropertyProvider.notAllowedFileException; + readonly allowedExtensions: XStandardType> = + this.filePropertyProvider.allowedExtensions; + readonly maxAllowedFiles: XStandardType = + this.filePropertyProvider.maxAllowedFiles; + readonly maxAllowedFilesException: XStandardType = + this.filePropertyProvider.maxAllowedFilesException; + readonly maxAllowedSize: XStandardType = + this.filePropertyProvider.maxAllowedSize; + readonly maxAllowedSizeException: XStandardType = + this.filePropertyProvider.maxAllowedSizeException; + readonly minAllowedFileSize: XStandardType = + this.filePropertyProvider.minAllowedFileSize; + readonly minAllowedFileSizeException: XStandardType = + this.filePropertyProvider.minAllowedFileSizeException; + readonly maxAllowedFileSize: XStandardType = + this.filePropertyProvider.maxAllowedFileSize; + readonly maxAllowedFileSizeException: XStandardType = + this.filePropertyProvider.maxAllowedFileSizeException; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected filePropertyProvider: XFileContainer, + protected cardPropertyProvider: XComponentCardContainer, + protected listPropertyProvider: XComponentListContainer, + protected emptyPropertyProvider: XComponentEmptyContainer, + protected actionBarPropertyProvider: XComponentActionBarContainer, + protected searchBarPropertyProvider: XComponentSearchBarContainer, + protected fileDropAreaPropertyProvider: XComponentFileDropAreaContainer + ) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XFileUploadContainerComponent + extends XBaseContainerComponent + implements IXFileUploadContainer +{ + // + //#region Props ... + // + //#region Self ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Output() + fileChange: EventEmitter> = new EventEmitter(); + + @Input() + actionBarHideActions: XStandardType< + XOneOrManyType + > = this.propertyProvider.actionBarHideActions; + //#endregion + + // + //#region File Drop Props ... + // + @Input() + showFileDropArea: XStandardType = + this.propertyProvider.showFileDropArea; + + // + @Input() + fileDropAreaActionProvider: Subject = + this.propertyProvider.fileDropAreaActionProvider; + + @Input() + fileDropAreaCssClass: XStandardType> = + this.propertyProvider.fileDropAreaCssClass; + + // + @Input() + fileDropAreaColor: XStandardType = + this.propertyProvider.fileDropAreaColor; + + @Input() + fileDropAreaForegroundColor: XStandardType = + this.propertyProvider.fileDropAreaForegroundColor; + + // + @Input() + fileDropAreaIcon: XStandardType = + this.propertyProvider.fileDropAreaIcon; + + @Input() + fileDropAreaMessage: XStandardType = + this.propertyProvider.fileDropAreaMessage; + + // + @Input() + fileDropAreaDisableFileCheck: XStandardType = + this.propertyProvider.fileDropAreaDisableFileCheck; + + // + @Input() + fileDropAreaSupportsMultipleFiles: XStandardType = + this.propertyProvider.fileDropAreaSupportsMultipleFiles; + + @Input() + fileDropAreaAllowedExtensions: XStandardType> = + this.propertyProvider.fileDropAreaAllowedExtensions; + + // + @Input() + fileDropAreaDragDisabled: XStandardType = + this.propertyProvider.fileDropAreaDragDisabled; + + @Input() + fileDropAreaContentTemplate: TemplateRef = + this.propertyProvider.fileDropAreaContentTemplate; + + @Output() + fileDropAreaFilesChangeEmiter: EventEmitter> = + new EventEmitter(); + + // + @Input() + fileDropAreaSuccessColor: XStandardType = + this.propertyProvider.fileDropAreaSuccessColor; + + @Input() + fileDropAreaErrorColor: XStandardType = + this.propertyProvider.fileDropAreaErrorColor; + + @Input() + fileDropAreaAccentColor: XStandardType = + this.propertyProvider.fileDropAreaAccentColor; + //#endregion + + // + //#region Card Props ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + //#endregion + + // + //#region List Props ... + @Input() + showList: XStandardType = this.propertyProvider.showList; + + @Input() + listConfirmRemove: XStandardType = + this.propertyProvider.listConfirmRemove; + + @Input() + listActionProvider = this.propertyProvider.listActionProvider; + + @Input() + listCssClass: XStandardType> = + this.propertyProvider.listCssClass; + + @Input() + listShowToolbar: XStandardType = + this.propertyProvider.listShowToolbar; + + @Input() + listStickyToolbar: XStandardType = + this.propertyProvider.listStickyToolbar; + + @Input() + listEnableReordering: XStandardType = + this.propertyProvider.listEnableReordering; + + @Input() + listShowDivider: XStandardType = + this.propertyProvider.listShowDivider; + + @Input() + listFilteringHandler: XListFilteringHandler = + this.propertyProvider.listFilteringHandler; + //#endregion + + // + //#region ActionBar ... + // + @Input() + showActionBar: XStandardType = this.propertyProvider.showActionBar; + + // + @Input() + actionBarActionProvider: Subject = + this.propertyProvider.actionBarActionProvider; + + @Input() + actionBarCssClass: XStandardType> = + this.propertyProvider.actionBarCssClass; + + // + @Input() + actionBarColor: XStandardType = + this.propertyProvider.actionBarColor; + + @Input() + actionBarForegroundColor: XStandardType = + this.propertyProvider.actionBarForegroundColor; + + // + @Input() + actionBarActions: XStandardType> = + this.propertyProvider.actionBarActions; + + // + @Output() + actionBarActionFired: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region SearchBar ... + // + @Input() + showSearchBar: XStandardType = this.propertyProvider.showSearchBar; + + // + @Input() + searchBarActionProvider: Subject = + this.propertyProvider.searchBarActionProvider; + + @Input() + searchBarCssClass: XStandardType> = + this.propertyProvider.searchBarCssClass; + + // + @Input() + searchBarColor: XStandardType = + this.propertyProvider.searchBarColor; + + @Input() + searchBarForegroundColor: XStandardType = + this.propertyProvider.searchBarForegroundColor; + + // + @Input() + searchBarSearchInputColor: XStandardType = + this.propertyProvider.searchBarSearchInputColor; + + @Input() + searchBarForegroundSearchInputColor: XStandardType = + this.propertyProvider.searchBarForegroundSearchInputColor; + + // + @Input() + searchBarType: XStandardType = + this.propertyProvider.searchBarType; + + @Input() + searchBarTitle: XStandardType = + this.propertyProvider.searchBarTitle; + + @Input() + searchBarValue: XStandardType = this.propertyProvider.searchBarValue; + + @Input() + searchBarEnterKeyHint: XStandardType = + this.propertyProvider.searchBarEnterKeyHint; + + @Input() + searchBarKeyboardType: XStandardType = + this.propertyProvider.searchBarKeyboardType; + + @Input() + searchBarSpellcheck: XStandardType = + this.propertyProvider.searchBarSpellcheck; + + @Input() + searchBarDebounce: XStandardType = + this.propertyProvider.searchBarDebounce; + + // + @Input() + searchBarShowCancelButton: XStandardType = + this.propertyProvider.searchBarShowCancelButton; + + @Input() + searchBarCancelButtonIcon: XStandardType = + this.propertyProvider.searchBarCancelButtonIcon; + + @Input() + searchBarCancelButtonTitle: XStandardType = + this.propertyProvider.searchBarCancelButtonTitle; + + @Input() + searchBarCancelButtonIconColor: XStandardType = + this.propertyProvider.searchBarCancelButtonIconColor; + + // + @Input() + searchBarShowClearButton: XStandardType = + this.propertyProvider.searchBarShowClearButton; + + @Input() + searchBarClearButtonIcon: XStandardType = + this.propertyProvider.searchBarClearButtonIcon; + + @Input() + searchBarClearButtonIconColor: XStandardType = + this.propertyProvider.searchBarClearButtonIconColor; + + // + @Input() + searchBarSearchButtonIcon: XStandardType = + this.propertyProvider.searchBarSearchButtonIcon; + + @Input() + searchBarSearchButtonIconColor: XStandardType = + this.propertyProvider.searchBarSearchButtonIconColor; + + // + @Output() + searchBarQueryChange: EventEmitter = new EventEmitter(); + + @Output() + searchBarOnEvent: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Empty Props ... + @Input() + showEmpty: XStandardType = this.propertyProvider.showEmpty; + + @Input() + emptyActionProvider: Subject = + this.propertyProvider.emptyActionProvider; + + @Input() + emptyCssClass: XStandardType> = + this.propertyProvider.emptyCssClass; + + @Input() + emptyColor: XStandardType = + this.propertyProvider.emptyColor; + + @Input() + emptyForegroundColor: XStandardType = + this.propertyProvider.emptyForegroundColor; + + @Input() + emptyMessage: XStandardType = + this.propertyProvider.emptyMessage; + + @Input() + emptyMessageColor: XStandardType = + this.propertyProvider.emptyMessageColor; + + @Input() + emptyForegroundMessageColor: XStandardType = + this.propertyProvider.emptyForegroundMessageColor; + + @Input() + emptyHasRefresh: XStandardType = + this.propertyProvider.emptyHasRefresh; + + @Input() + emptyRefreshButtonColor: XStandardType = + this.propertyProvider.emptyRefreshButtonColor; + + @Input() + emptyForegroundRefreshButtonColor: XStandardType = + this.propertyProvider.emptyForegroundRefreshButtonColor; + + @Input() + emptyRefreshIcon: XStandardType = + this.propertyProvider.emptyRefreshIcon; + + @Output() + emptyRefresh: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region File Props ... + @Input() + emptyFileException: XStandardType = + this.propertyProvider.emptyFileException; + + @Input() + notAllowedFileException: XStandardType = + this.propertyProvider.notAllowedFileException; + + @Input() + allowedExtensions: XStandardType> = + this.propertyProvider.allowedExtensions; + + @Input() + maxAllowedFiles: XStandardType = + this.propertyProvider.maxAllowedFiles; + + @Input() + maxAllowedFilesException: XStandardType = + this.propertyProvider.maxAllowedFilesException; + + @Input() + maxAllowedSize: XStandardType = this.propertyProvider.maxAllowedSize; + + @Input() + maxAllowedSizeException: XStandardType = + this.propertyProvider.maxAllowedSizeException; + + @Input() + minAllowedFileSize: XStandardType = + this.propertyProvider.minAllowedFileSize; + + @Input() + minAllowedFileSizeException: XStandardType = + this.propertyProvider.minAllowedFileSizeException; + + @Input() + maxAllowedFileSize: XStandardType = + this.propertyProvider.maxAllowedFileSize; + + @Input() + maxAllowedFileSizeException: XStandardType = + this.propertyProvider.maxAllowedFileSizeException; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XFileUploadContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXFileUploadContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + actionBarHideActions: this.getValue( + container ? container.actionBarHideActions : this.actionBarHideActions + ), + showFileDropArea: this.getValue( + container ? container.showFileDropArea : this.showFileDropArea + ), + fileDropAreaCssClass: this.getValue( + container ? container.fileDropAreaCssClass : this.fileDropAreaCssClass + ), + fileDropAreaColor: this.getValue( + container ? container.fileDropAreaColor : this.fileDropAreaColor + ), + fileDropAreaForegroundColor: this.getValue( + container + ? container.fileDropAreaForegroundColor + : this.fileDropAreaForegroundColor + ), + fileDropAreaIcon: this.getValue( + container ? container.fileDropAreaIcon : this.fileDropAreaIcon + ), + fileDropAreaMessage: this.getValue( + container ? container.fileDropAreaMessage : this.fileDropAreaMessage + ), + fileDropAreaDisableFileCheck: this.getValue( + container + ? container.fileDropAreaDisableFileCheck + : this.fileDropAreaDisableFileCheck + ), + fileDropAreaSupportsMultipleFiles: this.getValue( + container + ? container.fileDropAreaSupportsMultipleFiles + : this.fileDropAreaSupportsMultipleFiles + ), + fileDropAreaAllowedExtensions: this.getValue( + container + ? container.fileDropAreaAllowedExtensions + : this.fileDropAreaAllowedExtensions + ), + fileDropAreaDragDisabled: this.getValue( + container + ? container.fileDropAreaDragDisabled + : this.fileDropAreaDragDisabled + ), + fileDropAreaContentTemplate: this.getValue( + container + ? container.fileDropAreaContentTemplate + : this.fileDropAreaContentTemplate + ), + fileDropAreaSuccessColor: this.getValue( + container + ? container.fileDropAreaSuccessColor + : this.fileDropAreaSuccessColor + ), + fileDropAreaErrorColor: this.getValue( + container + ? container.fileDropAreaErrorColor + : this.fileDropAreaErrorColor + ), + fileDropAreaAccentColor: this.getValue( + container + ? container.fileDropAreaAccentColor + : this.fileDropAreaAccentColor + ), + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardCssClass: this.getValue( + container ? container.cardCssClass : this.cardCssClass + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + showList: this.getValue(container ? container.showList : this.showList), + listConfirmRemove: this.getValue( + container ? container.listConfirmRemove : this.listConfirmRemove + ), + listCssClass: this.getValue( + container ? container.listCssClass : this.listCssClass + ), + listShowToolbar: this.getValue( + container ? container.listShowToolbar : this.listShowToolbar + ), + listStickyToolbar: this.getValue( + container ? container.listStickyToolbar : this.listStickyToolbar + ), + listEnableReordering: this.getValue( + container ? container.listEnableReordering : this.listEnableReordering + ), + listShowDivider: this.getValue( + container ? container.listShowDivider : this.listShowDivider + ), + listFilteringHandler: this.getValue( + container ? container.listFilteringHandler : this.listFilteringHandler + ), + showActionBar: this.getValue( + container ? container.showActionBar : this.showActionBar + ), + actionBarCssClass: this.getValue( + container ? container.actionBarCssClass : this.actionBarCssClass + ), + actionBarColor: this.getValue( + container ? container.actionBarColor : this.actionBarColor + ), + actionBarForegroundColor: this.getValue( + container + ? container.actionBarForegroundColor + : this.actionBarForegroundColor + ), + actionBarActions: this.getValue( + container ? container.actionBarActions : this.actionBarActions + ), + showSearchBar: this.getValue( + container ? container.showSearchBar : this.showSearchBar + ), + searchBarCssClass: this.getValue( + container ? container.searchBarCssClass : this.searchBarCssClass + ), + searchBarColor: this.getValue( + container ? container.searchBarColor : this.searchBarColor + ), + searchBarForegroundColor: this.getValue( + container + ? container.searchBarForegroundColor + : this.searchBarForegroundColor + ), + searchBarSearchInputColor: this.getValue( + container + ? container.searchBarSearchInputColor + : this.searchBarSearchInputColor + ), + searchBarForegroundSearchInputColor: this.getValue( + container + ? container.searchBarForegroundSearchInputColor + : this.searchBarForegroundSearchInputColor + ), + searchBarType: this.getValue( + container ? container.searchBarType : this.searchBarType + ), + searchBarTitle: this.getValue( + container ? container.searchBarTitle : this.searchBarTitle + ), + searchBarValue: this.getValue( + container ? container.searchBarValue : this.searchBarValue + ), + searchBarEnterKeyHint: this.getValue( + container ? container.searchBarEnterKeyHint : this.searchBarEnterKeyHint + ), + searchBarKeyboardType: this.getValue( + container ? container.searchBarKeyboardType : this.searchBarKeyboardType + ), + searchBarSpellcheck: this.getValue( + container ? container.searchBarSpellcheck : this.searchBarSpellcheck + ), + searchBarDebounce: this.getValue( + container ? container.searchBarDebounce : this.searchBarDebounce + ), + searchBarShowCancelButton: this.getValue( + container + ? container.searchBarShowCancelButton + : this.searchBarShowCancelButton + ), + searchBarCancelButtonIcon: this.getValue( + container + ? container.searchBarCancelButtonIcon + : this.searchBarCancelButtonIcon + ), + searchBarCancelButtonTitle: this.getValue( + container + ? container.searchBarCancelButtonTitle + : this.searchBarCancelButtonTitle + ), + searchBarCancelButtonIconColor: this.getValue( + container + ? container.searchBarCancelButtonIconColor + : this.searchBarCancelButtonIconColor + ), + searchBarShowClearButton: this.getValue( + container + ? container.searchBarShowClearButton + : this.searchBarShowClearButton + ), + searchBarClearButtonIcon: this.getValue( + container + ? container.searchBarClearButtonIcon + : this.searchBarClearButtonIcon + ), + searchBarClearButtonIconColor: this.getValue( + container + ? container.searchBarClearButtonIconColor + : this.searchBarClearButtonIconColor + ), + searchBarSearchButtonIcon: this.getValue( + container + ? container.searchBarSearchButtonIcon + : this.searchBarSearchButtonIcon + ), + searchBarSearchButtonIconColor: this.getValue( + container + ? container.searchBarSearchButtonIconColor + : this.searchBarSearchButtonIconColor + ), + showEmpty: this.getValue( + container ? container.showEmpty : this.showEmpty + ), + emptyCssClass: this.getValue( + container ? container.emptyCssClass : this.emptyCssClass + ), + emptyColor: this.getValue( + container ? container.emptyColor : this.emptyColor + ), + emptyForegroundColor: this.getValue( + container ? container.emptyForegroundColor : this.emptyForegroundColor + ), + emptyMessage: this.getValue( + container ? container.emptyMessage : this.emptyMessage + ), + emptyMessageColor: this.getValue( + container ? container.emptyMessageColor : this.emptyMessageColor + ), + emptyForegroundMessageColor: this.getValue( + container + ? container.emptyForegroundMessageColor + : this.emptyForegroundMessageColor + ), + emptyHasRefresh: this.getValue( + container ? container.emptyHasRefresh : this.emptyHasRefresh + ), + emptyRefreshButtonColor: this.getValue( + container + ? container.emptyRefreshButtonColor + : this.emptyRefreshButtonColor + ), + emptyForegroundRefreshButtonColor: this.getValue( + container + ? container.emptyForegroundRefreshButtonColor + : this.emptyForegroundRefreshButtonColor + ), + emptyRefreshIcon: this.getValue( + container ? container.emptyRefreshIcon : this.emptyRefreshIcon + ), + emptyFileException: this.getValue( + container ? container.emptyFileException : this.emptyFileException + ), + notAllowedFileException: this.getValue( + container + ? container.notAllowedFileException + : this.notAllowedFileException + ), + allowedExtensions: this.getValue( + container ? container.allowedExtensions : this.allowedExtensions + ), + maxAllowedFiles: this.getValue( + container ? container.maxAllowedFiles : this.maxAllowedFiles + ), + maxAllowedFilesException: this.getValue( + container + ? container.maxAllowedFilesException + : this.maxAllowedFilesException + ), + maxAllowedSize: this.getValue( + container ? container.maxAllowedSize : this.maxAllowedSize + ), + maxAllowedSizeException: this.getValue( + container + ? container.maxAllowedSizeException + : this.maxAllowedSizeException + ), + minAllowedFileSize: this.getValue( + container ? container.minAllowedFileSize : this.minAllowedFileSize + ), + minAllowedFileSizeException: this.getValue( + container + ? container.minAllowedFileSizeException + : this.minAllowedFileSizeException + ), + maxAllowedFileSize: this.getValue( + container ? container.maxAllowedFileSize : this.maxAllowedFileSize + ), + maxAllowedFileSizeException: this.getValue( + container + ? container.maxAllowedFileSizeException + : this.maxAllowedFileSizeException + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + fileChange: this.fileChange, + + // + fileDropAreaActionProvider: container + ? container.fileDropAreaActionProvider + : this.fileDropAreaActionProvider, + + // + fileDropAreaFilesChangeEmiter: this.fileDropAreaFilesChangeEmiter, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + + // + listActionProvider: container + ? container.listActionProvider + : this.listActionProvider, + + // + actionBarActionProvider: container + ? container.actionBarActionProvider + : this.actionBarActionProvider, + + // + actionBarActionFired: this.actionBarActionFired, + + // + searchBarActionProvider: container + ? container.searchBarActionProvider + : this.searchBarActionProvider, + + // + searchBarQueryChange: this.searchBarQueryChange, + searchBarOnEvent: this.searchBarOnEvent, + + // + emptyActionProvider: container + ? container.emptyActionProvider + : this.emptyActionProvider, + + // + emptyRefresh: this.emptyRefresh, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-form\x-form.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + + +
+ +
+ + + + + + + + + +
+ +
+ + + + + + {{ getControlLabel(control) | async }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + + + +
+
+
+
+ + + + +
+ + + + + + {{ getControlLabel(control) | async }} + + + + + {{ getResourceValue(option.viewValue) | async }} + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + {{ getResourceValue(option.viewValue) | async }} + + + + + +
+
+ + + + + + +
+
+ + + + +
+ +
+ + + + + + + + + + {{ getResourceValue(option.viewValue) | async }} + + +
+ + + + + + +
+
+ + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+
+ + + + +
+ +
+ + + + + + + + + + + + + + + {{ getControlLabel(control) | async }} + + + + + + + + + + + + + +
+ + + + + + +
+
+ + + + +
+ +
+ + + + + + + + + + +
+ + + + + + +
+
+ + + + +
+ + + + + + {{ getControlLabel(control) | async }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + +
+
+ + + + + + + + + +
+ +
+
+ + + + +
+ +
+
+ + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+
+ + + + +
+ +
+ + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + +
+ +
+
+
+ + + + + + + + + +
+ + + + + + + + + +
+ + + + + + +
+
+ + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+
+
+
+
+
+
+ + + + + +
+ + + + + {{ getErrorHandlerErrorMessage(handler) | async }} + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ getControlHint(control) | async }} + + + + +
+ + + + {{ getErrorHandlerErrorMessage(handler, control) | async }} + + + +
+
+ + + + +
+ + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ + + + + {{ getControlLabel(control) | async }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-form\x-form.component.scss + +// +:host { + // + --regular-color: var(--x-color-dark); + --valid-color: var(--x-color-success); + --hint-color: var(--x-color-tertiary); + --invalid-color: var(--x-color-danger); + --active-color: var(--x-color-tertiary); + --pending-color: var(--x-color-warning); + --disabled-color: var(--x-color-medium); + --accent-color: var(--x-color-secondary); + --fill-color: var(--x-color-secondary-tint); + --disabled-fill-color: var(--x-color-light-shade); + + // + --hint-text-size: 7pt; + --error-text-size: 7pt; + + // + --form-fields-vertical-margin: 16px; +} + +// +//#region Mixins ... +// +@mixin formFieldRegularProps { + // + x-icon { + --size: 20px; + } + + // + .x-field-label { + } + + // + .auto-size-flex { + flex: 0 0 auto !important; + } + + // + .full-heigh { + * { + height: 100%; + } + } +} + +@mixin formInputElementProps { + // + .mat-input-element { + @include colorHandler(var(--regular-color), false); + } + + // + .x-form-input-element { + width: 100%; + border: unset; + outline: unset; + border-color: transparent; + + // + &:focus { + // outline: var(--active-color) auto 1px; + } + + // + @include colorHandler(var(--regular-color), false); + @include backgroundColorHandler(transparent, false); + } + + // + .x-form-date-input-element, + .x-form-input-element:not(.mat-input-element) { + height: 100% !important; + } +} + +@mixin formFieldHintProps( + $color: var(--hint-color), + $size: var(--hint-text-size) +) { + .x-form-control-hint { + // + color: $color; + font-size: $size; + } +} + +@mixin formFieldErrorProps( + $color: var(--invalid-color), + $size: var(--error-text-size) +) { + // + // + $duration: 250ms; + $transform-show: scaleY(1); + $transform-hide: scaleY(0); + .x-form-error, + .x-form-field-error { + // + color: $color; + font-size: $size; + + // + height: 0; + + // + transform: $transform-hide; + -ms-transform: $transform-hide; + -webkit-transform: $transform-hide; + // + transition: transform; + -webkit-transition: transform; + // + transition-duration: $duration; + -webkit-transition-duration: $duration; + + // + &.show { + // + height: auto !important; + // + transform: $transform-show; + -ms-transform: $transform-show; + -webkit-transform: $transform-show; + // + transition-duration: $duration; + -webkit-transition-duration: $duration; + } + } +} + +@mixin formFieldProps { + // + .x-form-field { + // + @include dateProps; + @include sliderProps; + @include radioButtonProps; + @include selectOptionProps; + @include toogleColorHandler; + @include checkBoxColorHandler; + @include formFieldRegularProps; + @include formInputElementProps; + @include formFieldAppearanceHandler; + @include colorHandler(var(--regular-color), false); + @include backgroundColorHandler(transparent !important); + + // + &.x-mat-form-field { + // + width: 100%; + + // + &.mat-focused { + @include formFieldFocusHandler; + } + } + + // + &.x-form-radio-field { + // + outline: unset; + + // + &.x-focused { + // + @include formFieldFocusHandler; + @include iconStatusColorHandler(var(--active-color)); + } + } + + // + &:not(.x-mat-form-field):not(.x-form-radio-field) { + // + outline: unset; + + // + &.x-focused { + // + @include formFieldFocusHandler; + border-color: var(--active-color); + } + } + } +} + +@mixin autoCompleteProps { + /* Auto Complete Styling */ + // + $duration: 250ms; + $transform-show: scaleY(1); + $transform-hide: scaleY(0); + + .x-auto-complete-wrapper { + position: unset; + display: unset; + } + + .x-auto-complete { + // + position: content; + + // + &.x-auto-complete-margin { + margin-top: 0; + } + + // + height: calc(50%); + max-height: 300px; + + // + left: 8px; + right: 8px; + overflow-y: scroll; + + // + z-index: 113; + + // + transform: $transform-hide; + -ms-transform: $transform-hide; + -webkit-transform: $transform-hide; + + // + transition: transform; + -webkit-transition: transform; + + // + transition-duration: $duration; + -webkit-transition-duration: $duration; + + // + &.open { + // + transform: $transform-show; + -ms-transform: $transform-show; + -webkit-transform: $transform-show; + + // + transition-duration: $duration; + -webkit-transition-duration: $duration; + } + } + /* END Auto Complete Styling */ +} + +@mixin selectOptionProps { + // + .x-select-option { + width: 100%; + margin-bottom: 4px; + + &:last-child { + margin-bottom: 8px; + } + } +} + +@mixin dateProps { + // + .mat-icon-button { + display: table-cell; + vertical-align: bottom; + } +} + +@mixin radioButtonProps($disabled: false) { + // + .x-radio-button { + // + @include radioColorHandler(var(--regular-color), true, false); + + // + &.active { + // + @include radioColorHandler(var(--active-color) !important, true, true); + } + } + + &.x-form-radio-field { + // + outline: unset; + + // + &.x-focused { + // + .x-field-label { + color: var(--active-color) !important; + } + border-color: var(--active-color) !important; + + // + @include iconStatusColorHandler(var(--active-color)); + } + } +} + +@mixin formFieldFocusHandler { + // + .mat-form-field-ripple { + @include backgroundColorHandler(var(--active-color) !important, false); + } + + // + @include iconStatusColorHandler(var(--active-color)); + @include formFieldLabelColorHandler(var(--active-color)); + @include formFieldWrapperColorHandler(var(--active-color)); + @include matFormFieldOutlineColorHandler(var(--active-color) !important); +} + +@mixin formFieldAppearanceHandler($disabled: false) { + // + // Label Colors ... + @if $disabled { + // + @include formFieldLabelColorHandler(var(--disabled-color)); + + // + .x-form-field-appearance-fill:not(.x-mat-form-field) { + border-color: var(--disabled-fill-color); + } + + // + .mat-select-value-text { + color: var(--disabled-color) !important; + } + } + + // + .mat-select-value-text { + color: var(--regular-color); + } + + // + // None ... + .x-form-field-appearance-none { + // + @if $disabled { + @include formFieldLabelColorHandler(var(--disabled-color)); + } @else { + } + } + + // + // Legacy ... + // Standard ... + .x-form-field-appearance-legacy, + .x-form-field-appearance-standard { + // + @if $disabled { + @include matFormFieldUnderlineColorHandler( + var(--disabled-fill-color) !important, + true + ); + } @else { + @include matFormFieldUnderlineColorHandler(var(--fill-color) !important); + } + + // + &:not(.x-mat-form-field) { + @if $disabled { + } @else { + // + padding: 8px; + border-radius: 0; + border-bottom-width: 1px; + border-bottom-style: solid; + border-color: var(--fill-color); + } + } + } + + // + // Outline ... + .x-form-field-appearance-outline { + // + @if $disabled { + @include matFormFieldOutlineColorHandler(var(--disabled-fill-color)); + } @else { + @include formFieldLabelColorHandler(var(--regular-color)); + @include matFormFieldOutlineColorHandler(var(--fill-color)); + } + + // + &:not(.x-mat-form-field) { + @if $disabled { + @include backgroundColorHandler(transaprent!important, false); + } @else { + // + padding: 8px; + border-radius: 3px; + border-width: 1px; + border-style: solid; + } + } + } + + // + // Fill ... + .x-form-field-appearance-fill { + // + &.x-mat-form-field:not(.x-form-field-disabled) { + // + @if $disabled { + // + .mat-form-field-flex { + @include colorHandler(var(--disabled-fill-color) !important, false); + @include backgroundColorHandler( + var(--disabled-fill-color) !important, + false + ); + } + + // + @include matFormFieldUnderlineColorHandler(var(--disabled-color)); + } @else { + // + @include backgroundColorHandler(var(--fill-color) !important, false); + + // + .mat-form-field-flex { + @include colorHandler(var(--regular-color), false); + @include backgroundColorHandler(var(--fill-color) !important, false); + } + + // + @include matFormFieldUnderlineColorHandler(var(--accent-color)); + } + } + + // + @if $disabled { + // + &.x-form-field-disabled { + background-color: var(--disabled-fill-color) !important; + } + + // + .mat-form-field-flex { + @include colorHandler(var(--disabled-color) !important, false); + @include backgroundColorHandler( + var(--disabled-fill-color) !important, + false + ); + } + + // + .x-form-slider-control { + // + .mat-slider-thumb { + background-color: var(--disabled-color) !important; + } + + // + .mat-slider-wrapper, + .mat-slider-track-wrapper { + background-color: var(--disabled-color) !important; + } + } + + // + @include matFormFieldUnderlineColorHandler(var(--disabled-color)); + } @else { + // + .x-form-slider-control { + .mat-slider-wrapper, + .mat-slider-track-wrapper { + background-color: var(--regular-color) !important; + } + } + } + + // + &:not(.x-mat-form-field) { + @if $disabled { + @include backgroundColorHandler( + var(--disabled-fill-color) !important, + false + ); + } @else { + // + padding: 8px; + border-radius: 3px; + border-bottom-width: 1px; + border-bottom-style: solid; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-color: var(--regular-color); + + // + @include backgroundColorHandler(var(--fill-color) !important, false); + } + } + } +} + +@mixin formFieldStatusHandler { + // + // Global ... + @include formFieldAppearanceHandler(false); + + // + // Disabled ... + &.x-form-field-wrapper-disabled { + // + * { + cursor: default; + pointer-events: none; + } + + // + x-icon.x-action-icon > * { + color: var(--disabled-color) !important; + } + + // + .mat-form-field-disabled { + color: var(--disabled-color) !important; + } + + // + .x-form-input-element { + // + opacity: 1 !important; + border-color: var(--disabled-fill-color) !important; + + // + @include colorHandler(var(--disabled-color) !important, false); + } + + // + .x-form-control { + opacity: 1 !important; + @include colorHandler(var(--disabled-color) !important, false); + } + + // + .mat-slide-toggle-bar { + background-color: var(--disabled-fill-color) !important; + } + + // + @include sliderProps(true); + @include formFieldAppearanceHandler(true); + @include formFieldHintProps(var(--disabled-color)); + @include formFieldErrorProps(var(--disabled-color)); + @include checkBoxColorHandler(--disabled-color, true); + @include iconStatusColorHandler(var(--disabled-color), true); + @include formFieldWrapperColorHandler(var(--disabled-color) !important); + @include radioColorHandler(var(--disabled-color) !important, true, true); + } + + // + // Valid ... + &.x-form-field-wrapper-valid { + // + @include iconStatusColorHandler(var(--valid-color), true); + + // + &.apply-status { + @include formFieldWrapperColorHandler(var(--valid-color)); + } + } + + // + // Invalid ... + &.x-form-field-wrapper-invalid { + // + @include iconStatusColorHandler(var(--invalid-color), true); + + // + &.apply-status { + @include formFieldWrapperColorHandler(var(--invalid-color)); + } + } + + // + // Pending ... + &.x-form-field-wrapper-pending { + // + @include iconStatusColorHandler(var(--pending-color), true); + + // + &.apply-status { + @include formFieldWrapperColorHandler(var(--pending-color)); + } + } +} + +@mixin colorHandler($color: var(--regular-color), $applyToChilds: true) { + color: $color; + + // + @if $applyToChilds { + * { + :not(x-icon):not(x-icon > *) { + color: $color; + } + } + } + + // + @include matFormFieldOutlineColorHandler($color); +} + +@mixin backgroundColorHandler($color: transaprent, $applyToChilds: true) { + // + background-color: $color; + + // + @if $applyToChilds { + *:not(.x-field-label):not(.x-field-label + *):not(.mat-form-field-ripple):not(.mat-slide-toggle-thumb):not(.mat-slide-toggle-bar):not(.mat-checkbox-frame):not(.mat-radio-inner-circle):not(.mat-form-field-underline):not(.mat-slide):not(.mat-slider + *):not(.x-markdown *):not(.x-color *):not(.x-map + *):not(.x-selected-color):not(.x-color-palette):not(.x-color-alpha) { + background-color: $color; + } + } +} + +@mixin formFieldWrapperColorHandler($color: var(--regular-color)) { + // + .x-form-input-element { + // outline: $color auto 1px; + } + + // + .x-form-field:not(.x-mat-form-field) { + border-color: $color; + } + + // + @include sliderProps; + @include toogleColorHandler($color); + @include checkBoxColorHandler($color); + @include formFieldLabelColorHandler($color); + @include matFormFieldOutlineColorHandler($color); + @include iconStatusColorHandler($color !important); + @include matFormFieldUnderlineColorHandler($color !important); +} + +@mixin radioColorHandler( + $color: var(--regular-color), + $applyOnOuterCircle: false, + $applyOnInnerCircle: false +) { + // + .mat-radio-label { + // + color: $color; + + // + @if $applyOnOuterCircle { + .mat-radio-outer-circle { + border-color: $color; + } + } + + // + @if $applyOnInnerCircle { + .mat-radio-inner-circle { + background-color: $color; + } + } + } +} + +@mixin checkBoxColorHandler($color: var(--regular-color), $disabled: false) { + // + .mat-checkbox-frame { + @if $disabled { + border-color: var(--disabled-color) !important; + } @else { + border-color: var(--regular-color); + } + } + + // + .mat-checkbox-checked { + .mat-checkbox-frame, + .mat-checkbox-background { + background-color: var(--active-color) !important; + } + } + + // + .mat-checkbox-label { + .x-field-label { + width: 100%; + } + } + + // + .x-form-control { + &.has-icon { + .mat-checkbox-layout { + margin: { + left: 4px; + right: 4px; + } + } + } + } +} + +@mixin toogleColorHandler($color: var(--regular-color)) { + // + :not(.x-form-field-disabled) { + .mat-slide-toggle-bar { + background-color: var(--active-color) !important; + } + } + + // + .x-form-field-disabled { + .mat-slide-toggle-bar { + background-color: var(--disabled-color) !important; + } + .mat-slide-toggle-thumb { + background-color: var(--disabled-fill-color) !important; + } + } + + // + .mat-slide-toggle-thumb { + background-color: $color !important; + } + + // + .x-form-control { + &.has-icon { + .mat-slide-toggle-label { + margin: { + left: 4px; + right: 4px; + } + } + } + } +} + +@mixin sliderProps($disabled: false) { + // + .x-form-slider-control { + // + @if $disabled { + // + cursor: default; + pointer-events: none; + } + + // + &:not(.vertical) { + width: 100%; + } + + // + &.vertical { + display: block; + margin: { + left: auto; + right: auto; + } + } + + // + .mat-slider-wrapper, + .mat-slider-track-wrapper { + // + @if $disabled { + background-color: var(--disabled-fill-color) !important; + } @else { + background-color: var(--fill-color); + } + } + + // + .mat-slider-thumb, + .mat-slider-thumb-label, + .mat-slider-thumb-label-text { + // + @if $disabled { + color: var(--disabled-color) !important; + background-color: var(--disabled-fill-color) !important; + } @else { + color: var(--accent-color); + background-color: var(--active-color); + } + } + + // + .mat-slider-track-fill { + // + @if $disabled { + background-color: var(--disabled-color) !important; + } @else { + background-color: var(--active-color); + } + } + + // + .mat-slider-vertical { + } + } +} + +@mixin formFieldLabelColorHandler($color: var(--regular-color)) { + // + .mat-form-field-label { + color: $color; + } + + // + .x-field-label { + color: $color; + } + + // + .mat-select-placeholder { + color: $color; + } +} + +@mixin iconStatusColorHandler($color: var(--regular-color), $important: false) { + // + x-icon.x-status-handler > * { + // + @if $important { + color: $color !important; + } @else { + color: $color; + } + } +} + +@mixin matFormFieldUnderlineColorHandler( + $color: var(--fill-color), + $clearBackground: false +) { + // + .mat-form-field-underline { + // + border-color: $color; + background-color: $color; + + // + &::before { + background-color: $color; + } + + // + @if $clearBackground { + background-image: none; + } + } + + // + .mat-select-arrow { + color: $color; + } +} + +@mixin matFormFieldOutlineColorHandler($color: var(--regular-color)) { + // + color: $color; + + // + .mat-form-field-outline-start, + .mat-form-field-outline-end, + .mat-form-field-outline-gap { + border-width: 1px !important; + } + + // + .mat-form-field-outline, + .mat-form-field-outline-thick { + color: $color; + border-width: 1px !important; + } +} +//#endregion + +// +::ng-deep.cdk-overlay-pane { + // + transform: translateY(0px) !important; + + // + .mat-datepicker-content-container { + height: auto !important; + } +} + +// +::ng-deep.x-form-select-control { + // + min-width: 100% !important; + + // + &:not(.x-form-select-control-panel) { + max-width: 100% !important; + } + + // + &.x-form-select-control-panel { + } + + // // + // .mat-select-value-text { + // color: var(--regular-color) !important; + // } + + // + .x-select-option { + // + color: var(--regular-color) !important; + background-color: var(--fill-color) !important; + + // + &.mat-active { + color: var(--active-color) !important; + } + + // + &.mat-selected { + // + color: var(--active-color) !important; + + // + .mat-pseudo-checkbox-checked { + background: var(--active-color) !important; + } + + // + .mat-pseudo-checkbox::after { + color: var(--accent-color) !important; + } + } + + // + &:hover { + $level: 0.5; + filter: brightness($level); + -webkit-filter: brightness($level); + color: var(--active-color) !important; + } + } +} + +// +::ng-deep.x-form-control-date-picker { + // + &.mat-calendar { + // + color: var(--regular-color) !important; + background-color: var(--fill-color) !important; + + // + .mat-calendar-arrow { + border-top-color: unset; + } + + // + .mat-calendar-body-today { + color: var(--active-color); + border-color: var(--acttive-color); + } + + // + .mat-calendar-table-header { + color: var(--accent-color) !important; + } + + // + .mat-calendar-body-selected { + // + color: var(--accent-color); + background-color: var(--active-color); + border-color: var(--accent-color); + } + + // + .mat-calendar-next-button, + .mat-calendar-previous-button, + .mat-calendar-period-button { + color: var(--active-color) !important; + } + + // + .mat-calendar-body-label { + color: var(--active-color) !important; + } + + // + .mat-calendar-body-cell-content { + color: var(--regular-color) !important; + } + + // + .mat-calendar-body-disabled { + color: var(--disabled-color) !important; + filter: contrast(0.5) !important; + } + } +} + +// +:host ::ng-deep .x-form { + // + display: flex; + flex-wrap: wrap; + flex-direction: row; + + // + .x-form-field-wrapper { + // + width: 100%; + margin-bottom: var(--form-fields-vertical-margin); + + // + .mat-form-field-wrapper { + padding-bottom: 0; + } + + // + .mat-form-field-underline { + position: initial; + } + + // + .ng-invalid, + .mat-form-field-invalid { + // + *:not(.x-field-label):not(ion-icon):not(.mat-form-field-outline):not(.mat-form-field-outline-thick):not(.mat-form-field-outline-end):not(.mat-form-field-outline-gap):not(.mat-form-field-outline-start) { + color: var(--regular-color); + } + + // + &.apply-status { + * { + color: var(--invalid-color) !important; + } + } + + *:not(.x-field-label) { + background-color: transparent; + } + } + + // + &.readonly { + // + .x-field-label { + color: var(--disabled-color) !important; + } + + // + .x-form-control { + color: var(--disabled-color) !important; + } + } + + // + @include formFieldProps; + @include autoCompleteProps; + @include formFieldStatusHandler; + @include formFieldAppearanceHandler(false); + @include formFieldHintProps(var(--hint-color), var(--hint-text-size)); + @include formFieldErrorProps(var(--invalid-color), var(--error-text-size)); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-form\x-form.component.ts + +import { + FormGroup, + FormControl, + AbstractControl, + ValidationErrors, +} from '@angular/forms'; +import { + defaultFormControlClearIcon, + defaultFormControlSelectIcon, +} from './x-form.tools'; +import { + keys, + sort, + nameof, + XColor, + isDate, + assign, + entries, + XUiEvent, + hasChild, + isFunction, + parseXColor, + momentToDate, + XResourceIDs, + XBorderStyle, + XExceptionIDs, + XDialogResult, + XOneOrManyType, + isNullOrUndefined, + isNullOrEmptyString, + XColorWithBrightness, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XFormCssVar, + XFormStatus, + XFormUpdateOn, + XFormBaseAction, + XFormControlType, + XFormActionModel, + XControlIconRole, + XFormErrorHandler, + XFormControlConfig, + XFormControlAction, + XFormIconDescriptor, + XControlIconPosition, + XFormStatusIdentifier, + XFormMapControlConfig, + XControlIconDescriptor, + XFormControlAppearance, + XFormInputControlConfig, + XFormRadioControlConfig, + XFormContainerComponent, + XFormSelectControlConfig, + XFormSliderControlConfig, + XFormSelectControlOption, + XFormControlLabelPosition, + XControlIconRoleIdentifier, + XFormMarkdownControlConfig, + XFormControlNameIdentifier, + XFormMapControlPresentType, + XFormCheckBoxControlConfig, + XFormSelectorControlConfig, + XBaseFormControlActionModel, + XFormDatePickerControlConfig, + XFormColorPickerControlConfig, + XFormControlAutoCompleteConfig, + XFormAvatarUploadControlConfig, + XFormControlAppearanceDescriptor, + XFormControlValueChangeEventModel, + XFormControlStatusChangeEventModel, + XFormColorPickerControlPresentType, + XFormDatePickerControlPickerPosition, +} from './x-form.typings'; +import { + XMapAction, + XMapLocation, + XMapActionModel, + XMapItemPosition, + defaultMarkerStyle, + defaultMapSearchPlaceProvider, +} from '../x-map/x-map.typings'; +import { isMoment } from 'moment'; +import { + XMarkdownMode, + XMarkdownActionModel, +} from '../x-markdown/x-markdown.typings'; +import { + XSearchBarType, + XSearchBarActionModel, + XSearchBarEnterKeyHint, +} from '../x-search-bar/x-search-bar.typings'; +import { + XColorPickerActionModel, + XColorPickerOutputFormat, +} from '../x-color-picker/x-color-picker.typings'; +import { + XAvatarUploadActionModel, + defaultAvatarUploadActionButtons, +} from '../x-avatar-upload/x-avatar-upload.typings'; +import { map, concatMap, tap } from 'rxjs/operators'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import { toLongLat, fromLongLat } from '../x-map/x-map.tools'; +import { XStringInputFormatType } from 'x-framework-services'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { MatFormFieldAppearance } from '@angular/material/form-field'; +import { of, Subject, Subscription, Observable, forkJoin } from 'rxjs'; +import { MatDatepickerInputEvent } from '@angular/material/datepicker'; +import { XSlotName, XSlotLayout } from '../x-slotter/x-slotter.typings'; +import { XMapDialogConfig } from '../x-map/x-map-dialog/x-map-dialog.typings'; +import { XFileDropAreaActionModel } from '../x-file-drop-area/x-file-drop-area.typings'; +import { XColorPickerDialogConfig } from '../x-color-picker/x-color-picker-dialog/x-color-picker-dialog.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-form', + templateUrl: './x-form.component.html', + styleUrls: ['./x-form.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XFormComponent extends XFormContainerComponent { + // + //#region Props ... + // + private VALUE: any; + private FORM: FormGroup; + private FORM_CONFIG: any; + private ERRORS: ValidationErrors; + private actionProviders: any = {}; + private STATUS: XFormStatusIdentifier; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + readonly FormStatuses = Object.assign({}, XFormStatus); + readonly ControlTypes = Object.assign({}, XFormControlType); + readonly ControlIconRoles = Object.assign({}, XControlIconRole); + readonly ControlIconPositions = Object.assign({}, XControlIconPosition); + readonly FormControlAppearances = Object.assign({}, XFormControlAppearance); + + // + currentInputFocus: string; + + // + get value() { + return this.VALUE; + } + + get status() { + return this.STATUS; + } + + get errors() { + return this.ERRORS; + } + + get isStatusValid() { + return this.status === XFormStatus.Valid; + } + + get form() { + return this.FORM; + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.applyColors(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + // Form Config ... + const isFormConfigChanged = changeKeys.includes( + nameof('formConfig') + ); + if (isFormConfigChanged) { + await this.prepareFormGroup(); + } + + // + // Action Provider ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + await this.registerActionProvider(); + } + + // + //#region UI Disabled or Loading ... + const isUiDisabledChanged = changeKeys.includes( + nameof('uiDisabled') + ); + if (isUiDisabledChanged) { + await this.handleUiDisabledChanged(); + } + //#endregion + + // + //#region Color Change Detecotrs ... + // + const isRegularColorChanged = changeKeys.includes( + nameof('regularColor') + ); + if (isRegularColorChanged) { + await this.applyRegularColor(); + } + + // + const isAccentColorChanged = changeKeys.includes( + nameof('accentColor') + ); + if (isAccentColorChanged) { + await this.applyAccentColor(); + } + + // + const isHintColorChanged = changeKeys.includes( + nameof('hintColor') + ); + if (isHintColorChanged) { + await this.applyHintColor(); + } + + // + const isPendingColorChanged = changeKeys.includes( + nameof('pendingColor') + ); + if (isPendingColorChanged) { + await this.applyPendingColor(); + } + + // + const isActiveColorChanged = changeKeys.includes( + nameof('activeColor') + ); + if (isActiveColorChanged) { + await this.applyActiveColor(); + } + + // + const isValidColorChanged = changeKeys.includes( + nameof('validColor') + ); + if (isValidColorChanged) { + await this.applyValidColor(); + } + + // + const isInvalidColorChanged = changeKeys.includes( + nameof('invalidColor') + ); + if (isInvalidColorChanged) { + await this.applyInvalidColor(); + } + + // + const isFillColorChanged = changeKeys.includes( + nameof('fillColor') + ); + if (isFillColorChanged) { + await this.applyFillColor(); + } + + // + const isDisabledColorChanged = changeKeys.includes( + nameof('disabledColor') + ); + if (isDisabledColorChanged) { + await this.applyDisabledColor(); + } + + // + const isDisabledFillColorChanged = changeKeys.includes( + nameof('disabledFillColor') + ); + if (isDisabledFillColorChanged) { + await this.applyDisabledFillColor(); + } + //#endregion + } + + async afterViewInit() { + super.afterViewInit(); + + // + this.managerService.dispatchResizeEvent(); + setTimeout(() => this.managerService.dispatchResizeEvent(), 500); + this.detectChanges(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + // + //#region Form Providers ... + // + //#region Form Config ... + /** + * prepare form config object + */ + getFormConfig() { + return this.getValue(this.formConfig); + } + //#endregion + + // + //#region Form Model ... + /** + * get form name ... + */ + getFormName() { + return this.getFormConfig().pipe( + concatMap((formConfig) => this.getValue(formConfig.name)) + ); + } + //#endregion + + // + //#region Css Class Lists... + /** + * retrieve form cssClasses ... + */ + getFormCssClass() { + return this.getArrayValue(this.cssClass).pipe( + concatMap((cssClasses) => + this.getFormName().pipe( + map((formName) => { + return { + name: formName, + cssClasses, + }; + }) + ) + ), + map((descriptor) => { + return descriptor.cssClasses.concat( + 'x-form', + `x-status-${this.STATUS}`, + `x-form-${descriptor.name}` + ); + }) + ); + } + + /** + * retrieve form control wrapper classes list ... + * + * @param control form control config ... + */ + getFormControlWrapperClasses(control: XFormControlConfig) { + return this.getControlInfo(control).pipe( + map((cInfo) => { + // + const fControl = this.getControl(control.propName); + const isFocused = + this.getControlInputElementId(control) === this.currentInputFocus; + + // + return [ + 'x-form-field-wrapper', + `x-form-${cInfo.type}-field-wrapper`, + `x-form-field-${control.index.toString()}-wrapper`, + `x-form-field-${control.propName.toString()}-wrapper`, + `x-form-field-appearance-${cInfo.appearance + .toString() + .toLowerCase()}-wrapper`, + // + // Focused ... + ...(isFocused ? ['x-focused'] : []), + // + // Disabled ... + ...(cInfo.disabled || fControl.disabled + ? ['x-form-field-wrapper-disabled'] + : []), + // + // Valid ... + ...(fControl.valid ? ['x-form-field-wrapper-valid'] : []), + // + // Invalid ... + ...(fControl.invalid ? ['x-form-field-wrapper-invalid'] : []), + // + // Pending ... + ...(fControl.pending ? ['x-form-field-wrapper-pending'] : []), + ]; + }) + ); + } + + /** + * retrieve form errors classes ... + */ + getFormErrorsClasses() { + return this.getFormName().pipe( + map((formName) => { + return ['x-form-errors', `x-form-${formName}-errors`]; + }) + ); + } + + /** + * retrieve form specific error's class lists ... + * + * @param handler form error handler ... + */ + getFormErrorClasses(handler: XFormErrorHandler) { + return this.getFormName().pipe( + concatMap((formName) => + this.getErrorHandlerErrorName(handler).pipe( + map((errorName) => { + return { + formName, + errorName, + }; + }) + ) + ), + map((descriptor) => { + return [ + 'x-form-error', + `x-form-${descriptor.formName}-error`, + `x-form-${descriptor.errorName}-error`, + `x-form-${descriptor.formName}-${descriptor.errorName}-error`, + ]; + }) + ); + } + + /** + * retrieve form card footer css classes ... + */ + getFormCardFooterCssClass() { + return this.getFormName().pipe( + map((formName) => { + return ['x-form-footer', `x-form-${formName}-footer`]; + }) + ); + } + + /** + * retrieve form card actions css classes ... + */ + getFormCardActionsCssClass() { + return this.getFormName().pipe( + map((formName) => { + return ['x-form-actions', `x-form-${formName}-actions`]; + }) + ); + } + + /** + * retrieve form field classes list ... + * + * @param control form control config ... + */ + getFormFieldClasses(control: XFormControlConfig) { + return this.getControlInfo(control).pipe( + map((cInfo) => { + // + const fControl = this.getControl(control.propName); + const isFocused = + this.getControlInputElementId(control) === this.currentInputFocus; + + // + return [ + 'x-form-field', + `x-form-${cInfo.type}-field`, + `x-form-field-${control.index.toString()}`, + `x-form-field-${control.propName.toString()}`, + `x-form-field-appearance-${cInfo.appearance + .toString() + .toLowerCase()}`, + // + // Focused ... + ...(isFocused ? ['x-focused'] : []), + // + // Disabled ... + ...(cInfo.disabled || fControl.disabled + ? ['x-form-field-disabled'] + : []), + // + // Valid ... + ...(fControl.valid ? ['x-form-field-valid'] : []), + // + // Invalid ... + ...(fControl.invalid ? ['x-form-field-invalid'] : []), + // + // Pending ... + ...(fControl.pending ? ['x-form-field-pending'] : []), + ]; + }) + ); + } + + /** + * retrieve form control classes list ... + * + * @param control form control config ... + */ + getFormControlClasses(control: XFormControlConfig) { + return this.getControlType(control).pipe( + map((type) => { + return [ + 'x-form-control', + `x-form-${type}-control`, + `x-form-control-${control.index.toString()}`, + `x-form-control-${control.propName.toString()}`, + ]; + }) + ); + } + + /** + * retrieve focusHandler of a form control ... + * + * @param control form control config ... + */ + getControlFocusHandler(control: XFormControlConfig) { + return this.getValue(control).pipe(map((config) => config.focusHandler)); + } + + /** + * retriev a form control label classes ... + */ + getFormControlFieldLabelClasses(control: XFormControlConfig) { + return this.getControlType(control).pipe( + map((type) => { + return [ + 'x-form-field-label', + `x-form-${type}-field-label`, + `x-form-field-label-${control.index.toString()}`, + `x-form-field-label-${control.propName.toString()}`, + ]; + }) + ); + } + + /** + * retrieve form control hint class list + */ + getFormControlHintClasses(control: XFormControlConfig) { + return this.getControlInfo(control).pipe( + map((cInfo) => { + return [ + 'x-form-control-hint', + `x-form-${cInfo.type}-control-hint`, + `x-form-control-${control.index.toString()}-hint`, + `x-form-control-${control.propName.toString()}-hint`, + ]; + }) + ); + } + //#endregion + + // + //#region Form Model ... + /** + * retrieve for model which provide's initial input for each field. + * if the form model is this type is Partial ... + */ + getFormModel() { + return this.getFormConfig().pipe( + concatMap((formConfig) => this.getOptionalValue(formConfig.model)) + ); + } + + /** + * determines the form has intial value provider or not ... + */ + formHasModel() { + return this.getFormModel().pipe( + map((formModel) => !isNullOrUndefined(formModel)) + ); + } + //#endregion + + // + //#region Form Updte On ... + /** + * retrieve form update strategy identifier ... + */ + getFormUpdateOn() { + return this.getFormConfig().pipe( + concatMap((formConfig) => + this.getOptionalValue(formConfig.updateOn).pipe( + map((updateOn) => (updateOn ? updateOn : XFormUpdateOn.CHANGE)) + ) + ) + ); + } + //#endregion + + // + //#region Form Validators ... + /** + * retrieve form Validators object ... + */ + getFormValidators() { + return this.getFormConfig().pipe( + concatMap((formConfig) => this.getOptionalValue(formConfig.validators)) + ); + } + + /** + * determines form has Validators or not ... + */ + formHasValidators() { + return this.getFormValidators().pipe( + map((formValidators) => !isNullOrUndefined(formValidators)) + ); + } + //#endregion + + // + //#region Form Controls ... + /** + * retrieve form Controls Configs ... + */ + getFormControls() { + return this.getFormConfig().pipe( + concatMap((formConfig) => + this.getArrayValue(formConfig.controls).pipe( + map((arrVal) => sort(arrVal, (v1, v2) => v1.index - v2.index)) + ) + ) + ); + } + //#endregion + + // + //#region Form Error Handlers ... + /** + * retrieve form Error Handlers Descriptor object ... + */ + getFormErrorHandlers() { + return this.getFormConfig().pipe( + concatMap((formConfig) => this.getArrayValue(formConfig.errorHandlers)) + ); + } + + /** + * determines form has Error Handlers or not ... + */ + formHasErrorHandlers() { + return this.hasChildValue(this.getFormErrorHandlers()); + } + + /** + * determines for has specific error or not ... + * + * @param handler form error handler ... + */ + formHasError(handler: XFormErrorHandler) { + return this.getValue(handler.errorName).pipe( + map((errorName) => { + return ( + !isNullOrUndefined(this.ERRORS) && + keys(this.ERRORS).includes(errorName) && + !isNullOrUndefined(this.ERRORS[errorName]) + ); + }) + ); + } + + /** + * determine form has specific error by name ... + * + * @param name error name ... + */ + formHasErrorByName(name: string) { + return ( + !isNullOrUndefined(this.ERRORS) && + keys(this.ERRORS).includes(name) && + !isNullOrUndefined(this.ERRORS[name]) + ); + } + + /** + * retrieve specific error from form ... + * + * @param handler form error handler ... + */ + getFormError(handler: XFormErrorHandler) { + return this.getErrorHandlerErrorName(handler).pipe( + concatMap((errorName) => + this.formHasError(handler).pipe( + map((hasError) => { + return { + errorName, + hasError, + }; + }) + ) + ), + map((descriptor) => + descriptor.hasError ? this.ERRORS[descriptor.errorName] : null + ) + ); + } + + /** + * retrieve specific form error by name ... + * + * @param name error name ... + */ + getFormErrorByName(name: string) { + return this.formHasErrorByName(name) ? this.ERRORS[name] : null; + } + //#endregion + //#endregion + + // + //#region Form Control Providers ... + /** + * retrieve specific control config ... + * + * @param name control name + */ + getFormControl(name: XFormControlNameIdentifier) { + return this.getFormControls().pipe( + map((controls) => controls.find((c) => c.propName === name)) + ); + } + + // + getControlInputElementId(control: XFormControlConfig) { + return `x-form-${control.propName.toString()}-input-element`; + } + + // + getControlInputElementIdByName(name: XFormControlNameIdentifier) { + return `x-form-${name.toString()}-input-element`; + } + + // + getControlInputElementClasses(control: XFormControlConfig) { + return this.getControlInputElementCssClasses(control).pipe( + concatMap((inpCss) => + this.getControlType(control).pipe( + map((type) => { + // + return [ + 'x-form-input-element', + `x-form-${type}-input-element`, + `x-form-${control.index.toString()}-input-element`, + `x-form-${control.propName.toString()}-input-element`, + // + ...inpCss.map((i) => i.toString()), + ]; + }) + ) + ) + ); + } + + // + getControlInputElementCssClasses(control: XFormControlConfig) { + return this.getCustomControlConfig(control).pipe( + concatMap((config) => { + return this.getArrayValue( + !isNullOrUndefined(config) && !isNullOrUndefined(config.inputCssClass) + ? config.inputCssClass + : [] + ); + }) + ); + } + + // + getControlDisableInput(control: XFormControlConfig) { + return this.getCustomControlConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue( + !isNullOrUndefined(config) && !isNullOrUndefined(config.disableInput) + ? config.disableInput + : false + ); + }) + ); + } + + // + isInputControlReadOnly(control: XFormControlConfig) { + return this.getControlType(control).pipe( + concatMap((type) => { + // + if (type === XFormControlType.Text) { + return this.getControlTypeConfig(control).pipe( + concatMap((config) => + config && (config as XFormInputControlConfig) + ? this.getOptionalValue( + (config as XFormInputControlConfig).readonly + ) + : of(false) + ) + ); + } else { + return this.getValue(false); + } + }) + ); + } + + // + getControlDisableIcon(control: XFormControlConfig) { + return this.getCustomControlConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue( + !isNullOrUndefined(config) && !isNullOrUndefined(config.disableIcon) + ? config.disableIcon + : false + ); + }) + ); + } + + // + //#region Control Type Providers ... + // + //#region Type Descriptor ... + /** + * retrieve a control type descriptor object ... + * + * @param control form control config object ... + */ + getControlTypeDescriptor(control: XFormControlConfig) { + return this.getValue(control.type); + } + //#endregion + + // + //#region Control Type ... + /** + * retrieve a control type ... + * + * @param control form control config object ... + */ + getControlType(control: XFormControlConfig) { + return this.getControlTypeDescriptor(control).pipe( + concatMap((typeDesc) => + this.getValue( + typeDesc && typeDesc.type ? typeDesc.type : XFormControlType.Hidden + ) + ) + ); + } + //#endregion + + // + //#region Control Type Config ... + /** + * retrieve a control type specific config object ... + * + * @param control form control config object ... + */ + getControlTypeConfig(control: XFormControlConfig) { + return this.getControlTypeDescriptor(control).pipe( + concatMap((typeDesc) => this.getOptionalValue(typeDesc.config)) + ); + } + //#endregion + + // + //#region Control Action Icon Configs ... + /** + * determines a custom control has clear icon or not ... + */ + hasControlClearIcon(control: XFormControlConfig): Observable { + return this.getCustomControlConfig(control).pipe( + map((config) => { + return ( + !isNullOrUndefined(config) && !isNullOrUndefined(config.clearIcon) + ); + }) + ); + } + + /** + * retrieve custom control clear icon + * + * @param control form control config object ... + */ + getControlClearIcon( + control: XFormControlConfig + ): Observable { + return this.getCustomControlConfig(control).pipe( + concatMap((config) => { + return !isNullOrUndefined(config) + ? this.getValue(config.clearIcon) + : of(null); + }), + map((iconSelector) => { + return !isNullOrUndefined(iconSelector) + ? iconSelector + : defaultFormControlClearIcon; + }) + ); + } + + /** + * determines a custom control has select icon or not ... + */ + hasControlSelectIcon(control: XFormControlConfig): Observable { + return this.getCustomControlConfig(control).pipe( + map((config) => { + return ( + !isNullOrUndefined(config) && !isNullOrUndefined(config.selectIcon) + ); + }) + ); + } + + /** + * retrieve custom control select icon + * + * @param control form control config object ... + */ + getControlSelectIcon( + control: XFormControlConfig + ): Observable { + return this.getCustomControlConfig(control).pipe( + concatMap((config) => { + return !isNullOrUndefined(config) + ? this.getValue(config.selectIcon) + : of(null); + }), + map((iconSelector) => { + return !isNullOrUndefined(iconSelector) + ? iconSelector + : defaultFormControlSelectIcon; + }) + ); + } + + // + //#region XForm Custom Control IconDescriptor Providers ... + /** + * retrieve Custom Control Action Icon's Name ... + * + * @param role Icon Action Descriptor... + * @param descriptor Selector icon descriptor ... + */ + getControlIconDescriptorName( + role: XControlIconRoleIdentifier, + descriptor: XControlIconDescriptor + ) { + return this.getValue(descriptor.name).pipe( + map((name) => { + return !isNullOrUndefined(name) && !isNullOrEmptyString(name.toString()) + ? name + : role === this.ControlIconRoles.Select + ? XIconNames.touch + : XIconNames.clear; + }) + ); + } + + /** + * retrieve Custom Control Action Icon's color ... + * + * @param role Icon Action Descriptor... + * @param descriptor Selector icon descriptor ... + */ + getControlIconDescriptorColor( + role: XControlIconRoleIdentifier, + descriptor: XControlIconDescriptor + ) { + return this.getValue(descriptor.color).pipe( + map((color) => { + return !isNullOrEmptyString(color) + ? color + : role === this.ControlIconRoles.Select + ? XColorWithBrightness.Success + : XColorWithBrightness.Danger; + }) + ); + } + + /** + * retrieve Custom Control Action Icon's applyStateColor ... + * + * @param descriptor Selector icon descriptor ... + */ + getControlIconDescriptorApplyStateColor(descriptor: XControlIconDescriptor) { + return this.getOptionalValue(descriptor.applyStateColor); + } + + /** + * retrieve Custom Control Action Icon's tooltip ... + * + * @param role Icon Action Descriptor... + * @param descriptor Selector icon descriptor ... + */ + getControlIconDescriptorTooltip( + role: XControlIconRoleIdentifier, + descriptor: XControlIconDescriptor + ) { + return this.getResourceValue(descriptor.tooltip).pipe( + map((tooltip) => { + return !isNullOrEmptyString(tooltip) + ? tooltip + : role === this.ControlIconRoles.Select + ? this.resourceProvider(this.ResourceIDs.select) + : this.resourceProvider(this.ResourceIDs.clear); + }) + ); + } + + /** + * retrieve Custom Control Action Icon's position ... + * + * @param descriptor Selector icon descriptor ... + */ + getControlIconDescriptorPosition(descriptor: XControlIconDescriptor) { + return this.getValue(descriptor.position).pipe( + map((position) => { + return !isNullOrEmptyString(position) + ? position + : XControlIconPosition.Suffix; + }) + ); + } + //#endregion + //#endregion + + // + //#region Control Type Configs + // + //#region Markdown Config ... + /** + * retrieve Markdown Control Config ... + * + * @param control form control config object ... + */ + getMarkdownConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormMarkdownControlConfig) + ); + } + + /** + * determines control has Markdown Config or not ... + * + * @param control form control config object ... + */ + hasMarkdownConfig(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * determines markdown Card is In Page or not ... + * + * @param control form control config object ... + */ + isMarkdownInPage(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue(config.isInPage); + }) + ); + } + + /** + * retrieve markdown has toolbar ... + * + * @param control form control config object ... + */ + getMarkDownHasToolbar(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue(config.hasToolbar); + }) + ); + } + + /** + * retrieve markdown wrap with card ... + * + * @param control form control config object ... + */ + isMarkdownWrapWithCard(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue(config.wrapWithCard); + }) + ); + } + + /** + * retrieve markdown card color ... + * + * @param control form control config object ... + */ + getMarkdownCardColor(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getValue( + config && config.cardColor ? config.cardColor : XColor.Light + ); + }) + ); + } + + /** + * retrieve markdown editorBoxColor value ... + * + * @param control form control config object ... + */ + getMarkdownEditorBoxColor(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getValue( + config && config.editorBoxColor ? config.editorBoxColor : XColor.Light + ); + }) + ); + } + + /** + * retrieve markdown previewBoxColor value ... + * + * @param control form control config object ... + */ + getMarkdownPreviewBoxColor(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getValue( + config && config.previewBoxColor + ? config.previewBoxColor + : XColorWithBrightness.MediumTint + ); + }) + ); + } + + /** + * determines markdown toolbar color is foreground color or not ... + * + * @param control form control config object ... + */ + getMarkdownCardForegroundColor(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue(config.cardForegroundColor); + }) + ); + } + + /** + * retrieve markdown mode ... + * + * @param control form control config object ... + */ + getMarkdownMode(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => this.getValue(config.mode || null)), + map((mode) => (mode ? mode : XMarkdownMode.PREVIEW)) + ); + } + + /** + * retrieve markdown Toolbar Color ... + * + * @param control form control config object ... + */ + getMarkDownToolbarColor(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getValue( + config && config.toolbarColor ? config.toolbarColor : XColor.Light + ); + }) + ); + } + + /** + * retrieve markdown Card Title ... + * + * @param control form control config object ... + */ + getMarkdownCardTitle(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue(config.cardTitle); + }) + ); + } + + /** + * retrieve markdown action provider ... + * + * @param control form control config object ... + */ + getMarkdownActionProvider(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + map((config) => { + // + if (!config.actionProvider) { + // + if (!this.actionProviders[control.propName]) { + this.actionProviders[control.propName] = + new Subject(); + } + + // + return this.actionProviders[ + control.propName + ] as Subject; + } else { + return config.actionProvider; + } + }) + ); + } + + /** + * determines markdown toolbar color is foreground color or not ... + * + * @param control form control config object ... + */ + getMarkDownToolbarForegroundColor(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue(config.toolbarForegroundColor); + }) + ); + } + + /** + * retrieve markdown cssClass ... + * + * @param control form control config object ... + */ + getMarkdownCssClass(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getArrayValue(config.cssClass); + }) + ); + } + + /** + * retrieve markdown content ... + * + * @param control form control config object ... + */ + getMarkDownContent(control: XFormControlConfig) { + return this.getValue(control).pipe( + map((ctrl) => { + // + if (!this.hasControl(ctrl.propName)) { + return ''; + } + + // + const xFormControl = this.getControl(ctrl.propName); + if (!xFormControl) { + return ''; + } + + // + return (xFormControl.value || '') as string; + }) + ); + } + + /** + * retrieve markdown Card SubTitle ... + * + * @param control form control config object ... + */ + getMarkdownCardSubTitle(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getResourceValue(config.cardSubTitle); + }) + ); + } + + /** + * retrieve markdown toolbar css class ... + * + * @param control form control config object ... + */ + getMarkdownToolbarCssClass(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getArrayValue(config.toolbarCssClass); + }) + ); + } + + /** + * retrieve markdown toolbar config ... + * + * @param control form control config object ... + */ + getMarkdownToolbarConfig(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue(config.toolbarConfig); + }) + ); + } + //#endregion + + // + //#region Select/Radio Config ... + /** + * retrieve select control config ... + * + * @param control form control config object ... + */ + getSelectConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormSelectControlConfig) + ); + } + + /** + * determines select control has config or not ... + * + * @param control form control config object ... + */ + hasSelectConfig(control: XFormControlConfig) { + return this.getSelectConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * retrieve select control options ... + * + * @param control form control config object ... + */ + getSelectOptions(control: XFormControlConfig) { + return this.getSelectConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.options)) + ); + } + + /** + * determines select control can select multiple or not ... + * + * @param control form control config object ... + */ + getSelectMultiple(control: XFormControlConfig) { + return this.getSelectConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.multiple)) + ); + } + + /** + * retrieve Select Control Panel Calsses ... + */ + getSelectPanelClasses(control: XFormControlConfig) { + return this.getFormControlClasses(control).pipe( + concatMap((classes) => + this.getSelectMultiple(control).pipe( + map((isMultiple) => ({ classes, isMultiple })) + ) + ), + map((selectInfo) => { + return [ + ...selectInfo.classes, + 'x-form-select-control-panel', + ...(selectInfo.isMultiple ? ['multiple'] : []), + ]; + }) + ); + } + + /** + * retrieve select control option css classes ... + * + * @param control form control config object ... + */ + getOptionClassName(option: XFormSelectControlOption) { + return this.getValue(option).pipe( + map((opt) => ['x-select-option', `x-select-${opt.value}-option`]) + ); + } + + // + // Radio ... + /** + * retrieve radio control config ... + * + * @param control form control config object ... + */ + getRadioConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormRadioControlConfig) + ); + } + + /** + * determines radio control has config or not ... + * + * @param control form control config object ... + */ + hasRadioConfig(control: XFormControlConfig) { + return this.getRadioConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * retrieve radio control options ... + * + * @param control form control config object ... + */ + getRadioOptions(control: XFormControlConfig) { + return this.getRadioConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.options)) + ); + } + + /** + * retrieve a radio control options label position ... + * + * @param control form control config object ... + */ + getRadioLabelPosition(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.labelPosition + : XFormControlLabelPosition.After + ).pipe(map((result) => result as 'before' | 'after')) + ) + ); + } + //#endregion + + // + //#region CheckBox Config ... + /** + * retrieve checkbox control config ... + * + * @param control form control config object ... + */ + getCheckboxConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormCheckBoxControlConfig) + ); + } + + /** + * determines checkbox control has config or not ... + * + * @param control form control config object ... + */ + hasCheckboxConfig(control: XFormControlConfig) { + return this.getCheckboxConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * retrieve a checkbox control label position ... + * + * @param control form control config object ... + */ + getCheckboxLabelPosition(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.labelPosition + : XFormControlLabelPosition.After + ).pipe(map((result) => result as 'before' | 'after')) + ) + ); + } + //#endregion + + // + //#region Date Picker Config ... + /** + * retrieve date control config ... + * + * @param control form control config object ... + */ + getDateConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormDatePickerControlConfig) + ); + } + + /** + * determines date control has config or not ... + * + * @param control form control config object ... + */ + hasDateConfig(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * retrieve date control start at ... + * + * @param control form control config object ... + */ + getDateStartAt(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getValue(config.startAt)) + ); + } + + /** + * retrieve date control picker position ... + * + * @param control form control config object ... + */ + getDatePickerPosition(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getValue(config.datePickerPosition)) + ); + } + + /** + * retrieve date control filter ... + * + * @param control form control config object ... + */ + getDateFilter(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getValue(config.dateFilter)) + ); + } + + /** + * retrieve date control input handler ... + * + * @param control form control config object ... + */ + getDateInputHandler(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getValue(config.dateInput)) + ); + } + + /** + * retrieve date control change handler ... + * + * @param control form control config object ... + */ + getDateChangeHandler(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getValue(config.dateChange)) + ); + } + + /** + * determines date control input field disabled or not ... + * + * @param control form control config object ... + */ + isDatePickerInputDisabled(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.disableInput)) + ); + } + + /** + * determines date control input field disabled or not ... + * + * @param control form control config object ... + */ + isDatePickerToggleDisabled(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.disablePicker)) + ); + } + + /** + * retriece date control classes ... + * + * @param control form control config object ... + */ + getDatePickerClasses(control: XFormControlConfig) { + return this.getControlType(control).pipe( + map((type) => { + return [ + 'x-date-picker', + `x-form-control-${type}-picker`, + `x-form-control-${control.propName.toString()}-date-picker`, + ]; + }) + ); + } + + /** + * retriece date control toggle icon name ... + * + * @param control form control config object ... + */ + getDatePickerIcon(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.toggleIcon)), + map((icon) => (icon ? icon : XIconNames.calendar)) + ); + } + + /** + * retriece date control toggle icon color ... + * + * @param control form control config object ... + */ + getDatePickerIconColor(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.iconColor)), + map((color) => (color ? color : XColor.Light)) + ); + } + + /** + * retriece date control toggle icon tooltip ... + * + * @param control form control config object ... + */ + getDatePickerIconTooltip(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.iconTooltip)) + ); + } + + /** + * retriece date control toggle apply color state ... + * + * @param control form control config object ... + */ + getDatePickerIconApplyStateColor(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.applyStateColor)) + ); + } + + /** + * determines date control icon position is suffix or not ... + * + * @param control form control config object ... + */ + isDatePickerHasSuffixIcon(control: XFormControlConfig) { + return this.getDatePickerPosition(control).pipe( + map( + (position) => position === XFormDatePickerControlPickerPosition.Suffix + ) + ); + } + + /** + * determines date control icon position is prefix or not ... + * + * @param control form control config object ... + */ + isDatePickerHasPrefixIcon(control: XFormControlConfig) { + return this.getDatePickerPosition(control).pipe( + map( + (position) => position === XFormDatePickerControlPickerPosition.Prefix + ) + ); + } + //#endregion + + // + //#region Slider Config ... + /** + * retrieve slider control config ... + * + * @param control form control config object ... + */ + getSliderConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormSliderControlConfig) + ); + } + + /** + * determines slider control has config or not ... + * + * @param control form control config object ... + */ + hasSliderConfig(control: XFormControlConfig) { + return this.getSliderConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * retrieve slider control min value ... + * + * @param control form control config object ... + */ + getSliderMin(control: XFormControlConfig) { + return this.getSliderConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.min)), + map((min) => (min ? min : 0)) + ); + } + + /** + * retrieve slider control max value ... + * + * @param control form control config object ... + */ + getSliderMax(control: XFormControlConfig) { + return this.getSliderConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.max)), + map((max) => (max ? max : 99)) + ); + } + + /** + * retrieve slider control step value ... + * + * @param control form control config object ... + */ + getSliderStep(control: XFormControlConfig) { + return this.getSliderConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.step)), + map((step) => (step ? step : 1)) + ); + } + + /** + * retrieve slider control invert value ... + * + * @param control form control config object ... + */ + getSliderInvert(control: XFormControlConfig) { + return this.getSliderConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.invert)) + ); + } + + /** + * determines slider control is vertical or not ... + * + * @param control form control config object ... + */ + isSliderVertical(control: XFormControlConfig) { + return this.getSliderConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.vertical)) + ); + } + + /** + * determines slider control show thumb label or not ... + * + * @param control form control config object ... + */ + showSliderThumbLabel(control: XFormControlConfig) { + return this.getSliderConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.thumbLabel)) + ); + } + + /** + * retrieve slider formatter function ... + */ + getSliderFormatter(control: XFormControlConfig) { + return this.getSliderConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.formatter)) + ); + } + //#endregion + + // + //#region Toggle Config ... + /** + * retrieve a checkbox control label position ... + * + * @param control form control config object ... + */ + getToggleLabelPosition(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.labelPosition + : XFormControlLabelPosition.After + ).pipe(map((result) => result as 'before' | 'after')) + ) + ); + } + //#endregion + + // + //#region Map Config ... + /** + * retrieve map control config ... + * + * @param control form control config object ... + */ + getMapConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormMapControlConfig) + ); + } + + /** + * determines map control has config or not ... + * + * @param control form control config object ... + */ + hasMapConfig(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * retrieve map component's selected point value ... + * + * @param control form control config object ... + */ + getMapSelectedPoint(control: XFormControlConfig): Observable { + // + const fControl = this.getControl(control.propName); + if (!fControl || !fControl.value) { + return of(null); + } + + // + const fValue = fControl.value as string; + if (isNullOrEmptyString(fValue)) { + return of(null); + } + + // + const result = fromLongLat(fValue); + this.getMapActionProvider(control).pipe( + tap((actionProvider) => { + // + if (actionProvider) { + // + actionProvider.next({ + action: XMapAction.SetCenter, + payload: result, + }); + } + }) + ); + + // + return of(result); + } + + /** + * retrieve map control zoom value ... + * + * @param control form control config object ... + */ + getMapLatitude(control: XFormControlConfig) { + return this.getMapSelectedPoint(control).pipe( + map((point) => (point && point.latitude ? +point.latitude : 0)) + ); + } + + /** + * retrieve map control zoom value ... + * + * @param control form control config object ... + */ + getMapLongitude(control: XFormControlConfig) { + return this.getMapSelectedPoint(control).pipe( + map((point) => (point && point.longitude ? +point.longitude : 0)) + ); + } + + /** + * retrieve map control zoom value ... + * + * @param control form control config object ... + */ + getMapZoom(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => (config.zoom ? this.getValue(config.zoom) : of(0))) + ); + } + + /** + * retrieve map control markerStyle value ... + * + * @param control form control config object ... + */ + getMapMarkerStyle(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.markerStyle)), + map((markerStyle) => (markerStyle ? markerStyle : defaultMarkerStyle)) + ); + } + + /** + * retrieve map control cssClass value ... + * + * @param control form control config object ... + */ + getMapCssClass(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.cssClass)) + ); + } + + /** + * determines map control canSelect or not ... + * + * @param control form control config object ... + */ + mapCanSelect(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.canSelect)) + ); + } + + /** + * determines map control showProgressBar or not ... + * + * @param control form control config object ... + */ + mapShowProgressBar(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showProgressBar)), + map((canShow) => (isNullOrUndefined(canShow) ? true : canShow)) + ); + } + + /** + * retrieve map control progressBarColor value ... + * + * @param control form control config object ... + */ + getMapProgressBarColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.progressBarColor)), + map((color) => (color ? color : XColor.Light)) + ); + } + + /** + * retrieve map control progressBarPosition value ... + * + * @param control form control config object ... + */ + getMapProgressBarPosition(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.progressBarPosition)), + map((position) => + isNullOrEmptyString(position) ? XMapItemPosition.TOP : position + ) + ); + } + + // + //#region Map Search Bar ... + /** + * determines map control showSearchBar or not ... + * + * @param control form control config object ... + */ + mapShowSearchBar(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showSearchBar)), + map((result) => (isNullOrUndefined(result) ? true : result)) + ); + } + + /** + * retrieve map control searchBarType value ... + * + * @param control form control config object ... + */ + getMapSearchBarType(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.searchBarType)), + map((result) => + isNullOrEmptyString(result) ? XSearchBarType.Search : result + ) + ); + } + + /** + * retrieve map control searchBarValue value ... + * + * @param control form control config object ... + */ + getMapSearchBarValue(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getValue(config.searchBarValue)) + ); + } + + /** + * retrieve map control searchBarDebounce value ... + * + * @param control form control config object ... + */ + getMapSearchBarDebounce(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.searchBarDebounce)), + map((result) => + isNullOrUndefined(result) + ? this.config.defaultSearchDebounceTime + : result + ) + ); + } + + /** + * retrieve map control searchBarSpellcheck value ... + * + * @param control form control config object ... + */ + getMapSearchBarSpellcheck(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getValue(config.searchBarSpellcheck)), + map((result) => (isNullOrUndefined(result) ? false : result)) + ); + } + + /** + * retrieve map control searchPlaceProvider value ... + * + * @param control form control config object ... + */ + getMapSearchPlaceProvider(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getValue(config.searchPlaceProvider)), + map((result) => + isNullOrUndefined(result) ? defaultMapSearchPlaceProvider : result + ) + ); + } + + /** + * retrieve map control searchBarColor value ... + * + * @param control form control config object ... + */ + getMapSearchBarColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.searchBarColor)), + map((result) => + isNullOrEmptyString(result) ? XColorWithBrightness.Primary : result + ) + ); + } + + /** + * retrieve map control searchBarForegroundColor value ... + * + * @param control form control config object ... + */ + getMapSearchBarForegroundColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarForegroundColor) + ) + ); + } + + /** + * retrieve map control searchBarClearIcon value ... + * + * @param control form control config object ... + */ + getMapSearchBarClearButtonIcon(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarClearButtonIcon) + ), + map((result) => (isNullOrUndefined(result) ? XIconNames.clear : result)) + ); + } + + /** + * retrieve map control searchBarTitle value ... + * + * @param control form control config object ... + */ + getMapSearchBarTitle(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getValue(config.searchBarTitle)), + map((result) => + isNullOrUndefined(result) ? XResourceIDs.search : result + ) + ); + } + + /** + * retrieve map control searchBarSearchIcon value ... + * + * @param control form control config object ... + */ + getMapSearchBarSearchButtonIcon(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarSearchButtonIcon) + ), + map((result) => (isNullOrUndefined(result) ? XIconNames.search : result)) + ); + } + + /** + * retrieve map control searchBarInputColor value ... + * + * @param control form control config object ... + */ + getMapSearchBarSearchInputColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarSearchInputColor) + ), + map((result) => + isNullOrEmptyString(result) ? XColorWithBrightness.Primary : result + ) + ); + } + + /** + * retrieve map control searchBarForegroundInputColor value ... + * + * @param control form control config object ... + */ + getMapSearchBarForegroundSearchInputColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarForegroundSearchInputColor) + ) + ); + } + + /** + * retrieve map control searchResultListColor value ... + * + * @param control form control config object ... + */ + getMapSearchResultListColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchResultListColor) + ), + map((result) => + isNullOrEmptyString(result) ? XColorWithBrightness.Light : result + ) + ); + } + + /** + * retrieve map control searchBarActionProvider value ... + * + * @param control form control config object ... + */ + getMapSearchBarActionProvider(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + map((config) => { + // + const key = `${control.propName.toString()}_SearchBar`; + if (!config.searchBarActionProvider) { + // + if (!this.actionProviders[key]) { + this.actionProviders[key] = new Subject(); + } + + // + return this.actionProviders[key] as Subject; + } else { + return config.searchBarActionProvider; + } + }) + ); + } + + /** + * retrieve map control searchBarClearIconColor value ... + * + * @param control form control config object ... + */ + getMapSearchBarClearButtonIconColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarClearButtonIconColor) + ), + map((result) => + isNullOrEmptyString(result) ? XColorWithBrightness.Light : result + ) + ); + } + + /** + * retrieve map control searchBarCssClass value ... + * + * @param control form control config object ... + */ + getMapSearchBarCssClass(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.searchBarCssClass)) + ); + } + + /** + * retrieve map control searchBarSearchIconColor value ... + * + * @param control form control config object ... + */ + getMapSearchBarSearchButtonIconColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarSearchButtonIconColor) + ), + map((result) => + isNullOrEmptyString(result) ? XColorWithBrightness.Light : result + ) + ); + } + + /** + * retrieve map control searchBarCancelButtonIcon value ... + * + * @param control form control config object ... + */ + getMapSearchBarCancelButtonIcon(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarCancelButtonIcon) + ), + map((result) => (isNullOrUndefined(result) ? XIconNames.cancel : result)) + ); + } + + /** + * retrieve map control searchBarPosition value ... + * + * @param control form control config object ... + */ + getMapSearchBarPosition(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.searchBarPosition)), + map((result) => + isNullOrEmptyString(result) ? XMapItemPosition.TOP : result + ) + ); + } + + /** + * retrieve map control searchBarKeyboardType value ... + * + * @param control form control config object ... + */ + getMapSearchBarKeyboardType(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarKeyboardType) + ), + map((result) => + isNullOrEmptyString(result) ? XSearchBarType.Search : result + ) + ); + } + + /** + * retrieve map control searchBarCancelButtonTitle value ... + * + * @param control form control config object ... + */ + getMapSearchBarCancelButtonTitle(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getValue(config.searchBarCancelButtonTitle)) + ); + } + + /** + * retrieve map control searchBarEnterKeyHint value ... + * + * @param control form control config object ... + */ + getMapSearchBarEnterKeyHint(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarEnterKeyHint) + ), + map((result) => + isNullOrEmptyString(result) ? XSearchBarEnterKeyHint.Search : result + ) + ); + } + + /** + * determines map control searchBarShowCancelButton or not ... + * + * @param control form control config object ... + */ + mapShowSearchBarCancelButton(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarShowCancelButton) + ) + ); + } + //#endregion + + /** + * determines map control showSelectedLocation or not ... + * + * @param control form control config object ... + */ + mapShowSelectedLocation(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showSelectedLocation)), + map((result) => (isNullOrUndefined(result) ? false : result)) + ); + } + + /** + * retrieve map control selectedLocationPosition value ... + * + * @param control form control config object ... + */ + getMapSelectedLocationPosition(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.selectedLocationPosition) + ), + map((position) => + isNullOrEmptyString(position) ? XMapItemPosition.TOP : position + ) + ); + } + + /** + * determines map control showGoMarkedPlace or not ... + * + * @param control form control config object ... + */ + mapShowGoMarkedPlace(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showGoMarkedPlace)), + map((result) => (isNullOrUndefined(result) ? true : result)) + ); + } + + /** + * retrieve map control goMarkedPlacePosition value ... + * + * @param control form control config object ... + */ + getMapGoMarkedPlacePosition(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.goMarkedPlacePosition) + ), + map((position) => + isNullOrEmptyString(position) ? XMapItemPosition.TOP : position + ) + ); + } + + /** + * determines map control showClearMarker or not ... + * + * @param control form control config object ... + */ + mapShowClearMarker(control: XFormControlConfig) { + // + // Clear Marker in Form Components only must shown when user decide to it and the present Type is with dialog + // since the clear value handle clear marker actions in without dialog mode ... + return this.getMapConfig(control).pipe( + concatMap((config) => + forkJoin({ + showClearMarker: this.getOptionalValue(config.showClearMarker), + withDialog: this.isMapPresentWithDialog(control), + }) + ), + map( + (info) => + (isNullOrUndefined(info.showClearMarker) + ? true + : info.showClearMarker) && info.withDialog + ) + ); + } + + /** + * retrieve map control clearMarkerPosition value ... + * + * @param control form control config object ... + */ + getMapClearMarkerPosition(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.clearMarkerPosition)), + map((position) => + isNullOrEmptyString(position) ? XMapItemPosition.TOP : position + ) + ); + } + + /** + * determines map control showLocator or not ... + * + * @param control form control config object ... + */ + mapShowLocator(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showLocator)), + map((result) => (isNullOrUndefined(result) ? true : result)) + ); + } + + /** + * retrieve map control locatorPosition value ... + * + * @param control form control config object ... + */ + getMapLocatorPosition(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.locatorPosition)), + map((position) => + isNullOrEmptyString(position) ? XMapItemPosition.TOP : position + ) + ); + } + + /** + * determines map control showZoom or not ... + * + * @param control form control config object ... + */ + mapShowZoom(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showZoom)), + map((result) => (isNullOrUndefined(result) ? true : result)) + ); + } + + /** + * determines map control showRotate or not ... + * + * @param control form control config object ... + */ + mapShowRotate(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showRotate)), + map((result) => (isNullOrUndefined(result) ? true : result)) + ); + } + + /** + * determines map control showZoomSlider or not ... + * + * @param control form control config object ... + */ + mapShowZoomSlider(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showZoomSlider)), + map((result) => (isNullOrUndefined(result) ? false : result)) + ); + } + + /** + * determines map control isInPage or not ... + * + * @param control form control config object ... + */ + isMapInPage(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.isInPage)) + ); + } + + /** + * determines map control wrapWithCard or not ... + * + * @param control form control config object ... + */ + isMapWrapWithCard(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.wrapWithCard)) + ); + } + + /** + * retrieve map control cardColor value ... + * + * @param control form control config object ... + */ + getMapCardColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.cardColor)), + map((color) => (color ? color : XColor.Light)) + ); + } + + /** + * determines map control cardForegroundColor or not ... + * + * @param control form control config object ... + */ + isMapCardForegroundColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.cardForegroundColor)) + ); + } + + /** + * retrieve map control cardTitle value ... + * + * @param control form control config object ... + */ + getMapCardTitle(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.cardTitle)) + ); + } + + /** + * retrieve map control cardSubTitle value ... + * + * @param control form control config object ... + */ + getMapCardSubTitle(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.cardSubTitle)) + ); + } + + /** + * determines map control showCardFooter or not ... + * + * @param control form control config object ... + */ + mapCardShowFooter(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.cardShowFooter)) + ); + } + + /** + * retrieve map control actionProvider value ... + * + * @param control form control config object ... + */ + getMapActionProvider(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + map((config) => { + // + if (!config.actionProvider) { + // + if (!this.actionProviders[control.propName]) { + this.actionProviders[control.propName] = + new Subject(); + } + + // + return this.actionProviders[ + control.propName + ] as Subject; + } else { + return config.actionProvider; + } + }) + ); + } + + // + //#region Map Present Types ... + /** + * retrieve map control presentType value ... + * + * @param control form control config object ... + */ + getMapPresentType(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.presentType)), + map((presentType) => + !isNullOrEmptyString(presentType) + ? presentType + : XFormMapControlPresentType.WithoutDialog + ) + ); + } + + /** + * determines map control present WithoutDialog or not ... + * + * @param control form control config object ... + */ + isMapPresentWithoutDialog(control: XFormControlConfig) { + return this.getMapPresentType(control).pipe( + map( + (presentType) => + presentType === XFormMapControlPresentType.WithoutDialog + ) + ); + } + + /** + * determines map control present WithDialog or not ... + * + * @param control form control config object ... + */ + isMapPresentWithDialog(control: XFormControlConfig) { + return this.getMapPresentType(control).pipe( + map( + (presentType) => presentType === XFormMapControlPresentType.WithDialog + ) + ); + } + //#endregion + //#endregion + + // + //#region ColorPicker Config ... + /** + * retrieve color picker control config ... + * + * @param control form control config object ... + */ + getColorPickerConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormColorPickerControlConfig) + ); + } + + /** + * determines color picker control has config or not ... + * + * @param control form control config object ... + */ + hasColorPickerConfig(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * retrieve color picker control color value ... + * + * @param control form control config object ... + */ + getColorPickerColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.color)), + map((color) => (isNullOrEmptyString(color) ? XColor.Light : color)) + ); + } + + /** + * retrieve color picker control colorViewBorderColor value ... + * + * @param control form control config object ... + */ + getColorPickerColorViewBorderColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.colorViewBorderColor)), + map((color) => (isNullOrEmptyString(color) ? XColor.Light : color)) + ); + } + + /** + * retrieve color picker control colorViewBorderStyle value ... + * + * @param control form control config object ... + */ + getColorPickerColorViewBorderStyle(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.colorViewBorderStyle)), + map((style) => (isNullOrEmptyString(style) ? XBorderStyle.Solid : style)) + ); + } + + /** + * retrieve color picker control colorViewBorderWidth value ... + * + * @param control form control config object ... + */ + getColorPickerColorViewBorderWidth(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.colorViewBorderWidth)) + ); + } + + /** + * retrieve color picker control cssClass value ... + * + * @param control form control config object ... + */ + getColorPickerCssClass(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.cssClass)) + ); + } + + /** + * retrieve color picker control cardColor value ... + * + * @param control form control config object ... + */ + getColorPickerCardColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.cardColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Light)) + ); + } + + /** + * retrieve color picker control cardTitle value ... + * + * @param control form control config object ... + */ + getColorPickerCardTitle(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.cardTitle)) + ); + } + + /** + * retrieve color picker control cursorColor value ... + * + * @param control form control config object ... + */ + getColorPickerCursorColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.cursorColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Dark)) + ); + } + + /** + * retrieve color picker control presetColors value ... + * + * @param control form control config object ... + */ + getColorPickerPresetColors(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.presetColors)) + ); + } + + /** + * retrieve color picker control outputFormat value ... + * + * @param control form control config object ... + */ + getColorPickerOutputFormat(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.outputFormat)), + map((format) => + !isNullOrEmptyString(format) ? format : XColorPickerOutputFormat.XHEX + ) + ); + } + + /** + * retrieve color picker control cardSubTitle value ... + * + * @param control form control config object ... + */ + getColorPickerCardSubTitle(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.cardSubTitle)) + ); + } + + /** + * retrieve color picker control colorTextColor value ... + * + * @param control form control config object ... + */ + getColorPickerColorTextColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.colorTextColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Dark)) + ); + } + + /** + * determines color picker control showColorValue or not ... + * + * @param control form control config object ... + */ + colorPickerShowColorValue(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + config.showColorValue ? this.getValue(config.showColorValue) : of(true) + ) + ); + } + + /** + * determines color picker control showHueChannel or not ... + * + * @param control form control config object ... + */ + colorPickerShowHueChannel(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + config.showHueChannel ? this.getValue(config.showHueChannel) : of(true) + ) + ); + } + + /** + * determines color picker control foregroundColor or not ... + * + * @param control form control config object ... + */ + isColorPickerForegroundColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.foregroundColor)) + ); + } + + /** + * determines color picker control showPresetColors or not ... + * + * @param control form control config object ... + */ + colorPickerShowPresetColors(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + config.showPresetColors + ? this.getValue(config.showPresetColors) + : of(true) + ) + ); + } + + /** + * determines color picker control showAlphaChannel or not ... + * + * @param control form control config object ... + */ + colorPickerShowAlphaChannel(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + config.showAlphaChannel + ? this.getValue(config.showAlphaChannel) + : of(true) + ) + ); + } + + /** + * retrieve color picker control presetColorsLable value ... + * + * @param control form control config object ... + */ + getColorPickerPresetColorsLable(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.presetColorsLable)), + map((value) => + isNullOrEmptyString(value) ? XResourceIDs.preset_colors : value + ) + ); + } + + /** + * retrieve color picker control copyToClipboardIconLabel value ... + * + * @param control form control config object ... + */ + getColorPickerCopyToClipboardIconLabel(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + this.getResourceValue(config.copyToClipboardIconLabel) + ), + map((value) => + isNullOrEmptyString(value) ? XResourceIDs.copy_to_clipboard : value + ) + ); + } + + /** + * retrieve color picker control colorInputIconLabel value ... + * + * @param control form control config object ... + */ + getColorPickerColorInputIconLabel(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.colorInputIconLabel)), + map((value) => (isNullOrEmptyString(value) ? XResourceIDs.input : value)) + ); + } + + /** + * retrieve color picker control clearColorIconLabel value ... + * + * @param control form control config object ... + */ + getColorPickerClearColorIconLabel(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.clearColorIconLabel)), + map((value) => (isNullOrEmptyString(value) ? XResourceIDs.clear : value)) + ); + } + + /** + * determines color picker control showPaletteChannel or not ... + * + * @param control form control config object ... + */ + colorPickerShowPaletteChannel(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + config.showPaletteChannel + ? this.getValue(config.showPaletteChannel) + : of(true) + ) + ); + } + + /** + * determines color picker control cardForegroundColor or not ... + * + * @param control form control config object ... + */ + isColorPickerCardForegroundColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.cardForegroundColor)) + ); + } + + /** + * determines color picker control showCopyToClipboard or not ... + * + * @param control form control config object ... + */ + colorPickerShowCopyToClipboard(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + config.showCopyToClipboard + ? this.getValue(config.showCopyToClipboard) + : of(false) + ) + ); + } + + /** + * determines color picker control showColorInput or not ... + * + * @param control form control config object ... + */ + colorPickerShowColorInput(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + config.showCopyToClipboard + ? this.getValue(config.showColorInput) + : of(true) + ) + ); + } + + /** + * retrieve color picker control representColorFormats value ... + * + * @param control form control config object ... + */ + getColorPickerRepresentColorFormats(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.representColorFormats)), + map((formats) => + hasChild(formats) ? formats : [XColorPickerOutputFormat.XHEX] + ) + ); + } + + /** + * retrieve color picker control presetColorsLableColor value ... + * + * @param control form control config object ... + */ + getColorPickerPresetColorsLableColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.presetColorsLableColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Dark)) + ); + } + + /** + * retrieve color picker control copyToClipboardIconColor value ... + * + * @param control form control config object ... + */ + getColorPickerCopyToClipboardIconColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.copyToClipboardIconColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Tertiary)) + ); + } + + /** + * retrieve color picker control copyToClipboardIconColor value ... + * + * @param control form control config object ... + */ + getColorPickerColorInputIconColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.colorInputIconColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Secondary)) + ); + } + + /** + * retrieve color picker control clearColorIconColor value ... + * + * @param control form control config object ... + */ + getColorPickerClearColorIconColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.clearColorIconColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Danger)) + ); + } + + /** + * determines color picker control showSelectedColorPreview or not ... + * + * @param control form control config object ... + */ + colorPickerShowSelectedColorPreview(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + config.showSelectedColorPreview + ? this.getValue(config.showSelectedColorPreview) + : of(true) + ) + ); + } + + /** + * determines color picker control isInPage or not ... + * + * @param control form control config object ... + */ + isColorPickerInPage(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.isInPage)) + ); + } + + /** + * determines color picker control wrapWithCard or not ... + * + * @param control form control config object ... + */ + isColorPickerWrapWithCard(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.wrapWithCard)) + ); + } + + /** + * determines color picker control showCardFooter or not ... + * + * @param control form control config object ... + */ + colorPickercardShowFooter(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showCardFooter)) + ); + } + + /** + * retrieve color picker component's selected value ... + * + * @param control form control config object ... + */ + getColorPickerSelectedColor(control: XFormControlConfig) { + // + const fControl = this.getControl(control.propName); + if (!fControl) { + return of(''); + } + + // + const fValue = fControl.value as string; + if (isNullOrEmptyString(fValue)) { + return of(''); + } + + // + return of(fValue); + } + + /** + * retrieve color picker control actionProvider value ... + * + * @param control form control config object ... + */ + getColorPickerActionProvider(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + map((config) => { + // + if (!config.actionProvider) { + // + if (!this.actionProviders[control.propName]) { + this.actionProviders[control.propName] = + new Subject(); + } + + // + return this.actionProviders[ + control.propName + ] as Subject; + } else { + return config.actionProvider; + } + }) + ); + } + + // + //#region ColorPicker Present Types ... + /** + * retrieve color picker control presentType value ... + * + * @param control form control config object ... + */ + getColorPickerPresentType(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + !isNullOrUndefined(config) && !isNullOrUndefined(config.presentType) + ? this.getOptionalValue(config.presentType) + : of(null) + ), + map((presentType) => + !isNullOrEmptyString(presentType) + ? presentType + : XFormColorPickerControlPresentType.WithoutDialog + ) + ); + } + + /** + * determines color picker control present WithoutDialog or not ... + * + * @param control form control config object ... + */ + isColorPickerPresentWithoutDialog(control: XFormControlConfig) { + return this.getColorPickerPresentType(control).pipe( + map( + (presentType) => + presentType === XFormColorPickerControlPresentType.WithoutDialog + ) + ); + } + + /** + * determines color picker control present WithDialog or not ... + * + * @param control form control config object ... + */ + isColorPickerPresentWithDialog(control: XFormControlConfig) { + return this.getColorPickerPresentType(control).pipe( + map( + (presentType) => + presentType === XFormColorPickerControlPresentType.WithDialog + ) + ); + } + //#endregion + //#endregion + + // + //#region Avatar Config ... + /** + * retrieve avatar upload control config ... + * + * @param control form control config object ... + */ + getAvatarConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormAvatarUploadControlConfig) + ); + } + + /** + * determines avatar upload control has config or not ... + * + * @param control form control config object ... + */ + hasAvatarConfig(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * determines avatar upload control canDrop or not ... + * + * @param control form control config object ... + */ + avatarCanDrop(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.canDrop)) + ); + } + + /** + * determines avatar upload control isInPage or not ... + * + * @param control form control config object ... + */ + isAvatarInPage(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.isInPage)) + ); + } + + /** + * determines avatar upload control wrapWithCard or not ... + * + * @param control form control config object ... + */ + isAvatarWrapWithCard(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.wrapWithCard)) + ); + } + + /** + * retrieve avatar upload control color value ... + * + * @param control form control config object ... + */ + getAvatarColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.color)), + map((color) => (color ? color : XColor.Light)) + ); + } + + /** + * determines map control showCardFooter or not ... + * + * @param control form control config object ... + */ + avatarCardShowFooter(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showCardFooter)) + ); + } + + /** + * determines avatar upload control foregroundColor or not ... + * + * @param control form control config object ... + */ + isAvatarForegroundColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.foregroundColor)) + ); + } + + /** + * retrieve avatar upload control placeHolderAvatar value ... + * + * @param control form control config object ... + */ + getAvatarPlaceHolderAvatar(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getValue(config.placeHolderAvatar)) + ); + } + + /** + * retrieve avatar upload control minAllowedFileSize value ... + * + * @param control form control config object ... + */ + getAvatarMinAllowedFileSize(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.minAllowedFileSize)), + map((minAllowedFileSize) => + minAllowedFileSize ? minAllowedFileSize : this.config.minAllowedFileSize + ) + ); + } + + /** + * retrieve avatar upload control maxAllowedFileSize value ... + * + * @param control form control config object ... + */ + getAvatarMaxAllowedFileSize(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.maxAllowedFileSize)), + map((maxAllowedFileSize) => + maxAllowedFileSize + ? maxAllowedFileSize + : this.config.maxProfileImageSize + ) + ); + } + + /** + * retrieve avatar upload control cardColor value ... + * + * @param control form control config object ... + */ + getAvatarCardColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.cardColor)), + map((color) => (color ? color : XColor.Light)) + ); + } + + /** + * determines avatar upload control cardForegroundColor or not ... + * + * @param control form control config object ... + */ + isAvatarCardForegroundColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.cardForegroundColor)) + ); + } + + /** + * determines avatar upload control fileDropAreaIsInPage or not ... + * + * @param control form control config object ... + */ + isAvatarDropAreaInPage(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.fileDropAreaIsInPage)) + ); + } + + /** + * retrieve avatar upload control cardTitle value ... + * + * @param control form control config object ... + */ + getAvatarCardTitle(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.cardTitle)) + ); + } + + /** + * retrieve avatar upload control cssClass value ... + * + * @param control form control config object ... + */ + getAvatarCssClass(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.cssClass)) + ); + } + + /** + * determines avatar upload control fileDropAreaWrapWithCard or not ... + * + * @param control form control config object ... + */ + isAvatarDropAreaWrapWithCard(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaWrapWithCard) + ) + ); + } + + /** + * retrieve avatar upload control fileDropAreaIcon value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaIcon(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getValue(config.fileDropAreaIcon)), + map((icon) => (icon ? icon : XIconNames.arrow_down)) + ); + } + + /** + * determines avatar upload control fileDropAreaDragDisabled or not ... + * + * @param control form control config object ... + */ + isAvatarDropAreaDragDisabled(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaDragDisabled) + ) + ); + } + + /** + * retrieve avatar upload control cardSubTitle value ... + * + * @param control form control config object ... + */ + getAvatarCardSubTitle(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.cardSubTitle)) + ); + } + + /** + * retrieve avatar control actionProvider value ... + * + * @param control form control config object ... + */ + getAvatarActionProvider(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + map((config) => { + // + if (!config.actionProvider) { + // + if (!this.actionProviders[control.propName]) { + this.actionProviders[control.propName] = + new Subject(); + } + + // + return this.actionProviders[ + control.propName + ] as Subject; + } else { + return config.actionProvider; + } + }) + ); + } + + /** + * determines map control fileDropAreaShowCardFooter or not ... + * + * @param control form control config object ... + */ + avatarDropAreaCardShowFooter(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaShowCardFooter) + ) + ); + } + + /** + * retrieve avatar upload control fileDropAreaColor value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.fileDropAreaColor)), + map((color) => (color ? color : XColor.Light)) + ); + } + + /** + * determines avatar upload control fileDropAreaForegroundColor or not ... + * + * @param control form control config object ... + */ + isAvatarDropAreaForegroundColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaForegroundColor) + ) + ); + } + + /** + * determines avatar upload control fileDropAreaDisableFileCheck or not ... + * + * @param control form control config object ... + */ + isAvatarDropAreaDisableFileCheck(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaDisableFileCheck) + ) + ); + } + + /** + * retrieve avatar upload control fileDropAreaCardColor value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaCardColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaCardColor) + ), + map((color) => (color ? color : XColor.Light)) + ); + } + + /** + * determines avatar upload control fileDropAreaCardForegroundColor or not ... + * + * @param control form control config object ... + */ + isAvatarDropAreaCardForegroundColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaCardForegroundColor) + ) + ); + } + + /** + * retrieve avatar upload control emptyFileException value ... + * + * @param control form control config object ... + */ + getAvatarEmptyFileException(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.emptyFileException)), + map((exception) => (exception ? exception : XExceptionIDs.EmptyFile)) + ); + } + + /** + * retrieve avatar upload control fileDropAreaErrorColor value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaErrorColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaErrorColor) + ), + map((color) => (color ? color : XColor.Danger)) + ); + } + + /** + * retrieve avatar upload control fileDropAreaMessage value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaMessage(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.fileDropAreaMessage)), + map((message) => (message ? message : XResourceIDs.drag_drop_here)), + concatMap((resource) => this.getResourceValue(resource)) + ); + } + + /** + * retrieve avatar upload control fileDropAreaAccentColor value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaAccesntColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaAccentColor) + ), + map((color) => (color ? color : XColor.Secondary)) + ); + } + + /** + * retrieve avatar upload control allowedExtensions value ... + * + * @param control form control config object ... + */ + getAvatarAllowedExtensions(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.allowedExtensions)), + map((allowedExtensions) => + hasChild(allowedExtensions) + ? allowedExtensions + : this.config.allowedImageExtensions + ) + ); + } + + /** + * retrieve avatar upload control fileDropAreaCardTitle value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaCardTitle(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.fileDropAreaCardTitle)) + ); + } + + /** + * retrieve avatar upload control fileDropAreaSuccessColor value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaSuccessColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaSuccessColor) + ), + map((color) => (color ? color : XColor.Success)) + ); + } + + /** + * determines avatar upload control fileDropAreaSupportsMultipleFiles or not ... + * + * @param control form control config object ... + */ + isAvatarDropAreaSupportsMultipleFiles(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaSupportsMultipleFiles) + ) + ); + } + + /** + * retrieve avatar upload control fileDropAreaCssClass value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaCssClass(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.fileDropAreaCssClass)) + ); + } + + /** + * retrieve avatar upload control fileDropAreaCardSubTitle value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaCardSubTitle(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getResourceValue(config.fileDropAreaCardSubTitle) + ) + ); + } + + /** + * retrieve avatar upload control emptyFileException value ... + * + * @param control form control config object ... + */ + getAvatarNotAllowedFileException(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.notAllowedFileException) + ), + map((exception) => (exception ? exception : XExceptionIDs.InavlidFile)) + ); + } + + /** + * retrieve avatar upload control fileDropAreaContentTemplate value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaContentTemplate(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaContentTemplate) + ) + ); + } + + /** + * retrieve avatar upload control minAllowedFileSizeException value ... + * + * @param control form control config object ... + */ + getAvatarMinAllowedFileSizeException(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.minAllowedFileSizeException) + ), + map((exception) => + exception ? exception : XExceptionIDs.MinFilesSizeExceeded + ) + ); + } + + /** + * retrieve avatar upload control maxAllowedFileSizeException value ... + * + * @param control form control config object ... + */ + getAvatarMaxAllowedFileSizeException(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.maxAllowedFileSizeException) + ), + map((exception) => + exception ? exception : XExceptionIDs.MaxFilesSizeExceeded + ) + ); + } + + /** + * retrieve avatar control actions value ... + * + * @param control form control config object ... + */ + getAvatarActions(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.actions)), + concatMap((actions) => + hasChild(actions) + ? of(actions) + : this.getArrayValue(defaultAvatarUploadActionButtons) + ) + ); + } + + /** + * retrieve avatar control fileDropAreaActionProvider value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaActionProvider(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + map((config) => { + // + const key = `${control.propName.toString()}_drop_area`; + + // + if (!config.fileDropAreaActionProvider) { + // + if (!this.actionProviders[key]) { + this.actionProviders[key] = new Subject(); + } + + // + return this.actionProviders[key] as Subject; + } else { + return config.fileDropAreaActionProvider; + } + }) + ); + } + + /** + * retrieve avatar upload control allowedExtensions value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaAllowedExtensions(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getArrayValue(config.fileDropAreaAllowedExtensions) + ), + map((allowedExtensions) => + hasChild(allowedExtensions) + ? allowedExtensions + : this.config.allowedImageExtensions + ) + ); + } + //#endregion + + // + //#region Selector Config ... + /** + * retrieve selector control config ... + * + * @param control form control config object ... + */ + getSelectorConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormSelectorControlConfig) + ); + } + + /** + * determines selector control has config or not ... + * + * @param control form control config object ... + */ + hasSelectorConfig(control: XFormControlConfig) { + return this.getSelectorConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + //#endregion + //#endregion + //#endregion + + // + //#region Control Update On ... + /** + * retrieve a control UpdateOn strategy ... + * + * @param control form control config object ... + */ + getControlUpdateOn(control: XFormControlConfig) { + return this.getOptionalValue(control.updateOn).pipe( + map((updateOn) => (updateOn ? updateOn : XFormUpdateOn.CHANGE)) + ); + } + //#endregion + + // + //#region Control Validators ... + /** + * retrieve a control validators descriptor object ... + * + * @param control form control config object ... + */ + getControlValidators(control: XFormControlConfig) { + return this.getOptionalValue(control.validators); + } + + /** + * determines a control has Validators or not ... + * + * @param control form control config object ... + */ + controlHasValidators(control: XFormControlConfig) { + return this.getControlValidators(control).pipe( + map((controlValidators) => !isNullOrUndefined(controlValidators)) + ); + } + //#endregion + + // + //#region Control Event Handlers ... + /** + * retrieve a control event handlers descriptor object ... + * + * @param control form control config object ... + */ + getControlEventHandlers(control: XFormControlConfig) { + return this.getOptionalValue(control.eventHandlers); + } + + /** + * determines a control has validators or not ... + * + * @param control form control config object ... + */ + controlHasEventHandlers(control: XFormControlConfig) { + return this.getControlEventHandlers(control).pipe( + map((eventHandlers) => !isNullOrUndefined(eventHandlers)) + ); + } + //#endregion + + // + //#region Control Appearance Descriptor ... + /** + * retrieve a control appearance descriptor object ... + * + * @param control form control config object ... + */ + getControlAppearanceDesc( + control: XFormControlConfig + ): Observable { + return !isNullOrUndefined(control) + ? this.getValue(control.appearance) + : of(null); + } + + // + //#region Appearance Parts ... + // + //#region Control Hidden ... + /** + * determines a control hidden or not ... + * + * @param control form control config object ... + */ + getControlHidden(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) ? appearanceConfig.hidden : true + ) + ) + ); + } + //#endregion + + // + //#region Control Disabled ... + /** + * determines a control is disabled or not ... + * + * @param control form control config object ... + */ + getControlDisabled(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.disabled + : true + ) + ) + ); + } + //#endregion + + // + //#region Control Hint ... + /** + * retrieve a control hint ... + * + * @param control form control config object ... + */ + getControlHint(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getResourceValue( + !isNullOrUndefined(appearanceConfig) ? appearanceConfig.hint : '' + ) + ) + ); + } + //#endregion + + // + //#region Control Label ... + /** + * retrieve a control label ... + * + * @param control form control config object ... + */ + getControlLabel(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getResourceValue( + !isNullOrUndefined(appearanceConfig) ? appearanceConfig.label : '' + ) + ) + ); + } + //#endregion + + // + //#region Control Tooltip ... + /** + * retrieve a control tooltip ... + * + * @param control form control config object ... + */ + getControlTooltip(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getResourceValue( + !isNullOrUndefined(appearanceConfig) ? appearanceConfig.tooltip : '' + ) + ) + ); + } + //#endregion + + // + //#region Control PlaceHolder ... + /** + * retrieve a control placeholder ... + * + * @param control form control config object ... + */ + getControlPlaceHolder(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getResourceValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.placeholder + : '' + ) + ) + ); + } + //#endregion + + // + //#region Control CssClass ... + /** + * retrieve a control cssClaases ... + * + * @param control form control config object ... + */ + getControlCssClass(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getArrayValue( + !isNullOrUndefined(appearanceConfig) ? appearanceConfig.cssClass : [] + ) + ), + concatMap((cssClasses) => + this.getControlType(control).pipe( + map((type) => { + return { + type, + cssClasses, + }; + }) + ) + ), + map((descriptor) => { + return descriptor.cssClasses.concat( + 'x-form-control', + `x-form-control-${control.index}`, + `x-form-control-${descriptor.type}` + ); + }) + ); + } + //#endregion + + // + //#region Control Icons Descriptor ... + /** + * retrieve a control icons descriptor object ... + * + * @param control form control config object ... + */ + getControlIconDesc(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) ? appearanceConfig.icons : null + ) + ) + ); + } + + /** + * determines a control has icons descriptor object or not ... + * + * @param control form control config object ... + */ + controlHasIcons(control: XFormControlConfig) { + return this.getControlIconDesc(control).pipe( + map((iconDesc) => !isNullOrUndefined(iconDesc)) + ); + } + + /** + * determines a control has suffix icon or not ... + * + * @param control form control config object ... + */ + controlHasSuffixIcon(control: XFormControlConfig) { + return this.getControlIconDesc(control).pipe( + concatMap((iconDesc) => + this.getOptionalValue(iconDesc ? iconDesc.suffix : null).pipe( + map((iconSuffix) => !isNullOrUndefined(iconSuffix)) + ) + ) + ); + } + + /** + * retrieve a control suffix icon object ... + * + * @param control form control config object ... + */ + getControlSuffixIcon(control: XFormControlConfig) { + return this.getControlIconDesc(control).pipe( + concatMap((iconDesc) => + this.getOptionalValue(iconDesc ? iconDesc.suffix : null) + ) + ); + } + + /** + * determines a control has prefix icon or not ... + * + * @param control form control config object ... + */ + controlHasPrefixIcon(control: XFormControlConfig) { + return this.getControlIconDesc(control).pipe( + concatMap((iconDesc) => + this.getOptionalValue(iconDesc ? iconDesc.prefix : null).pipe( + map((iconPrefix) => !isNullOrUndefined(iconPrefix)) + ) + ) + ); + } + + /** + * retrieve a control prefix icon ... + * + * @param control form control config object ... + */ + getControlPrefixIcon(control: XFormControlConfig) { + return this.getControlIconDesc(control).pipe( + concatMap((iconDesc) => + this.getOptionalValue(iconDesc ? iconDesc.prefix : null) + ) + ); + } + + /** + * retrieve an icon descriptor's icon name ... + * + * @param icon form control icon descriptor ... + */ + getControlIconName(icon: XFormIconDescriptor) { + return this.getValue(icon.name); + } + + /** + * retrieve an icon descriptor's icon color ... + * + * @param icon form control icon descriptor ... + */ + getControlIconColor(icon: XFormIconDescriptor) { + return this.getOptionalValue(icon.color).pipe( + map((color) => (color ? color : XColor.Light)) + ); + } + + /** + * retrieve an icon descriptor's icon tooltip ... + * + * @param icon form control icon descriptor ... + */ + getControlIconTooltip(icon: XFormIconDescriptor) { + return this.getResourceValue(icon.tooltip); + } + + /** + * retrieve an icon descriptor's apply state color ... + * + * @param icon form control icon descriptor ... + */ + getControlIconApplyStateColor(icon: XFormIconDescriptor) { + return this.getOptionalValue(icon.applyStateColor); + } + //#endregion + + // + //#region Control Formatter Config ... + /** + * retrieve a control formatter config descriptor object ... + * + * @param control form control config object ... + */ + getControlFormatterConfig(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.formatter + : null + ) + ) + ); + } + + /** + * determines a control has formatter config descriptor or not ... + * + * @param control form control config object ... + */ + controlHasFormatter(control: XFormControlConfig) { + return this.getControlFormatterConfig(control).pipe( + map((formatterConfig) => !isNullOrUndefined(formatterConfig)) + ); + } + + /** + * retrieve a control formatter locale ... + * + * @param control form control config object ... + */ + getFormatterLocale(control: XFormControlConfig) { + return this.getControlFormatterConfig(control).pipe( + concatMap((formatterConfig) => this.getValue(formatterConfig.locale)) + ); + } + + /** + * retrieve a control formatter seperator ... + * + * @param control form control config object ... + */ + getFormatterSeperator(control: XFormControlConfig) { + return this.getControlFormatterConfig(control).pipe( + concatMap((formatterConfig) => + this.getOptionalValue(formatterConfig.seperator) + ) + ); + } + + /** + * retrieve a control formatter type ... + * + * @param control form control config object ... + */ + getFormatterType(control: XFormControlConfig) { + return this.getControlFormatterConfig(control).pipe( + concatMap((formatterConfig) => this.getValue(formatterConfig.type)) + ); + } + + /** + * retrieve a control formatter currency label ... + * + * @param control form control config object ... + */ + getFormatterCurrencyLabel(control: XFormControlConfig) { + return this.getControlFormatterConfig(control).pipe( + concatMap((formatterConfig) => + isNullOrUndefined(formatterConfig.currencyLable) + ? of('') + : this.getResourceValue(formatterConfig.currencyLable) + ) + ); + } + + /** + * retrieve a control formatter string formatter ... + * + * @param control form control config object ... + */ + getFormatterStringFormatter(control: XFormControlConfig) { + return this.getControlFormatterConfig(control).pipe( + concatMap((formatterConfig) => + this.getArrayValue(formatterConfig.stringFormatter) + ) + ); + } + + /** + * retrieve a control formatter string formatter ... + * + * @param control form control config object ... + */ + getFormatterInputType(control: XFormControlConfig) { + return this.getControlFormatterConfig(control).pipe( + concatMap((formatterConfig) => + isNullOrUndefined(formatterConfig.inputType) + ? of(XStringInputFormatType.None) + : this.getValue(formatterConfig.inputType) + ) + ); + } + //#endregion + + // + //#region Control Progressbar ... + /** + * retrieve a control progress bar config descriptor object ... + * + * @param control form control config object ... + */ + getControlProgressBarConfig(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.progressBar + : null + ) + ) + ); + } + + /** + * retrieve a control has progress bar object descriptor or not ... + * + * @param control form control config object ... + */ + controlHasProgressBar(control: XFormControlConfig) { + return this.getControlProgressBarConfig(control).pipe( + map((progressBarConfig) => !isNullOrUndefined(progressBarConfig)) + ); + } + + /** + * retrieve a control progressbar color ... + * + * @param control form control config object ... + */ + getProgressBarColor(control: XFormControlConfig) { + return this.getControlProgressBarConfig(control).pipe( + concatMap((progressBarConfig) => this.getValue(progressBarConfig.color)) + ); + } + //#endregion + + // + //#region Control AutoComplete ... + /** + * retrieve an id number for form control auto complete view ... + * + * @param control form control config object ... + */ + getAutoCompleteId(control: XFormControlConfig) { + return `x-${control.propName.toString()}-auto-complete`; + } + + /** + * retrieve a control autocomplete config object ... + * + * @param control form control config object ... + */ + getControlAutoCompleteConfig(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.autoComplete + : null + ) + ) + ); + } + + /** + * retrieve a control has autocomplete or not ... + * + * @param control form control config object ... + */ + controlHasAutoComplete(control: XFormControlConfig) { + return this.getControlAutoCompleteConfig(control).pipe( + map((autoConpleteConfig) => !isNullOrUndefined(autoConpleteConfig)) + ); + } + + /** + * retrieve a control autocomplete opened ... + * + * @param control form control config object ... + */ + getAutoCompleteOpened(control: XFormControlConfig) { + return this.getControlAutoCompleteConfig(control).pipe( + concatMap((autoCompleteConfig) => + this.getOptionalValue(autoCompleteConfig.opened) + ) + ); + } + + /** + * retrieve a control autocomplete item template ... + * + * @param control form control config object ... + */ + getAutoCompleteItemTemplate(control: XFormControlConfig) { + return this.getControlAutoCompleteConfig(control).pipe( + concatMap((autoCompleteConfig) => + this.getValue(autoCompleteConfig.itemTemplate) + ) + ); + } + + /** + * retrieve a control autocomplete color ... + * + * @param control form control config object ... + */ + getAutoCompleteColor(control: XFormControlConfig) { + return this.getControlAutoCompleteConfig(control).pipe( + concatMap((autoCompleteConfig) => + this.getOptionalValue(autoCompleteConfig.color).pipe( + map((color) => (color ? color : XColor.Light)) + ) + ) + ); + } + + /** + * retrieve a control autocomplete onSelect ... + * + * @param control form control config object ... + */ + getAutoCompleteOnSelect(control: XFormControlConfig) { + return this.getControlAutoCompleteConfig(control).pipe( + concatMap((autoCompleteConfig) => + this.getValue(autoCompleteConfig.onSelect) + ) + ); + } + + /** + * retrieve a control autocomplete Items ... + * + * @param control form control config object ... + */ + getAutoCompleteItems(control: XFormControlConfig) { + return this.getControlAutoCompleteConfig(control).pipe( + concatMap((autoCompleteConfig) => { + return !isNullOrUndefined(autoCompleteConfig.items) && + isFunction(autoCompleteConfig.items) + ? this.getArrayValue(autoCompleteConfig.items()) + : this.getValue([]); + }) + ); + } + + /** + * retrieve autocomplete CssClass ... + * + * @param control form control config object ... + */ + getAutoCompleteCssClass(control: XFormControlConfig) { + return this.getControlAutoCompleteConfig(control).pipe( + concatMap((autoCompleteConfig) => + this.getArrayValue(autoCompleteConfig.cssClass).pipe( + map((cssClasses) => + cssClasses.concat( + 'x-auto-complete', + `x-${control.propName.toString()}-auto-complete` + ) + ) + ) + ) + ); + } + + /** + * retrieve control errors classes ... + * + * @param control form control config ... + */ + getControlErrorsClasses(control: XFormControlConfig) { + return this.getControlType(control).pipe( + map((type) => { + return [ + 'x-form-control-errors', + `x-form-${type}-control-errors`, + `x-form-control-${control.index.toString()}-errors`, + `x-form-control-${control.propName.toString()}-errors`, + ]; + }) + ); + } + + /** + * retrieve special control's special error's class list ... + * + * @param control form control config ... + * @param handler form error handler instance ... + */ + getControlErrorClasses( + control: XFormControlConfig, + handler: XFormErrorHandler + ) { + return this.getControlType(control).pipe( + concatMap((type) => + this.getErrorHandlerErrorName(handler).pipe( + map((errorName) => { + return { + type, + errorName, + }; + }) + ) + ), + map((descriptor) => { + return [ + 'x-form-field-error', + `x-form-field-${descriptor.type}-error`, + `x-form-field-${descriptor.errorName}-error`, + `x-form-field-${control.propName.toString()}-error`, + `x-form-field-${descriptor.type}-${descriptor.errorName}-error`, + ]; + }) + ); + } + //#endregion + + // + //#region Control Appearance ... + /** + * retrieve a control appearance ... + * + * @param control form control config object ... + */ + getControlAppearance(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) && + !isNullOrUndefined(appearanceConfig.appearance) + ? appearanceConfig.appearance + : XFormControlAppearance.Fill + ).pipe(map((result) => result as MatFormFieldAppearance)) + ) + ); + } + getControlAppearanceObject(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) && + !isNullOrUndefined(appearanceConfig.appearance) + ? appearanceConfig.appearance + : XFormControlAppearance.Fill + ) + ) + ); + } + //#endregion + + // + //#region Control Label Position ... + /** + * retrieve a control label position ... + * + * @param control form control config object ... + */ + getControlLabelPosition(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.labelPosition + : XFormControlLabelPosition.Before + ) + ) + ); + } + //#endregion + //#endregion + //#endregion + + // + //#region Control Error Handlers ... + /** + * retrieve a control error handlers descriptor object ... + * + * @param control form control config object ... + */ + getControlErrorHandlers(control: XFormControlConfig) { + return this.getArrayValue(control.errorHandlers); + } + + /** + * determines a control has error handlers descriptors object or not ... + * + * @param control form control config object ... + */ + controlHasErrorHandlers(control: XFormControlConfig) { + return this.hasChildValue(this.getControlErrorHandlers(control)); + } + + controlHasErrors(control: XFormControlConfig) { + return this.getValue(control).pipe( + concatMap((ctrl) => + this.getValue(this.getControl(ctrl.propName)).pipe( + map((fControl) => ({ ctrl, fControl })) + ) + ), + map((ctrlInfo) => { + return isNullOrUndefined(ctrlInfo.ctrl) || + isNullOrUndefined(ctrlInfo.fControl) + ? false + : !isNullOrUndefined(ctrlInfo.fControl.errors); + }) + ); + } + + /** + * determines a control has specific error or not ... + * + * @param controlName control name ... + * @param errorName error name ... + */ + controlHasErrorByName( + controlName: XFormControlNameIdentifier, + errorName: string + ) { + // + const control = this.getControl(controlName); + if (!control) { + return false; + } + + // + const result = + !isNullOrUndefined(control.errors) && + keys(control.errors).includes(errorName) && + !isNullOrUndefined(control.errors[errorName]); + + // + return result; + } + + /** + * determine a control has specific error or not ... + * + * @param control form control config ... + * @param handler form error handler instance ... + */ + controlHasError(control: XFormControlConfig, handler: XFormErrorHandler) { + return this.getErrorHandlerErrorName(handler).pipe( + map((errorName) => { + return this.controlHasErrorByName(control.propName, errorName); + }) + ); + } + + controlGetError(control: XFormControlConfig, handler: XFormErrorHandler) { + return this.getErrorHandlerErrorName(handler).pipe( + map((errorName) => { + return this.controlHasErrorByName(control.propName, errorName); + }) + ); + } + + /** + * retrieve specific error of a control ... + * + * @param controlName control name ... + * @param errorName error name ... + */ + getControlErrorByName( + controlName: XFormControlNameIdentifier, + errorName: string + ) { + // + const control = this.getControl(controlName); + if (!this.controlHasErrorByName(controlName, errorName)) { + return false; + } + + // + return control.errors[errorName] || null; + } + + /** + * retrieve specific error of a control ... + * + * @param control form control config ... + * @param handler form error handler instance ... + */ + getControlError(control: XFormControlConfig, handler: XFormErrorHandler) { + return this.getErrorHandlerErrorName(handler).pipe( + map((errorName) => { + if (!isNullOrUndefined(control)) { + return this.getControlErrorByName(control.propName, errorName); + } else { + return this.getFormErrorByName(errorName); + } + }) + ); + } + //#endregion + //#endregion + + // + //#region Error Handler Providers ... + /** + * retrieve an error handler error name ... + * + * @param errorHandler an error handler descriptor object ... + */ + getErrorHandlerErrorName(errorHandler: XFormErrorHandler) { + return this.getValue(errorHandler.errorName); + } + + /** + * retrieve an error handler error message ... + * + * @param errorHandler an error handler descriptor object ... + */ + getErrorHandlerErrorMessage( + errorHandler: XFormErrorHandler, + control?: XFormControlConfig + ) { + // + if ( + errorHandler.errorDataProvider && + isFunction(errorHandler.errorDataProvider) + ) { + // + return this.getControlError(control, errorHandler).pipe( + concatMap((controlError) => + this.getResourceValue(errorHandler.errorDataProvider(controlError)) + ) + ); + } else { + return this.getResourceValue(errorHandler.errorMessage); + } + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + // + //#region Common Controls UI Handlers ... + async handleControlBlured(name: XFormControlNameIdentifier) { + // + if (!this.FORM_CONFIG || !this.hasControl(name)) { + return; + } + + // + const control = await this.getValueAsync(this.getFormControl(name)); + if (!control) { + return; + } + + // + const eventModel: XBaseFormControlActionModel = { + name: control.propName, + }; + + // + this.controlBlured.emit(eventModel); + + // + const eventHandlers = await this.getValueAsync( + this.getControlEventHandlers(control) + ); + if (!eventHandlers || !eventHandlers.onBlured) { + return; + } + + // + await this.getValueAsync(eventHandlers.onBlured(eventModel)); + } + + async handleControlFocused(name: XFormControlNameIdentifier) { + // + if (!this.FORM_CONFIG || !this.hasControl(name)) { + return; + } + + // + const control = await this.getValueAsync(this.getFormControl(name)); + if (!control) { + return; + } + + // + const inputId = await this.getValueAsync( + this.getControlInputElementId(control) + ); + this.currentInputFocus = inputId; + + // + const eventModel: XBaseFormControlActionModel = { + name: control.propName, + }; + + // + this.controlFocused.emit(eventModel); + + // + const eventHandlers = await this.getValueAsync( + this.getControlEventHandlers(control) + ); + if (!eventHandlers || !eventHandlers.onFocused) { + return; + } + + // + await this.getValueAsync(eventHandlers.onFocused(eventModel)); + } + + async handleControlValueChanged(event: XFormControlValueChangeEventModel) { + // + if ( + !this.FORM || + this.uiDisabled || + !this.FORM_CONFIG || + !this.hasControl(event.name) + ) { + return; + } + + // + const control = await this.getValueAsync(this.getFormControl(event.name)); + if (control) { + // + const isDisabled = await this.getValueAsync( + this.getControlDisabled(control) + ); + if (isDisabled) { + return; + } + } + + // + this.controlValueChanged.emit(event); + } + + async handleControlStatusChanged(event: XFormControlStatusChangeEventModel) { + // + if ( + !this.FORM || + this.uiDisabled || + !this.FORM_CONFIG || + !this.hasControl(event.name) + ) { + return; + } + + // + const control = await this.getValueAsync(this.getFormControl(event.name)); + if (control) { + // + const isDisabled = await this.getValueAsync( + this.getControlDisabled(control) + ); + if (isDisabled) { + return; + } + } + + // + this.controlStatusChanged.emit(event); + } + + async handleControlIconClicked(icon: XFormIconDescriptor) { + // + if (!this.FORM || !this.FORM_CONFIG || !icon.handler) { + return; + } + + // + await this.getValueAsync(icon.handler()); + } + + async handleControlActionIconClicked( + role: XControlIconRoleIdentifier, + descriptor: XControlIconDescriptor, + control: XFormControlConfig + ) { + // + const type = await this.getValueAsync(this.getControlType(control)); + + // + switch (type) { + // + case XFormControlType.Map: + // + await this.handleMapIconClicked(role, descriptor, control); + break; + + // + case XFormControlType.Selector: + await this.handleItemSelectorIconClicked(role, descriptor, control); + break; + + // + case XFormControlType.ColorPicker: + // + await this.handleColorPickerIconClicked(role, descriptor, control); + break; + + // + default: + break; + } + } + //#endregion + + // + //#region DatePicker UI Handlers ... + async handleDatePickerDateInput( + name: XFormControlNameIdentifier, + event: MatDatepickerInputEvent + ) { + // + let result: Date = null; + const isDateInput = isDate(event.value); + const isMomentInput = isMoment(event.value); + if (isMomentInput) { + result = momentToDate(event.value); + } else if (isDateInput) { + result = event.value as Date; + } + + // + await this.setValue(name, result); + + // + if (!this.FORM_CONFIG || !this.hasControl(name)) { + return; + } + + // + const control = await this.getValueAsync(this.getFormControl(name)); + if (!control) { + return; + } + + // + const handler = await this.getValueAsync(this.getDateInputHandler(control)); + if (!handler) { + return; + } + + // + // Fire Hanlder ... + await this.getValueAsync(handler(result)); + } + + async handleDatePickerDateChange( + name: XFormControlNameIdentifier, + event: MatDatepickerInputEvent + ) { + // + let result: Date = null; + const isDateInput = isDate(event.value); + const isMomentInput = isMoment(event.value); + if (isMomentInput) { + result = momentToDate(event.value); + } else if (isDateInput) { + result = event.value as Date; + } + + // + await this.setValue(name, result); + + // + if (!this.FORM_CONFIG || !this.hasControl(name)) { + return; + } + + // + const control = await this.getValueAsync(this.getFormControl(name)); + if (!control) { + return; + } + + // + const handler = await this.getValueAsync( + this.getDateChangeHandler(control) + ); + if (!handler) { + return; + } + + // + // Fire Hanlder ... + await this.getValueAsync(handler(result)); + } + //#endregion + + // + //#region Auto Complete Controls UI Handlers ... + async handleAutoCompleteResultSelected( + control: XFormControlConfig, + autoComplete: XFormControlAutoCompleteConfig, + event: any + ) { + // + if (!this.FORM || !this.FORM_CONFIG || !this.hasControl(control.propName)) { + return; + } + + // + const formCtrl = this.getControl(control.propName); + if (!formCtrl) { + return; + } + + // + formCtrl.setValue(event.data); + const formInputControl = this.getElementById( + this.getControlInputElementId(control) + ); + if (formInputControl) { + (formInputControl as HTMLInputElement).select(); + } + + // + if (autoComplete.onSelect && isFunction(autoComplete.onSelect)) { + await this.getValueAsync(autoComplete.onSelect(event)); + } + + // + this.focusOnControl(control.propName); + + // + this.detectChanges(); + } + + async handleAutoCompleteEscaped(control: XFormControlConfig) { + // + if (!this.FORM || !this.FORM_CONFIG || !this.hasControl(control.propName)) { + return; + } + + // + const autoComplete = await this.getValueAsync( + this.getControlAutoCompleteConfig(control) + ); + if ( + autoComplete && + autoComplete.onEscape && + isFunction(autoComplete.onEscape) + ) { + await this.getValueAsync(autoComplete.onEscape()); + } + + // + this.detectChanges(); + } + + async handleAutoCompleteOutsideClicked(control: XFormControlConfig) { + // + if (!this.FORM || !this.FORM_CONFIG || !this.hasControl(control.propName)) { + return; + } + + // + const autoComplete = await this.getValueAsync( + this.getControlAutoCompleteConfig(control) + ); + if ( + autoComplete && + autoComplete.onOutsideClicked && + isFunction(autoComplete.onOutsideClicked) + ) { + await this.getValueAsync(autoComplete.onOutsideClicked()); + } + + // + this.detectChanges(); + } + //#endregion + + // + //#region Markdown Control UI Handlers ... + async handleMarkDownContentChanged( + name: XFormControlNameIdentifier, + event: string + ) { + // + if (!this.hasControl(name)) { + return; + } + + // + await this.setValue(name, event); + + // + const controlConfig = await this.getValueAsync(this.getFormControl(name)); + if (!controlConfig) { + return; + } + + // + const markDownConfig = await this.getValueAsync( + this.getMarkdownConfig(controlConfig) + ); + if (!markDownConfig || !markDownConfig.contentChanged) { + return; + } + + // + await this.getValueAsync(markDownConfig.contentChanged(event)); + } + //#endregion + + // + //#region Checkbox Control UI Handlers ... + async handleCheckboxCheckedChange( + control: XFormControlConfig, + checked: boolean + ) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + const config = await this.getValueAsync(this.getCheckboxConfig(control)); + if (!config || !config.checkedChanged) { + return; + } + + // + await this.getValueAsync(config.checkedChanged(checked)); + } + //#endregion + + // + //#region Map UI Handlers ... + async handleMapIsReady(control: XFormControlConfig) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + const config = await this.getValueAsync(this.getMapConfig(control)); + if (!config || !config.isReady) { + return; + } + + // + config.isReady.emit(); + } + + async handleMapZoomChanged(control: XFormControlConfig, zoom: number) { + } + + async handleMapLocationSelected( + control: XFormControlConfig, + event: XMapLocation + ) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + const longLat = event ? toLongLat(event) : null; + await this.setValue(control.propName, longLat); + + // + const config = await this.getValueAsync(this.getMapConfig(control)); + if (!config || !config.locationSelected) { + return; + } + + // + config.locationSelected.emit(event); + } + + async handleMapIconClicked( + role: XControlIconRoleIdentifier, + descriptor: XControlIconDescriptor, + control: XFormControlConfig + ) { + // + const presentType = await this.getValueAsync( + this.getMapPresentType(control) + ); + + // + switch (role) { + // + case this.ControlIconRoles.Clear: + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + async () => { + // + await this.setValue(control.propName, undefined); + + // + const mapControlActionPtovider = await this.getValueAsync( + this.getMapActionProvider(control) + ); + if ( + mapControlActionPtovider && + presentType === XFormMapControlPresentType.WithoutDialog + ) { + // + // Clear Marker ... + mapControlActionPtovider.next({ + action: XMapAction.ClearMarker, + }); + } + + // + // Notify Handler ... + if ( + !isNullOrUndefined(descriptor) && + !isNullOrUndefined(descriptor.handler) && + isFunction(descriptor.handler) + ) { + await this.getValueAsync(descriptor.handler()); + } + } + ); + break; + + // + case this.ControlIconRoles.Select: + // + // Notify Handler ... + if ( + !isNullOrUndefined(descriptor) && + !isNullOrUndefined(descriptor.handler) && + isFunction(descriptor.handler) + ) { + await this.getValueAsync(descriptor.handler()); + } + + // + let center: XMapLocation = null; + const longLatControl = this.getControl(control.propName); + if (longLatControl) { + // + const longLat = longLatControl.value as string; + if (!isNullOrEmptyString(longLat)) { + center = fromLongLat(longLat); + } + } + + // + // Prevent go forward if map control present type does't support map dialog ... + if (presentType === XFormMapControlPresentType.WithoutDialog) { + return; + } + + // + // Prepare Selector Dialog Config ... + const controlConfig = await this.getValueAsync( + this.getMapConfig(control) + ); + const dialogConfig: XMapDialogConfig = { + ...controlConfig, + // + latitude: center && center.latitude ? +center.latitude : 0, + longitude: center && center.longitude ? +center.longitude : 0, + + // + showSelectedLocation: true, + }; + + // + // Open Dialog ... + await this.mapDialogService.openDialog(dialogConfig); + + // + // Wait for Handle Dialog Result ... + const s = this.mapDialogService.onResult + .asObservable() + .subscribe(async (result) => { + // + let value: any; + + // + if ( + !isNullOrUndefined(result) && + !isNullOrUndefined(result.payload) && + result.result === XDialogResult.Ok + ) { + value = toLongLat(result.payload); + } + + // + if (!isNullOrUndefined(value)) { + await this.setValue(control.propName, value); + } + + // + s.unsubscribe(); + }); + break; + } + } + + async handleMapControlInputValueChanged( + control: XFormControlConfig, + event: string + ) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + const config = await this.getValueAsync(this.getMapConfig(control)); + if ( + !config || + !config.inputValueChanged || + !isFunction(config.inputValueChanged) + ) { + return; + } + + // + await this.getValueAsync(config.inputValueChanged(event)); + } + + async handleMapSearchBarQueryChange( + control: XFormControlConfig, + event: string + ) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + const config = await this.getValueAsync(this.getMapConfig(control)); + if (!config || !config.searchBarQueryChange) { + return; + } + + // + config.searchBarQueryChange.emit(event); + } + //#endregion + + // + //#region ColorPicker UI Handlers ... + async handleColorPickerIconClicked( + role: XControlIconRoleIdentifier, + descriptor: XControlIconDescriptor, + control: XFormControlConfig + ) { + // + switch (role) { + // + case this.ControlIconRoles.Clear: + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + async () => { + // + await this.setValue(control.propName, undefined); + + // + // Notify Handler ... + if ( + !isNullOrUndefined(descriptor) && + !isNullOrUndefined(descriptor.handler) && + isFunction(descriptor.handler) + ) { + await this.getValueAsync(descriptor.handler()); + } + } + ); + break; + + // + case this.ControlIconRoles.Select: + // + // Notify Handler ... + if ( + !isNullOrUndefined(descriptor) && + !isNullOrUndefined(descriptor.handler) && + isFunction(descriptor.handler) + ) { + await this.getValueAsync(descriptor.handler()); + } + + // + const presentType = await this.getValueAsync( + this.getColorPickerPresentType(control) + ); + + // + // Prevent go forward if color picker control present type does't support map dialog ... + if (presentType === XFormColorPickerControlPresentType.WithoutDialog) { + return; + } + + // + let selectedColor: string = null; + const colorPickerControl = this.getControl(control.propName); + if (colorPickerControl) { + // + const colorVal = colorPickerControl.value as string; + if (!isNullOrEmptyString(colorVal)) { + selectedColor = colorVal; + } + } + + // + // Prepare Selector Dialog Config ... + const controlConfig = await this.getValueAsync( + this.getColorPickerConfig(control) + ); + const dialogConfig: XColorPickerDialogConfig = { + ...controlConfig, + + // + selectedColor, + }; + + // + // Open Dialog ... + await this.colorPickerDialogService.openDialog(dialogConfig); + + // + // Wait for Handle Dialog Result ... + const s = this.colorPickerDialogService.onResult + .asObservable() + .subscribe(async (result) => { + // + let value: any; + + // + if ( + !isNullOrUndefined(result) && + !isNullOrUndefined(result.payload) && + result.result === XDialogResult.Ok + ) { + value = result.payload; + } + + // + if (!isNullOrUndefined(value)) { + await this.setValue(control.propName, value); + } + + // + s.unsubscribe(); + }); + break; + } + } + + async handleColorPickerControlInputValueChanged( + control: XFormControlConfig, + event: string + ) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + const config = await this.getValueAsync(this.getColorPickerConfig(control)); + if ( + !config || + !config.inputValueChanged || + !isFunction(config.inputValueChanged) + ) { + return; + } + + // + await this.getValueAsync(config.inputValueChanged(event)); + } + + async handleColorPickerColorSelected( + control: XFormControlConfig, + event: string + ) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + await this.setValue(control.propName, event); + + // + const config = await this.getValueAsync(this.getColorPickerConfig(control)); + if (!config || !config.colorSelected) { + return; + } + + // + config.colorSelected.emit(event); + } + //#endregion + + // + //#region Avatar UI Handlers ... + async handleAvatarFileDropAreaFilesChangeEmitter( + control: XFormControlConfig, + event?: XOneOrManyType + ) { + // + if (!event || !this.hasControl(control.propName)) { + return; + } + + // + const config = await this.getValueAsync(this.getAvatarConfig(control)); + if ( + !config || + !config.fileDropAreaFilesChangeEmiter || + !isFunction(config.fileDropAreaFilesChangeEmiter) + ) { + return; + } + + // + await this.getValueAsync(config.fileDropAreaFilesChangeEmiter(event)); + } + + async handleAvatarAvatarChange(control: XFormControlConfig, event?: File) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + await this.setValue(control.propName, event); + + // + const config = await this.getValueAsync(this.getAvatarConfig(control)); + if (!config || !config.avatarChange || !isFunction(config.avatarChange)) { + return; + } + + // + await this.getValueAsync(config.avatarChange(event)); + } + //#endregion + + // + //#region Selector UI Handlers ... + async handleItemSelectorIconClicked( + role: XControlIconRoleIdentifier, + descriptor: XControlIconDescriptor, + control: XFormControlConfig + ) { + // + switch (role) { + // + case this.ControlIconRoles.Clear: + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + async () => { + // + await this.setValue(control.propName, undefined); + + // + // Notify Handler ... + if ( + !isNullOrUndefined(descriptor) && + !isNullOrUndefined(descriptor.handler) && + isFunction(descriptor.handler) + ) { + await this.getValueAsync(descriptor.handler()); + } + } + ); + break; + + // + case this.ControlIconRoles.Select: + // + // Notify Handler ... + if ( + !isNullOrUndefined(descriptor) && + !isNullOrUndefined(descriptor.handler) && + isFunction(descriptor.handler) + ) { + await this.getValueAsync(descriptor.handler()); + } + + // + // Prepare Selector Dialog Config ... + const controlConfig = await this.getValueAsync( + this.getSelectorConfig(control) + ); + const dialogConfig: XFormSelectorControlConfig = { + ...controlConfig, + }; + + // + // Open Dialog ... + await this.selectorDialogService.openDialog(dialogConfig); + + // + // Wait for Handle Dialog Result ... + const s = this.selectorDialogService.onResult + .asObservable() + .subscribe(async (result) => { + // + let content: any; + + // + if ( + !isNullOrUndefined(result) && + !isNullOrUndefined(result.payload) && + result.result === XDialogResult.Ok + ) { + content = result.payload; + } + + // + if (!isNullOrUndefined(content)) { + // + const value = + !controlConfig || + !controlConfig.formatSelectedItem || + !isFunction(controlConfig.formatSelectedItem) + ? content + : await this.getValueAsync( + controlConfig.formatSelectedItem(content) + ); + + // + await this.setValue(control.propName, value); + } + + // + s.unsubscribe(); + }); + break; + } + } + + async handleSelectorControlInputValueChanged( + control: XFormControlConfig, + event: string + ) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + const config = await this.getValueAsync(this.getSelectorConfig(control)); + if ( + !config || + !config.inputValueChanged || + !isFunction(config.inputValueChanged) + ) { + return; + } + + // + await this.getValueAsync(config.inputValueChanged(event)); + } + //#endregion + //#endregion + + // + //#region Actions ... + /** + * determine a control exists or not ... + * + * @param name a control name ... + */ + hasControl(name: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return false; + } + + // + const controlName = this.getControlName(name); + if (!controlName) { + return false; + } + + // + return ( + this.FORM.contains(controlName) || + !isNullOrUndefined(this.FORM.controls[controlName]) + ); + } + + /** + * retrieve a control based on it's name ... + * + * @param name a control name ... + */ + getControl(name: XFormControlNameIdentifier) { + // + if (!this.hasControl(name)) { + return null; + } + + // + const controlName = this.getControlName(name); + if (!controlName) { + return null; + } + + // + return this.FORM.get(controlName) || null; + } + + /** + * retrieve a form control status if exists in form ... + * + * @param control a form control config ... + */ + getControlStatus(control: XFormControlConfig) { + // + const mControl = this.getControl(control.propName); + if (!mControl) { + return XFormStatus.UnKnown; + } + + // + return mControl.status as XFormStatus; + } + + /** + * focus on a control ... + * + * @param name a control name ... + */ + focusOnControl(name: XFormControlNameIdentifier) { + // + if (!this.hasControl(name)) { + return; + } + + // + const inputElement = this.getElementById( + this.getControlInputElementIdByName(name) + ); + if (!inputElement) { + return; + } + + // + setTimeout(() => inputElement.focus(), 100); + } + + /** + * blur from a control ... + * + * @param name a control name ... + */ + blurOfControl(name: XFormControlNameIdentifier) { + // + if (!this.hasControl(name)) { + return; + } + + // + const inputElement = this.getElementById( + this.getControlInputElementIdByName(name) + ); + if (!inputElement) { + return; + } + + // + setTimeout(() => inputElement.blur(), 100); + } + + /** + * determines specific control has value or not ... + * + * @param name a control name ... + */ + controlHasValue(name: XFormControlNameIdentifier) { + return !this.hasControl(name) + ? false + : this.getControl(name).value && + !isNullOrEmptyString(this.getControl(name).value.toString()); + } + + /** + * retrieve an specific control value ... + * + * @param name a control name ... + */ + getControlValue(name: XFormControlNameIdentifier) { + return !this.hasControl(name) ? undefined : this.getControl(name).value; + } + + /** + * determines a control has error by it's name ... + * + * @param name a control name ... + */ + controlHasErrorByControlName(name: XFormControlNameIdentifier) { + return !this.hasControl(name) + ? false + : !isNullOrUndefined(this.getControl(name).errors); + } + + /** + * set a value to an specific control or form ... + * + * @param name a control name ... + * @param value a value of any type ... + */ + async setValue(name?: XFormControlNameIdentifier, value?: any) { + // + if (!name) { + return; + } + + // + if (name) { + // + const control = this.getControl(name); + if (!control) { + return; + } + + // + control.setValue(value); + } else { + this.FORM.setValue(value, { emitEvent: true }); + } + + // + this.detectChanges(); + } + + /** + * trigger markAsDirty on form or specific control ... + * if provide a control name and it's exists ... + * + * @param name a control name ... + */ + async markAsDirty(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG || this.uiDisabled) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.markAsDirty(); + this.detectChanges(); + return; + } + + // + control.markAsDirty(); + this.detectChanges(); + } + + /** + * trigger markAsTouched on form or specific control ... + * if provide a control name and it's exists ... + * + * @param name a control name ... + */ + async markAsTouched(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.markAsTouched(); + this.detectChanges(); + return; + } + + // + control.markAsTouched(); + this.detectChanges(); + } + + /** + * trigger markAsPending on form or specific control ... + * if provide a control name and it's exists ... + * + * @param name a control name ... + */ + async markAsPending(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.markAsPending(); + this.detectChanges(); + return; + } + + // + control.markAsPending(); + this.detectChanges(); + } + + /** + * trigger markAsPristine on form or specific control ... + * if provide a control name and it's exists ... + * + * @param name a control name ... + */ + async markAsPristine(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.markAsPristine(); + this.detectChanges(); + return; + } + + // + control.markAsPristine(); + this.detectChanges(); + } + + /** + * trigger markAsUntouched on form or specific control ... + * if provide a control name and it's exists ... + * + * @param name a control name ... + */ + async markAsUnTouched(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.markAsUntouched(); + this.detectChanges(); + return; + } + + // + control.markAsUntouched(); + this.detectChanges(); + } + + /** + * trigger updateValueAndValidity on form or specific control ... + * if provide a control name and it's exists ... + * + * @param name a control name ... + */ + async updateValueAndValidity(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.updateValueAndValidity({ emitEvent: true }); + this.detectChanges(); + return; + } + + // + control.updateValueAndValidity({ emitEvent: true }); + this.detectChanges(); + } + + /** + * trigger setError on form or specific control ... + * if provide a control name and it's exists ... + * + * @param errors: an instance of ValidationErrors object ... + * @param name a control name ... + */ + async setError(errors: ValidationErrors, name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.setErrors(errors, { emitEvent: true }); + this.detectChanges(); + return; + } + + // + control.setErrors(errors, { emitEvent: true }); + this.detectChanges(); + } + + /** + * clearErrors on form or specific control ... + * if provide a control name and it's exists ... + * + * @param name a control name ... + */ + async clearError(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.setErrors(null, { emitEvent: true }); + this.detectChanges(); + return; + } + + // + control.setErrors(null, { emitEvent: true }); + this.detectChanges(); + } + + /** + * enable form or form control ... + * + * @param name a control name ... + */ + async enable(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.enable({ emitEvent: true }); + this.detectChanges(); + return; + } + + // + control.enable({ emitEvent: true }); + this.detectChanges(); + } + + /** + * disable form or form control ... + * + * @param name a control name ... + */ + async disable(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.disable({ emitEvent: true }); + this.detectChanges(); + return; + } + + // + control.disable({ emitEvent: true }); + this.detectChanges(); + } + + /** + * remove all data and form elements ... + */ + async clearData() { + // + // this.formConfig = null; + this.FORM_CONFIG = null; + this.FORM = null; + + // + this.detectChanges(); + + // + await this.prepareFormGroup(); + } + + /** + * refresh component ... + */ + async refresh() { + this.detectChanges(); + } + //#endregion + + // + //#region Private ... + // + //#region Prepare Form Group ... + private async prepareFormGroup() { + // + //#region Form Config ... + // + this.currentInputFocus = undefined; + const modelFormConfig = await this.getValueAsync(this.formConfig); + if (!modelFormConfig) { + return; + } + + // + const formConfig = { + name: await this.getValueAsync(this.getFormName()), + model: (await this.getValueAsync(this.formHasModel())) + ? await this.getValueAsync(this.getFormModel()) + : undefined, + updateOn: await this.getValueAsync(this.getFormUpdateOn()), + controls: await Promise.all( + ( + await this.getValueAsync(this.getFormControls()) + ).map(async (c) => { + // + //#region Action Provider ... + let actionProvider = c.actionProvider ? c.actionProvider : undefined; + + // + if (!actionProvider) { + // + if (!this.actionProviders[c.propName]) { + this.actionProviders[c.propName] = new Subject(); + } + + // + actionProvider = this.actionProviders[c.propName]; + } + //#endregion + + // + //#region Control ... + return { + index: c.index, + propName: c.propName, + type: { + type: await this.getValueAsync(this.getControlType(c)), + config: await this.getValueAsync(this.getControlTypeConfig(c)), + }, + updateOn: await this.getValueAsync(this.getControlUpdateOn(c)), + validators: (await this.getValueAsync(this.controlHasValidators(c))) + ? await this.getValueAsync(this.getControlValidators(c)) + : undefined, + actionProvider, + eventHandlers: (await this.getValueAsync( + this.controlHasEventHandlers(c) + )) + ? await this.getValueAsync(this.getControlEventHandlers(c)) + : undefined, + appearance: { + hidden: await this.getValueAsync(this.getControlHidden(c)), + disabled: await this.getValueAsync(this.getControlDisabled(c)), + hint: await this.getValueAsync(this.getControlHint(c)), + label: await this.getValueAsync(this.getControlLabel(c)), + tooltip: await this.getValueAsync(this.getControlTooltip(c)), + placeholder: await this.getValueAsync( + this.getControlPlaceHolder(c) + ), + cssClass: await this.getValueAsync(this.getControlCssClass(c)), + icons: (await this.getValueAsync(this.controlHasIcons(c))) + ? { + suffix: (await this.getValueAsync( + this.controlHasSuffixIcon(c) + )) + ? { + name: await this.getValueAsync( + this.getControlIconName( + await this.getValueAsync( + this.getControlSuffixIcon(c) + ) + ) + ), + color: await this.getValueAsync( + this.getControlIconColor( + await this.getValueAsync( + this.getControlSuffixIcon(c) + ) + ) + ), + applyStateColor: await this.getValueAsync( + this.getControlIconApplyStateColor( + await this.getValueAsync( + this.getControlSuffixIcon(c) + ) + ) + ), + handler: ( + await this.getValueAsync( + this.getControlSuffixIcon(c) + ) + ).handler, + } + : undefined, + prefix: (await this.getValueAsync( + this.controlHasPrefixIcon(c) + )) + ? { + name: await this.getValueAsync( + this.getControlIconName( + await this.getValueAsync( + this.getControlPrefixIcon(c) + ) + ) + ), + color: await this.getValueAsync( + this.getControlIconColor( + await this.getValueAsync( + this.getControlPrefixIcon(c) + ) + ) + ), + applyStateColor: await this.getValueAsync( + this.getControlIconApplyStateColor( + await this.getValueAsync( + this.getControlPrefixIcon(c) + ) + ) + ), + handler: ( + await this.getValueAsync( + this.getControlPrefixIcon(c) + ) + ).handler, + } + : undefined, + } + : undefined, + formatter: (await this.getValueAsync(this.controlHasFormatter(c))) + ? { + locale: await this.getValueAsync( + this.getFormatterLocale(c) + ), + seperator: await this.getValueAsync( + this.getFormatterSeperator(c) + ), + type: await this.getValueAsync(this.getFormatterType(c)), + currencyLable: await this.getValueAsync( + this.getFormatterCurrencyLabel(c) + ), + inputType: await this.getValueAsync( + this.getFormatterInputType(c) + ), + stringFormatter: await this.getValueAsync( + this.getFormatterStringFormatter(c) + ), + } + : undefined, + progressBar: (await this.getValueAsync( + this.controlHasProgressBar(c) + )) + ? { + color: await this.getValueAsync( + this.getProgressBarColor(c) + ), + } + : undefined, + autoComplete: (await this.getValueAsync( + this.controlHasAutoComplete(c) + )) + ? { + itemTemplate: await this.getValueAsync( + this.getAutoCompleteItemTemplate(c) + ), + opened: await this.getValueAsync( + this.getAutoCompleteOpened(c) + ), + color: await this.getValueAsync( + this.getAutoCompleteColor(c) + ), + onSelect: await this.getValueAsync( + this.getAutoCompleteOnSelect(c) + ), + items: await this.getValueAsync( + this.getAutoCompleteItems(c) + ), + cssClass: await this.getValueAsync( + this.getAutoCompleteCssClass(c) + ), + } + : undefined, + appearance: await this.getValueAsync( + this.getControlAppearance(c) + ), + labelPosition: await this.getValueAsync( + this.getControlLabelPosition(c) + ), + }, + errorHandlers: (await this.getValueAsync( + this.controlHasErrorHandlers(c) + )) + ? await Promise.all( + ( + await this.getValueAsync(this.getControlErrorHandlers(c)) + ).map(async (e) => { + return { + errorName: await this.getValueAsync(e.errorName), + errorMessage: await this.getResourceValueAsync( + e.errorMessage + ), + }; + }) + ) + : undefined, + }; + //#endregion + }) + ), + validators: (await this.getValueAsync(this.formHasValidators())) + ? await this.getValueAsync(this.getFormValidators()) + : undefined, + errorHandlers: (await this.getValueAsync(this.formHasErrorHandlers())) + ? await Promise.all( + ( + await this.getValueAsync(this.getFormErrorHandlers()) + ).map(async (e) => { + return { + errorName: await this.getValueAsync(e.errorName), + errorMessage: await this.getResourceValueAsync(e.errorMessage), + }; + }) + ) + : undefined, + }; + + // + if (!this.FORM_CONFIG) { + this.FORM_CONFIG = formConfig; + } + //#endregion + + // + //#region Preparing Form Controls ... + const controls = assign( + {}, + ...formConfig.controls + .map((c) => { + // + const initialValue = { + value: + !isNullOrUndefined(formConfig.model) && + !isNullOrUndefined(formConfig.model[c.propName.toString()]) + ? formConfig.model[c.propName.toString()] + : null, + disabled: + this.uiDisabled || + (c.appearance && c.appearance.disabled + ? c.appearance.disabled + : false), + }; + + // + const formControl = new FormControl(initialValue, { + updateOn: c.updateOn.toString() as 'change' | 'blur' | 'submit', + validators: c.validators ? c.validators.validators : null, + asyncValidators: c.validators ? c.validators.asyncValidators : null, + }); + + // + //#region Apply Event Handlers ... + // + //#region Value Change ... + formControl.valueChanges.subscribe(async (res) => { + // + const eventModel: XFormControlValueChangeEventModel = { + name: c.propName, + value: res, + }; + + // + if ( + c.eventHandlers && + c.eventHandlers.valueChanged && + isFunction(c.eventHandlers.valueChanged) + ) { + c.eventHandlers.valueChanged(eventModel); + } + + // + await this.handleControlValueChanged(eventModel); + + // + //#region Handle Map Control Input Value Change Action ... + const controlConfig = await this.getValueAsync( + this.getFormControl(c.propName) + ); + if (controlConfig) { + // + const type = await this.getValueAsync( + this.getControlType(controlConfig) + ); + + // + if (type === XFormControlType.Map) { + await this.handleMapControlInputValueChanged( + controlConfig, + res + ); + } else if (type === XFormControlType.ColorPicker) { + await this.handleColorPickerControlInputValueChanged( + controlConfig, + res + ); + } else if (type === XFormControlType.Selector) { + await this.handleSelectorControlInputValueChanged( + controlConfig, + res + ); + } + } + //#endregion + + // + this.detectChanges(); + }); + + // + // Notify Initial Value ... + if ( + !isNullOrUndefined(initialValue) && + !isNullOrUndefined(initialValue.value) + ) { + // + const eventModel: XFormControlValueChangeEventModel = { + name: c.propName, + value: initialValue.value, + }; + + // + if ( + c.eventHandlers && + c.eventHandlers.valueChanged && + isFunction(c.eventHandlers.valueChanged) + ) { + c.eventHandlers.valueChanged(eventModel); + } + + // + this.controlValueChanged.emit(eventModel); + } + //#endregion + + // + //#region Status Change ... + formControl.statusChanges.subscribe(async (res) => { + // + const eventModel: XFormControlStatusChangeEventModel = { + name: c.propName, + status: res, + }; + + // + if ( + c.eventHandlers && + c.eventHandlers.statusChanged && + isFunction(c.eventHandlers.statusChanged) + ) { + c.eventHandlers.statusChanged(eventModel); + } + + // + await this.handleControlStatusChanged(eventModel); + + // + this.detectChanges(); + }); + //#endregion + //#endregion + + // + //#region Apply Action Provider ... + if (c.actionProvider) { + // + c.actionProvider.asObservable().subscribe(async (model) => { + // + if ( + !model || + !model.name || + isNullOrEmptyString(model.name.toString()) || + !this.hasControl(model.name) + ) { + return; + } + + // + switch (model.action) { + // + case XFormBaseAction.Enable: + await this.enable(model.name); + break; + + // + case XFormBaseAction.Disable: + await this.disable(model.name); + break; + + // + case XFormBaseAction.SetError: + // + const errors = model.payload as ValidationErrors; + if (model.payload && errors) { + await this.setError(errors, model.name); + } + break; + + // + case XFormBaseAction.ClearError: + await this.clearError(model.name); + break; + + // + case XFormBaseAction.MarkAsDirty: + await this.markAsDirty(model.name); + break; + + // + case XFormBaseAction.MarkAsTouched: + await this.markAsTouched(model.name); + break; + + // + case XFormBaseAction.MarkAsPending: + await this.markAsPending(model.name); + break; + + // + case XFormBaseAction.MarkAsPristine: + await this.markAsPending(model.name); + break; + + // + case XFormBaseAction.MarkAsUnTouched: + await this.markAsTouched(model.name); + break; + + // + case XFormBaseAction.UpdateValueAndValidity: + await this.updateValueAndValidity(model.name); + break; + } + }); + } + //#endregion + + // + return { name: c.propName, control: formControl }; + }) + .map((fc) => { + return { key: fc.name.toString(), value: fc.control }; + }) + ) as { [key: string]: AbstractControl }; + //#endregion + + // + //#region Prepare Form Group ... + if (!this.FORM) { + // + this.FORM = new FormGroup( + controls, + formConfig.validators + ? { + updateOn: formConfig.updateOn.toString() as + | 'change' + | 'blur' + | 'submit', + validators: formConfig.validators.validators || null, + asyncValidators: formConfig.validators.asyncValidators || null, + } + : null + ); + + // + // Form Value Changed ... + this.FORM.valueChanges.subscribe((res) => { + // + if (this.uiDisabled) { + return; + } + + // + this.valueChanged.emit({ + name: formConfig.name, + value: res, + }); + + // + this.VALUE = res; + + // + this.detectChanges(); + }); + + // + // Form Status Changed ... + this.FORM.statusChanges.subscribe((res) => { + // + if (this.uiDisabled) { + return; + } + + // + this.STATUS = res; + + // + if (this.FORM.errors) { + this.ERRORS = this.FORM.errors; + } else { + this.ERRORS = null; + } + + // + this.statusChanged.emit({ + name: formConfig.name, + status: res, + }); + + // + this.detectChanges(); + }); + } else { + // + entries(controls).forEach((ctrlEtry) => { + // + const name = ctrlEtry[0]; + const control = ctrlEtry[1] as AbstractControl; + + // + if (this.FORM.contains(name)) { + this.FORM.setControl(name, control); + } + }); + } + //#endregion + + // + this.FORM.markAllAsTouched(); + this.FORM.updateValueAndValidity(); + + // + this.detectChanges(); + } + //#endregion + + // + //#region Control Config Object ... + // + private getControlName(name?: XFormControlNameIdentifier) { + return name ? name.toString() : null; + } + + // + private getCustomControlConfig(control: XFormControlConfig) { + return this.getControlType(control).pipe( + concatMap((type) => { + switch (type) { + // + case XFormControlType.Map: + return this.getMapConfig(control); + + // + case XFormControlType.Selector: + return this.getSelectorConfig(control); + + // + case XFormControlType.ColorPicker: + return this.getColorPickerConfig(control); + + // + default: + return of(null); + } + }) + ); + } + + // + private getControlInfo(control: XFormControlConfig) { + return this.getControlType(control).pipe( + concatMap((type) => + this.getControlDisabled(control).pipe( + map((disabled) => ({ type, disabled })) + ) + ), + concatMap((typeInfo) => + this.getControlAppearance(control).pipe( + map((appearance) => ({ ...typeInfo, appearance })) + ) + ) + ); + } + //#endregion + + // + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XFormBaseAction.Enable: + await this.enable(model.name); + break; + + // + case XFormBaseAction.Disable: + await this.disable(model.name); + break; + + // + case XFormBaseAction.Refresh: + await this.refresh(); + break; + + // + case XFormBaseAction.SetError: + // + if (model.payload) { + await this.setError(model.payload, model.name); + } + break; + + // + case XFormBaseAction.ClearError: + await this.clearError(model.name); + break; + + // + case XFormBaseAction.MarkAsDirty: + await this.markAsDirty(model.name); + break; + + // + case XFormBaseAction.MarkAsTouched: + await this.markAsTouched(model.name); + break; + + // + case XFormBaseAction.MarkAsPending: + await this.markAsPending(model.name); + break; + + // + case XFormBaseAction.MarkAsPristine: + await this.markAsPending(model.name); + break; + + // + case XFormBaseAction.MarkAsUnTouched: + await this.markAsUnTouched(model.name); + break; + + // + case XFormBaseAction.UpdateValueAndValidity: + await this.updateValueAndValidity(model.name); + break; + + // + case XFormBaseAction.SetValue: + await this.setValue(model.name, model.payload); + break; + + // + case XFormBaseAction.ClearData: + await this.clearData(); + break; + + // + case XFormControlAction.Focuse: + if (model.name) { + this.focusOnControl(model.name); + } + break; + + // + case XFormControlAction.Blur: + if (model.name) { + this.blurOfControl(model.name); + } + break; + } + }); + } + + // + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + + // + private async handleUiDisabledChanged() { + // + if (this.uiDisabled) { + await this.disable(); + } else { + await this.enable(); + } + } + + // + //#region Color Appliers ... + private async applyColors() { + // + await this.applyRegularColor(); + await this.applyAccentColor(); + await this.applyHintColor(); + await this.applyPendingColor(); + await this.applyActiveColor(); + await this.applyValidColor(); + await this.applyInvalidColor(); + await this.applyFillColor(); + await this.applyDisabledColor(); + await this.applyDisabledFillColor(); + } + + private async applyRegularColor() { + // + const color = await this.getValueAsync(this.regularColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.RegularColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.RegularColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyAccentColor() { + // + let color = await this.getValueAsync(this.accentColor); + if (!color) { + color = XColor.Secondary; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.AccentColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.AccentColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyHintColor() { + // + let color = await this.getValueAsync(this.hintColor); + if (!color) { + color = XColor.Tertiary; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.HintColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.HintColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyPendingColor() { + // + let color = await this.getValueAsync(this.pendingColor); + if (!color) { + color = XColor.Warning; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.PendingColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.PendingColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyActiveColor() { + // + let color = await this.getValueAsync(this.activeColor); + if (!color) { + color = XColor.Tertiary; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.ActiveColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.ActiveColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyValidColor() { + // + let color = await this.getValueAsync(this.validColor); + if (!color) { + color = XColor.Success; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.ValidColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.ValidColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyInvalidColor() { + // + let color = await this.getValueAsync(this.invalidColor); + if (!color) { + color = XColor.Danger; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.InvalidColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.InvalidColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyFillColor() { + // + let color = await this.getValueAsync(this.fillColor); + if (!color) { + color = XColorWithBrightness.SecondaryTint; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.FillColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.FillColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyDisabledColor() { + // + let color = await this.getValueAsync(this.disabledColor); + if (!color) { + color = XColorWithBrightness.LightTint; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.DisableColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.DisableColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyDisabledFillColor() { + // + let color = await this.getValueAsync(this.disabledFillColor); + if (!color) { + color = XColorWithBrightness.LightShade; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.DisabledFillColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.DisabledFillColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-form\x-form.module.ts + +import { + LocaleFactory, + XPersianDateAdapter, + MatDateFormatFactory, +} from 'x-framework-core'; +import { + DateAdapter, + MAT_DATE_LOCALE, + MAT_DATE_FORMATS, +} from '@angular/material/core'; +import { CommonModule } from '@angular/common'; +import { XMapModule } from '../x-map/x-map.module'; +import { LOCALE_ID, NgModule } from '@angular/core'; +import { XFormComponent } from './x-form.component'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XListModule } from '../x-list/x-list.module'; +import { XColorModule } from '../x-color/x-color.module'; +import { XDialogsModule } from '../dialogs/x-dialog.module'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { XMarkdownModule } from '../x-markdown/x-markdown.module'; +import { XDataPagerModule } from '../x-data-pager/x-data-pager.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XFileUploadModule } from '../x-file-upload/x-file-upload.module'; +import { XColorPickerModule } from '../x-color-picker/x-color-picker.module'; +import { XAvatarUploadModule } from '../x-avatar-upload/x-avatar-upload.module'; +import { MAT_MOMENT_DATE_ADAPTER_OPTIONS } from '@angular/material-moment-adapter'; + +@NgModule({ + entryComponents: [], + declarations: [XFormComponent], + imports: [ + XMapModule, + XCardModule, + XListModule, + XIconModule, + FormsModule, + CommonModule, + XColorModule, + XDialogsModule, + XSlotterModule, + XMarkdownModule, + XDataPagerModule, + XFileUploadModule, + XColorPickerModule, + ReactiveFormsModule, + XAvatarUploadModule, + XBaseComponentModule, + ], + exports: [ + XMapModule, + XCardModule, + XListModule, + XIconModule, + FormsModule, + CommonModule, + XColorModule, + XDialogsModule, + XFormComponent, + XSlotterModule, + XMarkdownModule, + XDataPagerModule, + XFileUploadModule, + XColorPickerModule, + ReactiveFormsModule, + XAvatarUploadModule, + XBaseComponentModule, + ], + providers: [ + { + provide: LOCALE_ID, + useFactory: LocaleFactory, + }, + { + provide: MAT_DATE_LOCALE, + useFactory: LocaleFactory, + }, + { + deps: [MAT_DATE_LOCALE], + provide: MAT_DATE_FORMATS, + useFactory: MatDateFormatFactory, + }, + { + provide: DateAdapter, + useClass: XPersianDateAdapter, + deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS], + }, + ], +}) +export class XFormModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-form\x-form.tools.ts + +import { XIconNames } from '../x-icon/x-icon.typings'; +import { XColorWithBrightness } from 'x-framework-core'; +import { XControlIconPosition, XControlIconDescriptor } from './x-form.typings'; + +export const defaultFormControlClearIcon: XControlIconDescriptor = { + name: XIconNames.clear, + applyStateColor: false, + color: XColorWithBrightness.Danger, + position: XControlIconPosition.Suffix, +} + +export const defaultFormControlSelectIcon: XControlIconDescriptor = { + name: XIconNames.touch, + applyStateColor: false, + color: XColorWithBrightness.Success, + position: XControlIconPosition.Suffix, +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-form\x-form.typings.ts + +import { + Input, + Inject, + NgZone, + Output, + Renderer2, + Component, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XLocale, + XBaseDto, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XExceptionIdentifier, + XColorWithBrightness, + XBorderStyleIdentifier, +} from 'x-framework-core'; +import { + XManagerService, + XFormInputFormatterIdentifier, + XStringInputFormatTypeIdentifier, +} from 'x-framework-services'; +import { + XMarkdownActionModel, + XMarkdownModeIdentifier, + XMarkdownToolbarConfigProps, +} from '../x-markdown/x-markdown.typings'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { + XAvatarUploadActionModel, + XAvatarUploadActionButton, +} from '../x-avatar-upload/x-avatar-upload.typings'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { AbstractControlOptions } from '@angular/forms'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { IXMapContainer, XMapActionModel } from '../x-map/x-map.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XMapDialogService } from '../x-map/x-map-dialog/x-map-dialog.service'; +import { XColorPickerActionModel } from '../x-color-picker/x-color-picker.typings'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; +import { XFileDropAreaActionModel } from '../x-file-drop-area/x-file-drop-area.typings'; +import { XSelectorDialogConfig } from '../dialogs/x-selector-dialog/x-selector-dialog.typings'; +import { XSelectorDialogService } from '../dialogs/x-selector-dialog/x-selector-dialog.service'; +import { XColorPickerDialogConfig } from '../x-color-picker/x-color-picker-dialog/x-color-picker-dialog.typings'; +import { XColorPickerDialogService } from '../x-color-picker/x-color-picker-dialog/x-color-picker-dialog.service'; + +// +//#region Css Vars ... +export enum XFormCssVar { + FillColor = 'fill-color', + HintColor = 'hint-color', + ValidColor = 'valid-color', + ActiveColor = 'active-color', + AccentColor = 'accent-color', + RegularColor = 'regular-color', + PendingColor = 'pending-color', + InvalidColor = 'invalid-color', + DisableColor = 'disabled-color', + DisabledFillColor = 'disabled-fill-color', +} +//#endregion + +// +//#region Base Form Control Action Model ... +export type XFormControlNameIdentifier = string | number | symbol; + +export interface XBaseFormControlActionModel { + name?: XFormControlNameIdentifier; +} +//#endregion + +// +//#region Control Auto Complete Config ... +export interface XFormControlAutoCompleteConfig { + opened?: XStandardType; + color?: XStandardType; + onSelect?: (item: T) => XStandardType; + items: () => XStandardType>; + itemTemplate: XStandardType>; + cssClass?: XStandardType>; + onEscape?: () => XStandardType; + onOutsideClicked?: () => XStandardType; +} +//#endregion + +// +//#region XForm Validator Model ... +export type XFormValidatorDescriptor = Partial< + Pick +>; +//#endregion + +// +//#region Error Handler Configs ... +export interface XFormErrorHandler { + errorName: XStandardType; + errorMessage: XStandardType; + errorDataProvider?: (errorObject: any) => XStandardType; +} +//#endregion + +// +//#region Formatter Config ... +export interface XFormControlFormatterConfig { + locale?: XStandardType; + seperator?: XStandardType; + type: XStandardType; + inputType?: XStandardType; + currencyLable?: XStandardType; + stringFormatter?: XStandardType>; +} +//#endregion + +// +//#region Control Progressbar Config ... +export interface XFormControlProgressbarConfig { + color: XStandardType; +} +//#endregion + +// +//#region XForm Control Appearance ... +export enum XFormControlAppearance { + None = 'none', + Legacy = 'legacy', + Standard = 'standard', + Fill = 'fill', + Outline = 'outline', +} + +export type XFormControlAppearanceIdentifier = XFormControlAppearance | string; + +export interface XFormControlAppearanceDescriptor { + hidden?: XStandardType; + disabled?: XStandardType; + hint?: XStandardType; + label: XStandardType; + tooltip?: XStandardType; + placeholder?: XStandardType; + cssClass?: XStandardType>; + icons?: XStandardType; + formatter?: XStandardType; + progressBar?: XStandardType; + autoComplete?: XStandardType; + appearance?: XStandardType; + labelPosition?: XStandardType; +} +//#endregion + +// +//#region Icon Configs ... +export enum XControlIconPosition { + Prefix = 'prefix', + Suffix = 'suffix', +} + +export type XControlIconPositionIdentifier = XControlIconPosition | string; + +export enum XControlIconRole { + Clear = 'clear', + Select = 'select', +} + +export type XControlIconRoleIdentifier = XControlIconRole | string; + +export interface XControlIconDescriptor { + handler?: () => XStandardType; + name?: XStandardType; + color?: XStandardType; + applyStateColor?: XStandardType; + tooltip?: XStandardType; + position?: XStandardType; +} + +export interface XFormIconDescriptor + extends Pick< + Partial, + 'handler' | 'name' | 'color' | 'applyStateColor' | 'tooltip' + > {} + +export interface XFormControlIconDescriptor { + suffix?: XStandardType; + prefix?: XStandardType; +} +//#endregion + +// +//#region Form Control Status and Values Changed ... +export interface XFormControlValueChangeEventModel + extends XBaseFormControlActionModel { + value: any; +} + +export interface XFormControlStatusChangeEventModel + extends XBaseFormControlActionModel { + status: XFormStatusIdentifier; +} + +// tslint:disable-next-line:no-empty-interface +export interface XFormValueChangeEventModel + extends XFormControlValueChangeEventModel {} + +// tslint:disable-next-line:no-empty-interface +export interface XFormStatusChangeEventModel + extends XFormControlStatusChangeEventModel {} + +// +// Form and Form Control Status ... +export enum XFormStatus { + UnKnown = 'UNKNOWN', + Valid = 'VALID', + Invalid = 'INVALID', + Pending = 'PENDING', + Disabled = 'DISABLED', +} + +export type XFormStatusIdentifier = XFormStatus | string; + +export interface XFormControlEventsDescriptor { + statusChanged?: ( + model: XFormControlStatusChangeEventModel + ) => XStandardType; + valueChanged?: ( + model: XFormControlValueChangeEventModel + ) => XStandardType; + onBlured?: (model: XBaseFormControlActionModel) => XStandardType; + onFocused?: (model: XBaseFormControlActionModel) => XStandardType; +} +//#endregion + +// +//#region Form Control Label Position ... +export enum XFormControlLabelPosition { + After = 'after', + Before = 'before', +} + +export type XFormControlLabelPositionIdentifier = + | XFormControlLabelPosition + | string; +//#endregion + +// +//#region Form and Form Control Action Provider ... +export enum XFormBaseAction { + // + MarkAsDirty = 'mark_as_dirty', + MarkAsTouched = 'mark_as_touched', + MarkAsPending = 'mark_as_pending', + MarkAsPristine = 'mark_as_pristine', + MarkAsUnTouched = 'mark_as_untouched', + UpdateValueAndValidity = 'update_value_and_validity', + // + SetError = 'set_error', + ClearError = 'clear_error', + // + SetValue = 'set_value', + // + Enable = 'enable', + Disable = 'disable', + // + Refresh = 'refresh', + // + ClearData = 'clear_data', +} + +export enum XFormControlAction { + Focuse = 'focuse', + Blur = 'blur', +} + +export type XFormControlActionIdentifier = + | XFormBaseAction + | XFormControlAction + | string; + +export interface XFormControlActionProviderModel + extends XBaseActionModel, + XBaseFormControlActionModel {} + +export interface XFormActionModel extends XFormControlActionProviderModel {} +//#endregion + +// +//#region XForm and XForm Control Update On ... +export enum XFormUpdateOn { + BLUR = 'blur', + CHANGE = 'change', + SUBMIT = 'submit', +} + +export type XFormUpdateOnIdentifier = XFormUpdateOn | string; +//#endregion + +// +//#region Form Control Types ... +export enum XFormControlType { + Text = 'text', + Number = 'number', + Password = 'password', + Color = 'color', + Email = 'email', + Tel = 'tel', + Url = 'url', + Select = 'select', + Radio = 'radio', + CheckBox = 'checkbox', + Hidden = 'hidden', + Date = 'date', + Slider = 'slider', + Toggle = 'toggle', + MarkDown = 'markdown', + Map = 'map', + ColorPicker = 'color_picker', + AvatarUpload = 'avatar_upload', + Selector = 'selector', +} + +export type XFormControlTypeIdentifier = XFormControlType | string; + +export type XFormControlTypeConfig = + | XFormMapControlConfig + | XFormRadioControlConfig + | XFormInputControlConfig + | XFormSelectControlConfig + | XFormSliderControlConfig + | XFormSelectorControlConfig + | XFormMarkdownControlConfig + | XFormCheckBoxControlConfig + | XFormDatePickerControlConfig + | XFormColorPickerControlConfig + | XFormAvatarUploadControlConfig + | undefined; +export interface XFormControlTypeDescriptor { + type: XStandardType; + config?: XStandardType; +} + +// +//#region Type Configs ... +// +//#region Input Type ... +export interface XFormInputControlConfig { + readonly?: XStandardType; +} +//#endregion + +// +//#region Select Type ... +export interface XFormSelectControlOption { + value: XStandardType; + viewValue: XStandardType; +} + +export interface XFormSelectControlConfig { + options?: XStandardType>>; + multiple?: XStandardType; +} +//#endregion + +// +//#region Radio Type ... +export interface XFormRadioControlOption { + value: XStandardType; + viewValue: XStandardType; +} + +export interface XFormRadioControlConfig { + options?: XStandardType>>; +} +//#endregion + +// +//#region Date Control Config ... +export enum XFormDatePickerControlPickerPosition { + Suffix = 'suffix', + Prefix = 'prefix', +} + +export type XFormDatePickerControlPickerPositionIdentifier = + | XFormDatePickerControlPickerPosition + | string; + +export interface XFormDatePickerControlConfig { + startAt?: XStandardType; + dateFilter?: (d: any) => boolean; + applyStateColor?: XStandardType; + toggleIcon?: XStandardType; + iconColor?: XStandardType; + datePickerPosition: XStandardType; + disableInput?: XStandardType; + disablePicker?: XStandardType; + iconTooltip?: XStandardType; + dateInput?: (event: Date) => XStandardType; + dateChange?: (event: Date) => XStandardType; +} +//#endregion + +// +//#region Slider Control ... +export interface XFormSliderControlConfig { + min?: XStandardType; + max?: XStandardType; + step?: XStandardType; + invert?: XStandardType; + vertical?: XStandardType; + thumbLabel?: XStandardType; + formatter?: (value: number) => string; +} +//#endregion + +// +//#region Markdown Control Config ... +export interface XFormMarkdownControlConfig { + // + isInPage?: XStandardType; + hasToolbar?: XStandardType; + wrapWithCard?: XStandardType; + cardColor?: XStandardType; + cardForegroundColor?: XStandardType; + mode?: XStandardType; + toolbarColor?: XStandardType; + cardTitle?: XStandardType; + actionProvider?: Subject; + toolbarForegroundColor?: XStandardType; + cssClass?: XStandardType>; + editorBoxColor?: XStandardType; + previewBoxColor?: XStandardType; + cardSubTitle?: XStandardType; + toolbarCssClass?: XStandardType>; + contentChanged?: (content: string) => XStandardType; + toolbarConfig?: XStandardType; +} +//#endregion + +// +//#region Check Box Config ... +export interface XFormCheckBoxControlConfig { + checkedChanged?: (checked: boolean) => XStandardType; +} +//#endregion + +// +//#region Map Config ... +export enum XFormMapControlPresentType { + WithDialog = 'width_dialog', + WithoutDialog = 'without_dialog', +} + +export type XFormMapControlPresentTypeIdentifier = + | XFormMapControlPresentType + | string; + +// +export interface XFormMapControlConfig + extends Pick< + Partial, + | 'zoom' + | 'markerStyle' + | 'cssClass' + | 'canSelect' + // + | 'showProgressBar' + | 'progressBarColor' + | 'progressBarPosition' + // + | 'showSearchBar' + | 'searchBarType' + | 'searchBarValue' + | 'searchBarDebounce' + | 'searchBarQueryChange' + | 'searchBarSpellcheck' + | 'searchPlaceProvider' + | 'searchBarColor' + | 'searchBarForegroundColor' + | 'searchBarClearButtonIcon' + | 'searchBarTitle' + | 'searchBarSearchButtonIcon' + | 'searchBarSearchInputColor' + | 'searchBarForegroundSearchInputColor' + | 'searchResultListColor' + | 'searchBarActionProvider' + | 'searchBarClearButtonIconColor' + | 'searchBarCssClass' + | 'searchBarSearchButtonIconColor' + | 'searchBarCancelButtonIcon' + | 'searchBarPosition' + | 'searchBarKeyboardType' + | 'searchBarCancelButtonTitle' + | 'searchBarEnterKeyHint' + | 'searchBarShowCancelButton' + // + | 'showSelectedLocation' + | 'selectedLocationPosition' + // + | 'showGoMarkedPlace' + | 'goMarkedPlacePosition' + // + | 'showClearMarker' + | 'clearMarkerPosition' + // + | 'showLocator' + | 'locatorPosition' + // + | 'showZoom' + | 'showRotate' + | 'showZoomSlider' + // + | 'isReady' + | 'locationSelected' + // + | 'isInPage' + | 'wrapWithCard' + | 'cardShowFooter' + | 'cardColor' + | 'cardForegroundColor' + | 'cardTitle' + | 'cardSubTitle' + > { + // + disableIcon?: XStandardType; + disableInput?: XStandardType; + actionProvider?: Subject; + clearIcon?: XStandardType; + selectIcon?: XStandardType; + inputCssClass?: XStandardType>; + inputValueChanged?: (value?: string) => XStandardType; + presentType?: XStandardType; +} +//#endregion + +// +//#region ColorPicker Config ... +export enum XFormColorPickerControlPresentType { + WithDialog = 'width_dialog', + WithoutDialog = 'without_dialog', + // Both = 'both', +} + +export type XFormColorPickerControlPresentTypeIdentifier = + | XFormColorPickerControlPresentType + | string; + +// +export interface XFormColorPickerControlConfig + extends Pick< + Partial, + | 'color' + | 'cssClass' + | 'cardColor' + | 'cardTitle' + | 'cursorColor' + | 'presetColors' + | 'outputFormat' + | 'cardSubTitle' + | 'colorSelected' + | 'colorTextColor' + | 'showColorValue' + | 'showHueChannel' + | 'showColorInput' + | 'foregroundColor' + | 'showPresetColors' + | 'showAlphaChannel' + | 'presetColorsLable' + | 'showPaletteChannel' + | 'cardForegroundColor' + | 'showCopyToClipboard' + | 'colorInputIconLabel' + | 'colorInputIconColor' + | 'clearColorIconColor' + | 'clearColorIconLabel' + | 'representColorFormats' + | 'presetColorsLableColor' + | 'copyToClipboardIconLabel' + | 'copyToClipboardIconColor' + | 'showSelectedColorPreview' + > { + // + isInPage?: XStandardType; + disableIcon?: XStandardType; + disableInput?: XStandardType; + wrapWithCard?: XStandardType; + showCardFooter?: XStandardType; + colorViewBorderWidth?: XStandardType; + actionProvider?: Subject; + clearIcon?: XStandardType; + selectIcon?: XStandardType; + inputCssClass?: XStandardType>; + colorViewBorderColor?: XStandardType; + colorViewBorderStyle?: XStandardType; + inputValueChanged?: (value?: string) => XStandardType; + presentType?: XStandardType; +} +//#endregion + +// +//#region Avatar Upload Config ... +export interface XFormAvatarUploadControlConfig { + fileDropAreaFilesChangeEmiter?: ( + files?: XOneOrManyType + ) => XStandardType; + canDrop?: XStandardType; + isInPage?: XStandardType; + wrapWithCard?: XStandardType; + color?: XStandardType; + showCardFooter?: XStandardType; + foregroundColor?: XStandardType; + placeHolderAvatar?: XStandardType; + minAllowedFileSize?: XStandardType; + maxAllowedFileSize?: XStandardType; + cardColor?: XStandardType; + cardForegroundColor?: XStandardType; + fileDropAreaIsInPage?: XStandardType; + cardTitle?: XStandardType; + cssClass?: XStandardType>; + fileDropAreaWrapWithCard?: XStandardType; + fileDropAreaIcon?: XStandardType; + fileDropAreaDragDisabled?: XStandardType; + cardSubTitle?: XStandardType; + actionProvider?: Subject; + fileDropAreaShowCardFooter?: XStandardType; + fileDropAreaColor?: XStandardType; + fileDropAreaForegroundColor?: XStandardType; + avatarChange?: (avatar?: File) => XStandardType; + fileDropAreaDisableFileCheck?: XStandardType; + fileDropAreaCardColor?: XStandardType; + fileDropAreaCardForegroundColor?: XStandardType; + emptyFileException?: XStandardType; + fileDropAreaErrorColor?: XStandardType; + fileDropAreaMessage?: XStandardType; + fileDropAreaAccentColor?: XStandardType; + allowedExtensions?: XStandardType>; + fileDropAreaCardTitle?: XStandardType; + fileDropAreaSuccessColor?: XStandardType; + fileDropAreaSupportsMultipleFiles?: XStandardType; + fileDropAreaCssClass?: XStandardType>; + fileDropAreaCardSubTitle?: XStandardType; + notAllowedFileException?: XStandardType; + fileDropAreaContentTemplate?: XStandardType>; + fileDropAreaActionProvider?: Subject; + minAllowedFileSizeException?: XStandardType; + maxAllowedFileSizeException?: XStandardType; + actions?: XStandardType>; + fileDropAreaAllowedExtensions?: XStandardType>; +} +//#endregion + +// +//#region Selector Config ... +export interface XFormSelectorControlConfig + extends Partial { + // + clearIcon?: XStandardType; + selectIcon?: XStandardType; + inputCssClass?: XStandardType>; + inputValueChanged?: (value?: string) => XStandardType; + formatSelectedItem?: (item: XOneOrManyType) => XStandardType; +} +//#endregion +//#endregion +//#endregion + +// +//#region Form Control Config ... +export interface XFormControlConfig { + index: number; + propName: keyof Partial; + focusHandler?: Subject; + type: XStandardType; + updateOn?: XStandardType; + validators?: XStandardType; + actionProvider?: Subject; + eventHandlers?: XStandardType; + appearance: XStandardType; + errorHandlers?: XStandardType>; +} +//#endregion + +// +// Form Config ... +export interface XFormConfig { + name: XStandardType; + model?: XStandardType>; + updateOn?: XStandardType; + validators?: XStandardType; + controls: XStandardType>>; + errorHandlers?: XStandardType>; +} + +// +//#region Inside Card Container ... +export interface IXFormContainer extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + formConfig: XStandardType; + applyStatusOnControls: XStandardType; + + // + regularColor: XStandardType; + accentColor: XStandardType; + hintColor: XStandardType; + activeColor: XStandardType; + validColor: XStandardType; + invalidColor: XStandardType; + pendingColor: XStandardType; + fillColor: XStandardType; + disabledColor: XStandardType; + disabledFillColor: XStandardType; + + // + valueChanged: EventEmitter; + statusChanged: EventEmitter; + controlFocused: EventEmitter; + controlBlured: EventEmitter; + controlValueChanged: EventEmitter; + controlStatusChanged: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XFormContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + readonly formConfig: XStandardType = null; + readonly applyStatusOnControls: XStandardType = false; + + // + readonly regularColor: XStandardType = + XColorWithBrightness.Dark; + readonly accentColor: XStandardType = + XColorWithBrightness.Secondary; + readonly hintColor: XStandardType = + XColorWithBrightness.Tertiary; + readonly activeColor: XStandardType = + XColorWithBrightness.Tertiary; + readonly validColor: XStandardType = + XColorWithBrightness.Success; + readonly invalidColor: XStandardType = + XColorWithBrightness.Danger; + readonly pendingColor: XStandardType = + XColorWithBrightness.Warning; + readonly fillColor: XStandardType = + XColorWithBrightness.SecondaryTint; + readonly disabledColor: XStandardType = + XColorWithBrightness.LightTint; + readonly disabledFillColor: XStandardType = + XColorWithBrightness.LightShade; + + // + readonly valueChanged: EventEmitter = + new EventEmitter(); + readonly statusChanged: EventEmitter = + new EventEmitter(); + readonly controlFocused: EventEmitter = + new EventEmitter(); + readonly controlBlured: EventEmitter = + new EventEmitter(); + readonly controlValueChanged: EventEmitter = + new EventEmitter(); + readonly controlStatusChanged: EventEmitter = + new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XFormContainerComponent + extends XComponentCardContainerComponent + implements IXFormContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + formConfig: XStandardType = this.propertyProvider.formConfig; + + @Input() + applyStatusOnControls: XStandardType = + this.propertyProvider.applyStatusOnControls; + + // + @Input() + regularColor: XStandardType = + this.propertyProvider.regularColor; + + @Input() + accentColor: XStandardType = + this.propertyProvider.accentColor; + + @Input() + hintColor: XStandardType = this.propertyProvider.hintColor; + + @Input() + activeColor: XStandardType = + this.propertyProvider.activeColor; + + @Input() + validColor: XStandardType = + this.propertyProvider.validColor; + + @Input() + invalidColor: XStandardType = + this.propertyProvider.invalidColor; + + @Input() + pendingColor: XStandardType = + this.propertyProvider.pendingColor; + + @Input() + fillColor: XStandardType = this.propertyProvider.fillColor; + + @Input() + disabledColor: XStandardType = + this.propertyProvider.disabledColor; + + @Input() + disabledFillColor: XStandardType = + this.propertyProvider.disabledFillColor; + + // + @Output() + valueChanged: EventEmitter = new EventEmitter(); + + @Output() + statusChanged: EventEmitter = new EventEmitter(); + + @Output() + controlFocused: EventEmitter = new EventEmitter(); + + @Output() + controlBlured: EventEmitter = new EventEmitter(); + + @Output() + controlValueChanged: EventEmitter = new EventEmitter(); + + @Output() + controlStatusChanged: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XFormContainer, + protected mapDialogService: XMapDialogService, + protected selectorDialogService: XSelectorDialogService, + protected colorPickerDialogService: XColorPickerDialogService + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXFormContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + formConfig: this.getValue( + container ? container.formConfig : this.formConfig + ), + applyStatusOnControls: this.getValue( + container + ? container.applyStatusOnControls + : this.applyStatusOnControls + ), + regularColor: this.getValue( + container ? container.regularColor : this.regularColor + ), + accentColor: this.getValue( + container ? container.accentColor : this.accentColor + ), + hintColor: this.getValue( + container ? container.hintColor : this.hintColor + ), + activeColor: this.getValue( + container ? container.activeColor : this.activeColor + ), + validColor: this.getValue( + container ? container.validColor : this.validColor + ), + invalidColor: this.getValue( + container ? container.invalidColor : this.invalidColor + ), + pendingColor: this.getValue( + container ? container.pendingColor : this.pendingColor + ), + fillColor: this.getValue( + container ? container.fillColor : this.fillColor + ), + disabledColor: this.getValue( + container ? container.disabledColor : this.disabledColor + ), + disabledFillColor: this.getValue( + container ? container.disabledFillColor : this.disabledFillColor + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + valueChanged: this.valueChanged, + statusChanged: this.statusChanged, + controlFocused: this.controlFocused, + controlBlured: this.controlBlured, + controlValueChanged: this.controlValueChanged, + controlStatusChanged: this.controlStatusChanged, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-form\validators\x-form-validators.ts + +import { + isValidURL, + XCountryCode, + isNullOrUndefined, + isValidPhoneNumber, + isNullOrEmptyString, + toNumber, + toXString, +} from 'x-framework-core'; +import { ValidatorFn, AbstractControl, ValidationErrors } from '@angular/forms'; + +export class XFormValidators { + /** + * check given date in between start and end date ... + * + * @param startDate start date for validating + * @param endDate end date for validating + */ + static validateDate( + startDate: Date | string, + endDate: Date | string + ): ValidatorFn { + // + const errorObject = { + startDate, + endDate, + currentDate: new Date(), + xValidateDate: true, + }; + + // + const validatorFn = (control: AbstractControl): ValidationErrors | null => { + // + if ( + isNullOrEmptyString(control.value) || + isNullOrUndefined(startDate) || + isNullOrUndefined(endDate) + ) { + return null; + } + + // + const mStartDate = new Date(startDate); + const mEndDate = new Date(endDate); + const mDate = new Date(control.value); + + // + const isValid = mDate >= mStartDate && mDate <= mEndDate; + if (!isValid) { + // + errorObject.currentDate = mDate; + return errorObject; + } + + // + return null; + }; + + // + return validatorFn; + } + + /** + * check given value be a valid phone number ... + * + * @param defaultCountryCode an optional value which used for country code if not exists in value + */ + static validatePhoneNumber(defaultCountry?: XCountryCode): ValidatorFn { + // + const errorObject = { + xPhoneNumberValidator: true, + }; + + // + const validatorFn = (control: AbstractControl): ValidationErrors | null => { + // + if (isNullOrEmptyString(control.value)) { + return null; + } + + // + const value = control.value as string; + const isValid = isValidPhoneNumber(value, defaultCountry); + + // + if (!isValid) { + return errorObject; + } + + // + return null; + }; + + // + return validatorFn; + } + + /** + * check given value is Valid URL or not ... + */ + static validateUrl(): ValidatorFn { + // + const errorObject = { + xUrlValidator: true, + }; + + // + const validatorFn = (control: AbstractControl): ValidationErrors | null => { + // + if (isNullOrEmptyString(control.value)) { + return null; + } + + // + const value = control.value as string; + const isValid = isValidURL(value); + + // + if (!isValid) { + return errorObject; + } + + // + return null; + }; + + // + return validatorFn; + } + + /** + * check give value notEquals to Zero ... + * usually works for id's in Selector Component ... + */ + static validateNotZero(): ValidatorFn { + // + const errorObject = { + xNotZeroValidator: true, + }; + + // + const validatorFn = (control: AbstractControl): ValidationErrors | null => { + // + if (isNullOrEmptyString(control.value)) { + return null; + } + + // + const value = control.value as string; + const isValid = + !isNullOrEmptyString(value) && + (toXString(value) !== '0' || toNumber(value) !== 0); + + // + if (!isValid) { + return errorObject; + } + + // + return null; + }; + + // + return validatorFn; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-frame\x-frame.component.html + + + + + + + + +
+ +
+ + + +
+
+ + + +
+ + + + + + + + + +
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-frame\x-frame.component.scss + +:host { + --x-frame-height: auto; + } + + .v-frame-frame, + .v-frame-container { + width: 100%; + height: var(--x-frame-height); + } + + .v-frame-frame { + border: unset; + } + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-frame\x-frame.component.ts + +import { + Component, + ViewChild, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + generateUuid, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XFrameAction, + XFrameActionModel, + XFrameContainerComponent, +} from './x-frame.typings'; +import { Subject, Subscription } from 'rxjs'; +import { concatMap, map } from 'rxjs/operators'; +import { SafeHtml, SafeResourceUrl } from '@angular/platform-browser'; + +@Component({ + selector: 'x-frame', + templateUrl: './x-frame.component.html', + styleUrls: ['./x-frame.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XFrameComponent extends XFrameContainerComponent { + // + //#region Props ... + // + readonly id = generateUuid('VFrame'); + + // + private ORIGIN: string = ''; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private FRAME_VIEW: ElementRef = null; + + // + @ViewChild('frameView', { static: false }) + set frameView(v: ElementRef) { + // + if (isNullOrUndefined(v)) { + return; + } + + // + this.FRAME_VIEW = v; + this.applyFrameProps(); + } + + // + //#region Getters ... + get iFrame() { + return this.FRAME_VIEW!.nativeElement; + } + + get contentWindow() { + return this.iFrame!.contentWindow; + } + + get contentDocuent() { + return this.iFrame!.contentDocument; + } + + get innerHTML() { + return this.iFrame!.innerHTML; + } + + get innerText() { + return this.iFrame!.innerText; + } + + get frameView() { + return this.FRAME_VIEW; + } + + get origin() { + return this.ORIGIN; + } + //#endregion + + // + contentHTML: SafeHtml; + contentUrl: SafeResourceUrl; + //#endregion + + // + //#region Constructor ... + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Action Providers ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Url Changed ... + const isUrlChanged = changeKeys.includes(nameof('url')); + if (isUrlChanged) { + this.prepareContentUrl(); + } + //#endregion + + // + //#region Content ... + const isContentChanged = changeKeys.includes( + nameof('content') + ); + if (isContentChanged) { + this.prepareContent(); + } + //#endregion + + // + //#region VFrame Props ... + // + //#region Allow ... + const isAllowChanged = changeKeys.includes( + nameof('allow') + ); + if (isAllowChanged) { + this.handleAllowChanged(); + } + //#endregion + + // + //#region FrameLoading ... + const isFrameLoadingChanged = changeKeys.includes( + nameof('frameLoading') + ); + if (isFrameLoadingChanged) { + this.handleFrameLoadingChanged(); + } + //#endregion + + // + //#region FrameReferrerPolicy ... + const isReferrerPolicyChanged = changeKeys.includes( + nameof('referrerPolicy') + ); + if (isReferrerPolicyChanged) { + this.handleReferrerPolicyChanged(); + } + //#endregion + + // + //#region FrameSandbox ... + const isSandboxChanged = changeKeys.includes( + nameof('sandbox') + ); + if (isSandboxChanged) { + this.handleSandboxChanged(); + } + //#endregion + + // + //#region AllowFullScreen ... + const isAllowFullScreenChanged = changeKeys.includes( + nameof('allowFullscreen') + ); + if (isAllowFullScreenChanged) { + this.handleAllowFullScreenChanged(); + } + //#endregion + + // + //#region AllowPaymentRequest ... + const isAllowPaymentRequestChanged = changeKeys.includes( + nameof('allowPaymentRequest') + ); + if (isAllowPaymentRequestChanged) { + this.handleAllowPaymentRequestChanged(); + } + //#endregion + + // + //#region AddAllowTransparency ... + if ( + isAllowChanged || + isSandboxChanged || + isFrameLoadingChanged || + isReferrerPolicyChanged || + isAllowFullScreenChanged || + isAllowPaymentRequestChanged + ) { + this.addTransparency(); + } + //#endregion + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Register Handlers ... + registerInstanceHandlers() { + super.registerInstanceHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Task Handlers ... + //#endregion + + // + //#region UI Providers ... + getCssClasses(infix: string) { + return this.getValue(false).pipe( + concatMap(() => this.getArrayValue(this.cssClass)), + map((classes) => { + // + const result: string[] = []; + + // + result.push(`v-frame-${infix}`); + result.push(`v-frame-${this.id}-${infix}`); + + // + if (this.hasChild(classes)) { + result.push(...classes); + } + + // + return result; + }) + ); + } + //#endregion + + // + //#region UI Handlers ... + //#endregion + + // + //#region Actions ... + appendElement(element: HTMLElement) { + // + if ( + isNullOrUndefined(element) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.appendChild(this.frameView.nativeElement, element); + } + + loadHtml(html: string) { + // + if ( + isNullOrEmptyString(html) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.frameView.nativeElement.contentWindow.frames.document.body.insertAdjacentHTML( + 'beforebegin', + html + ); + } + + loadText(text: string) { + // + if ( + isNullOrEmptyString(text) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.frameView.nativeElement.contentWindow.frames.document.body.insertAdjacentText( + 'beforebegin', + text + ); + } + + loadElement(element: HTMLElement) { + // + if ( + isNullOrUndefined(element) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.frameView.nativeElement.contentWindow.frames.document.body.insertAdjacentElement( + 'beforebegin', + element + ); + } + + applyUrl(url: string) { + // + this.contentUrl = url; + this.detectChanges(); + } + + applyContent(content: string) { + // + this.content = content; + this.detectChanges(); + } + + fullScreen(options?: FullscreenOptions) { + this.frameView.nativeElement.requestFullscreen(options); + } + //#endregion + + // + //#region Abstracts ... + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + // tslint:disable-next-line: deprecation + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XFrameAction.Refresh: + this.detectChanges(); + break; + + // + case XFrameAction.AppendElement: + this.appendElement(model.payload); + break; + + // + case XFrameAction.ApplyContent: + this.applyContent(model.payload); + break; + + // + case XFrameAction.ApplyUrl: + this.applyUrl(model.payload); + break; + + // + case XFrameAction.LoadElement: + this.loadElement(model.payload); + break; + + // + case XFrameAction.LoadText: + this.loadText(model.payload); + break; + + // + case XFrameAction.LoadHtml: + this.loadHtml(model.payload); + break; + + // + case XFrameAction.FullScreen: + this.fullScreen(model.payload); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + //#region Preparation ... + private prepareContent(content?: string) { + this.getValue(this.content).subscribe((tContent) => { + // + this.ORIGIN = ''; + this.contentUrl = ''; + this.contentHTML = ''; + + // + content = !isNullOrEmptyString(content) ? content : tContent; + if (isNullOrEmptyString(content)) { + this.detectChanges(); + return; + } + + // + this.contentHTML = + this.managerService.securityService.toSafeHTML(content); + }); + } + + private prepareContentUrl(url?: string) { + this.getValue(this.url).subscribe((tUrl) => { + // + this.ORIGIN = ''; + this.contentUrl = ''; + this.contentHTML = ''; + + // + url = !isNullOrEmptyString(url) ? url : tUrl; + if (isNullOrEmptyString(tUrl)) { + this.detectChanges(); + return; + } + + // + // Retrieve Origin ... + const mUrl = new URL(url); + this.ORIGIN = mUrl.origin; + + // + this.contentUrl = + this.managerService.securityService.toSafeResourceURL(url); + }); + } + + private applyFrameAttribute( + attr: string, + value: string, + element: ElementRef + ) { + // + if (isNullOrUndefined(element) || isNullOrEmptyString(attr)) { + return; + } + + // + if (!isNullOrEmptyString(value)) { + // + // Set Attribute ... + this.setAttribute(attr, value, element); + } else { + // + // Remove Attribute ... + this.removeAttribute(attr, element); + } + } + + // + //#region Frame Props ... + private applyFrameProps() { + this.addTransparency(); + this.handleAllowChanged(); + this.handleSandboxChanged(); + this.handleFrameLoadingChanged(); + this.handleReferrerPolicyChanged(); + this.handleAllowFullScreenChanged(); + this.handleAllowPaymentRequestChanged(); + } + + private addTransparency() { + // + const value = true.toString(); + if ( + isNullOrEmptyString(value) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.applyFrameAttribute('allowtransparency', value, this.frameView); + this.detectChanges(); + } + + private handleAllowChanged() { + this.getArrayValue(this.allow).subscribe((allows) => { + // + if ( + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + const value = this.hasChild(allows) ? allows.join('; ') : null; + + // + this.applyFrameAttribute('allow', value, this.frameView); + this.detectChanges(); + }); + } + + private handleSandboxChanged() { + this.getValue(this.sandbox).subscribe((value) => { + // + if ( + isNullOrEmptyString(value) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.applyFrameAttribute('sandbox', value, this.frameView); + this.detectChanges(); + }); + } + + private handleFrameLoadingChanged() { + this.getValue(this.frameLoading).subscribe((value) => { + // + if ( + isNullOrEmptyString(value) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.applyFrameAttribute('loading', value, this.frameView); + this.detectChanges(); + }); + } + + private handleReferrerPolicyChanged() { + this.getArrayValue(this.referrerPolicy).subscribe((referrerPolicies) => { + // + if ( + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + const value = this.hasChild(referrerPolicies) + ? referrerPolicies.join('|') + : null; + + // + this.applyFrameAttribute('referrerpolicy', value, this.frameView); + this.detectChanges(); + }); + } + + private handleAllowFullScreenChanged() { + this.getValue(this.allowFullscreen).subscribe((allowFullscreen) => { + // + const value = allowFullscreen.toString(); + if ( + isNullOrEmptyString(value) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.applyFrameAttribute('allowfullscreen', value, this.frameView); + this.detectChanges(); + }); + } + + private handleAllowPaymentRequestChanged() { + this.getValue(this.allowPaymentRequest).subscribe((allowPaymentRequest) => { + // + const value = allowPaymentRequest.toString(); + if ( + isNullOrEmptyString(value) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.applyFrameAttribute('allowpaymentrequest', value, this.frameView); + this.detectChanges(); + }); + } + //#endregion + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-frame\x-frame.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XFrameComponent } from './x-frame.component'; +import { XCardModule } from '../x-card/x-card.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XFrameComponent], + imports: [CommonModule, XBaseComponentModule, XCardModule], + exports: [CommonModule, XBaseComponentModule, XCardModule, XFrameComponent], +}) +export class XFrameModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-frame\x-frame.typings.ts + +import { + Input, + Inject, + NgZone, + Output, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { XManagerService } from 'x-framework-services'; +import { ViewportRuler } from '@angular/cdk/scrolling'; +import { XOneOrManyType, XStandardType } from 'x-framework-core'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XFrameAction { + Refresh = 'refresh', + AppendElement = 'appendElement', + LoadHtml = 'loadHtml', + LoadText = 'loadText', + LoadElement = 'loadElement', + ApplyUrl = 'applyUrl', + ApplyContent = 'applyContent', + FullScreen = 'fullScreen', +} + +export type XFrameActionIdentifier = XFrameAction | string; + +export interface XFrameActionModel + extends XBaseActionModel {} + +// +//#region XFrame Loading ... +export enum XFrameLoading { + Eager = 'eager', + Lazy = 'lazy', +} + +export type XFrameLoadingIdentifier = XFrameLoading | string; +//#endregion + +// +//#region XFrame ReferrerPolicy ... +export enum XFrameReferrerPolicy { + NoReferrer = 'no-referrer', + NoReferrerWhenDowngrade = 'no-referrer-when-downgrade', + Origin = 'origin', + OriginWhenCrossOrigin = 'origin-when-cross-origin', + SameOrigin = 'same-origin', + StrictOriginWhenCrossOrigin = 'strict-origin-when-cross-origin', + UnsafeUrl = 'unsafe-url', +} + +export type XFrameReferrerPolicyIdentifier = XFrameReferrerPolicy | string; +//#endregion + +// +//#region Sandbox ... +export enum XFrameSandbox { + AllowForms = 'allow-forms', + AllowModals = 'allow-modals', + AllowOrientationLock = 'allow-orientation-lock', + AllowPointerLock = 'allow-pointer-lock', + AllowPopups = 'allow-popups', + AllowPopupsToEscapeSandbox = 'allow-popups-to-escape-sandbox', + AllowPresentation = 'allow-presentation', + AllowSameOrigin = 'allow-same-origin', + AllowScripts = 'allow-scripts', + AllowTopNavigation = 'allow-top-navigation', + AllowTopNavigationByUserActivation = 'allow-top-navigation-by-user-activation', +} + +export type XFrameSandboxIdentifier = XFrameSandbox | string; +//#endregion + +// +//#region Containers ... +export interface IXFrameContainer extends IXComponentCardContainer { + url: XStandardType; + content: XStandardType; + onContentLoaded: EventEmitter; + allowFullscreen: XStandardType; + actionProvider: Subject; + allowPaymentRequest: XStandardType; + allow: XStandardType>; + cssClass: XStandardType>; + sandbox: XStandardType; + frameLoading: XStandardType; + referrerPolicy: XStandardType>; +} + +@Injectable({ + providedIn: 'root', +}) +export class XFrameContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + readonly url: XStandardType; + readonly content: XStandardType; + readonly referrerPolicy: XStandardType< + XOneOrManyType + > = null; + readonly actionProvider: Subject = + new Subject(); + readonly allow: XStandardType>; + readonly allowFullscreen: XStandardType = false; + readonly cssClass: XStandardType>; + readonly allowPaymentRequest: XStandardType = false; + readonly sandbox: XStandardType = null; + readonly frameLoading: XStandardType = null; + readonly onContentLoaded: EventEmitter = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XFrameContainerComponent + extends XComponentCardContainerComponent + implements IXFrameContainer +{ + // + //#region Props ... + @Input() + allowFullscreen: XStandardType = + this.propertyProvider.allowFullscreen; + + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + allowPaymentRequest: XStandardType = + this.propertyProvider.allowPaymentRequest; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + sandbox: XStandardType = + this.propertyProvider.sandbox; + + @Input() + frameLoading: XStandardType = + this.propertyProvider.frameLoading; + + @Input() + url: XStandardType = this.propertyProvider.url; + + @Input() + content: XStandardType = this.propertyProvider.content; + + @Input() + referrerPolicy: XStandardType< + XOneOrManyType + > = this.propertyProvider.referrerPolicy; + + @Output() + onContentLoaded: EventEmitter = this.propertyProvider.onContentLoaded; + + @Input() + allow: XStandardType> = this.propertyProvider.allow; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XFrameContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXFrameContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + url: this.getValue(container ? container.url : this.url), + content: this.getValue( + container ? container.content : this.content + ), + allowFullscreen: this.getValue( + container ? container.allowFullscreen : this.allowFullscreen + ), + allowPaymentRequest: this.getValue( + container + ? container.allowPaymentRequest + : this.allowPaymentRequest + ), + allow: this.getArrayValue(container ? container.allow : this.allow), + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + sandbox: this.getValue( + container ? container.sandbox : this.sandbox + ), + frameLoading: this.getValue( + container ? container.frameLoading : this.frameLoading + ), + referrerPolicy: this.getValue( + container ? container.referrerPolicy : this.referrerPolicy + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + onContentLoaded: container + ? container.onContentLoaded + : this.onContentLoaded, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-grid\x-grid.component.html + + + + + + + + +
+ +
+
+
+ + + +
+ + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ item | json }}
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-grid\x-grid.component.scss + +ion-item-option > ion-item { + --background: transparent !important; +} + +ion-button.transparent-button { + --box-shadow: transparent !important; + --background: transparent !important; + --background-hover: transparent !important; + --background-focused: transparent !important; + --background-activated: transparent !important; +} + +ion-list, +ion-item.item, +ion-item-divider { + --background: transparent !important; +} + +ion-item-divider { + --color: var(--ion-color-dark-shade); +} + +ion-item.item.mobile { + --ion-safe-area-end: 0px; + + --padding-start: 0px; + --inner-padding-start: 0px; + + --padding-end: 0px; + --inner-padding-end: 0px; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; +} + +.x-menu { + z-index: 10; +} + +.x-check { + z-index: 1; +} + +.grid-box { + margin-top: 8px; +} + +.grid-box.mobile { + margin-top: 0px; +} + +gridster { + width: 100%; + display: flex; + direction: ltr; + position: inherit; + flex-direction: column; + background-color: transparent; + height: calc(var(--grid-height, inherit)); + + &.inCard { + position: relative; + } +} + +gridster-item { + padding: 0; + background-color: transparent; +} + +.x-grid-item-wrapper { + position: relative; + background: inherit; +} + +a[href=''], +[disabled], +.disable { + color: inherit; + cursor: default; + pointer-events: none; + text-decoration: none; +} + +.transparent { + --background: transparent !important; + background-color: transparent !important; +} + +x-card { + width: inherit; + height: inherit; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-grid\x-grid.component.ts + +import { + XGridItem, + XGridAction, + XGridActionModel, + XGridPresentType, + defaultGridOptions, + XStaggedGridOptions, + XGridContainerComponent, +} from './x-grid.typings'; +import { Subject, Subscription } from 'rxjs'; +import { nameof, cloneJson, toObservable } from 'x-framework-core'; +import { GridsterItem, GridsterComponent } from 'angular-gridster2'; +import { Component, ViewChild, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-grid', + templateUrl: './x-grid.component.html', + styleUrls: ['./x-grid.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XGridComponent extends XGridContainerComponent { + // + //#region Props ... + // + private COLS: number; + private TYPE: XGridPresentType; + private GRIDSTER: GridsterComponent; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + colItems: number[] = []; + + get preparedCols() { + return this.COLS; + } + + get preparedType() { + return this.TYPE; + } + + // + @ViewChild('gridster', { static: false }) + set gridster(v: GridsterComponent) { + // + if (!v) { + return; + } + + // + this.GRIDSTER = v; + this.prepareData(); + } + + get gridster() { + return this.GRIDSTER; + } + //#endregion + + // + //#region LifeCycles ... + afterViewInit() { + super.afterContentInit(); + + // + this.prepareData(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + const isColsChanged = changeKeys.includes(nameof('cols')); + const isTypeChanged = changeKeys.includes(nameof('type')); + const isOptionsChanged = changeKeys.includes( + nameof('options') + ); + + // + if (isColsChanged) { + this.COLS = await this.getValueAsync(this.cols); + this.detectChanges(); + } + + // + if (isTypeChanged) { + this.TYPE = await this.getValueAsync(this.type); + this.detectChanges(); + } + + // + if (isColsChanged || isTypeChanged || isOptionsChanged) { + this.prepareData(); + } + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Ui Provider Actions ... + // + //#region Type Detectors ... + isTypeLinear() { + return this.preparedType === XGridPresentType.AsLinear; + } + + isTypeGrid() { + return this.preparedType === XGridPresentType.AsGrid; + } + + isTypeFlex() { + return this.preparedType === XGridPresentType.AsFlex; + } + + isTypeStaggedGrid() { + return this.preparedType === XGridPresentType.AsStaggedGrid; + } + //#endregion + + // + isNewRow(i: number, cols: number) { + return (i + 1) % cols === 1; + } + + // + toStaggedGrid(index: number) { + return { + id: index, + data: this.items[index], + cols: 1, + rows: 1, + x: 0, + y: 0, + } as GridsterItem; + } + + // + getItemHref(item?: XGridItem) { + // + if (!item || !item.href) { + return toObservable(''); + } + + // + return this.getValue(item.href); + } + + // + getItemHide(item?: XGridItem) { + // + if (!item || !item.hide) { + return toObservable(false); + } + + // + return this.getValue(item.hide); + } + + // + getItemDisabled(item?: XGridItem) { + // + if (!item || !item.disabled) { + return toObservable(false); + } + + // + return this.getValue(item.disabled); + } + //#endregion + + // + //#region Type Detectors ... + //#endregion + + // + //#region Actions ... + async handleItemSelected(item: XGridItem) { + // + if (!this.selectableItems) { + return; + } + + // + const hided = await this.toPromise(this.getItemHide(item)); + const disabled = await this.toPromise(this.getItemDisabled(item)); + + // + if (!hided && !disabled) { + this.itemSelected.emit(item); + } + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.actionProvider.asObservable() + ).subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XGridAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + private prepareData() { + // + if (!this.preparedCols) { + return; + } + + // + const mCols = this.preparedCols < 0 ? 0 : this.preparedCols; + this.colItems = Array.from(Array(mCols).keys()); + + // + setTimeout(() => { + // + this.options = { + ...cloneJson(defaultGridOptions), + minCols: mCols, + maxCols: mCols, + } as XStaggedGridOptions; + + // + this.detectChanges(); + }, 300); + + // + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-grid\x-grid.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { GridsterModule } from 'angular-gridster2'; +import { XGridComponent } from './x-grid.component'; +import { XCardModule } from '../x-card/x-card.module'; +import { XEmptyModule } from '../x-empty/x-empty.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XGridComponent], + imports: [ + CommonModule, + XCardModule, + XEmptyModule, + GridsterModule, + XBaseComponentModule, + ], + exports: [XCardModule, XGridComponent, XEmptyModule, XBaseComponentModule], +}) +export class XGridModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-grid\x-grid.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + IXCardEmptyContainer, + XCardEmptyContainer, + XCardEmptyContainerComponent, +} from '../classes/i-x-card-empty.container'; +import { concatMap, map } from 'rxjs/operators'; +import { GridsterConfig } from 'angular-gridster2'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XStandardType, XOneOrManyType } from 'x-framework-core'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export interface XGridItem { + data: T; + hide?: XStandardType; + disabled?: XStandardType; + href?: XStandardType>; +} + +export interface XStaggedGridOptions extends Partial {} +export const defaultGridOptions: XStaggedGridOptions = { + gridType: 'scrollVertical', + compactType: 'compactLeft&Up', + disableWarnings: true, + draggable: { + enabled: true, + }, + pushItems: true, + resizable: { + enabled: true, + }, + minCols: 2, + maxCols: 2, + minRows: 1, +}; + +export enum XGridPresentType { + AsLinear, + AsGrid, + AsFlex, + AsStaggedGrid, +} + +export enum XGridAction { + Refresh = 'refresh', +} + +export type XGridActionIdentifier = XGridAction | string; + +export interface XGridActionModel + extends XBaseActionModel {} + +// +//#region Inside Card/Empty Container ... +export interface IXGridContainer extends IXCardEmptyContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + options: XStandardType; + cols: XStandardType; + type: XStandardType; + gap: string; + layout: string; + align: string; + selectableItems: XStandardType; + items: XGridItem[]; + itemTemplate: TemplateRef; + + // + itemSelected: EventEmitter>; +} + +@Injectable({ + providedIn: 'root', +}) +export class XGridContainer + extends XCardEmptyContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly options: XStandardType; + readonly cols: XStandardType; + readonly type: XStandardType; + readonly gap: string = '1px'; + readonly layout: string = 'row wrap'; + readonly align: string = 'center start'; + readonly selectableItems: XStandardType = false; + readonly items: XGridItem[]; + readonly itemTemplate: TemplateRef; + + // + readonly itemSelected: EventEmitter>; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XGridContainerComponent + extends XCardEmptyContainerComponent + implements IXGridContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + options: XStandardType = this.propertyProvider.options; + + @Input() + cols: XStandardType = this.propertyProvider.cols; + + @Input() + type: XStandardType = this.propertyProvider.type; + + @Input() + gap: string = this.propertyProvider.gap; + + @Input() + layout: string = this.propertyProvider.layout; + + @Input() + align: string = this.propertyProvider.align; + + @Input() + selectableItems: XStandardType = + this.propertyProvider.selectableItems; + + @Input() + items: XGridItem[] = this.propertyProvider.items; + + @Input() + itemTemplate: TemplateRef = this.propertyProvider.itemTemplate; + + // + @Output() + itemSelected: EventEmitter> = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XGridContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXGridContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + options: this.getValue( + container ? container.options : this.options + ), + cols: this.getValue(container ? container.cols : this.cols), + type: this.getValue(container ? container.type : this.type), + gap: this.getValue(container ? container.gap : this.gap), + layout: this.getValue(container ? container.layout : this.layout), + align: this.getValue(container ? container.align : this.align), + selectableItems: this.getValue( + container ? container.selectableItems : this.selectableItems + ), + items: this.getValue(container ? container.items : this.items), + itemTemplate: this.getValue( + container ? container.itemTemplate : this.itemTemplate + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + itemSelected: this.itemSelected, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-icon\x-icon.component.html + + +
+ + + + + + +
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-icon\x-icon.component.scss + +:host { + // + --size: 24px; + + // + display: inline-block; + position: relative; + + // + font-size: var(--size); + line-height: var(--size); + width: var(--size); + height: var(--size); + + // + min-width: var(--size); + min-height: var(--size); + + // + margin: 0 !important; + padding: 0 !important; +} + +// +$delay: 0; +$duration: 1; +$transform: scaleX(-1); + +.x-icon { + // + display: inline-block; + position: relative; + + // + margin: 0 !important; + padding: 0 !important; + + // + box-sizing: border-box !important; + + // + min-width: var(--size); + min-height: var(--size); + + &.rtl { + // + transform: $transform; + -ms-transform: $transform; + -webkit-transform: $transform; + + // + transition: transform; + -webkit-transition: transform; + + // + transition-delay: $delay; + -webkit-transition-delay: $delay; + + // + transition-duration: $duration; + -webkit-transition-duration: $duration; + } +} + +ion-icon, +ion-ripple-effect { + font-size: var(--size); + + min-width: var(--size); + min-height: var(--size); +} + +.ion-activatable { + cursor: pointer; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-icon\x-icon.component.ts + +import { + XIconAction, + XCustomIcons, + XProvidedIcons, + XIconActionModel, + XIconContainerComponent, +} from './x-icon.typings'; +import { map } from 'rxjs/operators'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-icon', + templateUrl: './x-icon.component.html', + styleUrls: ['./x-icon.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XIconComponent extends XIconContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Icon Provider Properties ... + get providedIcons() { + return XProvidedIcons; + } + + get icon() { + return this.getValue(this.name).pipe( + map((name) => { + return (this.providedIcons as any)[name.toString()] as string; + }) + ); + } + //#endregion + + // + //#region UI Providers ... + isCustomIcon() { + return this.getValue(this.name).pipe( + map((name) => { + return name && XCustomIcons.includes(name.toString()); + }) + ); + } + + customIconUrl() { + return this.getValue(this.name).pipe( + map((name) => { + // + const svgName = (this.providedIcons as any)[name.toString()] + return this.name + ? `./assets/icon/svg/${svgName}.svg` + : null; + }) + ); + } + //#endregion + + // + //#region Action Handlers ... + async handleClicked(event?: Event) { + // + const clickable = await this.toPromise(this.getValue(this.clickable)); + const disabled = this.uiDisabled; + if (!clickable || disabled) { + return; + } + + // + if (event && event.stopPropagation) { + event.stopPropagation(); + } + + // + this.clicked.emit(event); + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XIconAction.Click: + this.clicked.emit(); + break; + + // + case XIconAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-icon\x-icon.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconComponent } from './x-icon.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XIconComponent], + imports: [CommonModule, XBaseComponentModule], + exports: [XBaseComponentModule, XIconComponent], +}) +export class XIconModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-icon\x-icon.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XIconAction { + Click = 'click', + Refresh = 'refresh', +} + +export type XIconActionIdentifier = XIconAction | string; + +export interface XIconActionModel + extends XBaseActionModel {} + +export enum XIconNames { + tv = 'tv', + ol = 'ol', + ul = 'ul', + job = 'job', + add = 'add', + sad = 'sad', + web = 'web', + map = 'map', + dns = 'dns', + dvr = 'dvr', + fax = 'fax', + mic = 'mic', + mop = 'mop', + info = 'info', + lock = 'lock', + mail = 'mail', + shop = 'shop', + edit = 'edit', + done = 'done', + list = 'list', + user = 'user', + star = 'star', + sync = 'sync', + undo = 'undo', + redo = 'redo', + home = 'home', + flag = 'flag', + save = 'save', + last = 'last', + next = 'next', + copy = 'copy', + link = 'link', + bold = 'bold', + code = 'code', + crop = 'crop', + call = 'call', + tabs = 'tabs', + cast = 'cast', + wifi = 'wifi', + stop = 'stop', + play = 'play', + phone = 'phone', + reply = 'reply', + label = 'label', + liked = 'liked', + terms = 'terms', + event = 'event', + clear = 'clear', + input = 'input', + close = 'close', + users = 'users', + files = 'files', + login = 'login', + posts = 'posts', + enter = 'enter', + power = 'power', + cloud = 'cloud', + first = 'first', + touch = 'touch', + error = 'error', + print = 'print', + share = 'share', + image = 'image', + audio = 'audio', + video = 'video', + guide = 'guide', + quote = 'quote', + toast = 'toast', + cards = 'cards', + mouse = 'mouse', + album = 'album', + badge = 'badge', + radio = 'radio', + watch = 'watch', + movie = 'movie', + pause = 'pause', + unlock = 'unlock', + cancel = 'cancel', + expand = 'expand', + mobile = 'mobile', + search = 'search', + survey = 'survey', + remove = 'remove', + invite = 'invite', + logout = 'logout', + school = 'school', + server = 'server', + upload = 'upload', + locate = 'locate', + italic = 'italic', + repeat = 'repeat', + report = 'report', + policy = 'policy', + router = 'router', + shield = 'shield', + stylus = 'stylus', + tv_off = 'tv_off', + swords = 'swords', + nearby = 'nearby', + flight = 'flight', + memory = 'memory', + replay = 'replay', + visible = 'visible', + checked = 'checked', + unliked = 'unliked', + profile = 'profile', + account = 'account', + avatars = 'avatars', + archive = 'archive', + refresh = 'refresh', + unknown = 'unknown', + preview = 'preview', + heading = 'heading', + toolbar = 'toolbar', + dialogs = 'dialogs', + dialpad = 'dialpad', + vpn_key = 'vpn_key', + widgets = 'widgets', + passkey = 'passkey', + mic_off = 'mic_off', + monitor = 'monitor', + gpp_bad = 'gpp_bad', + charger = 'charger', + devices = 'devices', + capture = 'capture', + live_tv = 'live_tv', + sd_card = 'sd_card', + verified = 'verified', + collapse = 'collapse', + arrow_up = 'arrow_up', + contacts = 'contacts', + contrast = 'contrast', + calendar = 'calendar', + register = 'register', + explorer = 'explorer', + settings = 'settings', + business = 'business', + bookmark = 'bookmark', + chat_box = 'chat_box', + location = 'location', + previous = 'previous', + download = 'download', + language = 'language', + document = 'document', + logo_npm = 'logo_npm', + logo_pwa = 'logo_pwa', + logo_rss = 'logo_rss', + logo_usd = 'logo_usd', + logo_vue = 'logo_vue', + dropdown = 'dropdown', + add_call = 'add_call', + sim_card = 'sim_card', + security = 'security', + password = 'password', + podcasts = 'podcasts', + wifi_off = 'wifi_off', + no_sound = 'no_sound', + keyboard = 'keyboard', + exercise = 'exercise', + computer = 'computer', + autoplay = 'autoplay', + autostop = 'autostop', + videocam = 'videocam', + label_off = 'label_off', + swap_vert = 'swap_vert', + dashboard = 'dashboard', + checkmark = 'checkmark', + star_half = 'star_half', + thumbs_up = 'thumbs_up', + bookmarks = 'bookmarks', + bluetooth = 'bluetooth', + analytics = 'analytics', + clipboard = 'clipboard', + logo_css3 = 'logo_css3', + logo_edge = 'logo_edge', + logo_sass = 'logo_sass', + touch_app = 'touch_app', + voicemail = 'voicemail', + call_made = 'call_made', + subtitles = 'subtitles', + music_off = 'music_off', + encrypted = 'encrypted', + apartment = 'apartment', + dark_mode = 'dark_mode', + deskphone = 'deskphone', + autopause = 'autopause', + video_cam = 'video_cam', + swap_horiz = 'swap_horiz', + first_name = 'first_name', + un_checked = 'un_checked', + arrow_down = 'arrow_down', + arrow_back = 'arrow_back', + select_all = 'select_all', + management = 'management', + logo_apple = 'logo_apple', + logo_ionic = 'logo_ionic', + logo_html5 = 'logo_html5', + blockchain = 'blockchain', + logo_react = 'logo_react', + logo_skype = 'logo_skype', + logo_slack = 'logo_slack', + logo_yahoo = 'logo_yahoo', + data_table = 'data_table', + device_hub = 'device_hub', + call_split = 'call_split', + router_off = 'router_off', + light_mode = 'light_mode', + mouse_lock = 'mouse_lock', + nightlight = 'nightlight', + volume_off = 'volume_off', + volume_low = 'volume_low', + play_arrow = 'play_arrow', + video_call = 'video_call', + video_chat = 'video_chat', + video_file = 'video_file', + voice_chat = 'voice_chat', + fit_screen = 'fit_screen', + fullscreen = 'fullscreen', + rotate_left = 'rotate_left', + visible_off = 'visible_off', + friendships = 'friendships', + thumbs_down = 'thumbs_down', + chat_bubble = 'chat_bubble', + stats_chart = 'stats_chart', + logo_amazon = 'logo_amazon', + logo_chrome = 'logo_chrome', + logo_docker = 'logo_docker', + logo_github = 'logo_github', + logo_gitlab = 'logo_gitlab', + logo_google = 'logo_google', + blockchain1 = 'blockchain1', + logo_nodejs = 'logo_nodejs', + logo_paypal = 'logo_paypal', + voicemail_2 = 'voicemail_2', + devices_off = 'devices_off', + exit_to_app = 'exit_to_app', + ring_volume = 'ring_volume', + call_missed = 'call_missed', + privacy_tip = 'privacy_tip', + dual_screen = 'dual_screen', + engineering = 'engineering', + volume_high = 'volume_high', + volume_mute = 'volume_mute', + play_circle = 'play_circle', + fast_rewind = 'fast_rewind', + rotate_right = 'rotate_right', + chat_bubbles = 'chat_bubbles', + deselect_all = 'deselect_all', + arrow_dropup = 'arrow_dropup', + logo_android = 'logo_android', + logo_angular = 'logo_angular', + logo_bitcoin = 'logo_bitcoin', + logo_dropbox = 'logo_dropbox', + logo_firefox = 'logo_firefox', + logo_windows = 'logo_windows', + logo_twitter = 'logo_twitter', + logo_youtube = 'logo_youtube', + wifi_calling = 'wifi_calling', + phone_locked = 'phone_locked', + phone_missed = 'phone_missed', + call_quality = 'call_quality', + screen_share = 'screen_share', + room_service = 'room_service', + other_houses = 'other_houses', + dock_to_left = 'dock_to_left', + keyboard_off = 'keyboard_off', + cameraswitch = 'cameraswitch', + architecture = 'architecture', + fast_forward = 'fast_forward', + arrow_forward = 'arrow_forward', + accessibility = 'accessibility', + cloud_offline = 'cloud_offline', + menu_vertical = 'menu_vertical', + logo_electron = 'logo_electron', + logo_facebook = 'logo_facebook', + logo_firebase = 'logo_firebase', + logo_linkedin = 'logo_linkedin', + logo_markdown = 'logo_markdown', + logo_whatsapp = 'logo_whatsapp', + color_palette = 'color_palette', + phone_enabled = 'phone_enabled', + phone_in_talk = 'phone_in_talk', + call_received = 'call_received', + print_connect = 'print_connect', + screen_record = 'screen_record', + shield_person = 'shield_person', + verified_user = 'verified_user', + network_check = 'network_check', + encrypted_off = 'encrypted_off', + dock_to_right = 'dock_to_right', + electric_bolt = 'electric_bolt', + reset_password = 'reset_password', + customers_club = 'customers_club', + checkmark_done = 'checkmark_done', + arrow_dropdown = 'arrow_dropdown', + logo_instagram = 'logo_instagram', + logo_wordpress = 'logo_wordpress', + devices_fold_2 = 'devices_fold_2', + perm_phone_msg = 'perm_phone_msg', + phone_callback = 'phone_callback', + phone_disabled = 'phone_disabled', + settings_phone = 'settings_phone', + print_disabled = 'print_disabled', + wifi_tethering = 'wifi_tethering', + speech_to_text = 'speech_to_text', + mouse_lock_off = 'mouse_lock_off', + directions_car = 'directions_car', + local_shipping = 'local_shipping', + cast_connected = 'cast_connected', + voice_chat_off = 'voice_chat_off', + password_change = 'password_change', + menu_horizontal = 'menu_horizontal', + logo_javascript = 'logo_javascript', + phone_forwarded = 'phone_forwarded', + string_resources = 'string_resources', + chatbox_ellipses = 'chatbox_ellipses', + checkmark_circle = 'checkmark_circle', + select_all_group = 'select_all_group', + blockchain_block = 'blockchain_block', + friendship_block = 'friendship_block', + blockchain_token = 'blockchain_token', + document_scanner = 'document_scanner', + splitscreen_left = 'splitscreen_left', + close_fullscreen = 'close_fullscreen', + inverse_selection = 'inverse_selection', + friendship_reuest = 'friendship_reuest', + friendship_friend = 'friendship_friend', + friendship_reject = 'friendship_reject', + friendship_cancel = 'friendship_cancel', + friendship_accept = 'friendship_accept', + stop_screen_share = 'stop_screen_share', + missed_video_call = 'missed_video_call', + keyboard_lock_off = 'keyboard_lock_off', + video_camera_back = 'video_camera_back', + deselect_all_group = 'deselect_all_group', + logo_stackoverflow = 'logo_stackoverflow', + logo_web_component = 'logo_web_component', + blockchain_address = 'blockchain_address', + friendship_unblock = 'friendship_unblock', + friendship_unknown = 'friendship_unknown', + wifi_calling_bar_1 = 'wifi_calling_bar_1', + wifi_calling_bar_2 = 'wifi_calling_bar_2', + video_camera_front = 'video_camera_front', + picture_in_picture = 'picture_in_picture', + logo_apple_appstore = 'logo_apple_appstore', + friendship_follower = 'friendship_follower', + friendship_unfollow = 'friendship_unfollow', + signal_cellular_alt = 'signal_cellular_alt', + signal_disconnected = 'signal_disconnected', + chat_bubble_ellipses = 'chat_bubble_ellipses', + friendship_following = 'friendship_following', + call_missed_outgoing = 'call_missed_outgoing', + audio_video_receiver = 'audio_video_receiver', + admin_panel_settings = 'admin_panel_settings', + checkmark_done_circle = 'checkmark_done_circle', + logo_google_playstore = 'logo_google_playstore', + video_camera_back_add = 'video_camera_back_add', + blockchain_transaction = 'blockchain_transaction', + display_external_input = 'display_external_input', + picture_in_picture_off = 'picture_in_picture_off', + video_camera_front_off = 'video_camera_front_off', + phone_bluetooth_speaker = 'phone_bluetooth_speaker', + desktop_access_disabled = 'desktop_access_disabled', + blockchain_smart_contract = 'blockchain_smart_contract', + blockchain_contract_address = 'blockchain_contract_address', +} + +export const defaultIcon = ''; + +export type XIconName = [keyof typeof XIconNames]; + +export type XIconIdentifier = XIconName | XIconNames | string; + +export type XIconsIndex = { + [key in keyof typeof XIconNames]?: string; +}; + +// +// Contains Path of Custom icons File Names ... +export const XProvidedIcons = { + tv: 'tv', + ol: 'ol', + ul: 'ul', + add: 'add', + dns: 'dns', + dvr: 'dvr', + fax: 'fax', + mic: 'mic', + mop: 'mop', + sad: 'sad', + map: 'map', + mail: 'mail', + undo: 'undo', + redo: 'redo', + home: 'home', + flag: 'flag', + save: 'save', + sync: 'sync', + play: 'play', + star: 'star', + cast: 'cast', + wifi: 'wifi', + call: 'call', + tabs: 'tabs', + stop: 'stop', + code: 'code', + bold: 'bold', + crop: 'crop', + web: 'globe', + link: 'link', + last: 'last', + next: 'next', + copy: 'copy', + phone: 'call', + guide: 'help', + reply: 'reply', + label: 'label', + visible: 'eye', + liked: 'heart', + mobile: 'call', + shop: 'basket', + event: 'pulse', + clear: 'close', + edit: 'create', + close: 'close', + enter: 'enter', + power: 'power', + pause: 'pause', + cloud: 'cloud', + toast: 'toast', + cards: 'cards', + mouse: 'mouse', + album: 'album', + badge: 'badge', + radio: 'radio', + watch: 'watch', + movie: 'movie', + image: 'image', + quote: 'quote', + touch: 'touch', + error: 'error', + first: 'first', + print: 'print', + cancel: 'close', + remove: 'trash', + terms: 'hammer', + input: 'pencil', + login: 'log-in', + posts: 'albums', + files: 'folder', + italic: 'italic', + locate: 'locate', + search: 'search', + job: 'megaphone', + list: 'list-box', + rewind: 'rewind', + school: 'school', + server: 'server', + repeat: 'repeat', + report: 'report', + policy: 'policy', + router: 'router', + shield: 'shield', + stylus: 'stylus', + tv_off: 'tv_off', + swords: 'swords', + nearby: 'nearby', + flight: 'flight', + memory: 'memory', + replay: 'replay', + unknown: 'attach', + video: 'videocam', + done: 'checkmark', + logout: 'log-out', + avatars: 'images', + account: 'person', + invite: 'contract', + archive: 'archive', + toolbar: 'toolbar', + dialogs: 'dialogs', + dialpad: 'dialpad', + vpn_key: 'vpn_key', + widgets: 'widgets', + passkey: 'passkey', + mic_off: 'mic_off', + monitor: 'monitor', + gpp_bad: 'gpp_bad', + charger: 'charger', + devices: 'devices', + sd_card: 'sd_card', + capture: 'capture', + live_tv: 'live_tv', + preview: 'preview', + heading: 'heading', + refresh: 'refresh', + lock: 'lock-closed', + unlock: 'lock-open', + un_checked: 'close', + survey: 'newspaper', + chat_box: 'chatbox', + verified: 'verified', + arrow_up: 'arrow-up', + business: 'business', + contrast: 'contrast', + first_name: 'person', + calendar: 'calendar', + checked: 'checkmark', + dashboard: 'journal', + explorer: 'browsers', + settings: 'settings', + bookmark: 'bookmark', + dropdown: 'dropdown', + add_call: 'add_call', + sim_card: 'sim_card', + security: 'security', + password: 'password', + podcasts: 'podcasts', + wifi_off: 'wifi_off', + no_sound: 'no_sound', + keyboard: 'keyboard', + exercise: 'exercise', + computer: 'computer', + videocam: 'videocam', + autoplay: 'autoplay', + autostop: 'autostop', + logo_npm: 'logo-npm', + logo_pwa: 'logo-pwa', + logo_rss: 'logo-rss', + logo_usd: 'logo-usd', + logo_vue: 'logo-vue', + previous: 'previous', + location: 'location', + language: 'language', + document: 'document', + expand: 'expand_more', + friendships: 'people', + user: 'person-circle', + video_cam: 'videocam', + share: 'share-social', + deselect_all: 'close', + label_off: 'label_off', + visible_off: 'eye-off', + users: 'people-circle', + checkmark: 'checkmark', + star_half: 'star-half', + bookmarks: 'bookmarks', + thumbs_up: 'thumbs-up', + bluetooth: 'bluetooth', + logo_css3: 'logo-css3', + logo_edge: 'logo-edge', + logo_sass: 'logo-sass', + voicemail: 'voicemail', + call_made: 'call_made', + touch_app: 'touch_app', + subtitles: 'subtitles', + music_off: 'music_off', + encrypted: 'encrypted', + apartment: 'apartment', + dark_mode: 'dark_mode', + deskphone: 'deskphone', + autopause: 'autopause', + upload: 'cloud-upload', + audio: 'musical-notes', + clipboard: 'clipboard', + analytics: 'analytics', + select_all: 'checkbox', + management: 'construct', + unliked: 'heart-outline', + arrow_down: 'arrow-down', + arrow_back: 'arrow-back', + profile: 'person-circle', + register: 'finger-print', + customers_club: 'trophy', + volume_off: 'volume-off', + volume_low: 'volume-low', + logo_html5: 'logo-html5', + logo_ionic: 'logo-ionic', + logo_apple: 'logo-apple', + logo_react: 'logo-react', + logo_skype: 'logo-skype', + logo_slack: 'logo-slack', + logo_yahoo: 'logo-yahoo', + blockchain: 'blockchain', + data_table: 'data_table', + device_hub: 'device_hub', + call_split: 'call_split', + router_off: 'router_off', + light_mode: 'light_mode', + mouse_lock: 'mouse_lock', + nightlight: 'nightlight', + play_arrow: 'play_arrow', + video_call: 'video_call', + video_chat: 'video_chat', + video_file: 'video_file', + voice_chat: 'voice_chat', + fit_screen: 'fit_screen', + fullscreen: 'fullscreen', + friendship_reuest: 'add', + arrow_dropup: 'caret-up', + collapse: 'chevron_right', + contacts: 'people-circle', + friendship_block: 'block', + chat_bubble: 'chatbubble', + info: 'information-circle', + rotate_left: 'rotate_left', + swap_vert: 'swap-vertical', + friendship_friend: 'forum', + download: 'cloud-download', + thumbs_down: 'thumbs-down', + stats_chart: 'stats-chart', + volume_high: 'volume-high', + volume_mute: 'volume-mute', + play_circle: 'play-circle', + logo_amazon: 'logo-amazon', + logo_chrome: 'logo-chrome', + logo_docker: 'logo-docker', + logo_github: 'logo-github', + logo_gitlab: 'logo-gitlab', + logo_google: 'logo-google', + logo_paypal: 'logo-paypal', + blockchain1: 'blockchain1', + logo_nodejs: 'logo-nodejs', + voicemail_2: 'voicemail_2', + devices_off: 'devices_off', + exit_to_app: 'exit_to_app', + ring_volume: 'ring_volume', + call_missed: 'call_missed', + privacy_tip: 'privacy_tip', + dual_screen: 'dual_screen', + engineering: 'engineering', + fast_rewind: 'fast_rewind', + reset_password: 'construct', + chat_bubbles: 'chatbubbles', + friendship_reject: 'remove', + friendship_follower: 'chat', + rotate_right: 'rotate_right', + logo_android: 'logo-android', + logo_angular: 'logo-angular', + logo_bitcoin: 'logo-bitcoin', + logo_dropbox: 'logo-dropbox', + logo_firefox: 'logo-firefox', + logo_twitter: 'logo-twitter', + logo_youtube: 'logo-youtube', + logo_windows: 'logo-windows', + wifi_calling: 'wifi_calling', + phone_locked: 'phone_locked', + phone_missed: 'phone_missed', + call_quality: 'call_quality', + screen_share: 'screen_share', + room_service: 'room_service', + other_houses: 'other_houses', + dock_to_left: 'dock_to_left', + keyboard_off: 'keyboard_off', + cameraswitch: 'cameraswitch', + architecture: 'architecture', + fast_forward: 'fast_forward', + arrow_dropdown: 'caret-down', + string_resources: 'pricetags', + swap_horiz: 'swap-horizontal', + arrow_forward: 'arrow-forward', + password_change: 'lock-closed', + cloud_offline: 'cloud-offline', + accessibility: 'accessibility', + logo_electron: 'logo-electron', + logo_facebook: 'logo-facebook', + logo_firebase: 'logo-firebase', + logo_linkedin: 'logo-linkedin', + logo_markdown: 'logo-markdown', + logo_whatsapp: 'logo-whatsapp', + phone_enabled: 'phone_enabled', + phone_in_talk: 'phone_in_talk', + call_received: 'call_received', + print_connect: 'print_connect', + screen_record: 'screen_record', + shield_person: 'shield_person', + verified_user: 'verified_user', + network_check: 'network_check', + encrypted_off: 'encrypted_off', + dock_to_right: 'dock_to_right', + electric_bolt: 'electric_bolt', + color_palette: 'color-palette', + friendship_accept: 'checkmark', + friendship_following: 'comment', + checkmark_done: 'checkmark-done', + logo_instagram: 'logo-instagram', + logo_wordpress: 'logo-wordpress', + devices_fold_2: 'devices_fold_2', + perm_phone_msg: 'perm_phone_msg', + phone_callback: 'phone_callback', + phone_disabled: 'phone_disabled', + settings_phone: 'settings_phone', + print_disabled: 'print_disabled', + wifi_tethering: 'wifi_tethering', + speech_to_text: 'speech_to_text', + mouse_lock_off: 'mouse_lock_off', + directions_car: 'directions_car', + local_shipping: 'local_shipping', + cast_connected: 'cast_connected', + voice_chat_off: 'voice_chat_off', + logo_javascript: 'logo-javascript', + phone_forwarded: 'phone_forwarded', + menu_vertical: 'ellipsis-vertical', + chatbox_ellipses: 'chatbox-ellipses', + checkmark_circle: 'checkmark-circle', + blockchain_block: 'blockchain-block', + blockchain_token: 'blockchain-token', + document_scanner: 'document_scanner', + splitscreen_left: 'splitscreen_left', + close_fullscreen: 'close_fullscreen', + friendship_unblock: 'thumbs_up_down', + friendship_unfollow: 'highlight_off', + select_all_group: 'select_all_group', + stop_screen_share: 'stop_screen_share', + missed_video_call: 'missed_video_call', + keyboard_lock_off: 'keyboard_lock_off', + video_camera_back: 'video_camera_back', + menu_horizontal: 'ellipsis-horizontal', + logo_stackoverflow: 'logo-stackoverflow', + blockchain_address: 'blockchain-address', + logo_web_component: 'logo-web-component', + wifi_calling_bar_1: 'wifi_calling_bar_1', + wifi_calling_bar_2: 'wifi_calling_bar_2', + video_camera_front: 'video_camera_front', + picture_in_picture: 'picture_in_picture', + deselect_all_group: 'deselect_all_group', + logo_apple_appstore: 'logo-apple-appstore', + signal_cellular_alt: 'signal_cellular_alt', + signal_disconnected: 'signal_disconnected', + friendship_cancel: 'remove_circle_outline', + inverse_selection: 'remove-circle-outline', + chat_bubble_ellipses: 'chatbubble-ellipses', + call_missed_outgoing: 'call_missed_outgoing', + audio_video_receiver: 'audio_video_receiver', + admin_panel_settings: 'admin_panel_settings', + friendship_unknown: 'radio_button_unchecked', + checkmark_done_circle: 'checkmark-done-circle', + video_camera_back_add: 'video_camera_back_add', + logo_google_playstore: 'logo-google-playstore', + blockchain_transaction: 'blockchain-transaction', + video_camera_front_off: 'video_camera_front_off', + display_external_input: 'display_external_input', + picture_in_picture_off: 'picture_in_picture_off', + phone_bluetooth_speaker: 'phone_bluetooth_speaker', + desktop_access_disabled: 'desktop_access_disabled', + blockchain_smart_contract: 'blockchain-smart-contract', + blockchain_contract_address: 'blockchain-contract-address', +}; + +export const XCustomIcons: string[] = [ + XIconNames.ol, + XIconNames.ul, + XIconNames.tv, + XIconNames.dns, + XIconNames.dvr, + XIconNames.fax, + XIconNames.mic, + XIconNames.mop, + XIconNames.call, + XIconNames.tabs, + XIconNames.cast, + XIconNames.wifi, + XIconNames.stop, + XIconNames.cast, + XIconNames.link, + XIconNames.bold, + XIconNames.code, + XIconNames.undo, + XIconNames.redo, + XIconNames.next, + XIconNames.last, + XIconNames.list, + XIconNames.toast, + XIconNames.cards, + XIconNames.mouse, + XIconNames.album, + XIconNames.badge, + XIconNames.radio, + XIconNames.watch, + XIconNames.guide, + XIconNames.image, + XIconNames.quote, + XIconNames.touch, + XIconNames.error, + XIconNames.first, + XIconNames.reply, + XIconNames.label, + XIconNames.movie, + XIconNames.repeat, + XIconNames.report, + XIconNames.policy, + XIconNames.router, + XIconNames.shield, + XIconNames.stylus, + XIconNames.tv_off, + XIconNames.swords, + XIconNames.nearby, + XIconNames.flight, + XIconNames.memory, + XIconNames.replay, + XIconNames.expand, + XIconNames.italic, + XIconNames.toolbar, + XIconNames.devices, + XIconNames.dialogs, + XIconNames.dialpad, + XIconNames.vpn_key, + XIconNames.widgets, + XIconNames.passkey, + XIconNames.mic_off, + XIconNames.monitor, + XIconNames.gpp_bad, + XIconNames.charger, + XIconNames.devices, + XIconNames.sd_card, + XIconNames.capture, + XIconNames.live_tv, + XIconNames.monitor, + XIconNames.preview, + XIconNames.heading, + XIconNames.dropdown, + XIconNames.add_call, + XIconNames.sim_card, + XIconNames.security, + XIconNames.password, + XIconNames.podcasts, + XIconNames.wifi_off, + XIconNames.no_sound, + XIconNames.keyboard, + XIconNames.exercise, + XIconNames.computer, + XIconNames.videocam, + XIconNames.autoplay, + XIconNames.autostop, + XIconNames.previous, + XIconNames.collapse, + XIconNames.verified, + XIconNames.touch_app, + XIconNames.voicemail, + XIconNames.call_made, + XIconNames.subtitles, + XIconNames.music_off, + XIconNames.encrypted, + XIconNames.apartment, + XIconNames.dark_mode, + XIconNames.deskphone, + XIconNames.autopause, + XIconNames.label_off, + XIconNames.data_table, + XIconNames.device_hub, + XIconNames.call_split, + XIconNames.router_off, + XIconNames.light_mode, + XIconNames.mouse_lock, + XIconNames.nightlight, + XIconNames.play_arrow, + XIconNames.video_call, + XIconNames.video_chat, + XIconNames.video_file, + XIconNames.voice_chat, + XIconNames.fit_screen, + XIconNames.fullscreen, + XIconNames.blockchain, + XIconNames.voicemail_2, + XIconNames.devices_off, + XIconNames.exit_to_app, + XIconNames.ring_volume, + XIconNames.call_missed, + XIconNames.privacy_tip, + XIconNames.dual_screen, + XIconNames.engineering, + XIconNames.fast_rewind, + XIconNames.blockchain1, + XIconNames.logo_docker, + XIconNames.logo_gitlab, + XIconNames.logo_paypal, + XIconNames.rotate_left, + XIconNames.wifi_calling, + XIconNames.phone_locked, + XIconNames.phone_missed, + XIconNames.call_quality, + XIconNames.screen_share, + XIconNames.room_service, + XIconNames.other_houses, + XIconNames.dock_to_left, + XIconNames.keyboard_off, + XIconNames.cameraswitch, + XIconNames.architecture, + XIconNames.rotate_right, + XIconNames.fast_forward, + XIconNames.phone_enabled, + XIconNames.phone_in_talk, + XIconNames.call_received, + XIconNames.print_connect, + XIconNames.screen_record, + XIconNames.shield_person, + XIconNames.verified_user, + XIconNames.network_check, + XIconNames.encrypted_off, + XIconNames.dock_to_right, + XIconNames.electric_bolt, + XIconNames.devices_fold_2, + XIconNames.perm_phone_msg, + XIconNames.phone_callback, + XIconNames.phone_disabled, + XIconNames.settings_phone, + XIconNames.print_disabled, + XIconNames.wifi_tethering, + XIconNames.speech_to_text, + XIconNames.mouse_lock_off, + XIconNames.directions_car, + XIconNames.local_shipping, + XIconNames.cast_connected, + XIconNames.voice_chat_off, + XIconNames.phone_forwarded, + XIconNames.document_scanner, + XIconNames.splitscreen_left, + XIconNames.close_fullscreen, + XIconNames.blockchain_block, + XIconNames.blockchain_token, + XIconNames.select_all_group, + XIconNames.friendship_block, + XIconNames.stop_screen_share, + XIconNames.missed_video_call, + XIconNames.keyboard_lock_off, + XIconNames.video_camera_back, + XIconNames.friendship_friend, + XIconNames.friendship_reject, + XIconNames.friendship_cancel, + XIconNames.wifi_calling_bar_1, + XIconNames.wifi_calling_bar_2, + XIconNames.video_camera_front, + XIconNames.picture_in_picture, + XIconNames.blockchain_address, + XIconNames.deselect_all_group, + XIconNames.friendship_unblock, + XIconNames.friendship_unknown, + XIconNames.signal_cellular_alt, + XIconNames.signal_disconnected, + XIconNames.friendship_follower, + XIconNames.friendship_unfollow, + XIconNames.call_missed_outgoing, + XIconNames.audio_video_receiver, + XIconNames.admin_panel_settings, + XIconNames.friendship_following, + XIconNames.video_camera_back_add, + XIconNames.video_camera_front_off, + XIconNames.display_external_input, + XIconNames.picture_in_picture_off, + XIconNames.blockchain_transaction, + XIconNames.phone_bluetooth_speaker, + XIconNames.desktop_access_disabled, + XIconNames.blockchain_smart_contract, + XIconNames.blockchain_contract_address, +]; + +export type XIconSlot = 'start' | 'end'; + +// +//#region Container ... +export interface IXIconContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + slot: XStandardType; + name: XStandardType; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + applyDirection: XStandardType; + + // + selectable: XStandardType; + clickable: XStandardType; + clicked: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XIconContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly slot: XStandardType = 'start'; + readonly name: XStandardType; + + // + readonly color: XStandardType; + readonly foregroundColor: XStandardType = true; + + readonly selectable: XStandardType = false; + readonly clickable: XStandardType = false; + readonly applyDirection: XStandardType = false; + + // + readonly clicked = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XIconContainerComponent + extends XBaseContainerComponent + implements IXIconContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + slot: XStandardType = this.propertyProvider.slot; + + @Input() + name: XStandardType = this.propertyProvider.name; + + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + @Input() + selectable: XStandardType = this.propertyProvider.selectable; + + @Input() + clickable: XStandardType = this.propertyProvider.clickable; + + @Input() + applyDirection: XStandardType = this.propertyProvider.applyDirection; + + @Output() + clicked: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XIconContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXIconContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + slot: this.getValue(container ? container.slot : this.slot), + name: this.getValue(container ? container.name : this.name), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + selectable: this.getValue( + container ? container.selectable : this.selectable + ), + clickable: this.getValue( + container ? container.clickable : this.clickable + ), + applyDirection: this.getValue( + container ? container.applyDirection : this.applyDirection + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + clicked: this.clicked, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-ide\x-ide.component.html + + + + + + + + +
+ +
+ + + +
+
+ + + +
+ + + + + + + +
+ {{ tab.name }} +
+
+
+
+ + +
+
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-ide\x-ide.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-ide\x-ide.component.ts + +import { + Component, + ViewChild, + ElementRef, + HostListener, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XIDEMode, + XIDETheme, + XIDEAction, + XIDEActionModel, + XIDEMinFontSize, + XIDEMaxFontSize, + XIDEActionBarItem, + XIDETabDescriptor, + XIDEModeIdentifier, + XIDEDefaultTabName, + XIDEThemeIdentifier, + XIDEContainerComponent, +} from './x-ide.typings'; +import { + nameof, + toArray, + isNumber, + toBoolean, + isBoolean, + countChild, + XStandardType, + toNormalString, + XOneOrManyType, + isNullOrUndefined, + isNullOrEmptyString, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XTabAlign, + XTabsAction, + XTabChangeEvent, + XTabsActionModel, + XTabsHeaderPosition, +} from '../x-tabs/x-tabs.typings'; +import * as ace from 'ace-builds'; +import { + XActionBarItem, + XActionBarItemSlot, + XActionBarActionFiredModel, +} from '../x-action-bar/x-action-bar.typings'; +import { concatMap, map } from 'rxjs/operators'; +import 'ace-builds/src-noconflict/ext-beautify'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import 'ace-builds/src-noconflict/ext-language_tools'; +import { forkJoin, Subject, Subscription } from 'rxjs'; +import { XPopoverOption } from '../dialogs/x-popover/x-popover.typings'; + +// +//#region Themes ... +import 'ace-builds/src-noconflict/theme-ambiance'; +import 'ace-builds/src-noconflict/theme-chaos'; +import 'ace-builds/src-noconflict/theme-chrome'; +import 'ace-builds/src-noconflict/theme-cloud9_day'; +import 'ace-builds/src-noconflict/theme-cloud9_night'; +import 'ace-builds/src-noconflict/theme-cloud9_night_low_color'; +import 'ace-builds/src-noconflict/theme-clouds'; +import 'ace-builds/src-noconflict/theme-clouds_midnight'; +import 'ace-builds/src-noconflict/theme-cobalt'; +import 'ace-builds/src-noconflict/theme-crimson_editor'; +import 'ace-builds/src-noconflict/theme-dawn'; +import 'ace-builds/src-noconflict/theme-dracula'; +import 'ace-builds/src-noconflict/theme-dreamweaver'; +import 'ace-builds/src-noconflict/theme-eclipse'; +import 'ace-builds/src-noconflict/theme-github'; +import 'ace-builds/src-noconflict/theme-gob'; +import 'ace-builds/src-noconflict/theme-gruvbox'; +import 'ace-builds/src-noconflict/theme-gruvbox_dark_hard'; +import 'ace-builds/src-noconflict/theme-gruvbox_light_hard'; +import 'ace-builds/src-noconflict/theme-idle_fingers'; +import 'ace-builds/src-noconflict/theme-iplastic'; +import 'ace-builds/src-noconflict/theme-katzenmilch'; +import 'ace-builds/src-noconflict/theme-kr_theme'; +import 'ace-builds/src-noconflict/theme-kuroir'; +import 'ace-builds/src-noconflict/theme-merbivore'; +import 'ace-builds/src-noconflict/theme-merbivore_soft'; +import 'ace-builds/src-noconflict/theme-mono_industrial'; +import 'ace-builds/src-noconflict/theme-monokai'; +import 'ace-builds/src-noconflict/theme-nord_dark'; +import 'ace-builds/src-noconflict/theme-one_dark'; +import 'ace-builds/src-noconflict/theme-pastel_on_dark'; +import 'ace-builds/src-noconflict/theme-solarized_dark'; +import 'ace-builds/src-noconflict/theme-solarized_light'; +import 'ace-builds/src-noconflict/theme-sqlserver'; +import 'ace-builds/src-noconflict/theme-terminal'; +import 'ace-builds/src-noconflict/theme-textmate'; +import 'ace-builds/src-noconflict/theme-tomorrow'; +import 'ace-builds/src-noconflict/theme-tomorrow_night'; +import 'ace-builds/src-noconflict/theme-tomorrow_night_blue'; +import 'ace-builds/src-noconflict/theme-tomorrow_night_bright'; +import 'ace-builds/src-noconflict/theme-tomorrow_night_eighties'; +import 'ace-builds/src-noconflict/theme-twilight'; +import 'ace-builds/src-noconflict/theme-vibrant_ink'; +import 'ace-builds/src-noconflict/theme-xcode'; +//#endregion + +// +//#region Modes ... +import 'ace-builds/src-noconflict/mode-abap'; +import 'ace-builds/src-noconflict/mode-abc'; +import 'ace-builds/src-noconflict/mode-actionscript'; +import 'ace-builds/src-noconflict/mode-ada'; +import 'ace-builds/src-noconflict/mode-alda'; +import 'ace-builds/src-noconflict/mode-apache_conf'; +import 'ace-builds/src-noconflict/mode-apex'; +import 'ace-builds/src-noconflict/mode-applescript'; +import 'ace-builds/src-noconflict/mode-aql'; +import 'ace-builds/src-noconflict/mode-asciidoc'; +import 'ace-builds/src-noconflict/mode-asl'; +import 'ace-builds/src-noconflict/mode-assembly_x86'; +import 'ace-builds/src-noconflict/mode-autohotkey'; +import 'ace-builds/src-noconflict/mode-batchfile'; +import 'ace-builds/src-noconflict/mode-c9search'; +import 'ace-builds/src-noconflict/mode-c_cpp'; +import 'ace-builds/src-noconflict/mode-cirru'; +import 'ace-builds/src-noconflict/mode-clojure'; +import 'ace-builds/src-noconflict/mode-cobol'; +import 'ace-builds/src-noconflict/mode-coffee'; +import 'ace-builds/src-noconflict/mode-coldfusion'; +import 'ace-builds/src-noconflict/mode-crystal'; +import 'ace-builds/src-noconflict/mode-csharp'; +import 'ace-builds/src-noconflict/mode-csound_document'; +import 'ace-builds/src-noconflict/mode-csound_orchestra'; +import 'ace-builds/src-noconflict/mode-csound_score'; +import 'ace-builds/src-noconflict/mode-csp'; +import 'ace-builds/src-noconflict/mode-css'; +import 'ace-builds/src-noconflict/mode-curly'; +import 'ace-builds/src-noconflict/mode-d'; +import 'ace-builds/src-noconflict/mode-dart'; +import 'ace-builds/src-noconflict/mode-diff'; +import 'ace-builds/src-noconflict/mode-django'; +import 'ace-builds/src-noconflict/mode-dockerfile'; +import 'ace-builds/src-noconflict/mode-dot'; +import 'ace-builds/src-noconflict/mode-drools'; +import 'ace-builds/src-noconflict/mode-edifact'; +import 'ace-builds/src-noconflict/mode-eiffel'; +import 'ace-builds/src-noconflict/mode-ejs'; +import 'ace-builds/src-noconflict/mode-elixir'; +import 'ace-builds/src-noconflict/mode-elm'; +import 'ace-builds/src-noconflict/mode-erlang'; +import 'ace-builds/src-noconflict/mode-forth'; +import 'ace-builds/src-noconflict/mode-fortran'; +import 'ace-builds/src-noconflict/mode-fsharp'; +import 'ace-builds/src-noconflict/mode-fsl'; +import 'ace-builds/src-noconflict/mode-ftl'; +import 'ace-builds/src-noconflict/mode-gcode'; +import 'ace-builds/src-noconflict/mode-gherkin'; +import 'ace-builds/src-noconflict/mode-gitignore'; +import 'ace-builds/src-noconflict/mode-glsl'; +import 'ace-builds/src-noconflict/mode-gobstones'; +import 'ace-builds/src-noconflict/mode-golang'; +import 'ace-builds/src-noconflict/mode-graphqlschema'; +import 'ace-builds/src-noconflict/mode-groovy'; +import 'ace-builds/src-noconflict/mode-haml'; +import 'ace-builds/src-noconflict/mode-handlebars'; +import 'ace-builds/src-noconflict/mode-haskell'; +import 'ace-builds/src-noconflict/mode-haskell_cabal'; +import 'ace-builds/src-noconflict/mode-haxe'; +import 'ace-builds/src-noconflict/mode-hjson'; +import 'ace-builds/src-noconflict/mode-html'; +import 'ace-builds/src-noconflict/mode-html_elixir'; +import 'ace-builds/src-noconflict/mode-html_ruby'; +import 'ace-builds/src-noconflict/mode-ini'; +import 'ace-builds/src-noconflict/mode-io'; +import 'ace-builds/src-noconflict/mode-ion'; +import 'ace-builds/src-noconflict/mode-jack'; +import 'ace-builds/src-noconflict/mode-jade'; +import 'ace-builds/src-noconflict/mode-java'; +import 'ace-builds/src-noconflict/mode-javascript'; +import 'ace-builds/src-noconflict/mode-json'; +import 'ace-builds/src-noconflict/mode-json5'; +import 'ace-builds/src-noconflict/mode-jsoniq'; +import 'ace-builds/src-noconflict/mode-jsp'; +import 'ace-builds/src-noconflict/mode-jssm'; +import 'ace-builds/src-noconflict/mode-jsx'; +import 'ace-builds/src-noconflict/mode-julia'; +import 'ace-builds/src-noconflict/mode-kotlin'; +import 'ace-builds/src-noconflict/mode-latex'; +import 'ace-builds/src-noconflict/mode-latte'; +import 'ace-builds/src-noconflict/mode-less'; +import 'ace-builds/src-noconflict/mode-liquid'; +import 'ace-builds/src-noconflict/mode-lisp'; +import 'ace-builds/src-noconflict/mode-livescript'; +import 'ace-builds/src-noconflict/mode-logiql'; +import 'ace-builds/src-noconflict/mode-logtalk'; +import 'ace-builds/src-noconflict/mode-lsl'; +import 'ace-builds/src-noconflict/mode-lua'; +import 'ace-builds/src-noconflict/mode-luapage'; +import 'ace-builds/src-noconflict/mode-lucene'; +import 'ace-builds/src-noconflict/mode-makefile'; +import 'ace-builds/src-noconflict/mode-markdown'; +import 'ace-builds/src-noconflict/mode-mask'; +import 'ace-builds/src-noconflict/mode-matlab'; +import 'ace-builds/src-noconflict/mode-maze'; +import 'ace-builds/src-noconflict/mode-mediawiki'; +import 'ace-builds/src-noconflict/mode-mel'; +import 'ace-builds/src-noconflict/mode-mips'; +import 'ace-builds/src-noconflict/mode-mixal'; +import 'ace-builds/src-noconflict/mode-mushcode'; +import 'ace-builds/src-noconflict/mode-mysql'; +import 'ace-builds/src-noconflict/mode-nginx'; +import 'ace-builds/src-noconflict/mode-nim'; +import 'ace-builds/src-noconflict/mode-nix'; +import 'ace-builds/src-noconflict/mode-nsis'; +import 'ace-builds/src-noconflict/mode-nunjucks'; +import 'ace-builds/src-noconflict/mode-objectivec'; +import 'ace-builds/src-noconflict/mode-ocaml'; +import 'ace-builds/src-noconflict/mode-partiql'; +import 'ace-builds/src-noconflict/mode-pascal'; +import 'ace-builds/src-noconflict/mode-perl'; +import 'ace-builds/src-noconflict/mode-pgsql'; +import 'ace-builds/src-noconflict/mode-php'; +import 'ace-builds/src-noconflict/mode-php_laravel_blade'; +import 'ace-builds/src-noconflict/mode-pig'; +import 'ace-builds/src-noconflict/mode-plain_text'; +import 'ace-builds/src-noconflict/mode-powershell'; +import 'ace-builds/src-noconflict/mode-praat'; +import 'ace-builds/src-noconflict/mode-prisma'; +import 'ace-builds/src-noconflict/mode-prolog'; +import 'ace-builds/src-noconflict/mode-properties'; +import 'ace-builds/src-noconflict/mode-protobuf'; +import 'ace-builds/src-noconflict/mode-puppet'; +import 'ace-builds/src-noconflict/mode-python'; +import 'ace-builds/src-noconflict/mode-qml'; +import 'ace-builds/src-noconflict/mode-r'; +import 'ace-builds/src-noconflict/mode-raku'; +import 'ace-builds/src-noconflict/mode-razor'; +import 'ace-builds/src-noconflict/mode-rdoc'; +import 'ace-builds/src-noconflict/mode-red'; +import 'ace-builds/src-noconflict/mode-redshift'; +import 'ace-builds/src-noconflict/mode-rhtml'; +import 'ace-builds/src-noconflict/mode-robot'; +import 'ace-builds/src-noconflict/mode-rst'; +import 'ace-builds/src-noconflict/mode-ruby'; +import 'ace-builds/src-noconflict/mode-rust'; +import 'ace-builds/src-noconflict/mode-sac'; +import 'ace-builds/src-noconflict/mode-sass'; +import 'ace-builds/src-noconflict/mode-scad'; +import 'ace-builds/src-noconflict/mode-scala'; +import 'ace-builds/src-noconflict/mode-scheme'; +import 'ace-builds/src-noconflict/mode-scrypt'; +import 'ace-builds/src-noconflict/mode-scss'; +import 'ace-builds/src-noconflict/mode-sh'; +import 'ace-builds/src-noconflict/mode-sjs'; +import 'ace-builds/src-noconflict/mode-slim'; +import 'ace-builds/src-noconflict/mode-smarty'; +import 'ace-builds/src-noconflict/mode-smithy'; +import 'ace-builds/src-noconflict/mode-snippets'; +import 'ace-builds/src-noconflict/mode-soy_template'; +import 'ace-builds/src-noconflict/mode-space'; +import 'ace-builds/src-noconflict/mode-sparql'; +import 'ace-builds/src-noconflict/mode-sql'; +import 'ace-builds/src-noconflict/mode-sqlserver'; +import 'ace-builds/src-noconflict/mode-stylus'; +import 'ace-builds/src-noconflict/mode-svg'; +import 'ace-builds/src-noconflict/mode-swift'; +import 'ace-builds/src-noconflict/mode-tcl'; +import 'ace-builds/src-noconflict/mode-terraform'; +import 'ace-builds/src-noconflict/mode-tex'; +import 'ace-builds/src-noconflict/mode-text'; +import 'ace-builds/src-noconflict/mode-textile'; +import 'ace-builds/src-noconflict/mode-toml'; +import 'ace-builds/src-noconflict/mode-tsx'; +import 'ace-builds/src-noconflict/mode-turtle'; +import 'ace-builds/src-noconflict/mode-twig'; +import 'ace-builds/src-noconflict/mode-typescript'; +import 'ace-builds/src-noconflict/mode-vala'; +import 'ace-builds/src-noconflict/mode-vbscript'; +import 'ace-builds/src-noconflict/mode-velocity'; +import 'ace-builds/src-noconflict/mode-verilog'; +import 'ace-builds/src-noconflict/mode-vhdl'; +import 'ace-builds/src-noconflict/mode-visualforce'; +import 'ace-builds/src-noconflict/mode-wollok'; +import 'ace-builds/src-noconflict/mode-xml'; +import 'ace-builds/src-noconflict/mode-xquery'; +import 'ace-builds/src-noconflict/mode-yaml'; +import 'ace-builds/src-noconflict/mode-zeek'; +//#endregion + +@Component({ + selector: 'x-ide', + templateUrl: './x-ide.component.html', + styleUrls: ['./x-ide.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XIDEComponent extends XIDEContainerComponent { + // + //#region Props ... + // + //#region Private Props ... + private CODE_EDITOR_BEAUTIFY: any = null; + private CODE_EDITOR: ace.Ace.Editor = null; + private CODE_EDITOR_UNDO_MANAGER: any; + + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private CONTENT_CHANGE_SUBJECT = new Subject(); + + // + readonly Modes = Object.assign({}, XIDEMode); + readonly Themes = Object.assign({}, XIDETheme); + readonly TabAligns = Object.assign({}, XTabAlign); + readonly IconNames = Object.assign({}, XIconNames); + readonly ColorNames = Object.assign({}, XColorWithBrightness); + readonly ActionBarItems = Object.assign({}, XIDEActionBarItem); + readonly ActionBarItemSlots = Object.assign({}, XActionBarItemSlot); + readonly TabHeaderPositions = Object.assign({}, XTabsHeaderPosition); + + // + readonly tabsActionProvider = new Subject(); + //#endregion + + // + @ViewChild('codeEditor') + codeEditorElmRef: ElementRef; + + @HostListener('resize', ['$event']) + onResize (event: any) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.CODE_EDITOR.resize(); + } + + // + lastContent: string; + currentTab = XIDEDefaultTabName; + + // + readonly Sessions: Pick< + XIDETabDescriptor, + 'name' | 'session' | 'canSave' | 'canBeautify' + >[] = []; + //#endregion + + // + //#region Constructor ... + //#endregion + + // + //#region LifeCycles ... + afterViewInit () { + super.afterViewInit(); + + // + this.prepareCodeEditor(); + } + + async onChange (changeKeys: string[]): Promise { + // + //#region Action Provider Changed ... + // + const isActionProviderChaged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChaged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Theme Changed ... + const isThemeChanged = changeKeys.includes(nameof('theme')); + if (isThemeChanged) { + this.setTheme(this.theme); + } + //#endregion + + // + //#region Mode Changed ... + const isModeChanged = changeKeys.includes(nameof('mode')); + if (isModeChanged) { + this.setMode(this.mode); + } + //#endregion + + // + //#region MinLines Changed ... + const isMinLinesChanged = changeKeys.includes( + nameof('minLines') + ); + if (isMinLinesChanged) { + this.setMinLines(this.minLines); + } + //#endregion + + // + //#region MaxLines Changed ... + const isMaxLinesChanged = changeKeys.includes( + nameof('maxLines') + ); + if (isMaxLinesChanged) { + this.setMaxLines(this.maxLines); + } + //#endregion + + // + //#region FontSize Changed ... + const isFontSizeChanged = changeKeys.includes( + nameof('fontSize') + ); + if (isFontSizeChanged) { + this.setFontSize(this.fontSize); + } + //#endregion + + // + //#region Readonly Changed ... + const isReadonlyChanged = changeKeys.includes( + nameof('readonly') + ); + if (isReadonlyChanged) { + this.setReadOnly(this.readonly); + } + //#endregion + + // + //#region Content Changed ... + const isContentChanged = changeKeys.includes( + nameof('content') + ); + if (isContentChanged) { + this.setContent(this.content); + } + //#endregion + + // + //#region WordWrapping Changed ... + const isWordWrappingChanged = changeKeys.includes( + nameof('wordWrapping') + ); + if (isWordWrappingChanged) { + this.setWordWrapping(this.wordWrapping); + } + //#endregion + + // + //#region ShowPrintMargin Changed ... + const isShowPrintMarginChanged = changeKeys.includes( + nameof('showPrintMargin') + ); + if (isShowPrintMarginChanged) { + this.setShowPrintMargin(this.showPrintMargin); + } + //#endregion + + // + //#region HighlightActiveLine Changed ... + const isHighlightActiveLineChanged = changeKeys.includes( + nameof('highlightActiveLine') + ); + if (isHighlightActiveLineChanged) { + this.setHighlightActiveLine(this.highlightActiveLine); + } + //#endregion + + // + //#region BasicAutocompletion Changed ... + const isBasicAutocompletionChanged = changeKeys.includes( + nameof('basicAutocompletion') + ); + if (isBasicAutocompletionChanged) { + this.setBasicAutocompletion(this.basicAutocompletion); + } + //#endregion + + // + //#region DefaultTabName Changed ... + const isDefaultTabNameChanged = changeKeys.includes( + nameof('defaultTabName') + ); + if (isDefaultTabNameChanged) { + await this.handleDefaultTabNameChanged(this.defaultTabName); + } + //#endregion + + // + //#region ActionBar ... + const isDefaultActionBarItemColorChanged = changeKeys.includes( + nameof('defaultActionBarItemColor') + ); + const isAvailableActionBarItemsChanged = changeKeys.includes( + nameof('availableActionBarItems') + ); + if ( + isAvailableActionBarItemsChanged || + isDefaultActionBarItemColorChanged + ) { + // + if (isNullOrUndefined(this.availableActionBarItems)) { + this.availableActionBarItems = + this.propertyProvider.availableActionBarItems; + } + await this.prepareActionBar(); + } + //#endregion + } + + onDestroy () { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Register Handlers ... + registerViewHandlers (): void { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + + // + this.applyTakeUntilWrapper( + this.CONTENT_CHANGE_SUBJECT.asObservable() + ).subscribe(async (content) => { + // + if (content === this.lastContent) { + return; + } + + // + this.lastContent = content; + this.updateCurrentTabDescriptor({ + canSave: true, + canBeautify: true, + }); + + // + this.contentChange.emit(content); + + // + await this.prepareActionBar(); + }); + } + //#endregion + + // + //#region Task Handlers ... + //#endregion + + // + //#region UI Providers ... + //#endregion + + // + //#region UI Handlers ... + async handleActionBarActionFired (model: XActionBarActionFiredModel) { + // + if (isNullOrUndefined(model) || isNullOrEmptyString(model.action)) { + return; + } + + // + switch (model.action) { + // + case this.ActionBarItems.ChangeMode: + this.handleActionModeFired(model.event); + break; + + // + case this.ActionBarItems.ChangeTheme: + this.handleActionThemFired(model.event); + break; + + // + case this.ActionBarItems.Close: + this.closeTab(this.currentTab); + break; + + // + case this.ActionBarItems.Undo: + this.undo(); + break; + + // + case this.ActionBarItems.Redo: + this.redo(); + break; + + // + case this.ActionBarItems.Beautify: + await this.beautifyCode(); + break; + + // + case this.ActionBarItems.Save: + // + this.updateCurrentTabDescriptor({ + canSave: false, + }); + this.prepareActionBar(); + break; + } + + // + this.actionBarActionFired.emit(model); + } + + async handleTabsSelectedTabChanged (event: XTabChangeEvent) { + // + if ( + event.index < 0 || + isNullOrUndefined(event) || + event.index > this.Sessions.length || + isNullOrUndefined(this.CODE_EDITOR) + ) { + return; + } + + // + const descriptor = this.Sessions[event.index]; + this.showTab(descriptor.name); + } + //#endregion + + // + //#region Actions ... + setTheme (theme: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(theme).subscribe((value) => { + // + if (isNullOrEmptyString(value)) { + return; + } + + // + this.CODE_EDITOR.setTheme(value); + }); + } + + setMode (mode: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(mode).subscribe((value) => { + // + if (isNullOrEmptyString(value)) { + return; + } + + // + this.CODE_EDITOR.getSession().setMode(value); + this.detectChanges(); + }); + } + + setMinLines (minLines: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(minLines).subscribe((value) => { + // + if (value <= 0) { + return; + } + + // + this.CODE_EDITOR.setOption('minLines', value); + this.detectChanges(); + }); + } + + setMaxLines (maxLines: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(maxLines).subscribe((value) => { + // + if (value <= 0) { + return; + } + + // + this.CODE_EDITOR.setOption('maxLines', value); + this.detectChanges(); + }); + } + + setFontSize (fontSize: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(fontSize).subscribe((value) => { + // + if (value <= XIDEMinFontSize || value >= XIDEMaxFontSize) { + return; + } + + // + this.CODE_EDITOR.setFontSize(value); + this.detectChanges(); + }); + } + + setReadOnly (readonly: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(readonly).subscribe((value) => { + // + this.CODE_EDITOR.setReadOnly(value); + this.detectChanges(); + }); + } + + getFontSize () { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return ''; + } + + // + return this.CODE_EDITOR.getFontSize(); + } + + setHighlightActiveLine (highlightActiveLine: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(highlightActiveLine).subscribe((value) => { + // + this.CODE_EDITOR.setOption('highlightActiveLine', value); + this.detectChanges(); + }); + } + + setBasicAutocompletion (basicAutocompletion: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(basicAutocompletion).subscribe((value) => { + // + this.CODE_EDITOR.setOption('enableBasicAutocompletion' as any, value); + this.detectChanges(); + }); + } + + setWordWrapping (wordWrapping: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(wordWrapping).subscribe((value) => { + // + this.CODE_EDITOR.getSession().setUseWrapMode(value); + this.detectChanges(); + }); + } + + setShowLineNumbers (showLineNumbers: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(showLineNumbers).subscribe((value) => { + // + this.CODE_EDITOR.setOption('showLineNumbers', value); + this.detectChanges(); + }); + } + + setShowPrintMargin (showPrintMargin: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(showPrintMargin).subscribe((value) => { + // + this.CODE_EDITOR.setShowPrintMargin(value); + this.detectChanges(); + }); + } + + setShowFoldWidgets (showFoldWidgets: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(showFoldWidgets).subscribe((value) => { + // + this.CODE_EDITOR.setShowFoldWidgets(value); + this.detectChanges(); + }); + } + + // + //#region IDEEdit ... + async beautifyCode () { + // + if ( + isNullOrUndefined(this.CODE_EDITOR) || + isNullOrUndefined(this.CODE_EDITOR_BEAUTIFY) + ) { + return; + } + + // + const session = this.CODE_EDITOR.getSession(); + this.CODE_EDITOR_BEAUTIFY.beautify(session); + + // + this.updateCurrentTabDescriptor({ + canBeautify: false, + }); + await this.prepareActionBar(); + } + + getContent () { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return ''; + } + + // + const result = this.CODE_EDITOR.getValue(); + return result; + } + + setContent (value: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(value).subscribe((content) => { + // + if (isNullOrEmptyString(content)) { + return; + } + + // + if (value === this.getContent()) { + return; + } + + // + this.CODE_EDITOR.setValue(content); + }); + } + + clearcontent (force = false) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + if (!!force) { + // + this.CODE_EDITOR.setValue(''); + this.detectChanges(); + } else { + // + this.managerService.dialogService.presentAreYouSureYesNoDialog(() => { + this.CODE_EDITOR.setValue(''); + this.detectChanges(); + }); + } + } + + goToLine (lineNumber: number) { + // + const totalLines = this.getTotalLines(); + if ( + lineNumber <= 0 || + totalLines <= 0 || + lineNumber > totalLines || + isNullOrUndefined(this.CODE_EDITOR) + ) { + return; + } + + // + this.CODE_EDITOR.gotoLine(lineNumber, 0, true); + } + + getTotalLines () { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return -1; + } + + // + return this.CODE_EDITOR.getSession().getLength(); + } + + canUndo () { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return false; + } + + // + const session = this.CODE_EDITOR.getSession(); + if (isNullOrUndefined(session)) { + return false; + } + + // + const undoManager = session.getUndoManager(); + if (isNullOrUndefined(undoManager)) { + return false; + } + + // + return undoManager.canUndo(); + } + + undo (dontSelect = true) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + const session = this.CODE_EDITOR.getSession(); + if (isNullOrUndefined(session)) { + return; + } + + // + const undoManager = session.getUndoManager(); + if (isNullOrUndefined(undoManager) || !this.canUndo()) { + return; + } + + // + undoManager.undo(session, dontSelect); + this.prepareActionBar(); + } + + canRedo () { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return false; + } + + // + const session = this.CODE_EDITOR.getSession(); + if (isNullOrUndefined(session)) { + return false; + } + + // + const undoManager = session.getUndoManager(); + if (isNullOrUndefined(undoManager)) { + return false; + } + + // + return undoManager.canRedo(); + } + + redo (dontSelect = true) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + const session = this.CODE_EDITOR.getSession(); + if (isNullOrUndefined(session)) { + return; + } + + // + const undoManager = session.getUndoManager(); + if (isNullOrUndefined(undoManager) || !this.canRedo()) { + return; + } + + // + undoManager.redo(session, dontSelect); + this.prepareActionBar(); + } + + cleaEditorHistory () { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + const session = this.CODE_EDITOR.getSession(); + if (isNullOrUndefined(session)) { + return; + } + + // + const undoManager = session.getUndoManager(); + if (isNullOrUndefined(undoManager)) { + return; + } + + // + undoManager.reset(); + this.prepareActionBar(); + } + //#endregion + + // + //#region IDETabs ... + getTabDescritor (name: string) { + // + if ( + isNullOrEmptyString(name) || + !this.hasChild(this.Sessions) || + isNullOrUndefined(this.CODE_EDITOR) + ) { + return null; + } + + // + return this.Sessions.find( + (sd) => toNormalString(sd.name) === toNormalString(name) + ); + } + + getTabDescritorIndex (name: string) { + // + if ( + isNullOrEmptyString(name) || + !this.hasChild(this.Sessions) || + isNullOrUndefined(this.CODE_EDITOR) + ) { + return null; + } + + // + return this.Sessions.findIndex( + (sd) => toNormalString(sd.name) === toNormalString(name) + ); + } + + getCurrentTabDescritor () { + return this.getTabDescritor(this.currentTab); + } + + updateCurrentTabDescriptor ( + options: Pick + ) { + // + const currentTabDescriptor = this.getCurrentTabDescritor(); + const currentTabdescriptorIndex = this.Sessions.findIndex( + (sd) => toNormalString(sd.name) === toNormalString(this.currentTab) + ); + if ( + currentTabdescriptorIndex < 0 || + isNullOrUndefined(currentTabDescriptor) + ) { + return; + } + + // + const optionsKeys = Object.keys(options); + const hasCanSave = optionsKeys.includes( + nameof('canSave') + ); + const hasCanBeautify = optionsKeys.includes( + nameof('canBeautify') + ); + + // + const canSave = hasCanSave ? options.canSave : currentTabDescriptor.canSave; + const canBeautify = hasCanBeautify + ? options.canBeautify + : currentTabDescriptor.canBeautify; + + // + this.Sessions[currentTabdescriptorIndex] = { + ...currentTabDescriptor, + canSave, + canBeautify, + }; + + // + this.detectChanges(); + } + + addTab ( + tab: Pick + ) { + // + if (isNullOrUndefined(tab) || isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + // Set Default Content if not Provided ... + if (isNullOrEmptyString(tab.content)) { + tab.content = ''; + } + + // + // Set Default Mode if Not Provided ... + if ( + isNullOrEmptyString(tab.mode) || + isNullOrUndefined(tab.mode as XIDEMode) || + !isNullOrUndefined(this.getTabDescritor(tab.name)) + ) { + tab.mode = XIDEMode.Text; + } + + // + // Create Editor Session ... + const session = ace.createEditSession(tab.content, tab.mode as any); + const undoManager = new this.CODE_EDITOR_UNDO_MANAGER(); + + // + session.setUndoManager(undoManager); + undoManager.reset(); + + // + this.Sessions.push({ + session, + name: tab.name, + }); + + // + if (!!tab.forceShow) { + // + this.runTimedOut(() => { + // + this.tabsActionProvider.next({ + action: XTabsAction.TO, + payload: this.Sessions.length - 1, + }); + }, 500); + } + + // + this.detectChanges(); + } + + addTabs ( + tabs: XOneOrManyType> + ) { + // + if (!this.hasChild(tabs) || isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + toArray(tabs).forEach((tab) => + this.addTab({ + ...tab, + forceShow: false, + }) + ); + } + + showTab (name: string) { + // + if ( + isNullOrEmptyString(name) || + !this.hasChild(this.Sessions) || + isNullOrUndefined(this.CODE_EDITOR) + ) { + return; + } + + // + const tabDescriptor = this.getTabDescritor(name); + if (isNullOrUndefined(tabDescriptor)) { + return; + } + + // + this.currentTab = tabDescriptor.name; + this.lastContent = tabDescriptor.session.getValue(); + this.CODE_EDITOR.setSession(tabDescriptor.session); + + // + this.prepareActionBar(); + } + + closeTab (name: string, force = false) { + // + this.getValue(this.defaultTabName).subscribe((defaultTabName) => { + // + if ( + isNullOrEmptyString(name) || + !this.hasChild(this.Sessions) || + isNullOrUndefined(this.CODE_EDITOR) || + toNormalString(name) === toNormalString(defaultTabName) + ) { + return; + } + + // + const destinationTab = this.getTabDescritor(name); + const destinationTabIndex = this.getTabDescritorIndex(name); + if (isNullOrUndefined(destinationTab) || destinationTabIndex < 0) { + return; + } + + // + if (!!force || !destinationTab.canSave) { + // + this.handleRemoveTab(destinationTab.name, destinationTabIndex); + this.detectChanges(); + } else { + // + this.managerService.dialogService.presentAreYouSureYesNoDialog(() => { + // + this.handleRemoveTab(destinationTab.name, destinationTabIndex); + this.detectChanges(); + }); + } + }); + } + //#endregion + //#endregion + + // + //#region Abstracts ... + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider () { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe((action) => { + // + if (isNullOrUndefined(action) || isNullOrEmptyString(action.action)) { + return; + } + + // + switch (action.action) { + // + // Undo ... + case XIDEAction.Undo: + // + const undoDontSelect = toBoolean(action.payload) || false; + this.undo(undoDontSelect); + break; + + // + // Redo ... + case XIDEAction.Redo: + // + const redoDontSelect = toBoolean(action.payload) || false; + this.redo(redoDontSelect); + break; + + // + // CleaEditorHistory ... + case XIDEAction.CleaEditorHistory: + this.cleaEditorHistory(); + break; + + // + // AddTab ... + case XIDEAction.AddTab: + // + const tabDescriptor = action.payload as XIDETabDescriptor; + if ( + !isNullOrUndefined(tabDescriptor) && + !isNullOrUndefined(action.payload) + ) { + this.addTab({ + ...tabDescriptor, + }); + } + break; + + // + // AddTabs ... + case XIDEAction.AddTabs: + // + const tabDescriptors = action.payload as XIDETabDescriptor[]; + if ( + this.hasChild(tabDescriptors) && + !isNullOrUndefined(action.payload) + ) { + this.addTabs([...tabDescriptors]); + } + break; + + // + // ShowTab ... + case XIDEAction.ShowTab: + // + const showTabDescriptor = action.payload as string; + if ( + !isNullOrEmptyString(showTabDescriptor) && + !isNullOrEmptyString(action.payload) + ) { + // + const index = this.getTabDescritorIndex(showTabDescriptor); + + // + this.runTimedOut(() => { + // + this.tabsActionProvider.next({ + action: XTabsAction.TO, + payload: index, + }); + }, 500).then(() => { + this.showTab(showTabDescriptor); + }); + } + break; + + // + // CloseTab ... + case XIDEAction.CloseTab: + // + const forceCloseTab = action.payload.force || false; + const closeTabDescriptor = action.payload.name as string; + if ( + !isNullOrEmptyString(closeTabDescriptor) && + !isNullOrEmptyString(action.payload) + ) { + this.closeTab(closeTabDescriptor, forceCloseTab); + } + break; + + // + // Refresh ... + case XIDEAction.Refresh: + this.detectChanges(); + break; + + // + // SetMode ... + case XIDEAction.SetMode: + // + const mode = action.payload as XIDEMode; + if ( + !isNullOrUndefined(mode) && + !isNullOrUndefined(action.payload) + ) { + this.setMode(mode); + } + break; + + // + // Beautify ... + case XIDEAction.Beautify: + this.beautifyCode(); + break; + + // + // SetTheme ... + case XIDEAction.SetTheme: + // + const theme = action.payload as XIDETheme; + if ( + !isNullOrUndefined(theme) && + !isNullOrUndefined(action.payload) + ) { + this.setTheme(theme); + } + break; + + // + // GoToLine ... + case XIDEAction.GoToLine: + // + const lineNumber = action.payload as number; + if ( + isNumber(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.goToLine(lineNumber); + } + break; + + // + // SetContent ... + case XIDEAction.SetContent: + // + const content = action.payload as string; + if ( + !isNullOrEmptyString(content) && + !isNullOrUndefined(action.payload) + ) { + this.setContent(content); + } + break; + + // + // SetMinLines ... + case XIDEAction.SetMinLines: + // + const minLines = action.payload as number; + if ( + isNumber(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setMinLines(minLines); + } + break; + + // + // SetMaxLines ... + case XIDEAction.SetMaxLines: + // + const maxLines = action.payload as number; + if ( + isNumber(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setMaxLines(maxLines); + } + break; + + // + // SetReadOnly ... + case XIDEAction.SetReadOnly: + // + const isReadonly = toBoolean(action.payload); + if ( + isBoolean(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setReadOnly(isReadonly); + } + break; + + // + // SetFontSize ... + case XIDEAction.SetFontSize: + // + const fontSize = action.payload as number; + if ( + isNumber(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setFontSize(fontSize); + } + break; + + // + // ClearContent ... + case XIDEAction.ClearContent: + // + const isForceClearContent = toBoolean(action.payload); + if ( + isBoolean(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.clearcontent(isForceClearContent); + } else { + this.clearcontent(); + } + break; + + // + // SetWordWrapping ... + case XIDEAction.SetWordWrapping: + // + const isWrapping = toBoolean(action.payload); + if ( + isBoolean(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setWordWrapping(isWrapping); + } + break; + + // + // SetShowFoldWidgets ... + case XIDEAction.SetShowFoldWidgets: + // + const showFoldingWidgets = toBoolean(action.payload); + if ( + isBoolean(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setReadOnly(showFoldingWidgets); + } + break; + + // + // SetShowLineNumbers ... + case XIDEAction.SetShowLineNumbers: + // + const showLineNumbers = toBoolean(action.payload); + if ( + isBoolean(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setShowLineNumbers(showLineNumbers); + } + break; + + // + // SetShowPrintMargin ... + case XIDEAction.SetShowPrintMargin: + // + const showPrintMargin = toBoolean(action.payload); + if ( + isBoolean(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setShowPrintMargin(showPrintMargin); + } + break; + + // + // SetHighlightActiveLine ... + case XIDEAction.SetHighlightActiveLine: + // + const highlightActiveLine = toBoolean(action.payload); + if ( + isBoolean(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setHighlightActiveLine(highlightActiveLine); + } + break; + + // + // SetBasicAutocompletion ... + case XIDEAction.SetBasicAutocompletion: + // + const basicAutoCompletion = toBoolean(action.payload); + if ( + isBoolean(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setBasicAutocompletion(basicAutoCompletion); + } + break; + } + }); + } + + private unregisterActionProvider () { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private prepareCodeEditor () { + // + const element = this.codeEditorElmRef.nativeElement; + if (isNullOrUndefined(element)) { + return; + } + + // + this.CODE_EDITOR_BEAUTIFY = ace.require('ace/ext/beautify'); + this.CODE_EDITOR_UNDO_MANAGER = ace.require('ace/undomanager').UndoManager; + + // + this.CODE_EDITOR = ace.edit(element, { + minLines: 1, + maxLines: 1, + }); + + // + //#region Attach Undo Manager ... + this.CODE_EDITOR.getSession().setUndoManager( + new this.CODE_EDITOR_UNDO_MANAGER() + ); + //#endregion + + // + //#region Apply Code Editor Options ... + this.setMode(this.mode); + this.setTheme(this.theme); + this.setMinLines(this.minLines); + this.setMaxLines(this.maxLines); + this.setFontSize(this.fontSize); + this.setReadOnly(this.readonly); + this.setWordWrapping(this.wordWrapping); + this.setShowLineNumbers(this.showLineNumbers); + this.setShowPrintMargin(this.showPrintMargin); + this.setShowFoldWidgets(this.showFoldWidgets); + this.setHighlightActiveLine(this.highlightActiveLine); + this.setBasicAutocompletion(this.basicAutocompletion); + //#endregion + + // + //#region Setting Default Content ... + this.setContent(this.content); + + // + // Reset Undo Manager after first code attached ... + this.CODE_EDITOR.getSession().getUndoManager().reset(); + //#endregion + + // + //#region Default Session Handler ... + forkJoin({ + mode: this.getValue(this.mode), + content: this.getValue(this.content), + name: this.getValue(this.defaultTabName).pipe( + map((defTabName) => defTabName || XIDEDefaultTabName) + ), + }).subscribe((info) => { + // + const name = info.name; + const session = this.CODE_EDITOR.getSession(); + this.Sessions.push({ + session, + name, + }); + }); + //#endregion + + // + //#region Code Editor Event Handlers ... + // + // Change Event handler ... + this.CODE_EDITOR.on('change', (e) => { + // + const currentContent = this.getContent(); + this.CONTENT_CHANGE_SUBJECT.next(currentContent); + }); + //#endregion + + // + this.prepareActionBar(); + + // + // Detectin Changes ... + this.detectChanges(); + } + + private async prepareActionBar () { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + const showActionBar = await this.getValueAsync(this.showActionBar); + if (!showActionBar) { + return; + } + + // + //#region DefaultActionBarActions ... + // + const defaultThemeItem: XActionBarItem = { + onlyIcon: true, + disabled: this.uiDisabled, + title: this.ResourceIDs.theme, + icon: this.IconNames.color_palette, + slot: this.ActionBarItemSlots.START, + id: this.ActionBarItems.ChangeTheme, + iconColor: this.defaultActionBarItemColor, + }; + + // + const defaultModeItem: XActionBarItem = { + onlyIcon: true, + disabled: this.uiDisabled, + icon: this.IconNames.list, + title: this.ResourceIDs.ide_mode, + id: this.ActionBarItems.ChangeMode, + slot: this.ActionBarItemSlots.START, + iconColor: this.defaultActionBarItemColor, + }; + + // + const defaultSaveItem: XActionBarItem = { + onlyIcon: true, + disabled: this.uiDisabled, + icon: this.IconNames.save, + title: this.ResourceIDs.save, + id: this.ActionBarItems.Save, + slot: this.ActionBarItemSlots.END, + iconColor: this.defaultActionBarItemColor, + }; + + // + const defaultCloseItem: XActionBarItem = { + onlyIcon: true, + disabled: this.uiDisabled, + icon: this.IconNames.close, + title: this.ResourceIDs.close, + id: this.ActionBarItems.Close, + slot: this.ActionBarItemSlots.END, + iconColor: this.ColorNames.Danger, + }; + + // + const defaultUndoItem: XActionBarItem = { + onlyIcon: true, + disabled: this.uiDisabled, + icon: this.IconNames.undo, + title: this.ResourceIDs.undo, + id: this.ActionBarItems.Undo, + slot: this.ActionBarItemSlots.END, + iconColor: this.defaultActionBarItemColor, + }; + + // + const defaultRedoItem: XActionBarItem = { + onlyIcon: true, + disabled: this.uiDisabled, + icon: this.IconNames.redo, + title: this.ResourceIDs.redo, + id: this.ActionBarItems.Redo, + slot: this.ActionBarItemSlots.END, + iconColor: this.defaultActionBarItemColor, + }; + + // + const defaultBeautifyItem: XActionBarItem = { + onlyIcon: true, + disabled: this.uiDisabled, + icon: this.IconNames.ul, + id: this.ActionBarItems.Beautify, + slot: this.ActionBarItemSlots.END, + title: this.ResourceIDs.ide_format, + iconColor: this.defaultActionBarItemColor, + }; + //#endregion + + // + this.actionBarActions = []; + this.detectChanges(); + + // + const availableItems = await this.getArrayValueAsync( + this.availableActionBarItems + ); + if (!this.hasChild(availableItems)) { + return; + } + + // + const actionBarItems: XActionBarItem[] = []; + const currentTabDescriptor = this.getCurrentTabDescritor(); + + // + //#region Save ... + const hasSaveItem = availableItems + .map((ai) => ai.actionId) + .includes(this.ActionBarItems.Save); + + // + if (hasSaveItem) { + // + const selectedItem = availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.Save + ); + const providedSaveItem = selectedItem.descriptor; + let saveItem = { + ...defaultSaveItem, + }; + if (!isNullOrUndefined(providedSaveItem)) { + saveItem = { + ...saveItem, + ...providedSaveItem, + }; + } + + // + saveItem = { + ...saveItem, + disabled: + this.uiDisabled || !isNullOrUndefined(currentTabDescriptor) + ? !currentTabDescriptor.canSave + : true, + }; + + // + actionBarItems.push(saveItem); + } + //#endregion + + // + //#region Close ... + const hasCloseItem = availableItems + .map((ai) => ai.actionId) + .includes(this.ActionBarItems.Close); + const hasCloseOnDefaultTab = await this.getValueAsync( + this.hasCloseOnDefaultTab + ); + + // + const currentTabIndex = this.getTabDescritorIndex(this.currentTab); + if ( + hasCloseItem + && (!!hasCloseOnDefaultTab + ? true + : currentTabIndex !== 0 + ) + && currentTabIndex > -1) { + // + const selectedItem = availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.Close + ); + const providedCloseItem = selectedItem.descriptor; + let closeItem = { + ...defaultCloseItem, + }; + if (!isNullOrUndefined(providedCloseItem)) { + closeItem = { + ...closeItem, + ...providedCloseItem, + }; + } + + // + closeItem = { + ...closeItem, + disabled: this.uiDisabled, + }; + + // + actionBarItems.push(closeItem); + } + //#endregion + + // + //#region Unde ... + const hasUndoItem = availableItems + .map((ai) => ai.actionId) + .includes(this.ActionBarItems.Undo); + + // + if (hasUndoItem) { + // + const selectedItem = availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.Undo + ); + const providedUndoItem = selectedItem.descriptor; + let undoItem = { + ...defaultUndoItem, + }; + if (!isNullOrUndefined(providedUndoItem)) { + undoItem = { + ...undoItem, + ...providedUndoItem, + }; + } + + // + // Prepare Disable/Enable Toggling of Item ... + undoItem = { + ...undoItem, + disabled: + this.uiDisabled || + !this.CODE_EDITOR.getSession().getUndoManager().canUndo() || + isNullOrUndefined(this.CODE_EDITOR.getSession().getUndoManager()), + }; + + // + actionBarItems.push(undoItem); + } + //#endregion + + // + //#region Redo ... + const hasRedoItem = availableItems + .map((ai) => ai.actionId) + .includes(this.ActionBarItems.Redo); + + // + if (hasRedoItem) { + // + const selectedItem = availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.Redo + ); + const providedRedoItem = selectedItem.descriptor; + let redoItem = { + ...defaultRedoItem, + }; + if (!isNullOrUndefined(providedRedoItem)) { + redoItem = { + ...redoItem, + ...providedRedoItem, + }; + } + + // + // Prepare Disable/Enable Toggling of Item ... + redoItem = { + ...redoItem, + disabled: + this.uiDisabled || + !this.CODE_EDITOR.getSession().getUndoManager().canRedo() || + isNullOrUndefined(this.CODE_EDITOR.getSession().getUndoManager()), + }; + + // + actionBarItems.push(redoItem); + } + //#endregion + + // + //#region Beautify ... + const hasBeautifyItem = availableItems + .map((ai) => ai.actionId) + .includes(this.ActionBarItems.Beautify); + + // + if (hasBeautifyItem) { + // + const selectedItem = availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.Beautify + ); + const providedBeautifyItem = selectedItem.descriptor; + let beautifyItem = { + ...defaultBeautifyItem, + }; + if (!isNullOrUndefined(providedBeautifyItem)) { + beautifyItem = { + ...beautifyItem, + ...providedBeautifyItem, + }; + } + + // + // Prepare Disable/Enable Toggling of Item ... + beautifyItem = { + ...beautifyItem, + disabled: + this.uiDisabled || + isNullOrUndefined(this.CODE_EDITOR) || + !isNullOrUndefined(currentTabDescriptor) + ? !currentTabDescriptor.canBeautify + : true || isNullOrUndefined(this.CODE_EDITOR_BEAUTIFY), + }; + + // + actionBarItems.push(beautifyItem); + } + //#endregion + + // + //#region Mode ... + const hasModeChangeItem = availableItems + .map((ai) => ai.actionId) + .includes(this.ActionBarItems.ChangeMode); + + // + if (hasModeChangeItem) { + // + const selectedItem = availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.ChangeMode + ); + const providedModeItem = selectedItem.descriptor; + let modeItem = { + ...defaultModeItem, + }; + if (!isNullOrUndefined(providedModeItem)) { + modeItem = { + ...modeItem, + ...providedModeItem, + }; + } + + // + modeItem = { + ...modeItem, + disabled: this.uiDisabled, + }; + + // + actionBarItems.push(modeItem); + } + //#endregion + + // + //#region Theme ... + const hasThemeChangeItem = availableItems + .map((ai) => ai.actionId) + .includes(this.ActionBarItems.ChangeTheme); + + // + if (hasThemeChangeItem) { + // + const selectedItem = availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.ChangeTheme + ); + const providedThemeItem = selectedItem.descriptor; + let themeItem = { + ...defaultThemeItem, + }; + if (!isNullOrUndefined(providedThemeItem)) { + themeItem = { + ...themeItem, + ...providedThemeItem, + }; + } + + // + themeItem = { + ...themeItem, + disabled: this.uiDisabled, + }; + + // + actionBarItems.push(themeItem); + } + //#endregion + + // + this.actionBarActions = [...actionBarItems]; + this.detectChanges(); + } + + // + //#region Tabs Handlers ... + private async handleDefaultTabNameChanged ( + defaultTabName: XStandardType + ) { + // + let value = await this.getValueAsync(defaultTabName); + if (isNullOrEmptyString(value)) { + value = XIDEDefaultTabName; + } + + // + if (!this.hasChild(this.Sessions)) { + return; + } + + // + if ( + this.currentTab === XIDEDefaultTabName || + this.currentTab === this.Sessions[0].name + ) { + this.currentTab = value; + } + + // + this.Sessions[0].name = value; + + // + this.prepareActionBar(); + this.detectChanges(); + } + + private handleRemoveTab (name: string, destinationTabIndex: number) { + // + if ( + destinationTabIndex <= 0 || + isNullOrEmptyString(name) || + destinationTabIndex > this.Sessions.length - 1 + ) { + return; + } + + // + this.Sessions.splice(destinationTabIndex, 1); + this.detectChanges(); + this.tabsActionProvider.next({ + action: XTabsAction.Refresh, + }); + + // + const canShowNewTab = name === this.currentTab; + if (canShowNewTab) { + // + const newTabIndex = this.Sessions.length - 1; + + // + if (countChild(this.Sessions) > 1) { + // + this.tabsActionProvider.next({ + action: XTabsAction.TO, + payload: newTabIndex, + }); + + // + this.tabsActionProvider.next({ + action: XTabsAction.Refresh, + }); + } + + // + const newTab = this.Sessions[newTabIndex]; + this.showTab(newTab.name); + } + + // + this.detectChanges(); + } + //#endregion + + // + //#region ActionBar Handlers ... + private handleActionModeFired (event: Event) { + this.getValue(this.mode) + .pipe( + concatMap((mode) => + this.getArrayValue(this.availableActionBarItems).pipe( + map((availableItems) => { + return { + mode, + availableItems, + }; + }) + ) + ) + ) + .subscribe((info) => { + // + // Selected Item Model ... + const selectedItemModel = info.availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.ChangeMode + ); + + // + // get Item Colors ... + const itemsColor = selectedItemModel.itemsColor; + + // + // Retrieve Popover Present Options ... + const popoverPresentOptions = selectedItemModel.popoverPresentOptions; + + // + // Generate Available Modes ... + const availableModes = Object.keys(this.Modes) + // + // By Filtering Curent Mode ... + .filter((key) => this.Modes[key] !== info.mode); + + // + // Convert Available Modes to XPopoverOptions ... + const options = availableModes.map((key) => { + return { + id: key, + color: !isNullOrEmptyString(itemsColor) + ? itemsColor + : this.ColorNames.Dark, + title: key, + handler: () => { + // + this.mode = this.Modes[key]; + this.setMode(this.Modes[key]); + }, + } as XPopoverOption; + }); + + // + // Present Popover Options ... + this.popoverService.presentContextMenu( + options, + event, + popoverPresentOptions + ); + }); + } + + private handleActionThemFired (event: Event) { + this.getValue(this.theme) + .pipe( + concatMap((theme) => + this.getArrayValue(this.availableActionBarItems).pipe( + map((availableItems) => { + return { + theme, + availableItems, + }; + }) + ) + ) + ) + .subscribe((info) => { + // + // Selected Item Model ... + const selectedItemModel = info.availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.ChangeTheme + ); + + // + // get Item Colors ... + const itemsColor = selectedItemModel.itemsColor; + + // + // Retrieve Popover Present Options ... + const popoverPresentOptions = selectedItemModel.popoverPresentOptions; + + // + // Generate Available Themes ... + const availableThemes = Object.keys(this.Themes) + // + // By Filtering Curent Theme ... + .filter((key) => this.Themes[key] !== info.theme); + + // + // Convert Available Themes to XPopoverOptions ... + const options = availableThemes.map((key) => { + return { + id: key, + color: !isNullOrEmptyString(itemsColor) + ? itemsColor + : this.ColorNames.Dark, + title: key, + handler: () => { + // + this.theme = this.Themes[key]; + this.setTheme(this.Themes[key]); + }, + } as XPopoverOption; + }); + + // + // Present Popover Options ... + this.popoverService.presentContextMenu( + options, + event, + popoverPresentOptions + ); + }); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-ide\x-ide.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIDEComponent } from './x-ide.component'; +import { XTabsModule } from '../x-tabs/x-tabs.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XActionBarModule } from '../x-action-bar/x-action-bar.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XIDEComponent], + imports: [ + XTabsModule, + XIconModule, + XCardModule, + CommonModule, + XActionBarModule, + XBaseComponentModule, + ], + exports: [ + XTabsModule, + XIconModule, + XCardModule, + CommonModule, + XIDEComponent, + XActionBarModule, + XBaseComponentModule, + ], +}) +export class XIDEModule { } + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-ide\x-ide.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XCardActionModel, + XComponentCardContainer, + IXComponentCardContainer, +} from '../x-card/x-card.typings'; +import * as ace from 'ace-builds'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { + XActionBarItem, + XActionBarActionModel, + XActionBarActionFiredModel, + XComponentActionBarContainer, + IXComponentActionBarContainer, +} from '../x-action-bar/x-action-bar.typings'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/scrolling'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XPopoverService } from '../dialogs/x-popover/x-popover.service'; +import { XTabAlign, XTabAlignIdentifier } from '../x-tabs/x-tabs.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XPopoverPresentOptions } from '../dialogs/x-popover/x-popover.typings'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region Action Provider ... +export enum XIDEAction { + // + Refresh = 'refresh', + SetReadOnly = 'set_read_only', + + // + AddTab = 'add_tab', + ShowTab = 'show_tab', + AddTabs = 'add_tabs', + CloseTab = 'close_tab', + + // + Undo = 'undo', + Redo = 'redo', + CleaEditorHistory = 'clear_editor_history', + Beautify = 'beautify', + GoToLine = 'go_to_line', + SetContent = 'set_content', + ClearContent = 'clear_content', + + // + SetMode = 'set_mode', + SetTheme = 'set_theme', + SetMinLines = 'set_min_lines', + SetMaxLines = 'set_max_lines', + SetFontSize = 'set_font_size', + SetWordWrapping = 'set_word_wrapping', + SetShowFoldWidgets = 'set_show_fold_widgets', + SetShowLineNumbers = 'set_show_line_numbers', + SetShowPrintMargin = 'set_show_print_margin', + SetHighlightActiveLine = 'set_highlight_active_line', + SetBasicAutocompletion = 'set_basic_autoc_ompletion', +} + +export type XIDEActionIdentifier = XIDEAction | string; + +export interface XIDEActionModel + extends XBaseActionModel {} +//#endregion + +// +// Font Size Constants ... +export const XIDEMinFontSize = 12; +export const XIDEMaxFontSize = 25; + +export const XIDEDefaultTabName = 'DEFAULT'; + +// +type AnyTypeIndex = { + [key: string]: any; +}; + +// +export type XIDEOptionsType = Partial & AnyTypeIndex; + +// +//#region Theme ... +export enum XIDETheme { + Ambiance = 'ace/theme/ambiance', + Chaos = 'ace/theme/chaos', + Chrome = 'ace/theme/chrome', + Cloud9Day = 'ace/theme/cloud9_day', + Cloud9Night = 'ace/theme/cloud9_night', + Cloud9NightLowColor = 'ace/theme/cloud9_night_low_color', + Clouds = 'ace/theme/clouds', + CloudsMidnight = 'ace/theme/clouds_midnight', + Cobalt = 'ace/theme/cobalt', + CrimsonEditor = 'ace/theme/crimson_editor', + Dawn = 'ace/theme/dawn', + Dracula = 'ace/theme/dracula', + Dreamweaver = 'ace/theme/dreamweaver', + Eclipse = 'ace/theme/eclipse', + Github = 'ace/theme/github', + Gob = 'ace/theme/gob', + Gruvbox = 'ace/theme/gruvbox', + GruvboxDarkHard = 'ace/theme/gruvbox_dark_hard', + GruvboxLightHard = 'ace/theme/gruvbox_light_hard', + IdleFingers = 'ace/theme/idle_fingers', + Iplastic = 'ace/theme/iplastic', + Katzenmilch = 'ace/theme/katzenmilch', + KrTheme = 'ace/theme/kr_theme', + Kuroir = 'ace/theme/kuroir', + Merbivore = 'ace/theme/merbivore', + MerbivoreSoft = 'ace/theme/merbivore_soft', + MonoIndustrial = 'ace/theme/mono_industrial', + Monokai = 'ace/theme/monokai', + NordDark = 'ace/theme/nord_dark', + OneDark = 'ace/theme/one_dark', + PastelOnDark = 'ace/theme/pastel_on_dark', + SolarizedDark = 'ace/theme/solarized_dark', + SolarizedLight = 'ace/theme/solarized_light', + Sqlserver = 'ace/theme/sqlserver', + Terminal = 'ace/theme/terminal', + Textmate = 'ace/theme/textmate', + Tomorrow = 'ace/theme/tomorrow', + TomorrowNight = 'ace/theme/tomorrow_night', + TomorrowNightBlue = 'ace/theme/tomorrow_night_blue', + TomorrowNightBright = 'ace/theme/tomorrow_night_bright', + TomorrowNightEighties = 'ace/theme/tomorrow_night_eighties', + Twilight = 'ace/theme/twilight', + VibrantInk = 'ace/theme/vibrant_ink', + Xcode = 'ace/theme/xcode', +} + +export type XIDEThemeIdentifier = XIDETheme | string; +//#endregion + +// +//#region Mode ... +export enum XIDEMode { + Abap = 'ace/mode/abap', + Abc = 'ace/mode/abc', + Actionscript = 'ace/mode/actionscript', + Ada = 'ace/mode/ada', + Alda = 'ace/mode/alda', + ApacheConf = 'ace/mode/apache_conf', + Apex = 'ace/mode/apex', + Applescript = 'ace/mode/applescript', + Aql = 'ace/mode/aql', + Asciidoc = 'ace/mode/asciidoc', + Asl = 'ace/mode/asl', + AssemblyX86 = 'ace/mode/assembly_x86', + Autohotkey = 'ace/mode/autohotkey', + Batchfile = 'ace/mode/batchfile', + C9search = 'ace/mode/c9search', + CCpp = 'ace/mode/c_cpp', + Cirru = 'ace/mode/cirru', + Clojure = 'ace/mode/clojure', + Cobol = 'ace/mode/cobol', + Coffee = 'ace/mode/coffee', + Coldfusion = 'ace/mode/coldfusion', + Crystal = 'ace/mode/crystal', + Csharp = 'ace/mode/csharp', + CsoundDocument = 'ace/mode/csound_document', + CsoundOrchestra = 'ace/mode/csound_orchestra', + CsoundScore = 'ace/mode/csound_score', + Csp = 'ace/mode/csp', + Css = 'ace/mode/css', + Curly = 'ace/mode/curly', + D = 'ace/mode/d', + Dart = 'ace/mode/dart', + Diff = 'ace/mode/diff', + Django = 'ace/mode/django', + Dockerfile = 'ace/mode/dockerfile', + Dot = 'ace/mode/dot', + Drools = 'ace/mode/drools', + Edifact = 'ace/mode/edifact', + Eiffel = 'ace/mode/eiffel', + Ejs = 'ace/mode/ejs', + Elixir = 'ace/mode/elixir', + Elm = 'ace/mode/elm', + Erlang = 'ace/mode/erlang', + Forth = 'ace/mode/forth', + Fortran = 'ace/mode/fortran', + Fsharp = 'ace/mode/fsharp', + Fsl = 'ace/mode/fsl', + Ftl = 'ace/mode/ftl', + Gcode = 'ace/mode/gcode', + Gherkin = 'ace/mode/gherkin', + Gitignore = 'ace/mode/gitignore', + Glsl = 'ace/mode/glsl', + Gobstones = 'ace/mode/gobstones', + Golang = 'ace/mode/golang', + Graphqlschema = 'ace/mode/graphqlschema', + Groovy = 'ace/mode/groovy', + Haml = 'ace/mode/haml', + Handlebars = 'ace/mode/handlebars', + Haskell = 'ace/mode/haskell', + HaskellCabal = 'ace/mode/haskell_cabal', + Haxe = 'ace/mode/haxe', + Hjson = 'ace/mode/hjson', + Html = 'ace/mode/html', + HtmlElixir = 'ace/mode/html_elixir', + HtmlRuby = 'ace/mode/html_ruby', + Ini = 'ace/mode/ini', + Io = 'ace/mode/io', + Ion = 'ace/mode/ion', + Jack = 'ace/mode/jack', + Jade = 'ace/mode/jade', + Java = 'ace/mode/java', + Javascript = 'ace/mode/javascript', + Json = 'ace/mode/json', + Json5 = 'ace/mode/json5', + Jsoniq = 'ace/mode/jsoniq', + Jsp = 'ace/mode/jsp', + Jssm = 'ace/mode/jssm', + Jsx = 'ace/mode/jsx', + Julia = 'ace/mode/julia', + Kotlin = 'ace/mode/kotlin', + Latex = 'ace/mode/latex', + Latte = 'ace/mode/latte', + Less = 'ace/mode/less', + Liquid = 'ace/mode/liquid', + Lisp = 'ace/mode/lisp', + Livescript = 'ace/mode/livescript', + Logiql = 'ace/mode/logiql', + Logtalk = 'ace/mode/logtalk', + Lsl = 'ace/mode/lsl', + Lua = 'ace/mode/lua', + Luapage = 'ace/mode/luapage', + Lucene = 'ace/mode/lucene', + Makefile = 'ace/mode/makefile', + Markdown = 'ace/mode/markdown', + Mask = 'ace/mode/mask', + Matlab = 'ace/mode/matlab', + Maze = 'ace/mode/maze', + Mediawiki = 'ace/mode/mediawiki', + Mel = 'ace/mode/mel', + Mips = 'ace/mode/mips', + Mixal = 'ace/mode/mixal', + Mushcode = 'ace/mode/mushcode', + Mysql = 'ace/mode/mysql', + Nginx = 'ace/mode/nginx', + Nim = 'ace/mode/nim', + Nix = 'ace/mode/nix', + Nsis = 'ace/mode/nsis', + Nunjucks = 'ace/mode/nunjucks', + ObjectiveC = 'ace/mode/objectivec', + Ocaml = 'ace/mode/ocaml', + Partiql = 'ace/mode/partiql', + Pascal = 'ace/mode/pascal', + Perl = 'ace/mode/perl', + Pgsql = 'ace/mode/pgsql', + Php = 'ace/mode/php', + PhpLaravelBlade = 'ace/mode/php_laravel_blade', + Pig = 'ace/mode/pig', + PlainText = 'ace/mode/plain_text', + Powershell = 'ace/mode/powershell', + Praat = 'ace/mode/praat', + Prisma = 'ace/mode/prisma', + Prolog = 'ace/mode/prolog', + Properties = 'ace/mode/properties', + Protobuf = 'ace/mode/protobuf', + Puppet = 'ace/mode/puppet', + Python = 'ace/mode/python', + Qml = 'ace/mode/qml', + R = 'ace/mode/r', + Raku = 'ace/mode/raku', + Razor = 'ace/mode/razor', + Rdoc = 'ace/mode/rdoc', + Red = 'ace/mode/red', + Redshift = 'ace/mode/redshift', + Rhtml = 'ace/mode/rhtml', + Robot = 'ace/mode/robot', + Rst = 'ace/mode/rst', + Ruby = 'ace/mode/ruby', + Rust = 'ace/mode/rust', + Sac = 'ace/mode/sac', + Sass = 'ace/mode/sass', + Scad = 'ace/mode/scad', + Scala = 'ace/mode/scala', + Scheme = 'ace/mode/scheme', + Scrypt = 'ace/mode/scrypt', + Scss = 'ace/mode/scss', + Sh = 'ace/mode/sh', + Sjs = 'ace/mode/sjs', + Slim = 'ace/mode/slim', + Smarty = 'ace/mode/smarty', + Smithy = 'ace/mode/smithy', + Snippets = 'ace/mode/snippets', + SoyTemplate = 'ace/mode/soy_template', + Space = 'ace/mode/space', + Sparql = 'ace/mode/sparql', + Sql = 'ace/mode/sql', + Sqlserver = 'ace/mode/sqlserver', + Stylus = 'ace/mode/stylus', + Svg = 'ace/mode/svg', + Swift = 'ace/mode/swift', + Tcl = 'ace/mode/tcl', + Terraform = 'ace/mode/terraform', + Tex = 'ace/mode/tex', + Text = 'ace/mode/text', + Textile = 'ace/mode/textile', + Toml = 'ace/mode/toml', + Tsx = 'ace/mode/tsx', + Turtle = 'ace/mode/turtle', + Twig = 'ace/mode/twig', + Typescript = 'ace/mode/typescript', + Vala = 'ace/mode/vala', + Vbscript = 'ace/mode/vbscript', + Velocity = 'ace/mode/velocity', + Verilog = 'ace/mode/verilog', + Vhdl = 'ace/mode/vhdl', + Visualforce = 'ace/mode/visualforce', + Wollok = 'ace/mode/wollok', + Xml = 'ace/mode/xml', + Xquery = 'ace/mode/xquery', + Yaml = 'ace/mode/yaml', + Zeek = 'ace/mode/zeek', +} + +export type XIDEModeIdentifier = XIDEMode | string; +//#endregion + +// +//#region ActionBar ... +export enum XIDEActionBarItem { + ChangeMode = 'change_mode', + ChangeTheme = 'change_theme', + Save = 'save', + Close = 'close', + Undo = 'undo', + Redo = 'redo', + Beautify = 'beautify', +} + +export type XIDEActionBarItemIdentifier = XIDEActionBarItem | string; + +export type XIDEActionBarItemDescriptor = Partial< + Pick< + XActionBarItem, + | 'slot' + | 'icon' + | 'title' + | 'color' + | 'onlyIcon' + | 'iconColor' + | 'titleColor' + > +>; + +export interface XIDEAvailableActionBarItem { + itemsColor?: XColorIdentifier; + actionId: XIDEActionBarItemIdentifier; + descriptor?: XIDEActionBarItemDescriptor; + popoverPresentOptions?: XPopoverPresentOptions; +} +//#endregion + +// +//#region IDETabs ... +export interface XIDETabDescriptor { + name: string; + content?: string; + canSave?: boolean; + forceShow?: boolean; + canBeautify?: boolean; + mode?: XIDEModeIdentifier; + session?: ace.Ace.EditSession; +} +//#endregion + +// +//#region Container ... +type TXIDEContainer = IXComponentCardContainer & IXComponentActionBarContainer; +export interface IXIDEContainer extends TXIDEContainer { + // + //#region Props ... + content: XStandardType; + minLines: XStandardType; + maxLines: XStandardType; + fontSize: XStandardType; + readonly: XStandardType; + contentChange: EventEmitter; + wordWrapping: XStandardType; + defaultTabName: XStandardType; + showLineNumbers: XStandardType; + showPrintMargin: XStandardType; + showFoldWidgets: XStandardType; + mode: XStandardType; + actionProvider: Subject; + theme: XStandardType; + highlightActiveLine: XStandardType; + basicAutocompletion: XStandardType; + hasCloseOnDefaultTab: XStandardType; + cssClass: XStandardType>; + + // + //#region Tabs ... + alignTabs: XStandardType; + tabHeaderColor: XStandardType; + activeTabColor: XStandardType; + //#endregion + + // + //#region ActionBar ... + defaultActionBarItemColor: XStandardType; + availableActionBarItems: XStandardType< + XOneOrManyType + >; + //#endregion + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +export class XIDEContainer extends XBaseContainer implements IXIDEContainer { + // + //#region Props ... + // + //#region Self Props ... + readonly content: XStandardType = ''; + readonly minLines: XStandardType = 10; + readonly fontSize: XStandardType = XIDEMinFontSize; + readonly readonly: XStandardType = false; + readonly actionProvider: Subject = + new Subject(); + readonly maxLines: XStandardType = Infinity; + readonly wordWrapping: XStandardType = true; + readonly cssClass: XStandardType>; + readonly showLineNumbers: XStandardType = true; + readonly showFoldWidgets: XStandardType = true; + readonly showPrintMargin: XStandardType = false; + readonly highlightActiveLine: XStandardType = true; + readonly basicAutocompletion: XStandardType = true; + readonly hasCloseOnDefaultTab: XStandardType = false; + readonly defaultTabName: XStandardType = XIDEDefaultTabName; + readonly mode: XStandardType = XIDEMode.Typescript; + readonly theme: XStandardType = XIDETheme.Dracula; + readonly contentChange: EventEmitter = new EventEmitter(); + + // + //#region Tabs ... + readonly alignTabs: XStandardType = XTabAlign.START; + readonly tabHeaderColor: XStandardType = + XColorWithBrightness.Dark; + readonly activeTabColor: XStandardType = + XColorWithBrightness.Warning; + //#endregion + + // + readonly availableActionBarItems: XStandardType< + XOneOrManyType + > = [ + { + actionId: XIDEActionBarItem.Save, + }, + { + actionId: XIDEActionBarItem.Close, + }, + { + actionId: XIDEActionBarItem.Undo, + }, + { + actionId: XIDEActionBarItem.Redo, + }, + { + actionId: XIDEActionBarItem.Beautify, + }, + { + actionId: XIDEActionBarItem.ChangeTheme, + }, + { + actionId: XIDEActionBarItem.ChangeMode, + }, + ]; + readonly defaultActionBarItemColor: XStandardType = + XColorWithBrightness.Dark; + //#endregion + + // + //#region ComponentCard Props ... + readonly isInPage: XStandardType = + this.cardPropertyProvider.isInPage; + readonly wrapWithCard: XStandardType = + this.cardPropertyProvider.wrapWithCard; + readonly cardActionProvider: Subject = + this.cardPropertyProvider.cardActionProvider; + readonly cardCssClass: XStandardType> = + this.cardPropertyProvider.cardCssClass; + readonly cardColor: XStandardType = + this.cardPropertyProvider.cardColor; + readonly cardForegroundColor: XStandardType = + this.cardPropertyProvider.cardForegroundColor; + readonly cardTitle: XStandardType = + this.cardPropertyProvider.cardTitle; + readonly cardSubTitle: XStandardType = + this.cardPropertyProvider.cardSubTitle; + readonly cardShowFooter: XStandardType = + this.cardPropertyProvider.cardShowFooter; + readonly cardShowActions: XStandardType = + this.cardPropertyProvider.cardShowActions; + //#endregion + + // + //#region ComponentActionBar Props ... + readonly showActionBar: XStandardType = + this.actionBarPropertyProvider.showActionBar; + readonly actionBarActionProvider: Subject = + this.actionBarPropertyProvider.actionBarActionProvider; + readonly actionBarCssClass: XStandardType> = + this.actionBarPropertyProvider.actionBarCssClass; + readonly actionBarColor: XStandardType = + this.actionBarPropertyProvider.actionBarColor; + readonly actionBarForegroundColor: XStandardType = + this.actionBarPropertyProvider.actionBarForegroundColor; + readonly actionBarActions: XStandardType> = + this.actionBarPropertyProvider.actionBarActions; + readonly actionBarActionFired: EventEmitter = + this.actionBarPropertyProvider.actionBarActionFired; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected cardPropertyProvider: XComponentCardContainer, + protected actionBarPropertyProvider: XComponentActionBarContainer + ) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export class XIDEContainerComponent + extends XBaseContainerComponent + implements IXIDEContainer +{ + // + //#region Props ... + // + //#region Self Props ... + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + defaultTabName: XStandardType = this.propertyProvider.defaultTabName; + + @Input() + hasCloseOnDefaultTab: XStandardType = this.propertyProvider.hasCloseOnDefaultTab; + + @Input() + theme: XStandardType = this.propertyProvider.theme; + + @Input() + mode: XStandardType = this.propertyProvider.mode; + + @Input() + minLines: XStandardType = this.propertyProvider.minLines; + + @Input() + maxLines: XStandardType = this.propertyProvider.maxLines; + + @Input() + fontSize: XStandardType = this.propertyProvider.fontSize; + + @Input() + readonly: XStandardType = this.propertyProvider.readonly; + + @Input() + highlightActiveLine: XStandardType = + this.propertyProvider.highlightActiveLine; + + @Input() + wordWrapping: XStandardType = this.propertyProvider.wordWrapping; + + @Input() + showLineNumbers: XStandardType = + this.propertyProvider.showLineNumbers; + + @Input() + showPrintMargin: XStandardType = + this.propertyProvider.showPrintMargin; + + @Input() + showFoldWidgets: XStandardType = + this.propertyProvider.showFoldWidgets; + + @Input() + basicAutocompletion: XStandardType = + this.propertyProvider.basicAutocompletion; + + @Input() + content: XStandardType = this.propertyProvider.content; + + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Output() + contentChange: EventEmitter = this.propertyProvider.contentChange; + + // + //#region Tabs ... + @Input() + alignTabs: XStandardType = + this.propertyProvider.alignTabs; + + @Input() + tabHeaderColor: XStandardType = + this.propertyProvider.tabHeaderColor; + + @Input() + activeTabColor: XStandardType = + this.propertyProvider.activeTabColor; + //#endregion + + // + //#region ActionBar ... + @Input() + defaultActionBarItemColor: XStandardType = + this.propertyProvider.defaultActionBarItemColor; + + @Input() + availableActionBarItems: XStandardType< + XOneOrManyType + > = this.propertyProvider.availableActionBarItems; + //#endregion + //#endregion + + // + //#region ComponentCard Props ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + //#endregion + + // + //#region ComponentActionBar Props ... + @Input() + showActionBar: XStandardType = this.propertyProvider.showActionBar; + + @Input() + actionBarCssClass: XStandardType> = + this.propertyProvider.actionBarCssClass; + + @Input() + actionBarColor: XStandardType = + this.propertyProvider.actionBarColor; + + @Input() + actionBarForegroundColor: XStandardType = + this.propertyProvider.actionBarForegroundColor; + + @Input() + actionBarActionProvider: Subject = + this.propertyProvider.actionBarActionProvider; + + @Output() + actionBarActionFired: EventEmitter = + this.propertyProvider.actionBarActionFired; + + actionBarActions: XStandardType> = + this.propertyProvider.actionBarActions; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XIDEContainer, + protected popoverService: XPopoverService + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstracts ... + public extractProps(container?: IXIDEContainer): Observable { + return forkJoin({ + // + //#region Self Props ... + minLines: this.getValue(container ? container.minLines : this.minLines), + maxLines: this.getValue(container ? container.maxLines : this.maxLines), + fontSize: this.getValue(container ? container.fontSize : this.fontSize), + readonly: this.getValue(container ? container.readonly : this.readonly), + content: this.getValue(container ? container.content : this.content), + mode: this.getValue(container ? container.mode : this.mode), + theme: this.getValue(container ? container.theme : this.theme), + highlightActiveLine: this.getValue( + container ? container.highlightActiveLine : this.highlightActiveLine + ), + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + basicAutocompletion: this.getValue( + container ? container.basicAutocompletion : this.basicAutocompletion + ), + wordWrapping: this.getValue( + container ? container.wordWrapping : this.wordWrapping + ), + showPrintMargin: this.getValue( + container ? container.showPrintMargin : this.showPrintMargin + ), + showLineNumbers: this.getValue( + container ? container.showLineNumbers : this.showLineNumbers + ), + showFoldWidgets: this.getValue( + container ? container.showFoldWidgets : this.showFoldWidgets + ), + defaultTabName: this.getValue( + container ? container.defaultTabName : this.defaultTabName + ), + hasCloseOnDefaultTab: this.getValue( + container ? container.hasCloseOnDefaultTab : this.hasCloseOnDefaultTab + ), + + // + //#region Tabs ... + alignTabs: this.getValue( + container ? container.alignTabs : this.alignTabs + ), + tabHeaderColor: this.getValue( + container ? container.tabHeaderColor : this.tabHeaderColor + ), + activeTabColor: this.getValue( + container ? container.activeTabColor : this.activeTabColor + ), + //#endregion + + // + // ActionBar ... + defaultActionBarItemColor: this.getValue( + container + ? container.defaultActionBarItemColor + : this.defaultActionBarItemColor + ), + availableActionBarItems: this.getArrayValue( + container + ? container.availableActionBarItems + : this.availableActionBarItems + ), + //#endregion + + // + //#region XComponentCard ... + cardCssClass: this.getArrayValue( + container ? container.cardCssClass : this.cardCssClass + ), + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + //#endregion + + // + //#region XComponentActionBar ... + showActionBar: this.getValue( + container ? container.showActionBar : this.showActionBar + ), + actionBarCssClass: this.getValue( + container ? container.actionBarCssClass : this.actionBarCssClass + ), + actionBarColor: this.getValue( + container ? container.actionBarColor : this.actionBarColor + ), + actionBarForegroundColor: this.getValue( + container + ? container.actionBarForegroundColor + : this.actionBarForegroundColor + ), + actionBarActions: this.getValue( + container ? container.actionBarActions : this.actionBarActions + ), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + + // + actionBarActionProvider: container + ? container.actionBarActionProvider + : this.actionBarActionProvider, + + // + contentChange: this.contentChange, + actionBarActionFired: this.actionBarActionFired, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-image-cropper\x-image-cropper.component.html + +
+ +
+
+   +
+ + + + + + + + + + + + +
+ +
+ + + + + + + + + + + +
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-image-cropper\x-image-cropper.component.scss + +:host::ng-deep .mat-dialog-container { + padding: 0 !important; + margin: 0 !important; +} + +x-icon, +mat-icon { + color: white; +} + + +:host { + display: flex; + position: relative; + + width: 100%; + max-width: 100%; + max-height: 100%; + + padding-top: 6px; + + overflow: hidden; + + text-align: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +:host > div { + position: relative; + width: 100%; +} + +:host div > img.source-image { + max-width: 98%; + max-height: 98%; +} + +.actions { + margin: 2px; + margin-top: 6px; +} + +.actions > button { + width: 36px; + min-width: 36px; + + height: 36px36px; + min-height: 36px; + + padding: 0; +} + +.cropper { + position: absolute; + display: flex; + color: #53535c; + background: transparent; + outline-color: rgba(255, 255, 255, 0.3); + outline-width: 100vw; + outline-style: solid; + touch-action: none; +} +@media (orientation: portrait) { + .cropper { + outline-width: 100vh; + } +} +.cropper:after { + position: absolute; + content: ''; + top: 0; + bottom: 0; + left: 0; + right: 0; + pointer-events: none; + border: dashed 1px; + opacity: 0.75; + color: inherit; + z-index: 1; +} +.cropper .move { + width: 100%; + cursor: move; + border: 1px solid rgba(255, 255, 255, 0.5); +} +.cropper .resize { + position: absolute; + display: inline-block; + line-height: 6px; + padding: 8px; + opacity: 0.85; + z-index: 1; +} +.cropper .resize .square { + display: inline-block; + background: #53535c; + width: 6px; + height: 6px; + border: 1px solid rgba(255, 255, 255, 0.5); + box-sizing: content-box; +} +.cropper .resize.topleft { + top: -12px; + left: -12px; + cursor: nwse-resize; +} +.cropper .resize.top { + top: -12px; + left: calc(50% - 12px); + cursor: ns-resize; +} +.cropper .resize.topright { + top: -12px; + right: -12px; + cursor: nesw-resize; +} +.cropper .resize.right { + top: calc(50% - 12px); + right: -12px; + cursor: ew-resize; +} +.cropper .resize.bottomright { + bottom: -12px; + right: -12px; + cursor: nwse-resize; +} +.cropper .resize.bottom { + bottom: -12px; + left: calc(50% - 12px); + cursor: ns-resize; +} +.cropper .resize.bottomleft { + bottom: -12px; + left: -12px; + cursor: nesw-resize; +} +.cropper .resize.left { + top: calc(50% - 12px); + left: -12px; + cursor: ew-resize; +} +.cropper .resize-bar { + position: absolute; + z-index: 1; +} +.cropper .resize-bar.top { + top: -11px; + left: 11px; + width: calc(100% - 22px); + height: 22px; + cursor: ns-resize; +} +.cropper .resize-bar.right { + top: 11px; + right: -11px; + height: calc(100% - 22px); + width: 22px; + cursor: ew-resize; +} +.cropper .resize-bar.bottom { + bottom: -11px; + left: 11px; + width: calc(100% - 22px); + height: 22px; + cursor: ns-resize; +} +.cropper .resize-bar.left { + top: 11px; + left: -11px; + height: calc(100% - 22px); + width: 22px; + cursor: ew-resize; +} +.cropper.rounded { + outline-color: transparent; +} +.cropper.rounded:after { + border-radius: 100%; + box-shadow: 0 0 0 100vw rgba(255, 255, 255, 0.3); +} +@media (orientation: portrait) { + .cropper.rounded:after { + box-shadow: 0 0 0 100vh rgba(255, 255, 255, 0.3); + } +} +.cropper.rounded .move { + border-radius: 100%; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-image-cropper\x-image-cropper.component.ts + +import { + NgZone, + Inject, + Component, + ViewChild, + Renderer2, + ElementRef, + HostListener, + ChangeDetectorRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + isPromise, + resizeCanvas, + XDialogResult, + isNullOrUndefined, + isNullOrEmptyString, + resetExifOrientation, + transformBase64BasedOnExifRotation, +} from 'x-framework-core'; +import { + XMoveStart, + XDimensions, + XOutputType, + XCropperPosition, + XImageCroppedEvent, + XImageCropperAction, + XImageCropperContainer, + XImageCropperProperties, + XImageCropperActionModel, + XImageCropperContainerComponent, +} from './x-image-cropper.typings'; +import { Subject, Subscription } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { SafeUrl, SafeStyle, DomSanitizer } from '@angular/platform-browser'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from './../config/x-framework-components.config'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-image-cropper', + templateUrl: './x-image-cropper.component.html', + styleUrls: ['./x-image-cropper.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XImageCropperComponent extends XImageCropperContainerComponent { + // + //#region Props ... + private originalImage: any; + private originalBase64: string; + private moveStart: XMoveStart; + private maxSize: XDimensions; + private originalSize: XDimensions; + private setImageMaxSizeRetries = 0; + private cropperScaledMinWidth = 20; + private cropperScaledMinHeight = 20; + + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + safeImgDataUrl: SafeUrl | string; + marginLeft: SafeStyle | string = '0px'; + marginRight: SafeStyle | string = '0px'; + imageVisible = false; + + // + @ViewChild('sourceImage', { static: false }) + sourceImage: ElementRef; + + // + //#region Host Listeners ... + @HostListener('window:resize') + onResize(): void { + this.resizeCropperPosition(); + this.setMaxSize(); + this.setCropperScaledMinSize(); + } + + @HostListener('document:mousemove', ['$event']) + @HostListener('document:touchmove', ['$event']) + moveImg(event: any): void { + if (this.moveStart.active) { + event.stopPropagation(); + event.preventDefault(); + if (this.moveStart.type === 'move') { + this.move(event); + this.checkCropperPosition(true); + } else if (this.moveStart.type === 'resize') { + this.resize(event); + this.checkCropperPosition(false); + } + this.changeDetector.detectChanges(); + } + } + + @HostListener('document:mouseup') + @HostListener('document:touchend') + moveStop(): void { + if (this.moveStart.active) { + this.moveStart.active = false; + this.doAutoCrop(); + } + } + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XImageCropperContainer, + private sanitizer: DomSanitizer, + @Inject(MAT_DIALOG_DATA) private data: any, + private dialogRef: MatDialogRef + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region LifeCycles ... + onInit() { + this.startUp(); + } + + onChanges(changeKeys: string[]): void { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region ImageFileChanged ... + const isImageFileChangedChanged = changeKeys.includes( + nameof('imageFileChanged') + ); + if (isImageFileChangedChanged) { + // + this.initCropper(); + if (this.imageFileChanged) { + this.loadImageFile(this.imageFileChanged); + } + } + //#endregion + + // + //#region Image Changed Event Changed ... + const isImageChangedEventChanged = changeKeys.includes( + nameof('imageChangedEvent') + ); + if (isImageChangedEventChanged) { + // + this.initCropper(); + if ( + this.imageChangedEvent && + this.imageChangedEvent.target && + this.imageChangedEvent.target.files && + this.imageChangedEvent.target.files.length > 0 + ) { + this.loadImageFile(this.imageChangedEvent.target.files[0]); + } + } + //#endregion + + // + //#region CropperChanged ... + const isCropperChanged = changeKeys.includes( + nameof('cropper') + ); + if (isCropperChanged) { + // + this.setMaxSize(); + this.setCropperScaledMinSize(); + this.checkCropperPosition(false); + this.doAutoCrop(); + this.changeDetector.markForCheck(); + } + //#endregion + + // + //#region Aspect Ratio Changed ... + const isAspectRatioChanged = changeKeys.includes( + nameof('aspectRatio') + ); + if (isAspectRatioChanged && this.imageVisible) { + this.resetCropperPosition(); + } + //#endregion + + // + //#region ImageBase64 Changed ... + const isImageBase64Changed = changeKeys.includes( + nameof('imageBase64') + ); + if (isImageBase64Changed) { + // + this.initCropper(); + if (!isNullOrEmptyString(this.imageBase64)) { + this.loadBase64Image(this.imageBase64); + } + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Handlers ... + handleImageLoadedInView(): void { + if (this.originalImage != null) { + this.imageLoaded.emit(); + this.setImageMaxSizeRetries = 0; + setTimeout(() => this.checkImageMaxSizeRecursively()); + } + } + + handleCancel(): void { + this.dialogRef.close({ + result: XDialogResult.Cancel, + }); + } + + handleOk(): void { + // + // Get Crop Result and make Dialog close + // Strategy based on it ... + const res = this.crop(); + + // + // if result is null ... + if (isNullOrUndefined(res)) { + // + this.dialogRef.close({ + event: null, + result: XDialogResult.Ok, + }); + // + // if result is Promise Object ... + } else if (isPromise(res)) { + // + (res as Promise).then((event) => { + this.dialogRef.close({ + event, + result: XDialogResult.Ok, + }); + }); + // + // if result ir ImagedCroppedEvent instance ... + } else { + this.dialogRef.close({ + event: res, + result: XDialogResult.Ok, + }); + } + } + + handleRotateLeft() { + this.rotateLeft(); + } + + handleFlipHorizontal() { + this.flipHorizontal(); + } + + handleFlipVertical() { + this.flipVertical(); + } + + handleRotateRight() { + this.rotateRight(); + } + + handleStartMove( + event: any, + moveType: string, + position: string | null = null + ): void { + event.preventDefault(); + this.moveStart = { + active: true, + type: moveType, + position, + clientX: this.getClientX(event), + clientY: this.getClientY(event), + ...this.cropper, + }; + } + //#endregion + + // + //#region Actions ... + rotateLeft() { + this.transformBase64(8); + } + + rotateRight() { + this.transformBase64(6); + } + + flipHorizontal() { + this.transformBase64(2); + } + + flipVertical() { + this.transformBase64(4); + } + + crop( + outputType: XOutputType = this.outputType + ): XImageCroppedEvent | Promise | null { + if (this.sourceImage.nativeElement && this.originalImage != null) { + this.startCropImage.emit(); + const imagePosition = this.getImagePosition(); + const width = imagePosition.x2 - imagePosition.x1; + const height = imagePosition.y2 - imagePosition.y1; + + const cropCanvas = document.createElement('canvas') as HTMLCanvasElement; + cropCanvas.width = width; + cropCanvas.height = height; + + const ctx = cropCanvas.getContext('2d'); + if (ctx) { + ctx.drawImage( + this.originalImage, + imagePosition.x1, + imagePosition.y1, + width, + height, + 0, + 0, + width, + height + ); + const output = { + width, + height, + imagePosition, + cropperPosition: { ...this.cropper }, + }; + const resizeRatio = this.getResizeRatio(width); + if (resizeRatio !== 1) { + output.width = Math.floor(width * resizeRatio); + output.height = Math.floor(height * resizeRatio); + resizeCanvas(cropCanvas, output.width, output.height); + } + + // + const result = this.cropToOutputType(outputType, cropCanvas, output); + return result; + } + } + return null; + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XImageCropperAction.Refresh: + this.detectChanges(); + break; + + // + case XImageCropperAction.RotateLeft: + this.rotateLeft(); + break; + + // + case XImageCropperAction.RotateRight: + this.rotateRight(); + break; + + // + case XImageCropperAction.FlipVertical: + this.flipVertical(); + break; + + // + case XImageCropperAction.FlipHorizontal: + this.flipHorizontal(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private startUp() { + // + // Handle Dialog Data ... + if (this.data) { + // + //#region Handle Properties ... + if (this.data.props) { + // + const props = this.data.props as XImageCropperProperties; + + // + if (props.imagefile) { + this.imageFileChanged = props.imagefile; + } + + // + if (props.imageChangedEvent) { + this.imageChangedEvent = props.imageChangedEvent; + } + + // + if (props.imageBase64) { + this.imageBase64 = props.imageBase64; + } + + // + if (props.format) { + this.format = props.format; + } + + // + if (props.outputType) { + this.outputType = props.outputType; + } + + // + if (props.alignImage) { + this.alignImage = props.alignImage; + } + + // + if (props.cropper) { + this.cropper = props.cropper; + } + + // + if (props.autoCrop) { + this.autoCrop = props.autoCrop; + } + + // + if (props.aspectRatio) { + this.aspectRatio = props.aspectRatio; + } + + // + if (props.imageQuality) { + this.imageQuality = props.imageQuality; + } + + // + if (props.resizeToWidth) { + this.resizeToWidth = props.resizeToWidth; + } + + // + if (props.roundCropper) { + this.roundCropper = props.roundCropper; + } + + // + if (props.onlyScaleDown) { + this.onlyScaleDown = props.onlyScaleDown; + } + + // + if (props.cropperMinWidth) { + this.cropperMinWidth = props.cropperMinWidth; + } + + // + if (props.cropperMinHeight) { + this.cropperMinHeight = props.cropperMinHeight; + } + + // + if (props.maintainAspectRatio) { + this.maintainAspectRatio = props.maintainAspectRatio; + } + } + //#endregion + } + + // + this.initCropper(); + if (this.imageFileChanged) { + this.loadImageFile(this.imageFileChanged); + } + } + + private initCropper(): void { + this.imageVisible = false; + this.originalImage = null; + this.safeImgDataUrl = + 'data:image/png;base64,iVBORw0KGg' + + 'oAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAU' + + 'AAarVyFEAAAAASUVORK5CYII='; + this.moveStart = { + active: false, + type: null, + position: null, + x1: 0, + y1: 0, + x2: 0, + y2: 0, + clientX: 0, + clientY: 0, + }; + this.maxSize = { + width: 0, + height: 0, + }; + this.originalSize = { + width: 0, + height: 0, + }; + this.cropper.x1 = -100; + this.cropper.y1 = -100; + this.cropper.x2 = 10000; + this.cropper.y2 = 10000; + } + + private loadImageFile(file: File): void { + const fileReader = new FileReader(); + fileReader.onload = (event: any) => { + const imageType = file.type; + if (this.isValidImageType(imageType)) { + resetExifOrientation(event.target.result) + .then((resultBase64: string) => this.loadBase64Image(resultBase64)) + .catch(() => this.loadImageFailed.emit()); + } else { + this.loadImageFailed.emit(); + } + }; + fileReader.readAsDataURL(file); + } + + private isValidImageType(type: string): boolean { + return /image\/(png|jpg|jpeg|bmp|gif|tiff)/.test(type); + } + + private loadBase64Image(imageBase64: string): void { + this.originalBase64 = imageBase64; + this.safeImgDataUrl = + this.sanitizer.bypassSecurityTrustResourceUrl(imageBase64); + this.originalImage = new Image(); + this.originalImage.onload = () => { + this.originalSize.width = this.originalImage.width; + this.originalSize.height = this.originalImage.height; + this.changeDetector.markForCheck(); + }; + this.originalImage.src = imageBase64; + } + + private checkImageMaxSizeRecursively(): void { + if (this.setImageMaxSizeRetries > 40) { + this.loadImageFailed.emit(); + } else if ( + this.sourceImage && + this.sourceImage.nativeElement && + this.sourceImage.nativeElement.offsetWidth > 0 + ) { + this.setMaxSize(); + this.setCropperScaledMinSize(); + this.resetCropperPosition(); + this.cropperReady.emit(); + this.changeDetector.markForCheck(); + } else { + this.setImageMaxSizeRetries++; + setTimeout(() => { + this.checkImageMaxSizeRecursively(); + }, 50); + } + } + + private transformBase64(exifOrientation: number): void { + if (this.originalBase64) { + transformBase64BasedOnExifRotation( + this.originalBase64, + exifOrientation + ).then((rotatedBase64: string) => this.loadBase64Image(rotatedBase64)); + } + } + + private resizeCropperPosition(): void { + const sourceImageElement = this.sourceImage.nativeElement; + if ( + this.maxSize.width !== sourceImageElement.offsetWidth || + this.maxSize.height !== sourceImageElement.offsetHeight + ) { + this.cropper.x1 = + (this.cropper.x1 * sourceImageElement.offsetWidth) / this.maxSize.width; + this.cropper.x2 = + (this.cropper.x2 * sourceImageElement.offsetWidth) / this.maxSize.width; + this.cropper.y1 = + (this.cropper.y1 * sourceImageElement.offsetHeight) / + this.maxSize.height; + this.cropper.y2 = + (this.cropper.y2 * sourceImageElement.offsetHeight) / + this.maxSize.height; + } + } + + private resetCropperPosition(): void { + const sourceImageElement = this.sourceImage.nativeElement; + if (!this.maintainAspectRatio) { + this.cropper.x1 = 0; + this.cropper.x2 = sourceImageElement.offsetWidth; + this.cropper.y1 = 0; + this.cropper.y2 = sourceImageElement.offsetHeight; + } else if ( + sourceImageElement.offsetWidth / this.aspectRatio < + sourceImageElement.offsetHeight + ) { + this.cropper.x1 = 0; + this.cropper.x2 = sourceImageElement.offsetWidth; + const cropperHeight = sourceImageElement.offsetWidth / this.aspectRatio; + this.cropper.y1 = (sourceImageElement.offsetHeight - cropperHeight) / 2; + this.cropper.y2 = this.cropper.y1 + cropperHeight; + } else { + this.cropper.y1 = 0; + this.cropper.y2 = sourceImageElement.offsetHeight; + const cropperWidth = sourceImageElement.offsetHeight * this.aspectRatio; + this.cropper.x1 = (sourceImageElement.offsetWidth - cropperWidth) / 2; + this.cropper.x2 = this.cropper.x1 + cropperWidth; + } + this.doAutoCrop(); + this.imageVisible = true; + } + + private setMaxSize(): void { + const sourceImageElement = this.sourceImage.nativeElement; + this.maxSize.width = sourceImageElement.offsetWidth; + this.maxSize.height = sourceImageElement.offsetHeight; + this.marginLeft = this.sanitizer.bypassSecurityTrustStyle( + 'calc(50% - ' + this.maxSize.width / 2 + 'px)' + ); + this.marginRight = this.sanitizer.bypassSecurityTrustStyle( + 'calc(50% - ' + this.maxSize.width / 2 + 'px)' + ); + } + + private setCropperScaledMinSize(): void { + if (this.originalImage) { + this.setCropperScaledMinWidth(); + this.setCropperScaledMinHeight(); + } else { + this.cropperScaledMinWidth = 20; + this.cropperScaledMinHeight = 20; + } + } + + private setCropperScaledMinWidth(): void { + this.cropperScaledMinWidth = + this.cropperMinWidth > 0 + ? Math.max( + 20, + (this.cropperMinWidth / this.originalImage.width) * + this.maxSize.width + ) + : 20; + } + + private setCropperScaledMinHeight(): void { + if (this.maintainAspectRatio) { + this.cropperScaledMinHeight = Math.max( + 20, + this.cropperScaledMinWidth / this.aspectRatio + ); + } else if (this.cropperMinHeight > 0) { + this.cropperScaledMinHeight = Math.max( + 20, + (this.cropperMinHeight / this.originalImage.height) * + this.maxSize.height + ); + } else { + this.cropperScaledMinHeight = 20; + } + } + + private checkCropperPosition(maintainSize = false): void { + if (this.cropper.x1 < 0) { + this.cropper.x2 -= maintainSize ? this.cropper.x1 : 0; + this.cropper.x1 = 0; + } + if (this.cropper.y1 < 0) { + this.cropper.y2 -= maintainSize ? this.cropper.y1 : 0; + this.cropper.y1 = 0; + } + if (this.cropper.x2 > this.maxSize.width) { + this.cropper.x1 -= maintainSize + ? this.cropper.x2 - this.maxSize.width + : 0; + this.cropper.x2 = this.maxSize.width; + } + if (this.cropper.y2 > this.maxSize.height) { + this.cropper.y1 -= maintainSize + ? this.cropper.y2 - this.maxSize.height + : 0; + this.cropper.y2 = this.maxSize.height; + } + } + + private move(event: any) { + const diffX = this.getClientX(event) - this.moveStart.clientX; + const diffY = this.getClientY(event) - this.moveStart.clientY; + + this.cropper.x1 = this.moveStart.x1 + diffX; + this.cropper.y1 = this.moveStart.y1 + diffY; + this.cropper.x2 = this.moveStart.x2 + diffX; + this.cropper.y2 = this.moveStart.y2 + diffY; + } + + private resize(event: any): void { + const diffX = this.getClientX(event) - this.moveStart.clientX; + const diffY = this.getClientY(event) - this.moveStart.clientY; + switch (this.moveStart.position) { + case 'left': + this.cropper.x1 = Math.min( + this.moveStart.x1 + diffX, + this.cropper.x2 - this.cropperScaledMinWidth + ); + break; + case 'topleft': + this.cropper.x1 = Math.min( + this.moveStart.x1 + diffX, + this.cropper.x2 - this.cropperScaledMinWidth + ); + this.cropper.y1 = Math.min( + this.moveStart.y1 + diffY, + this.cropper.y2 - this.cropperScaledMinHeight + ); + break; + case 'top': + this.cropper.y1 = Math.min( + this.moveStart.y1 + diffY, + this.cropper.y2 - this.cropperScaledMinHeight + ); + break; + case 'topright': + this.cropper.x2 = Math.max( + this.moveStart.x2 + diffX, + this.cropper.x1 + this.cropperScaledMinWidth + ); + this.cropper.y1 = Math.min( + this.moveStart.y1 + diffY, + this.cropper.y2 - this.cropperScaledMinHeight + ); + break; + case 'right': + this.cropper.x2 = Math.max( + this.moveStart.x2 + diffX, + this.cropper.x1 + this.cropperScaledMinWidth + ); + break; + case 'bottomright': + this.cropper.x2 = Math.max( + this.moveStart.x2 + diffX, + this.cropper.x1 + this.cropperScaledMinWidth + ); + this.cropper.y2 = Math.max( + this.moveStart.y2 + diffY, + this.cropper.y1 + this.cropperScaledMinHeight + ); + break; + case 'bottom': + this.cropper.y2 = Math.max( + this.moveStart.y2 + diffY, + this.cropper.y1 + this.cropperScaledMinHeight + ); + break; + case 'bottomleft': + this.cropper.x1 = Math.min( + this.moveStart.x1 + diffX, + this.cropper.x2 - this.cropperScaledMinWidth + ); + this.cropper.y2 = Math.max( + this.moveStart.y2 + diffY, + this.cropper.y1 + this.cropperScaledMinHeight + ); + break; + } + + if (this.maintainAspectRatio) { + this.checkAspectRatio(); + } + } + + private checkAspectRatio(): void { + let overflowX = 0; + let overflowY = 0; + + switch (this.moveStart.position) { + case 'top': + this.cropper.x2 = + this.cropper.x1 + + (this.cropper.y2 - this.cropper.y1) * this.aspectRatio; + overflowX = Math.max(this.cropper.x2 - this.maxSize.width, 0); + overflowY = Math.max(0 - this.cropper.y1, 0); + if (overflowX > 0 || overflowY > 0) { + this.cropper.x2 -= + overflowY * this.aspectRatio > overflowX + ? overflowY * this.aspectRatio + : overflowX; + this.cropper.y1 += + overflowY * this.aspectRatio > overflowX + ? overflowY + : overflowX / this.aspectRatio; + } + break; + case 'bottom': + this.cropper.x2 = + this.cropper.x1 + + (this.cropper.y2 - this.cropper.y1) * this.aspectRatio; + overflowX = Math.max(this.cropper.x2 - this.maxSize.width, 0); + overflowY = Math.max(this.cropper.y2 - this.maxSize.height, 0); + if (overflowX > 0 || overflowY > 0) { + this.cropper.x2 -= + overflowY * this.aspectRatio > overflowX + ? overflowY * this.aspectRatio + : overflowX; + this.cropper.y2 -= + overflowY * this.aspectRatio > overflowX + ? overflowY + : overflowX / this.aspectRatio; + } + break; + case 'topleft': + this.cropper.y1 = + this.cropper.y2 - + (this.cropper.x2 - this.cropper.x1) / this.aspectRatio; + overflowX = Math.max(0 - this.cropper.x1, 0); + overflowY = Math.max(0 - this.cropper.y1, 0); + if (overflowX > 0 || overflowY > 0) { + this.cropper.x1 += + overflowY * this.aspectRatio > overflowX + ? overflowY * this.aspectRatio + : overflowX; + this.cropper.y1 += + overflowY * this.aspectRatio > overflowX + ? overflowY + : overflowX / this.aspectRatio; + } + break; + case 'topright': + this.cropper.y1 = + this.cropper.y2 - + (this.cropper.x2 - this.cropper.x1) / this.aspectRatio; + overflowX = Math.max(this.cropper.x2 - this.maxSize.width, 0); + overflowY = Math.max(0 - this.cropper.y1, 0); + if (overflowX > 0 || overflowY > 0) { + this.cropper.x2 -= + overflowY * this.aspectRatio > overflowX + ? overflowY * this.aspectRatio + : overflowX; + this.cropper.y1 += + overflowY * this.aspectRatio > overflowX + ? overflowY + : overflowX / this.aspectRatio; + } + break; + case 'right': + case 'bottomright': + this.cropper.y2 = + this.cropper.y1 + + (this.cropper.x2 - this.cropper.x1) / this.aspectRatio; + overflowX = Math.max(this.cropper.x2 - this.maxSize.width, 0); + overflowY = Math.max(this.cropper.y2 - this.maxSize.height, 0); + if (overflowX > 0 || overflowY > 0) { + this.cropper.x2 -= + overflowY * this.aspectRatio > overflowX + ? overflowY * this.aspectRatio + : overflowX; + this.cropper.y2 -= + overflowY * this.aspectRatio > overflowX + ? overflowY + : overflowX / this.aspectRatio; + } + break; + case 'left': + case 'bottomleft': + this.cropper.y2 = + this.cropper.y1 + + (this.cropper.x2 - this.cropper.x1) / this.aspectRatio; + overflowX = Math.max(0 - this.cropper.x1, 0); + overflowY = Math.max(this.cropper.y2 - this.maxSize.height, 0); + if (overflowX > 0 || overflowY > 0) { + this.cropper.x1 += + overflowY * this.aspectRatio > overflowX + ? overflowY * this.aspectRatio + : overflowX; + this.cropper.y2 -= + overflowY * this.aspectRatio > overflowX + ? overflowY + : overflowX / this.aspectRatio; + } + break; + } + } + + private doAutoCrop(): void { + if (this.autoCrop) { + this.crop(); + } + } + + private getImagePosition(): XCropperPosition { + const sourceImageElement = this.sourceImage.nativeElement; + const ratio = this.originalSize.width / sourceImageElement.offsetWidth; + return { + x1: Math.round(this.cropper.x1 * ratio), + y1: Math.round(this.cropper.y1 * ratio), + x2: Math.min( + Math.round(this.cropper.x2 * ratio), + this.originalSize.width + ), + y2: Math.min( + Math.round(this.cropper.y2 * ratio), + this.originalSize.height + ), + }; + } + + private cropToOutputType( + outputType: XOutputType, + cropCanvas: HTMLCanvasElement, + output: XImageCroppedEvent + ): XImageCroppedEvent | Promise { + switch (outputType) { + case 'file': + return this.cropToFile(cropCanvas).then((result: Blob | null) => { + output.file = result; + this.imageCropped.emit(output); + return output; + }); + case 'both': + return this.cropToFile(cropCanvas).then((result: Blob | null) => { + output.base64 = this.cropToBase64(cropCanvas); + output.file = result; + this.imageCropped.emit(output); + return output; + }); + default: + output.base64 = this.cropToBase64(cropCanvas); + this.imageCropped.emit(output); + return output; + } + } + + private cropToBase64(cropCanvas: HTMLCanvasElement): string { + const imageBase64 = cropCanvas.toDataURL( + 'image/' + this.format, + this.getQuality() + ); + this.imageCroppedBase64.emit(imageBase64); + return imageBase64; + } + + private cropToFile(cropCanvas: HTMLCanvasElement): Promise { + return this.getCanvasBlob(cropCanvas).then((result: Blob | null) => { + if (result) { + this.imageCroppedFile.emit(result); + } + return result; + }); + } + + private getCanvasBlob(cropCanvas: HTMLCanvasElement): Promise { + return new Promise((resolve) => { + cropCanvas.toBlob( + (result: Blob | null) => this.zone.run(() => resolve(result)), + 'image/' + this.format, + this.getQuality() + ); + }); + } + + private getQuality(): number { + return Math.min(1, Math.max(0, this.imageQuality / 100)); + } + + private getResizeRatio(width: number): number { + return this.resizeToWidth > 0 && + (!this.onlyScaleDown || width > this.resizeToWidth) + ? this.resizeToWidth / width + : 1; + } + + private getClientX(event: any): number { + return ( + event.clientX || + (event.touches && event.touches[0] && event.touches[0].clientX) + ); + } + + private getClientY(event: any): number { + return ( + event.clientY || + (event.touches && event.touches[0] && event.touches[0].clientY) + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-image-cropper\x-image-cropper.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XImageCropperService } from './x-image-cropper.service'; +import { XImageCropperComponent } from './x-image-cropper.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + providers: [XImageCropperService], + declarations: [XImageCropperComponent], + entryComponents: [XImageCropperComponent], + imports: [CommonModule, XBaseComponentModule, XIconModule], + exports: [XBaseComponentModule, XIconModule, XImageCropperComponent] +}) +export class XImageCropperModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-image-cropper\x-image-cropper.service.ts + +import { + toFileBlob, + blobToFile, + XDialogResult, + isNullOrUndefined, +} from 'x-framework-core'; +import { + MatDialog, + MatDialogRef, + MatDialogConfig, +} from '@angular/material/dialog'; +import { + XImageCroppedEvent, + XImageCropperResult, + XImageCropperProperties, +} from './x-image-cropper.typings'; +import { XImageCropperComponent } from './x-image-cropper.component'; +import { Injectable, Output, EventEmitter, Inject } from '@angular/core'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XImageCropperService { + // + //#region Props ... + // + private dialogMaxWidth: number; + private dialogMinWidth: number; + + // + private sourceFile: File; + private DIALOG_REF: MatDialogRef; + + // + // tslint:disable-next-line:no-output-on-prefix + @Output() + onResult = new EventEmitter(); + + // + minAllowedImageSize: number; + maxAllowedImageSize: number; + //#endregion + + // + //#region Constructor ... + constructor( + private dialog: MatDialog, + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig + ) { + // + this.minAllowedImageSize = config.minAllowedImageSize; + this.maxAllowedImageSize = config.maxAllowedImageSize; + + // + this.dialogMinWidth = Math.min(window.innerWidth * 0.8, 300); + this.dialogMaxWidth = Math.min(window.innerWidth * 0.8, 400); + } + //#endregion + + // + //#region Public Actions ... + /** + * show Image Cropper ... + * @param file the source image file ... + * @param props Image Cropper Properties ... + */ + showCropper(file: File, props?: XImageCropperProperties) { + // + this.sourceFile = file; + + // + // Prepare Cropper Props ... + let cropperProps: XImageCropperProperties; + if (props) { + cropperProps = props; + if (!props.imagefile) { + cropperProps.imagefile = file; + } + cropperProps.maxSingleFileThumbSize = this.maxAllowedImageSize; + } else { + cropperProps = this.getDefaultCropperProperties(file); + } + + // + const config: MatDialogConfig = { + width: this.dialogMinWidth + 'px', + minWidth: this.dialogMinWidth + 'px', + maxWidth: this.dialogMaxWidth + 'px', + data: { + props: cropperProps, + }, + }; + + // + // Show Cropper inside a Dialog ... + this.DIALOG_REF = this.dialog.open(XImageCropperComponent, config); + this.DIALOG_REF.disableClose = true; + + // + //#region Register Event Handlers ... + // + // Subscribe to Event Listener for catching Cropper Change ... + const cropperComponent = this.DIALOG_REF + ? this.DIALOG_REF.componentInstance + : null; + if (cropperComponent) { + // + if (cropperProps.startCropImage) { + cropperComponent.startCropImage.asObservable().subscribe(() => { + cropperProps.startCropImage(); + }); + } + + // + if (cropperProps.imageCropped) { + cropperComponent.imageCropped + .asObservable() + .subscribe((event: XImageCroppedEvent) => { + cropperProps.imageCropped(event); + }); + } + + // + if (cropperProps.imageCroppedBase64) { + cropperComponent.imageCroppedBase64 + .asObservable() + .subscribe((data: string) => { + cropperProps.imageCroppedBase64(data); + }); + } + + // + if (cropperProps.imageCroppedFile) { + cropperComponent.imageCroppedFile + .asObservable() + .subscribe((mFile: Blob) => { + cropperProps.imageCroppedFile(mFile); + }); + } + + // + if (cropperProps.imageLoaded) { + cropperComponent.imageLoaded.asObservable().subscribe(() => { + cropperProps.imageLoaded(); + }); + } + + // + if (cropperProps.cropperReady) { + cropperComponent.cropperReady.asObservable().subscribe(() => { + cropperProps.cropperReady(); + }); + } + + // + if (cropperProps.loadImageFailed) { + cropperComponent.loadImageFailed.asObservable().subscribe(() => { + cropperProps.loadImageFailed(); + }); + } + } + //#endregion + + // + // Handle Dialog Result ... + this.DIALOG_REF.afterClosed().subscribe((result) => { + // + if (isNullOrUndefined(result) || result.result !== XDialogResult.Ok) { + // + // Cancel Cropper ... + this.onResult.emit({ result: XDialogResult.Cancel }); + } else { + // + // Use Cropper Result ... + const res: XImageCroppedEvent = result.event as XImageCroppedEvent; + + // + let mFile: File; + if (res.file) { + // + mFile = this.toFile(res.file); + this.sourceFile = mFile; + } + + // + this.onResult.emit({ + result: result.result, + data: res.base64, + file: mFile, + }); + } + }); + } + + /** + * close opened Dialog manually ... + */ + closeCropper() { + // + if (this.DIALOG_REF != null) { + this.DIALOG_REF.close(); + } + + // + this.DIALOG_REF = null; + } + //#endregion + + // + //#region Private Actions ... + /** + * Prepare Default Properties for Image Cropper ... + */ + private getDefaultCropperProperties(file: File) { + return { + // + imagefile: file, + + // + aspectRatio: 1, + maintainAspectRatio: true, + + // + resizeToWidth: this.dialogMinWidth, + cropperMinWidth: this.minAllowedImageSize, + cropperMinHeight: this.minAllowedImageSize, + + // + onlyScaleDown: true, + roundCropper: true, + + // + format: 'png', + outputType: 'both', + + // + maxSingleFileThumbSize: this.maxAllowedImageSize, + } as XImageCropperProperties; + } + + /** + * Generate a File Blob Object based on Source Image and Cropped file ... + */ + private toFile(file: Blob): File { + // + if (!this.sourceFile) { + return null; + } + + // + const fileBlob = toFileBlob(this.sourceFile); + + // + return blobToFile(file, fileBlob.name); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-image-cropper\x-image-cropper.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XDialogResult, XOneOrManyType, XStandardType } from 'x-framework-core'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region Definitions ... +export interface XCropperPosition { + x1: number; + y1: number; + x2: number; + y2: number; +} + +export interface XDimensions { + width: number; + height: number; +} + +export interface XImageCroppedEvent { + base64?: string | null; + file?: Blob | null; + width: number; + height: number; + cropperPosition: XCropperPosition; + imagePosition: XCropperPosition; +} + +export interface XMoveStart { + active: boolean; + type: string | null; + position: string | null; + x1: number; + y1: number; + x2: number; + y2: number; + clientX: number; + clientY: number; +} + +export interface XImageCropperProperties { + // + imageChangedEvent?: (event: any) => void; + + // + imagefile?: File; + imageBase64?: string; + + // + format?: XImageFormat; + outputType?: XOutputType; + alignImage?: XImageAlign; + cropper?: XCropperPosition; + + // + autoCrop?: boolean; + aspectRatio?: number; + imageQuality?: number; + resizeToWidth?: number; + roundCropper?: boolean; + onlyScaleDown?: boolean; + cropperMinWidth?: number; + cropperMinHeight?: number; + maintainAspectRatio?: boolean; + + // + maxSingleFileThumbSize: number; + + // + startCropImage?: () => void; + imageCropped?: (event: XImageCroppedEvent) => void; + imageCroppedBase64?: (data: string) => void; + imageCroppedFile?: (file: Blob) => void; + imageLoaded?: () => void; + cropperReady?: () => void; + loadImageFailed?: () => void; +} + +export interface XImageCropperResult { + result: XDialogResult; + data?: string; + file?: File; +} + +export type XOutputType = 'base64' | 'file' | 'both'; + +export type XImageFormat = 'png' | 'jpeg' | 'bmp' | 'webp' | 'ico'; + +export type XImageAlign = 'left' | 'center'; +//#endregion + +export enum XImageCropperAction { + // + RotateLeft = 'rotate-left', + RotateRight = 'rotate-right', + FlipHorizontal = 'flip-horizontal', + FlipVertical = 'flip-vertical', + + // + Refresh = 'refresh', +} + +export type XImageCropperActionIdentifier = XImageCropperAction | string; + +export interface XImageCropperActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXImageCropperContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + imageFileChanged: File; + imageChangedEvent: any; + imageBase64: string; + format: XImageFormat; + outputType: XOutputType; + maintainAspectRatio: boolean; + aspectRatio: number; + resizeToWidth: number; + cropperMinWidth: number; + cropperMinHeight: number; + roundCropper: boolean; + onlyScaleDown: boolean; + imageQuality: number; + autoCrop: boolean; + alignImage: XImageAlign; + cropper: XCropperPosition; + + // + startCropImage: EventEmitter; + imageCropped: EventEmitter; + imageCroppedBase64: EventEmitter; + imageCroppedFile: EventEmitter; + imageLoaded: EventEmitter; + cropperReady: EventEmitter; + loadImageFailed: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XImageCropperContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly imageFileChanged: File; + readonly imageChangedEvent: any; + readonly imageBase64: string; + readonly format: XImageFormat = 'png'; + readonly outputType: XOutputType = 'both'; + readonly maintainAspectRatio: boolean = true; + readonly aspectRatio: number = 1; + readonly resizeToWidth: number = 0; + readonly cropperMinWidth: number = 0; + readonly cropperMinHeight: number = 0; + readonly roundCropper: boolean = false; + readonly onlyScaleDown: boolean = false; + readonly imageQuality: number = 92; + readonly autoCrop: boolean = true; + readonly alignImage: XImageAlign = 'center'; + readonly cropper: XCropperPosition = { + x1: -100, + y1: -100, + x2: 10000, + y2: 10000, + }; + + // + readonly startCropImage: EventEmitter = new EventEmitter(); + readonly imageCropped: EventEmitter = + new EventEmitter(); + readonly imageCroppedBase64: EventEmitter = + new EventEmitter(); + readonly imageCroppedFile: EventEmitter = new EventEmitter(); + readonly imageLoaded: EventEmitter = new EventEmitter(); + readonly cropperReady: EventEmitter = new EventEmitter(); + readonly loadImageFailed: EventEmitter = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XImageCropperContainerComponent + extends XBaseContainerComponent + implements IXImageCropperContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + imageFileChanged: File = this.propertyProvider.imageFileChanged; + + @Input() + imageChangedEvent: any = this.propertyProvider.imageChangedEvent; + + @Input() + imageBase64: string = this.propertyProvider.imageBase64; + + @Input() + format: XImageFormat = this.propertyProvider.format; + + @Input() + outputType: XOutputType = this.propertyProvider.outputType; + + @Input() + maintainAspectRatio: boolean = this.propertyProvider.maintainAspectRatio; + + @Input() + aspectRatio: number = this.propertyProvider.aspectRatio; + + @Input() + resizeToWidth: number = this.propertyProvider.resizeToWidth; + + @Input() + cropperMinWidth: number = this.propertyProvider.cropperMinWidth; + + @Input() + cropperMinHeight: number = this.propertyProvider.cropperMinHeight; + + @Input() + roundCropper: boolean = this.propertyProvider.roundCropper; + + @Input() + onlyScaleDown: boolean = this.propertyProvider.onlyScaleDown; + + @Input() + imageQuality: number = this.propertyProvider.imageQuality; + + @Input() + autoCrop: boolean = this.propertyProvider.autoCrop; + + @Input() + alignImage: XImageAlign = this.propertyProvider.alignImage; + + @Input() + cropper: XCropperPosition = this.propertyProvider.cropper; + + // + @Output() + startCropImage: EventEmitter = new EventEmitter(); + + @Output() + imageCropped: EventEmitter = new EventEmitter(); + + @Output() + imageCroppedBase64: EventEmitter = new EventEmitter(); + + @Output() + imageCroppedFile: EventEmitter = new EventEmitter(); + + @Output() + imageLoaded: EventEmitter = new EventEmitter(); + + @Output() + cropperReady: EventEmitter = new EventEmitter(); + + @Output() + loadImageFailed: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XImageCropperContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXImageCropperContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + imageFileChanged: this.getValue( + container ? container.imageFileChanged : this.imageFileChanged + ), + imageChangedEvent: this.getValue( + container ? container.imageChangedEvent : this.imageChangedEvent + ), + imageBase64: this.getValue( + container ? container.imageBase64 : this.imageBase64 + ), + format: this.getValue(container ? container.format : this.format), + outputType: this.getValue( + container ? container.outputType : this.outputType + ), + maintainAspectRatio: this.getValue( + container ? container.maintainAspectRatio : this.maintainAspectRatio + ), + aspectRatio: this.getValue( + container ? container.aspectRatio : this.aspectRatio + ), + resizeToWidth: this.getValue( + container ? container.resizeToWidth : this.resizeToWidth + ), + cropperMinWidth: this.getValue( + container ? container.cropperMinWidth : this.cropperMinWidth + ), + cropperMinHeight: this.getValue( + container ? container.cropperMinHeight : this.cropperMinHeight + ), + roundCropper: this.getValue( + container ? container.roundCropper : this.roundCropper + ), + onlyScaleDown: this.getValue( + container ? container.onlyScaleDown : this.onlyScaleDown + ), + imageQuality: this.getValue( + container ? container.imageQuality : this.imageQuality + ), + autoCrop: this.getValue(container ? container.autoCrop : this.autoCrop), + alignImage: this.getValue( + container ? container.alignImage : this.alignImage + ), + cropper: this.getValue(container ? container.cropper : this.cropper), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + startCropImage: this.startCropImage, + imageCropped: this.imageCropped, + imageCroppedBase64: this.imageCroppedBase64, + imageCroppedFile: this.imageCroppedFile, + imageLoaded: this.imageLoaded, + cropperReady: this.cropperReady, + loadImageFailed: this.loadImageFailed, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-list\x-list.component.html + + + + + + + + +
+ +
+
+
+ + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ getResourceValue(option.title) | async }} + + + + + + + + {{ getResourceValue(option.title) | async }} + + + + + + +
{{ item | json }}
+
+ + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-list\x-list.component.scss + +ion-item-option > ion-item { + --background: transparent !important; +} + +ion-list, +ion-item.item, +ion-item-divider { + --background: transparent !important; +} + +ion-item-divider { + --color: var(--ion-color-dark-shade); +} + +ion-item.item.mobile { + --ion-safe-area-end: 0px; + + --padding-start: 8px; + --inner-padding-start: 0px; + + --padding-end: 8px; + --inner-padding-end: 0px; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; +} + +.x-menu { + z-index: 10; +} + +.x-check { + z-index: 1; +} + +x-card { + width: inherit; + height: inherit; +} + +:host ::ng-deep .button-solid { + --color: unset !important; + --background: unset !important; + --box-shadow: unset !important; +} + +.x-list-toolbar { + &.sticky { + top: 0; + position: sticky; + position: -webkit-sticky; + z-index: 200; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-list\x-list.component.ts + +import { + nameof, + toArray, + hasChild, + isFunction, + removeItem, + XOneOrManyType, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XListItem, + XListAction, + XListActionModel, + XListItemSlideOption, + XListContainerComponent, +} from './x-list.typings'; +import { + XActionBarItem, + XActionBarAction, + XActionBarActionModel, + DefaultActionBarItemIds, + defaultActionBarAddItem, + defaultActionBarRemoveItem, + XActionBarActionFiredModel, + defaultActionBarSelectAllItem, + defaultActionBarDeSelectAllItem, + defaultActionBarInverseSelectionItem, +} from '../x-action-bar/x-action-bar.typings'; +import { map, concatMap } from 'rxjs/operators'; +import { IonItemSliding } from '@ionic/angular'; +import { + XPopoverResult, + XPopoverOption, + XPopoverComponentProps, +} from '../dialogs/x-popover/x-popover.typings'; +import { moveItemInArray } from '@angular/cdk/drag-drop'; +import { XButtonType } from '../x-button/x-button.typings'; +import { Subject, Subscription, BehaviorSubject, of } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { toPopoverOptions } from '../dialogs/x-popover/x-popover.tools'; +import { XSearchBarAction } from '../x-search-bar/x-search-bar.typings'; +import { XPopoverComponent } from '../dialogs/x-popover/x-popover.component'; + +@Component({ + // tslint:disable-next-line: component-selector + selector: 'x-list', + templateUrl: './x-list.component.html', + styleUrls: ['./x-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XListComponent extends XListContainerComponent { + // + //#region Props ... + // + private SELECTED_ITEMS: XListItem[] = []; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + private ACTION_BAR_ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + private HIDE_ADD = false; + private DISABLE_ADD = false; + private addDisablerSubject = new BehaviorSubject(false); + + // + private HIDE_REMOVE = false; + private DISABLE_REMOVE = false; + private removeDisablerSubject = new BehaviorSubject(true); + + // + private HIDE_SELECT_ALL = false; + private DISABLE_SELECT_ALL = false; + private selectAllDisablerSubject = new BehaviorSubject(true); + + // + private HIDE_DESELECT_ALL = false; + private DISABLE_DESELECT_ALL = false; + private deSelectAllDisablerSubject = new BehaviorSubject(true); + + // + private HIDE_INVERSE_SELECTION = false; + private DISABLE_INVERSE_SELECTION = false; + private inverseSelectionDisablerSubject = new BehaviorSubject(true); + + // + readonly ButtonTypes = Object.assign({}, XButtonType); + + // + filteredItems: XListItem[] = []; + + // + get count() { + return this.items ? this.getValue(this.items) : of(0); + } + + get filteredItemsCount() { + return this.filteredItems ? this.filteredItems.length : 0; + } + + get selectedItems() { + return this.SELECTED_ITEMS; + } + //#endregion + + // + //#region LifeCycle ... + async onInit() { + super.onInit(); + + // + // await this.handleStartup(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + const isActionBarActionProviderChanged = changeKeys.includes( + nameof('actionBarActionProvider') + ); + if (isActionProviderChanged || isActionBarActionProviderChanged) { + this.registerActionProvider(); + } + + // + const isItemsChanged = changeKeys.includes(nameof('items')); + if (isItemsChanged) { + // + await this.handleStartup(); + this.itemsChanged.emit(); + } + + // + const isActionBarActionsChanged = changeKeys.includes( + nameof('actionBarActions') + ); + const isActionBarHideActionsChanged = changeKeys.includes( + nameof('actionBarHideActions') + ); + if (isActionBarActionsChanged || isActionBarHideActionsChanged) { + await this.prepareActionBarActions(); + } + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + getStartSlides(item: XListItem) { + return item && item.slideOptions + ? item.slideOptions.filter((opt) => opt.slot === 'start') + : null; + } + + getEndSlides(item: XListItem) { + return item && item.slideOptions + ? item.slideOptions.filter((opt) => opt.slot === 'end') + : null; + } + + getOptionSlot(option: XListItemSlideOption) { + return this.getOptionOnlyIcon(option).pipe( + map((onlyIcon) => { + return onlyIcon ? 'icon-only' : ''; + }) + ); + } + + getOptionIconSlot(option: XListItemSlideOption) { + return this.getOptionOnlyIcon(option).pipe( + concatMap((onlyIcon) => { + return this.getOptionSlot(option).pipe( + map((slot) => { + return onlyIcon ? '' : slot; + }) + ); + }) + ); + } + + getNonSlidingOptionIconSlot(option: XListItemSlideOption) { + return this.getOptionOnlyIcon(option).pipe( + map((onlyIcon) => { + return onlyIcon ? 'icon-only' : 'end'; + }) + ); + } + + getOptionOnlyIcon(option: XListItemSlideOption) { + return this.getValue(!isNullOrUndefined(option) ? option.onlyIcon : false); + } + + getIonItemLine() { + return this.getValue(this.showDivider).pipe( + map((showDivider) => (showDivider ? 'full' : 'none')) + ); + } + //#endregion + + // + //#region UI Handlers ... + handleActionItemSelected(item: XListItem) { + // + if (this.selectableItems) { + item.isSelected = !item.isSelected; + this.issueSelectionChanged(); + } + + // + this.itemSelected.emit(item); + } + + handleDoReorderItems(ev: CustomEvent | any) { + // + const to = ev.detail.to; + // tslint:disable-next-line:no-shadowed-variable + const from = ev.detail.from; + moveItemInArray(this.filteredItems, from, to); + + // + this.orderChanged.emit(); + ev.detail.complete(); + } + + async handleMoreButtonClicked( + event: Event, + slide: IonItemSliding, + item: XListItem + ) { + // + if (!isNullOrUndefined(slide)) { + await slide.closeOpened(); + } + + // + // Disable Only Icons for Menu Options ... + const options: XPopoverOption[] = toArray(toPopoverOptions(item)); + options.forEach((o) => { + // + o.onlyIcon = false; + + // + o.slot = 'end'; + + // + o.handler = undefined; + }); + + // + // Prepare PopOver Component Props ... + const componentProps: XPopoverComponentProps = { + options, + }; + + // + // Present Context Menu Options ... + const popOver = await this.managerService.dialogService.presentPopover({ + event, + componentProps, + translucent: true, + showBackdrop: true, + component: XPopoverComponent, + }); + + // + // Extract Data from PopOver ... + const { data } = await popOver.onWillDismiss(); + if (!data) { + return; + } + + // + const result = data as XPopoverResult; + if ( + isNullOrUndefined(result.option) || + isNullOrEmptyString(result.option.id) + ) { + return; + } + + // + this.handleActionOptionSelected(slide, result.option.id, item); + } + + async handleActionOptionSelected( + slide: IonItemSliding, + id: string, + item: XListItem + ) { + // + if (isNullOrEmptyString(id) || isNullOrUndefined(item)) { + return; + } + + // + // if (!isNullOrUndefined) + const action = item.slideOptions.find((i) => i.id === id); + if ( + !isNullOrUndefined(action) && + !isNullOrUndefined(action.handler) && + isFunction(action.handler) + ) { + await this.getValueAsync(action.handler()); + } + + // + if (this.closeOptionsAfterSelect && !isNullOrUndefined(slide)) { + // + slide.closeOpened().then(() => { + // + this.optionSelected.emit({ + id, + data: item, + }); + }); + } else { + // + this.optionSelected.emit({ + id, + data: item, + }); + } + } + + async handleActionRemoveItems() { + // + if (!this.hasChild(this.SELECTED_ITEMS)) { + return; + } + + // + const items = this.SELECTED_ITEMS.map((i) => i.data); + + // + const confirmRemove = await this.getValueAsync(this.confirmRemove); + if (confirmRemove) { + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + () => { + this.removeItems.emit({ + items, + }); + } + ); + } else { + this.removeItems.emit({ + items, + }); + } + } + + async handleActionBarActionFired(model: XActionBarActionFiredModel) { + // + if (isNullOrUndefined(model) || isNullOrEmptyString(model.action)) { + return; + } + + // + switch (model.action) { + // + case DefaultActionBarItemIds.SelectAll: + await this.selectAll(); + break; + + // + case DefaultActionBarItemIds.DeSelectAll: + await this.deselectAll(); + break; + + // + case DefaultActionBarItemIds.InverseSelection: + await this.inverseSelection(); + break; + + // + case DefaultActionBarItemIds.Remove: + // + await this.handleActionRemoveItems(); + break; + } + + // + this.actionBarActionFired.emit(model); + } + //#endregion + + // + //#region Actions ... + async selectAll() { + // + this.filteredItems.forEach((item) => { + if (!item.disabled) { + // + item.isSelected = true; + this.changeDetector.markForCheck(); + } + }); + + // + this.issueSelectionChanged(); + this.handleChangeDetection(); + } + + async deselectAll() { + // + this.filteredItems.forEach((item) => { + if (!item.disabled) { + // + item.isSelected = false; + this.changeDetector.markForCheck(); + } + }); + + // + this.issueSelectionChanged(); + this.handleChangeDetection(); + } + + async inverseSelection() { + // + this.filteredItems.forEach((item) => { + if (!item.disabled) { + // + item.isSelected = !item.isSelected; + this.changeDetector.markForCheck(); + } + }); + + // + this.issueSelectionChanged(); + this.handleChangeDetection(); + } + + async clearFilter(fill = false) { + this.filteredItems = []; + + // + if (fill) { + // + const items = await this.getValueAsync(this.items); + this.filteredItems = items; + } + + // + this.handleChangeDetection(); + } + + async filterItems(query: string) { + // + this.searchBarQueryChange.emit(query); + + // + await this.clearFilter(); + const items = await this.getValueAsync(this.items); + + // + if (isNullOrEmptyString(query)) { + // + this.filteredItems = items; + this.handleChangeDetection(); + return; + } + + // + const result = await this.getValueAsync( + this.filteringHandler(query, items) + ); + this.filteredItems = result; + + // + this.handleChangeDetection(); + } + //#endregion + + // + //#region Private ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + if (!this.actionBarActionProvider) { + this.actionBarActionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XListAction.Add: + // + const newItem = model.payload as XListItem; + if (newItem) { + // + const items = await this.getArrayValueAsync(this.items); + this.items = [...items, newItem]; + this.itemsChanged.emit(); + this.handleChangeDetection(); + } + break; + + // + case XListAction.Set: + // + const newItems = model.payload as XListItem[]; + if (newItems) { + // + this.items = [...newItems]; + this.itemsChanged.emit(); + this.handleChangeDetection(); + } + break; + + // + case XListAction.Remove: + // + const mustRemoveItem = model.payload as XListItem; + if (mustRemoveItem) { + // + const items = await this.getArrayValueAsync(this.items); + this.items = [...removeItem(items, mustRemoveItem)]; + this.itemsChanged.emit(); + this.handleChangeDetection(); + } + break; + + // + case XListAction.SelectAll: + await this.selectAll(); + break; + + // + case XListAction.DeselectAll: + await this.deselectAll(); + break; + + // + case XListAction.InverseSelection: + await this.inverseSelection(); + break; + + // + case XListAction.Refresh: + this.handleChangeDetection(); + break; + } + }); + + // + // Handle ActionBar Provider ... + this.ACTION_BAR_ACTION_PROVIDER_SUBSCRIPTION = this.actionBarActionProvider + .asObservable() + .subscribe((model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XActionBarAction.EnableAction: + // + switch (model.id) { + // + case DefaultActionBarItemIds.Add: + // + this.DISABLE_ADD = false; + this.addDisablerSubject.next(false); + break; + + // + case DefaultActionBarItemIds.Remove: + // + this.DISABLE_REMOVE = false; + this.removeDisablerSubject.next(false); + break; + + // + case DefaultActionBarItemIds.SelectAll: + // + this.DISABLE_SELECT_ALL = false; + this.selectAllDisablerSubject.next(false); + break; + + // + case DefaultActionBarItemIds.DeSelectAll: + // + this.DISABLE_DESELECT_ALL = false; + this.deSelectAllDisablerSubject.next(false); + break; + + // + case DefaultActionBarItemIds.InverseSelection: + // + this.DISABLE_INVERSE_SELECTION = false; + this.inverseSelectionDisablerSubject.next(false); + break; + } + break; + + // + case XActionBarAction.DisbaleAction: + // + switch (model.id) { + // + case DefaultActionBarItemIds.Add: + // + this.DISABLE_ADD = true; + this.addDisablerSubject.next(true); + break; + + // + case DefaultActionBarItemIds.Remove: + // + this.DISABLE_REMOVE = true; + this.removeDisablerSubject.next(true); + break; + + // + case DefaultActionBarItemIds.SelectAll: + // + this.DISABLE_SELECT_ALL = true; + this.selectAllDisablerSubject.next(true); + break; + + // + case DefaultActionBarItemIds.DeSelectAll: + // + this.DISABLE_DESELECT_ALL = true; + this.deSelectAllDisablerSubject.next(true); + break; + + // + case DefaultActionBarItemIds.InverseSelection: + // + this.DISABLE_INVERSE_SELECTION = true; + this.inverseSelectionDisablerSubject.next(true); + break; + } + break; + } + }); + } + + private unregisterActionProvider() { + // + if (this.ACTION_PROVIDER_SUBSCRIPTION) { + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + + // + if (this.ACTION_BAR_ACTION_PROVIDER_SUBSCRIPTION) { + // + this.ACTION_BAR_ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_BAR_ACTION_PROVIDER_SUBSCRIPTION = null; + } + } + + private async handleStartup() { + // + await this.prepareActionBarActions(); + + // + this.SELECTED_ITEMS = []; + + // + const items = await this.getValueAsync( + isNullOrUndefined(this.items) ? [] : this.items + ); + if (!hasChild(items)) { + this.filteredItems = []; + } else { + this.filteredItems = items; + } + + // + this.issueSelectionChanged(); + // this.handleChangeDetection(); + } + + private handleChangeDetection() { + // + this.actionBarActionProvider.next({ + action: XActionBarAction.Refresh, + }); + + // + this.searchBarActionProvider.next({ + action: XSearchBarAction.Refresh, + }); + + // + this.detectChanges(); + } + + private issueSelectionChanged() { + // + this.SELECTED_ITEMS = []; + if (!this.selectableItems) { + return; + } + + // + const selectedItems = this.filteredItems.filter((item) => item.isSelected); + this.SELECTED_ITEMS = [...selectedItems]; + + // + this.selectionChanged.emit(selectedItems); + this.prepareActionBarActionsSubjects(selectedItems); + } + + // + //#region ActionBar Preparations ... + private async prepareActionBarActions() { + // + //#region Prepare Default Actions ... + const hideActions = await this.getArrayValueAsync( + this.actionBarHideActions + ); + + // + let idx = 0; + const defaultActions: XOneOrManyType = []; + + // + // Remove ... + const removeId = DefaultActionBarItemIds.Remove.toString(); + if (!hideActions.includes(removeId)) { + // + defaultActions.push({ + // + ...defaultActionBarRemoveItem, + + // + index: idx, + disabled: this.removeDisablerSubject.asObservable(), + }); + + // + idx++; + } + + // + // Add ... + const addId = DefaultActionBarItemIds.Add.toString(); + if (!hideActions.includes(addId)) { + // + defaultActions.push({ + // + ...defaultActionBarAddItem, + + // + index: idx, + disabled: this.addDisablerSubject.asObservable(), + }); + + // + idx++; + } + + // + // Select All ... + const selectAllId = DefaultActionBarItemIds.SelectAll.toString(); + if (!hideActions.includes(selectAllId)) { + // + defaultActions.push({ + // + ...defaultActionBarSelectAllItem, + + // + index: idx, + disabled: this.selectAllDisablerSubject.asObservable(), + }); + + // + idx++; + } + + // + // Inverse Selection ... + const inverseSelectionId = + DefaultActionBarItemIds.InverseSelection.toString(); + if (!hideActions.includes(inverseSelectionId)) { + // + defaultActions.push({ + // + ...defaultActionBarInverseSelectionItem, + + // + index: idx, + disabled: this.inverseSelectionDisablerSubject.asObservable(), + }); + + // + idx++; + } + + // + // De Select All ... + const deselectAllId = DefaultActionBarItemIds.DeSelectAll.toString(); + if (!hideActions.includes(deselectAllId)) { + // + defaultActions.push({ + // + ...defaultActionBarDeSelectAllItem, + + // + index: idx, + disabled: this.deSelectAllDisablerSubject.asObservable(), + }); + + // + idx++; + } + //#endregion + + // + const useDefaultActions = await this.getValueAsync(this.useDefaultActions); + if (!this.actionBarActions && !!useDefaultActions) { + // + this.actionBarActions = defaultActions; + return; + } + + // + const actions = await this.getArrayValueAsync(this.actionBarActions); + if (!hasChild(actions) && !!useDefaultActions) { + // + this.actionBarActions = defaultActions; + return; + } + + // + this.actionBarActions = actions; + } + + private async prepareActionBarActionsSubjects(items: XListItem[]) { + // + const listItems = await this.getValueAsync(this.items); + + // + // Check Select All ... + if (!hasChild(listItems)) { + // + // Remove ... + if (!this.DISABLE_REMOVE) { + this.removeDisablerSubject.next(true); + } + + // + // Select All ... + if (!this.DISABLE_SELECT_ALL) { + this.selectAllDisablerSubject.next(true); + } + + // + // Deselect All ... + if (!this.DISABLE_DESELECT_ALL) { + this.deSelectAllDisablerSubject.next(true); + } + + // + // Inverse Selection ... + if (!this.DISABLE_INVERSE_SELECTION) { + this.inverseSelectionDisablerSubject.next(true); + } + } else { + // + // DeSelectAll ... + if (!hasChild(items)) { + // + // Remove ... + if (!this.DISABLE_REMOVE) { + this.removeDisablerSubject.next(true); + } + + // + // Deselect All ... + if (!this.DISABLE_DESELECT_ALL) { + this.deSelectAllDisablerSubject.next(true); + } + + // + // Inverse Selection ... + if (!this.DISABLE_INVERSE_SELECTION) { + this.inverseSelectionDisablerSubject.next(true); + } + } else { + // + // Remove ... + if (!this.DISABLE_REMOVE) { + this.removeDisablerSubject.next(false); + } + + // + // Deselect All ... + if (!this.DISABLE_DESELECT_ALL) { + this.deSelectAllDisablerSubject.next(false); + } + + // + // Inverse Selection ... + if (!this.DISABLE_INVERSE_SELECTION) { + this.inverseSelectionDisablerSubject.next(false); + } + } + + // + if (listItems.length === items.length) { + // + // Select All ... + if (!this.DISABLE_SELECT_ALL) { + this.selectAllDisablerSubject.next(true); + } + + // + // Inverse Selection ... + if (!this.DISABLE_INVERSE_SELECTION) { + this.inverseSelectionDisablerSubject.next(true); + } + } else { + // + // Select All ... + if (!this.DISABLE_SELECT_ALL) { + this.selectAllDisablerSubject.next(false); + } + } + } + + // + this.handleChangeDetection(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-list\x-list.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XListComponent } from './x-list.component'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XEmptyModule } from '../x-empty/x-empty.module'; +import { XActionBarModule } from '../x-action-bar/x-action-bar.module'; +import { XSearchBarModule } from '../x-search-bar/x-search-bar.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XListComponent], + imports: [ + XIconModule, + XCardModule, + XEmptyModule, + CommonModule, + XActionBarModule, + XSearchBarModule, + XBaseComponentModule, + ], + exports: [ + XIconModule, + XCardModule, + XEmptyModule, + CommonModule, + XListComponent, + XActionBarModule, + XSearchBarModule, + XBaseComponentModule, + ], +}) +export class XListModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-list\x-list.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + Injectable, + ElementRef, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + isFile, + hasChild, + toJsonString, + XStandardType, + XOneOrManyType, + toNormalString, + XColorIdentifier, + fileToJsonString, + XResourceIdentifier, + XBaseRangeRequestDto, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XBaseComponentContainer, + IXBaseComponentContainer, + XBaseComponentContainerComponent, +} from '../classes/i-x-base.container'; +import { + XCardEmptyActionSearchBarContainer, + IXCardEmptyActionSearchBarContainer, + XCardEmptyActionSearchBarContainerComponent, +} from '../classes/i-x-card-empty.container'; +import { + XActionBarItem, + XActionBarActionModel, + XActionBarActionIdentifier, + XActionBarActionFiredModel, +} from '../x-action-bar/x-action-bar.typings'; +import { + XSearchBarActionModel, + XSearchBarTypeIdentifier, + XSearchBarEventIdentifier, +} from '../x-search-bar/x-search-bar.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { XCardActionModel } from '../x-card/x-card.typings'; +import { XEmptyActionModel } from '../x-empty/x-empty.typings'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region Definitions ... +export type XListItemOptionSlot = 'end' | 'start'; + +export interface XListItemSlideOption { + id: string; + icon?: XStandardType; + title?: XStandardType; + color?: XStandardType; + onlyIcon?: XStandardType; + slot: XStandardType; + handler?: (info?: any) => XStandardType; + disabled?: XStandardType; +} + +export interface XListItem { + data: T; + hide?: XStandardType; + disabled?: XStandardType; + isSelected?: boolean; + href?: XOneOrManyType; + showDetailedIcon?: XStandardType; + slideOptions?: XListItemSlideOption[]; +} + +export interface XListItemOptionSelectedEvent { + id: string; + data: any; +} + +export enum XListAction { + // + Add = 'add', + Set = 'set', + Remove = 'remove', + + // + SelectAll = 'select_all', + DeselectAll = 'deselect_all', + InverseSelection = 'inverse_selection', + + // + Refresh = 'refresh', +} + +export type XListActionIdentifier = XListAction | string; + +export interface XListActionModel + extends XBaseActionModel {} + +export type XListFilteringHandler = ( + query: string, + source: XListItem[] +) => XStandardType[]>; + +export function itemToJson(item: any) { + return isFile(item) ? fileToJsonString(item) : toJsonString(item); +} + +export const defaultListFilteringHandler: XListFilteringHandler = ( + query: string, + items: XListItem[] +) => { + // + if (!query || !hasChild(items)) { + return []; + } + + // + const result = items.filter((i) => + toNormalString(itemToJson(i.data)).includes(toNormalString(query)) + ); + + // + return result; +}; +//#endregion + +// +//#region Container ... +export interface IXListContainer extends IXCardEmptyActionSearchBarContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + actionBarHideActions: XStandardType< + XOneOrManyType + >; + + // + useDefaultActions: XStandardType; + + // + showToolbar: XStandardType; + stickyToolbar: XStandardType; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + enableReordering: XStandardType; + showDivider: XStandardType; + showOptionsAsSlide: XStandardType; + closeOptionsAfterSelect: XStandardType; + showOptionsAsMenu: XStandardType; + + // + selectableItems: XStandardType; + clickableItems: XStandardType; + itemTemplate: TemplateRef; + slidingOptionTemplate: TemplateRef; + nonSlidingOptionTemplate: TemplateRef; + + // + itemSelected: EventEmitter>; + optionSelected: EventEmitter; + confirmRemove: XStandardType; + removeItems: EventEmitter>; + + // + selectionChanged: EventEmitter[]>; + orderChanged: EventEmitter; + items: XStandardType[]>; + showItemOptions: XStandardType; + itemsChanged: EventEmitter; + filteringHandler: XListFilteringHandler; +} + +@Injectable({ + providedIn: 'root', +}) +export class XListContainer + extends XCardEmptyActionSearchBarContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly actionBarHideActions: XStandardType< + XOneOrManyType + >; + + readonly useDefaultActions: XStandardType = true; + + readonly showToolbar: XStandardType = false; + readonly stickyToolbar: XStandardType = true; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly enableReordering: XStandardType = false; + readonly showDivider: XStandardType = true; + readonly showOptionsAsSlide: XStandardType = false; + readonly closeOptionsAfterSelect: XStandardType = true; + readonly showOptionsAsMenu: XStandardType = false; + + // + readonly selectableItems: XStandardType = false; + readonly clickableItems: XStandardType = false; + readonly itemTemplate: TemplateRef; + readonly slidingOptionTemplate: TemplateRef; + readonly nonSlidingOptionTemplate: TemplateRef; + + // + readonly itemSelected: EventEmitter> = new EventEmitter< + XListItem + >(); + readonly optionSelected: EventEmitter = + new EventEmitter(); + readonly confirmRemove: XStandardType = true; + readonly removeItems: EventEmitter> = + new EventEmitter>(); + + // + readonly selectionChanged: EventEmitter[]> = new EventEmitter< + XListItem[] + >(); + readonly orderChanged: EventEmitter = new EventEmitter(); + readonly items: XStandardType[]> = []; + readonly showItemOptions: XStandardType = true; + readonly itemsChanged: EventEmitter = new EventEmitter(); + readonly filteringHandler: XListFilteringHandler = + defaultListFilteringHandler; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XListContainerComponent + extends XCardEmptyActionSearchBarContainerComponent + implements IXListContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + actionBarHideActions: XStandardType< + XOneOrManyType + > = this.propertyProvider.actionBarHideActions; + + @Input() + useDefaultActions: XStandardType = + this.propertyProvider.useDefaultActions; + + @Input() + showToolbar: XStandardType = this.propertyProvider.showToolbar; + + @Input() + stickyToolbar: XStandardType = this.propertyProvider.stickyToolbar; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Input() + enableReordering: XStandardType = + this.propertyProvider.enableReordering; + + @Input() + showDivider: XStandardType = this.propertyProvider.showDivider; + + @Input() + showOptionsAsSlide: XStandardType = + this.propertyProvider.showOptionsAsSlide; + + @Input() + closeOptionsAfterSelect: XStandardType = + this.propertyProvider.closeOptionsAfterSelect; + + @Input() + showOptionsAsMenu: XStandardType = + this.propertyProvider.showOptionsAsMenu; + + @Input() + selectableItems: XStandardType = + this.propertyProvider.selectableItems; + + @Input() + clickableItems: XStandardType = this.propertyProvider.clickableItems; + + @Input() + itemTemplate: TemplateRef = this.propertyProvider.itemTemplate; + + @Input() + slidingOptionTemplate: TemplateRef = + this.propertyProvider.slidingOptionTemplate; + + @Input() + nonSlidingOptionTemplate: TemplateRef = + this.propertyProvider.nonSlidingOptionTemplate; + + // + @Output() + itemSelected: EventEmitter> = new EventEmitter(); + + @Output() + optionSelected: EventEmitter = new EventEmitter(); + + @Input() + confirmRemove: XStandardType = this.propertyProvider.confirmRemove; + + @Output() + removeItems: EventEmitter> = new EventEmitter(); + + // + @Output() + selectionChanged: EventEmitter[]> = new EventEmitter(); + + @Output() + orderChanged: EventEmitter = new EventEmitter(); + + // + @Input() + items: XStandardType[]> = this.propertyProvider.items; + + @Input() + showItemOptions: XStandardType = + this.propertyProvider.showItemOptions; + + @Output() + itemsChanged: EventEmitter = new EventEmitter(); + + // + @Input() + filteringHandler: XListFilteringHandler = + this.propertyProvider.filteringHandler; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XListContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXListContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + actionBarHideActions: this.getValue( + container + ? container.actionBarHideActions + : this.actionBarHideActions + ), + useDefaultActions: this.getValue( + container ? container.useDefaultActions : this.useDefaultActions + ), + showToolbar: this.getValue( + container ? container.showToolbar : this.showToolbar + ), + stickyToolbar: this.getValue( + container ? container.stickyToolbar : this.stickyToolbar + ), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + enableReordering: this.getValue( + container ? container.enableReordering : this.enableReordering + ), + showDivider: this.getValue( + container ? container.showDivider : this.showDivider + ), + showOptionsAsSlide: this.getValue( + container ? container.showOptionsAsSlide : this.showOptionsAsSlide + ), + closeOptionsAfterSelect: this.getValue( + container + ? container.closeOptionsAfterSelect + : this.closeOptionsAfterSelect + ), + showOptionsAsMenu: this.getValue( + container ? container.showOptionsAsMenu : this.showOptionsAsMenu + ), + selectableItems: this.getValue( + container ? container.selectableItems : this.selectableItems + ), + clickableItems: this.getValue( + container ? container.clickableItems : this.clickableItems + ), + itemTemplate: this.getValue( + container ? container.itemTemplate : this.itemTemplate + ), + slidingOptionTemplate: this.getValue( + container + ? container.slidingOptionTemplate + : this.slidingOptionTemplate + ), + nonSlidingOptionTemplate: this.getValue( + container + ? container.nonSlidingOptionTemplate + : this.nonSlidingOptionTemplate + ), + confirmRemove: this.getValue( + container ? container.confirmRemove : this.confirmRemove + ), + items: this.getValue(container ? container.items : this.items), + showItemOptions: this.getValue( + container ? container.showItemOptions : this.showItemOptions + ), + filteringHandler: this.getValue( + container ? container.filteringHandler : this.filteringHandler + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + itemsChanged: this.itemsChanged, + itemSelected: this.itemSelected, + optionSelected: this.optionSelected, + removeItems: this.removeItems, + selectionChanged: this.selectionChanged, + orderChanged: this.orderChanged, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentListContainer = { + [TKey in keyof IXListContainer as `list${Capitalize< + string & TKey + >}`]: IXListContainer[TKey]; +}; + +export interface IXComponentListContainer + extends TXComponentListContainer, + IXBaseComponentContainer { + showList: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XComponentListContainer + extends XBaseComponentContainer + implements Readonly +{ + // + //#region Props ... + // + //#region Self ... + // + readonly showList: XStandardType = true; + + // + readonly listActionProvider: Subject = + this.propertyProvider.actionProvider; + readonly listCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly listActionBarHideActions: XStandardType< + XOneOrManyType + > = this.propertyProvider.actionBarHideActions; + readonly listUseDefaultActions: XStandardType = + this.propertyProvider.useDefaultActions; + readonly listShowToolbar: XStandardType = + this.propertyProvider.showToolbar; + readonly listStickyToolbar: XStandardType = + this.propertyProvider.stickyToolbar; + + // + readonly listColor: XStandardType = + this.propertyProvider.color; + readonly listForegroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + readonly listEnableReordering: XStandardType = + this.propertyProvider.enableReordering; + readonly listShowDivider: XStandardType = + this.propertyProvider.showDivider; + readonly listShowOptionsAsSlide: XStandardType = + this.propertyProvider.showOptionsAsSlide; + readonly listCloseOptionsAfterSelect: XStandardType = + this.propertyProvider.closeOptionsAfterSelect; + readonly listShowOptionsAsMenu: XStandardType = + this.propertyProvider.showOptionsAsMenu; + + // + readonly listSelectableItems: XStandardType = + this.propertyProvider.selectableItems; + readonly listClickableItems: XStandardType = + this.propertyProvider.clickableItems; + readonly listItemTemplate: TemplateRef = + this.propertyProvider.itemTemplate; + readonly listSlidingOptionTemplate: TemplateRef = + this.propertyProvider.slidingOptionTemplate; + readonly listNonSlidingOptionTemplate: TemplateRef = + this.propertyProvider.nonSlidingOptionTemplate; + + // + readonly listItemSelected: EventEmitter> = + this.propertyProvider.itemSelected; + readonly listOptionSelected: EventEmitter = + this.propertyProvider.optionSelected; + readonly listConfirmRemove: XStandardType = + this.propertyProvider.confirmRemove; + readonly listRemoveItems: EventEmitter> = + this.propertyProvider.removeItems; + + // + readonly listSelectionChanged: EventEmitter[]> = + this.propertyProvider.selectionChanged; + readonly listOrderChanged: EventEmitter = + this.propertyProvider.orderChanged; + readonly listItems: XStandardType[]> = + this.propertyProvider.items; + readonly listShowItemOptions: XStandardType = + this.propertyProvider.showItemOptions; + readonly listItemsChanged: EventEmitter = + this.propertyProvider.itemsChanged; + readonly listFilteringHandler: XListFilteringHandler = + this.propertyProvider.filteringHandler; + //#endregion + + // + //#region List Card ... + readonly listIsInPage: XStandardType = + this.propertyProvider.isInPage; + readonly listWrapWithCard: XStandardType = + this.propertyProvider.wrapWithCard; + readonly listCardShowFooter: XStandardType = + this.propertyProvider.cardShowFooter; + readonly listCardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + readonly listCardColor: XStandardType = + this.propertyProvider.cardColor; + readonly listCardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + readonly listCardTitle: XStandardType = + this.propertyProvider.cardTitle; + readonly listCardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + readonly listCardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + readonly listCardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + //#endregion + + // + //#region List Empty ... + // + readonly listShowEmpty: XStandardType = + this.propertyProvider.showEmpty; + readonly listEmptyHasRefresh: XStandardType = + this.propertyProvider.emptyHasRefresh; + readonly listEmptyActionProvider: Subject = + this.propertyProvider.emptyActionProvider; + readonly listEmptyCssClass: XStandardType> = + this.propertyProvider.emptyCssClass; + readonly listEmptyColor: XStandardType = + this.propertyProvider.emptyColor; + readonly listEmptyForegroundColor: XStandardType = + this.propertyProvider.emptyForegroundColor; + readonly listEmptyMessageColor: XStandardType = + this.propertyProvider.emptyMessageColor; + readonly listEmptyForegroundMessageColor: XStandardType = + this.propertyProvider.emptyForegroundMessageColor; + readonly listEmptyRefreshButtonColor: XStandardType = + this.propertyProvider.emptyRefreshButtonColor; + readonly listEmptyForegroundRefreshButtonColor: XStandardType = + this.propertyProvider.emptyForegroundRefreshButtonColor; + readonly listEmptyMessage: XStandardType = + this.propertyProvider.emptyMessage; + readonly listEmptyRefreshIcon: XStandardType = + this.propertyProvider.emptyRefreshIcon; + readonly listEmptyRefresh: EventEmitter = + this.propertyProvider.emptyRefresh; + //#endregion + + // + //#region ActionBar ... + // + readonly listShowActionBar: XStandardType = + this.propertyProvider.showActionBar; + + // + readonly listActionBarActionProvider: Subject = + this.propertyProvider.actionBarActionProvider; + readonly listActionBarCssClass: XStandardType> = + this.propertyProvider.actionBarCssClass; + + // + readonly listActionBarColor: XStandardType = + this.propertyProvider.actionBarColor; + readonly listActionBarForegroundColor: XStandardType = + this.propertyProvider.actionBarForegroundColor; + + // + readonly listActionBarActions: XStandardType> = + this.propertyProvider.actionBarActions; + + // + readonly listActionBarActionFired: EventEmitter = + this.propertyProvider.actionBarActionFired; + //#endregion + + // + //#region SearchBar ... + // + readonly listShowSearchBar: XStandardType = + this.propertyProvider.showSearchBar; + + // + readonly listSearchBarActionProvider: Subject = + this.propertyProvider.searchBarActionProvider; + readonly listSearchBarCssClass: XStandardType> = + this.propertyProvider.searchBarCssClass; + + // + readonly listSearchBarColor: XStandardType = + this.propertyProvider.searchBarColor; + readonly listSearchBarForegroundColor: XStandardType = + this.propertyProvider.searchBarForegroundColor; + + // + readonly listSearchBarSearchInputColor: XStandardType = + this.propertyProvider.searchBarSearchInputColor; + readonly listSearchBarForegroundSearchInputColor: XStandardType = + this.propertyProvider.searchBarForegroundSearchInputColor; + + // + readonly listSearchBarType: XStandardType = + this.propertyProvider.searchBarType; + readonly listSearchBarTitle: XStandardType = + this.propertyProvider.searchBarTitle; + readonly listSearchBarValue: XStandardType = + this.propertyProvider.searchBarValue; + readonly listSearchBarEnterKeyHint: XStandardType = + this.propertyProvider.searchBarEnterKeyHint; + readonly listSearchBarKeyboardType: XStandardType = + this.propertyProvider.searchBarKeyboardType; + readonly listSearchBarSpellcheck: XStandardType = + this.propertyProvider.searchBarSpellcheck; + readonly listSearchBarDebounce: XStandardType = + this.propertyProvider.searchBarDebounce; + + // + readonly listSearchBarShowCancelButton: XStandardType = + this.propertyProvider.searchBarShowCancelButton; + readonly listSearchBarCancelButtonIcon: XStandardType = + this.propertyProvider.searchBarCancelButtonIcon; + readonly listSearchBarCancelButtonTitle: XStandardType = + this.propertyProvider.searchBarCancelButtonTitle; + readonly listSearchBarCancelButtonIconColor: XStandardType = + this.propertyProvider.searchBarCancelButtonIconColor; + + // + readonly listSearchBarShowClearButton: XStandardType = + this.propertyProvider.searchBarShowClearButton; + readonly listSearchBarClearButtonIcon: XStandardType = + this.propertyProvider.searchBarClearButtonIcon; + readonly listSearchBarClearButtonIconColor: XStandardType = + this.propertyProvider.searchBarClearButtonIconColor; + + // + readonly listSearchBarSearchButtonIcon: XStandardType = + this.propertyProvider.searchBarSearchButtonIcon; + readonly listSearchBarSearchButtonIconColor: XStandardType = + this.propertyProvider.searchBarSearchButtonIconColor; + + // + readonly listSearchBarQueryChange: EventEmitter = + this.propertyProvider.searchBarQueryChange; + readonly listSearchBarOnEvent: EventEmitter = + this.propertyProvider.searchBarOnEvent; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XListContainer) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XComponentListContainerComponent + extends XBaseComponentContainerComponent + implements IXComponentListContainer +{ + // + //#region Props ... + // + //#region Self ... + // + @Input() + showList: XStandardType = this.propertyProvider.showList; + + // + @Input() + listActionProvider: Subject = + this.propertyProvider.listActionProvider; + + @Input() + listCssClass: XStandardType> = + this.propertyProvider.listCssClass; + + // + @Input() + listActionBarHideActions: XStandardType< + XOneOrManyType + > = this.propertyProvider.listActionBarHideActions; + + @Input() + listUseDefaultActions: XStandardType = + this.propertyProvider.listUseDefaultActions; + + @Input() + listShowToolbar: XStandardType = + this.propertyProvider.listShowToolbar; + + @Input() + listStickyToolbar: XStandardType = + this.propertyProvider.listStickyToolbar; + + // + @Input() + listColor: XStandardType = this.propertyProvider.listColor; + + @Input() + listForegroundColor: XStandardType = + this.propertyProvider.listForegroundColor; + + // + @Input() + listEnableReordering: XStandardType = + this.propertyProvider.listEnableReordering; + + @Input() + listShowDivider: XStandardType = + this.propertyProvider.listShowDivider; + + @Input() + listShowOptionsAsSlide: XStandardType = + this.propertyProvider.listShowOptionsAsSlide; + + @Input() + listCloseOptionsAfterSelect: XStandardType = + this.propertyProvider.listCloseOptionsAfterSelect; + + @Input() + listShowOptionsAsMenu: XStandardType = + this.propertyProvider.listShowOptionsAsMenu; + + // + @Input() + listSelectableItems: XStandardType = + this.propertyProvider.listSelectableItems; + + @Input() + listClickableItems: XStandardType = + this.propertyProvider.listClickableItems; + + @Input() + listItemTemplate: TemplateRef = this.propertyProvider.listItemTemplate; + + @Input() + listSlidingOptionTemplate: TemplateRef = + this.propertyProvider.listSlidingOptionTemplate; + + @Input() + listNonSlidingOptionTemplate: TemplateRef = + this.propertyProvider.listNonSlidingOptionTemplate; + + // + @Output() + listItemSelected: EventEmitter> = new EventEmitter(); + + @Output() + listOptionSelected: EventEmitter = new EventEmitter(); + + @Input() + listConfirmRemove: XStandardType = + this.propertyProvider.listConfirmRemove; + + @Output() + listRemoveItems: EventEmitter> = new EventEmitter(); + + // + @Output() + listSelectionChanged: EventEmitter[]> = new EventEmitter(); + + @Output() + listOrderChanged: EventEmitter = new EventEmitter(); + + @Input() + listItems: XStandardType[]> = this.propertyProvider.listItems; + + @Input() + listShowItemOptions: XStandardType = + this.propertyProvider.listShowItemOptions; + + @Output() + listItemsChanged: EventEmitter = new EventEmitter(); + + @Input() + listFilteringHandler: XListFilteringHandler = + this.propertyProvider.listFilteringHandler; + //#endregion + + // + //#region List Card ... + @Input() + listIsInPage: XStandardType = this.propertyProvider.listIsInPage; + + @Input() + listWrapWithCard: XStandardType = + this.propertyProvider.listWrapWithCard; + + @Input() + listCardShowFooter: XStandardType = + this.propertyProvider.listCardShowFooter; + + @Input() + listCardShowActions: XStandardType = + this.propertyProvider.listCardShowActions; + + @Input() + listCardColor: XStandardType = + this.propertyProvider.listCardColor; + + @Input() + listCardForegroundColor: XStandardType = + this.propertyProvider.listCardForegroundColor; + + @Input() + listCardTitle: XStandardType = + this.propertyProvider.listCardTitle; + + @Input() + listCardActionProvider: Subject = + this.propertyProvider.listCardActionProvider; + + @Input() + listCardSubTitle: XStandardType = + this.propertyProvider.listCardSubTitle; + + @Input() + listCardCssClass: XStandardType> = + this.propertyProvider.listCardCssClass; + //#endregion + + // + //#region List Empty ... + // + @Input() + listShowEmpty: XStandardType = this.propertyProvider.listShowEmpty; + + @Input() + listEmptyHasRefresh: XStandardType = + this.propertyProvider.listEmptyHasRefresh; + + @Input() + listEmptyActionProvider: Subject = + this.propertyProvider.listEmptyActionProvider; + + @Input() + listEmptyCssClass: XStandardType> = + this.propertyProvider.listEmptyCssClass; + + @Input() + listEmptyColor: XStandardType = + this.propertyProvider.listEmptyColor; + + @Input() + listEmptyForegroundColor: XStandardType = + this.propertyProvider.listEmptyForegroundColor; + + @Input() + listEmptyMessageColor: XStandardType = + this.propertyProvider.listEmptyMessageColor; + + @Input() + listEmptyForegroundMessageColor: XStandardType = + this.propertyProvider.listEmptyForegroundMessageColor; + + @Input() + listEmptyRefreshButtonColor: XStandardType = + this.propertyProvider.listEmptyRefreshButtonColor; + + @Input() + listEmptyForegroundRefreshButtonColor: XStandardType = + this.propertyProvider.listEmptyForegroundRefreshButtonColor; + + @Input() + listEmptyMessage: XStandardType = + this.propertyProvider.listEmptyMessage; + + @Input() + listEmptyRefreshIcon: XStandardType = + this.propertyProvider.listEmptyRefreshIcon; + + @Output() + listEmptyRefresh: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region ActionBar ... + // + @Input() + listShowActionBar: XStandardType = + this.propertyProvider.listShowActionBar; + + // + @Input() + listActionBarActionProvider: Subject = + this.propertyProvider.listActionBarActionProvider; + + @Input() + listActionBarCssClass: XStandardType> = + this.propertyProvider.listActionBarCssClass; + + // + @Input() + listActionBarColor: XStandardType = + this.propertyProvider.listActionBarColor; + + @Input() + listActionBarForegroundColor: XStandardType = + this.propertyProvider.listActionBarForegroundColor; + + // + @Input() + listActionBarActions: XStandardType> = + this.propertyProvider.listActionBarActions; + + // + @Output() + listActionBarActionFired: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region SearchBar ... + // + @Input() + listShowSearchBar: XStandardType = + this.propertyProvider.listShowSearchBar; + + // + @Input() + listSearchBarActionProvider: Subject = + this.propertyProvider.listSearchBarActionProvider; + + @Input() + listSearchBarCssClass: XStandardType> = + this.propertyProvider.listSearchBarCssClass; + + // + @Input() + listSearchBarColor: XStandardType = + this.propertyProvider.listSearchBarColor; + + @Input() + listSearchBarForegroundColor: XStandardType = + this.propertyProvider.listSearchBarForegroundColor; + + // + @Input() + listSearchBarSearchInputColor: XStandardType = + this.propertyProvider.listSearchBarSearchInputColor; + + @Input() + listSearchBarForegroundSearchInputColor: XStandardType = + this.propertyProvider.listSearchBarForegroundSearchInputColor; + + // + @Input() + listSearchBarType: XStandardType = + this.propertyProvider.listSearchBarType; + + @Input() + listSearchBarTitle: XStandardType = + this.propertyProvider.listSearchBarTitle; + + @Input() + listSearchBarValue: XStandardType = + this.propertyProvider.listSearchBarValue; + + @Input() + listSearchBarEnterKeyHint: XStandardType = + this.propertyProvider.listSearchBarEnterKeyHint; + + @Input() + listSearchBarKeyboardType: XStandardType = + this.propertyProvider.listSearchBarKeyboardType; + + @Input() + listSearchBarSpellcheck: XStandardType = + this.propertyProvider.listSearchBarSpellcheck; + + @Input() + listSearchBarDebounce: XStandardType = + this.propertyProvider.listSearchBarDebounce; + + // + @Input() + listSearchBarShowCancelButton: XStandardType = + this.propertyProvider.listSearchBarShowCancelButton; + + @Input() + listSearchBarCancelButtonIcon: XStandardType = + this.propertyProvider.listSearchBarCancelButtonIcon; + + @Input() + listSearchBarCancelButtonTitle: XStandardType = + this.propertyProvider.listSearchBarCancelButtonTitle; + + @Input() + listSearchBarCancelButtonIconColor: XStandardType = + this.propertyProvider.listSearchBarCancelButtonIconColor; + + // + @Input() + listSearchBarShowClearButton: XStandardType = + this.propertyProvider.listSearchBarShowClearButton; + + @Input() + listSearchBarClearButtonIcon: XStandardType = + this.propertyProvider.listSearchBarClearButtonIcon; + + @Input() + listSearchBarClearButtonIconColor: XStandardType = + this.propertyProvider.listSearchBarClearButtonIconColor; + + // + @Input() + listSearchBarSearchButtonIcon: XStandardType = + this.propertyProvider.listSearchBarSearchButtonIcon; + + @Input() + listSearchBarSearchButtonIconColor: XStandardType = + this.propertyProvider.listSearchBarSearchButtonIconColor; + + // + @Output() + listSearchBarQueryChange: EventEmitter = new EventEmitter(); + + @Output() + listSearchBarOnEvent: EventEmitter = new EventEmitter(); + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XComponentListContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXComponentListContainer + ): Observable { + return forkJoin({ + // + showList: this.getValue(container ? container.showList : this.showList), + listCssClass: this.getValue( + container ? container.listCssClass : this.listCssClass + ), + listActionBarHideActions: this.getValue( + container + ? container.listActionBarHideActions + : this.listActionBarHideActions + ), + listUseDefaultActions: this.getValue( + container ? container.listUseDefaultActions : this.listUseDefaultActions + ), + listShowToolbar: this.getValue( + container ? container.listShowToolbar : this.listShowToolbar + ), + listStickyToolbar: this.getValue( + container ? container.listStickyToolbar : this.listStickyToolbar + ), + listColor: this.getValue( + container ? container.listColor : this.listColor + ), + listForegroundColor: this.getValue( + container ? container.listForegroundColor : this.listForegroundColor + ), + listEnableReordering: this.getValue( + container ? container.listEnableReordering : this.listEnableReordering + ), + listShowDivider: this.getValue( + container ? container.listShowDivider : this.listShowDivider + ), + listShowOptionsAsSlide: this.getValue( + container + ? container.listShowOptionsAsSlide + : this.listShowOptionsAsSlide + ), + listCloseOptionsAfterSelect: this.getValue( + container + ? container.listCloseOptionsAfterSelect + : this.listCloseOptionsAfterSelect + ), + listShowOptionsAsMenu: this.getValue( + container ? container.listShowOptionsAsMenu : this.listShowOptionsAsMenu + ), + listSelectableItems: this.getValue( + container ? container.listSelectableItems : this.listSelectableItems + ), + listClickableItems: this.getValue( + container ? container.listClickableItems : this.listClickableItems + ), + listItemTemplate: this.getValue( + container ? container.listItemTemplate : this.listItemTemplate + ), + listSlidingOptionTemplate: this.getValue( + container + ? container.listSlidingOptionTemplate + : this.listSlidingOptionTemplate + ), + listNonSlidingOptionTemplate: this.getValue( + container + ? container.listNonSlidingOptionTemplate + : this.listNonSlidingOptionTemplate + ), + listConfirmRemove: this.getValue( + container ? container.listConfirmRemove : this.listConfirmRemove + ), + listItems: this.getValue( + container ? container.listItems : this.listItems + ), + listShowItemOptions: this.getValue( + container ? container.listShowItemOptions : this.listShowItemOptions + ), + listFilteringHandler: this.getValue( + container ? container.listFilteringHandler : this.listFilteringHandler + ), + listIsInPage: this.getValue( + container ? container.listIsInPage : this.listIsInPage + ), + listWrapWithCard: this.getValue( + container ? container.listWrapWithCard : this.listWrapWithCard + ), + listCardShowFooter: this.getValue( + container ? container.listCardShowFooter : this.listCardShowFooter + ), + listCardShowActions: this.getValue( + container ? container.listCardShowActions : this.listCardShowActions + ), + listCardColor: this.getValue( + container ? container.listCardColor : this.listCardColor + ), + listCardForegroundColor: this.getValue( + container + ? container.listCardForegroundColor + : this.listCardForegroundColor + ), + listCardTitle: this.getValue( + container ? container.listCardTitle : this.listCardTitle + ), + listCardSubTitle: this.getValue( + container ? container.listCardSubTitle : this.listCardSubTitle + ), + listCardCssClass: this.getValue( + container ? container.listCardCssClass : this.listCardCssClass + ), + listShowEmpty: this.getValue( + container ? container.listShowEmpty : this.listShowEmpty + ), + listEmptyHasRefresh: this.getValue( + container ? container.listEmptyHasRefresh : this.listEmptyHasRefresh + ), + listEmptyCssClass: this.getValue( + container ? container.listEmptyCssClass : this.listEmptyCssClass + ), + listEmptyColor: this.getValue( + container ? container.listEmptyColor : this.listEmptyColor + ), + listEmptyForegroundColor: this.getValue( + container + ? container.listEmptyForegroundColor + : this.listEmptyForegroundColor + ), + listEmptyMessageColor: this.getValue( + container ? container.listEmptyMessageColor : this.listEmptyMessageColor + ), + listEmptyForegroundMessageColor: this.getValue( + container + ? container.listEmptyForegroundMessageColor + : this.listEmptyForegroundMessageColor + ), + listEmptyRefreshButtonColor: this.getValue( + container + ? container.listEmptyRefreshButtonColor + : this.listEmptyRefreshButtonColor + ), + listEmptyForegroundRefreshButtonColor: this.getValue( + container + ? container.listEmptyForegroundRefreshButtonColor + : this.listEmptyForegroundRefreshButtonColor + ), + listEmptyMessage: this.getValue( + container ? container.listEmptyMessage : this.listEmptyMessage + ), + listEmptyRefreshIcon: this.getValue( + container ? container.listEmptyRefreshIcon : this.listEmptyRefreshIcon + ), + listShowActionBar: this.getValue( + container ? container.listShowActionBar : this.listShowActionBar + ), + listActionBarCssClass: this.getValue( + container ? container.listActionBarCssClass : this.listActionBarCssClass + ), + listActionBarColor: this.getValue( + container ? container.listActionBarColor : this.listActionBarColor + ), + listActionBarForegroundColor: this.getValue( + container + ? container.listActionBarForegroundColor + : this.listActionBarForegroundColor + ), + listActionBarActions: this.getValue( + container ? container.listActionBarActions : this.listActionBarActions + ), + listShowSearchBar: this.getValue( + container ? container.listShowSearchBar : this.listShowSearchBar + ), + listSearchBarCssClass: this.getValue( + container ? container.listSearchBarCssClass : this.listSearchBarCssClass + ), + listSearchBarColor: this.getValue( + container ? container.listSearchBarColor : this.listSearchBarColor + ), + listSearchBarForegroundColor: this.getValue( + container + ? container.listSearchBarForegroundColor + : this.listSearchBarForegroundColor + ), + listSearchBarSearchInputColor: this.getValue( + container + ? container.listSearchBarSearchInputColor + : this.listSearchBarSearchInputColor + ), + listSearchBarForegroundSearchInputColor: this.getValue( + container + ? container.listSearchBarForegroundSearchInputColor + : this.listSearchBarForegroundSearchInputColor + ), + listSearchBarType: this.getValue( + container ? container.listSearchBarType : this.listSearchBarType + ), + listSearchBarTitle: this.getValue( + container ? container.listSearchBarTitle : this.listSearchBarTitle + ), + listSearchBarValue: this.getValue( + container ? container.listSearchBarValue : this.listSearchBarValue + ), + listSearchBarEnterKeyHint: this.getValue( + container + ? container.listSearchBarEnterKeyHint + : this.listSearchBarEnterKeyHint + ), + listSearchBarKeyboardType: this.getValue( + container + ? container.listSearchBarKeyboardType + : this.listSearchBarKeyboardType + ), + listSearchBarSpellcheck: this.getValue( + container + ? container.listSearchBarSpellcheck + : this.listSearchBarSpellcheck + ), + listSearchBarDebounce: this.getValue( + container ? container.listSearchBarDebounce : this.listSearchBarDebounce + ), + listSearchBarShowCancelButton: this.getValue( + container + ? container.listSearchBarShowCancelButton + : this.listSearchBarShowCancelButton + ), + listSearchBarCancelButtonIcon: this.getValue( + container + ? container.listSearchBarCancelButtonIcon + : this.listSearchBarCancelButtonIcon + ), + listSearchBarCancelButtonTitle: this.getValue( + container + ? container.listSearchBarCancelButtonTitle + : this.listSearchBarCancelButtonTitle + ), + listSearchBarCancelButtonIconColor: this.getValue( + container + ? container.listSearchBarCancelButtonIconColor + : this.listSearchBarCancelButtonIconColor + ), + listSearchBarShowClearButton: this.getValue( + container + ? container.listSearchBarShowClearButton + : this.listSearchBarShowClearButton + ), + listSearchBarClearButtonIcon: this.getValue( + container + ? container.listSearchBarClearButtonIcon + : this.listSearchBarClearButtonIcon + ), + listSearchBarClearButtonIconColor: this.getValue( + container + ? container.listSearchBarClearButtonIconColor + : this.listSearchBarClearButtonIconColor + ), + listSearchBarSearchButtonIcon: this.getValue( + container + ? container.listSearchBarSearchButtonIcon + : this.listSearchBarSearchButtonIcon + ), + listSearchBarSearchButtonIconColor: this.getValue( + container + ? container.listSearchBarSearchButtonIconColor + : this.listSearchBarSearchButtonIconColor + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + listActionProvider: container + ? container.listActionProvider + : this.listActionProvider, + + // + listItemSelected: this.listItemSelected, + listOptionSelected: this.listOptionSelected, + listRemoveItems: this.listRemoveItems, + listSelectionChanged: this.listSelectionChanged, + listOrderChanged: this.listOrderChanged, + listItemsChanged: this.listItemsChanged, + + // + listCardActionProvider: container + ? container.listCardActionProvider + : this.listCardActionProvider, + listEmptyActionProvider: container + ? container.listEmptyActionProvider + : this.listEmptyActionProvider, + + // + listEmptyRefresh: this.listEmptyRefresh, + + // + listActionBarActionProvider: container + ? container.listActionBarActionProvider + : this.listActionBarActionProvider, + + // + listActionBarActionFired: this.listActionBarActionFired, + + // + listSearchBarActionProvider: container + ? container.listSearchBarActionProvider + : this.listSearchBarActionProvider, + + // + listSearchBarQueryChange: this.listSearchBarQueryChange, + listSearchBarOnEvent: this.listSearchBarOnEvent, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map.component.html + + + + + + + + +
+ +
+
+
+ + + +
+ +
+ + +
+ + + + + + + + + +
+ + +
+
+ + +
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ item.data.display_name }} + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map.component.scss + +:host { + --map-height: 200px; + --map-controls-offest: 5px; +} + +:host ::ng-deep .x-auto-size-flex { + flex-grow: 0 !important; + flex-shrink: 1 !important; + flex-basis: auto !important; +} + +.x-map { + position: relative; +} + +.x-map-inner { + height: var(--map-height); + width: 100%; +} + +.x-map-top, +.x-map-bottom { + width: 100%; + position: absolute; + background-color: transparent; +} + +.x-map-top { + top: 0; +} + +.x-map-bottom { + bottom: 0; +} + +.x-map-plugins-wrapper { + margin: .5em; +} + +.x-map-plugin { + &.ol-control { + padding: 0; + position: relative; + margin-top: 0 !important; + } + + button { + padding: 3px; + width: fit-content; + display: inline-block; + } +} + +// +//#region Search Result ... +// +$duration: 250ms; +$transform-show: scaleY(1); +$transform-hide: scaleY(0); + +// +.x-map-search-list { + // + position: absolute; + + // + z-index: 110; + + // + top: 0; + left: 0; + right: 0; + height: 0; + + // + transform: $transform-hide; + -ms-transform: $transform-hide; + -webkit-transform: $transform-hide; + + // + transition: transform; + -webkit-transition: transform; + + // + transition-duration: $duration; + -webkit-transition-duration: $duration; + + // + &.open { + // + height: auto; + + // + transform: $transform-show; + -ms-transform: $transform-show; + -webkit-transform: $transform-show; + + // + transition-duration: $duration; + -webkit-transition-duration: $duration; + } + + // + &.has-search { + top: 56px; + } +} +//#endregion + +// +//#region Ol Theme ... + +:host ::ng-deep { + .x-content.mat-card-content { + padding: 0 !important; + } + + .ol-control { + margin-top: var(--map-controls-offest); + } + + .ol-box { + box-sizing: border-box; + border-radius: 2px; + border: 2px solid blue; + } + + .ol-mouse-position { + top: 8px; + right: 8px; + position: absolute; + } + + .ol-scale-line { + background: rgba(0, 60, 136, 0.3); + border-radius: 4px; + bottom: 8px; + left: 8px; + padding: 2px; + position: absolute; + } + + .ol-scale-line-inner { + border: 1px solid #eee; + border-top: none; + color: #eee; + font-size: 10px; + text-align: center; + margin: 1px; + will-change: contents, width; + transition: all 0.25s; + } + + .ol-scale-bar { + position: absolute; + bottom: 8px; + left: 8px; + } + + .ol-scale-step-marker { + width: 1px; + height: 15px; + background-color: #000000; + float: right; + z-index: 10; + } + + .ol-scale-step-text { + position: absolute; + bottom: -5px; + font-size: 12px; + z-index: 11; + color: #000000; + text-shadow: -2px 0 #ffffff, 0 2px #ffffff, 2px 0 #ffffff, 0 -2px #ffffff; + } + + .ol-scale-text { + position: absolute; + font-size: 14px; + text-align: center; + bottom: 25px; + color: #000000; + text-shadow: -2px 0 #ffffff, 0 2px #ffffff, 2px 0 #ffffff, 0 -2px #ffffff; + } + + .ol-scale-singlebar { + position: relative; + height: 10px; + z-index: 9; + border: 1px solid black; + } + + .ol-unsupported { + display: none; + } + + .ol-viewport, + .ol-unselectable { + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + } + + .ol-selectable { + -webkit-touch-callout: default; + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + } + + .ol-grabbing { + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing; + } + + .ol-grab { + cursor: move; + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; + } + + .ol-control { + position: absolute; + background-color: rgba(255, 255, 255, 0.4); + border-radius: 4px; + padding: 2px; + } + + .ol-control:hover { + background-color: rgba(255, 255, 255, 0.6); + } + + .ol-zoom { + top: 0.5em; + left: 0.5em; + } + + .ol-rotate { + top: 0.5em; + right: 0.5em; + transition: opacity 0.25s linear, visibility 0s linear; + } + + .ol-rotate.ol-hidden { + opacity: 0; + visibility: hidden; + transition: opacity 0.25s linear, visibility 0s linear 0.25s; + } + + .ol-zoom-extent { + top: 4.643em; + left: 0.5em; + } + + .ol-full-screen { + right: 0.5em; + top: 0.5em; + } + + .ol-control button { + display: block; + margin: 1px; + padding: 0; + color: white; + font-size: 1.14em; + font-weight: bold; + text-decoration: none; + text-align: center; + height: 1.375em; + width: 1.375em; + line-height: 0.4em; + background-color: rgba(0, 60, 136, 0.5); + border: none; + border-radius: 2px; + } + + .ol-control button::-moz-focus-inner { + border: none; + padding: 0; + } + + .ol-zoom-extent button { + line-height: 1.4em; + } + + .ol-compass { + display: block; + font-weight: normal; + font-size: 1.2em; + will-change: transform; + } + + .ol-touch .ol-control button { + font-size: 1.5em; + } + + .ol-touch .ol-zoom-extent { + top: 5.5em; + } + + .ol-control button:hover, + .ol-control button:focus { + text-decoration: none; + background-color: rgba(0, 60, 136, 0.7); + } + + .ol-zoom .ol-zoom-in { + border-radius: 2px 2px 0 0; + } + + .ol-zoom .ol-zoom-out { + border-radius: 0 0 2px 2px; + } + + .ol-attribution { + text-align: right; + bottom: 0.5em; + right: 0.5em; + max-width: calc(100% - 1.3em); + } + + .ol-attribution ul { + margin: 0; + padding: 0 0.5em; + color: #000; + text-shadow: 0 0 2px #fff; + } + + .ol-attribution li { + display: inline; + list-style: none; + } + + .ol-attribution li:not(:last-child):after { + content: ' '; + } + + .ol-attribution img { + max-height: 2em; + max-width: inherit; + vertical-align: middle; + } + + .ol-attribution ul, + .ol-attribution button { + display: inline-block; + } + + .ol-attribution.ol-collapsed ul { + display: none; + } + + .ol-attribution:not(.ol-collapsed) { + background: rgba(255, 255, 255, 0.8); + } + + .ol-attribution.ol-uncollapsible { + bottom: 0; + right: 0; + border-radius: 4px 0 0; + } + + .ol-attribution.ol-uncollapsible img { + margin-top: -0.2em; + max-height: 1.6em; + } + + .ol-attribution.ol-uncollapsible button { + display: none; + } + + .ol-zoomslider { + top: 4.5em; + left: 0.5em; + height: 200px; + } + + .ol-zoomslider button { + position: relative; + height: 10px; + } + + .ol-touch .ol-zoomslider { + top: 5.5em; + } + + .ol-overviewmap { + left: 0.5em; + bottom: 0.5em; + } + + .ol-overviewmap.ol-uncollapsible { + bottom: 0; + left: 0; + border-radius: 0 4px 0 0; + } + + .ol-overviewmap .ol-overviewmap-map, + .ol-overviewmap button { + display: inline-block; + } + + .ol-overviewmap .ol-overviewmap-map { + border: 1px solid #7b98bc; + height: 150px; + margin: 2px; + width: 150px; + } + + .ol-overviewmap:not(.ol-collapsed) button { + bottom: 1px; + left: 2px; + position: absolute; + } + + .ol-overviewmap.ol-collapsed .ol-overviewmap-map, + .ol-overviewmap.ol-uncollapsible button { + display: none; + } + + .ol-overviewmap:not(.ol-collapsed) { + background: rgba(255, 255, 255, 0.8); + } + + .ol-overviewmap-box { + border: 2px dotted rgba(0, 60, 136, 0.7); + } + + .ol-overviewmap .ol-overviewmap-box:hover { + cursor: move; + } + + // + // Disable Attribution ... + .ol-attribution { + display: none !important; + } +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map.component.ts + +import { + of, + Subject, + forkJoin, + Observable, + throwError, + Subscription, +} from 'rxjs'; +import { + Component, + ViewChild, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XMapAction, + XMapCSSVar, + XPlaceResult, + XMapLocation, + XMapActionModel, + XMapItemPosition, + XMapContainerComponent, + XMapItemPositionIdentifier, +} from './x-map.typings'; +import { + DragPan, + DragZoom, + PinchZoom, + DragRotate, + Interaction, + PinchRotate, + KeyboardPan, + KeyboardZoom, + MouseWheelZoom, + DoubleClickZoom, +} from 'ol/interaction'; +import { + tap, + map, + concatMap, + catchError, + debounceTime, + distinctUntilChanged, +} from 'rxjs/operators'; +import { + nameof, + XHeader, + applyLocale, + applyFinally, + XContentType, + generateUuid, + XExceptionIDs, + XStandardType, + toNormalString, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { Point } from 'ol/geom'; +import BaseLayer from 'ol/layer/Base'; +import { MapOptions } from 'ol/PluggableMap'; +import { + XSearchBarEvent, + XSearchBarEventIdentifier, +} from '../x-search-bar/x-search-bar.typings'; +import { toMapLocation } from './x-map.tools'; +import { fromLonLat, transform } from 'ol/proj'; +import { HttpHeaders } from '@angular/common/http'; +import { XListItem } from '../x-list/x-list.typings'; +import { OSM, Vector as VectorSource } from 'ol/source'; +import { Map, View, Feature, Overlay, Kinetic } from 'ol'; +import { Zoom, Rotate, ZoomSlider, Control } from 'ol/control'; +import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'; +import { XSlotLayout, XSlotName } from '../x-slotter/x-slotter.typings'; +import { XSearchBarComponent } from '../x-search-bar/x-search-bar.component'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-map', + templateUrl: './x-map.component.html', + styleUrls: ['./x-map.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XMapComponent extends XMapContainerComponent { + // + //#region Props ... + // + private ZOOM_DELTA = 2; + private IMPORTANT_ZOOM = 18; + private MAP: Map = undefined; + private ANIMATION_DURATION = 500; + private MARKER: VectorLayer; + private ZOOM_CONTROL = new Zoom(); + private IS_MAP_INITIALIZED = false; + private MARKER_POSITION: XMapLocation; + private ROTATE_CONTROL = new Rotate(); + private ACTION_WAITIONG_DURATION = 500; + private MAP_OPTIONS: MapOptions = undefined; + private ZOOM_SLIDER_CONTROL = new ZoomSlider(); + private positionProvider = new Subject(); + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private searchFilterSubject$ = new Subject(); + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly componentId = generateUuid('XMapComponent'); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + readonly ItemPositions = Object.assign({}, XMapItemPosition); + + // + isSearching = false; + searchPaneOpen = false; + currentPosition: string = ''; + searchResult: XPlaceResult[] = []; + showMapView: XStandardType = false; + searchResultListItems: XListItem[] = []; + + // + get isInitialized() { + return this.IS_MAP_INITIALIZED; + } + + get map() { + return this.MAP; + } + + get options() { + return this.MAP_OPTIONS as MapOptions; + } + + get hasMarker() { + return !isNullOrUndefined(this.MARKER); + } + + get markerPosition() { + return this.MARKER_POSITION; + } + + get currentZoom() { + return !this.isInitialized ? -1 : this.MAP.getView().getZoom(); + } + + get currentLatitude() { + // + if (!this.isInitialized) { + return -1; + } + + // + const lonLat = this.map.getView().getCenter(); + const location = toMapLocation(lonLat); + + // + return +location.latitude; + } + + get currentLongitude() { + // + if (!this.isInitialized) { + return -1; + } + + // + const lonLat = this.map.getView().getCenter(); + const location = toMapLocation(lonLat); + + // + return +location.longitude; + } + + @ViewChild('mapView') + mapView: ElementRef; + + @ViewChild('pluginsBar') + pluginsBar: ElementRef; + + @ViewChild('searchBar') + searchBar: XSearchBarComponent; + //#endregion + + // + //#region LifeCycles ... + async afterViewInit() { + super.afterViewInit(); + + // + await this.initializeMap(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region CanSelect Change ... + const isCanSelectChanged = changeKeys.includes( + nameof('canSelect') + ); + if (isCanSelectChanged) { + await this.applyCanSelect(); + } + //#endregion + + // + //#region ActionProvider changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Location Info changed ... + // const isZoomChanged = changeKeys.includes(nameof('zoom')); + // const isLatitudeChanged = changeKeys.includes( + // nameof('latitude') + // ); + // const isLongitudeChanged = changeKeys.includes( + // nameof('longitude') + // ); + // const isAddMarkerOnSelectedLocationChanged = changeKeys.includes( + // nameof('addMarkerOnSelectedLocation') + // ); + // if ( + // isZoomChanged || + // isLatitudeChanged || + // isLongitudeChanged || + // isAddMarkerOnSelectedLocationChanged + // ) { + // await this.applyLocationInfo(); + // } + //#endregion + + // + //#region Show Zoom changed ... + const isShowZoomChanged = changeKeys.includes( + nameof('showZoom') + ); + if (isShowZoomChanged) { + await this.applyZoom(); + } + //#endregion + + // + //#region Show Rotate changed ... + const isShowRotateChanged = changeKeys.includes( + nameof('showRotate') + ); + if (isShowRotateChanged) { + await this.applyRotate(); + } + //#endregion + + // + //#region Show Zoom Slider changed ... + const isShowZoomSliderChanged = changeKeys.includes( + nameof('showZoomSlider') + ); + if (isShowZoomSliderChanged) { + await this.applyZoomSlider(); + } + //#endregion + + // + //#region Offset Height Handling ... + const isShowSearchBarChanged = changeKeys.includes( + nameof('showSearchBar') + ); + const isSearchBarPositionChanged = changeKeys.includes( + nameof('searchBarPosition') + ); + const isShowSelectedLoactionChanged = changeKeys.includes( + nameof('showSelectedLocation') + ); + const isSelectedLocationPositionChanged = changeKeys.includes( + nameof('selectedLocationPosition') + ); + if ( + isShowRotateChanged || + isShowZoomChanged || + isShowZoomSliderChanged || + isShowSearchBarChanged || + isSearchBarPositionChanged || + isShowSelectedLoactionChanged || + isSelectedLocationPositionChanged + ) { + await this.applyContentOffset(); + } + //#endregion + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + // Register Action Provider ... + this.registerActionProvider(); + + // + // Register Search ... + this.registerPluginHandler(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Providers ... + async getCenter( + latitude?: number | XStandardType, + longitude?: number | XStandardType + ) { + // + const desiredLatitude = await this.getNormallizedLatitued(latitude); + const desiredLongitude = await this.getNormallizedLongitude(longitude); + const result = fromLonLat([desiredLongitude, desiredLatitude]); + + // + return result; + } + //#endregion + + // + //#region UI Providers ... + canShowPlugins(position: XMapItemPositionIdentifier) { + return this.getValue(position).pipe( + concatMap((pos) => + isNullOrEmptyString(pos) + ? of(false) + : forkJoin({ + // + // progressBar: forkJoin({ + // canShow: this.getValue(this.showProgressBar), + // position: this.getValue(this.progressBarPosition), + // }), + + // + locator: forkJoin({ + canShow: this.getValue(this.showLocator), + position: this.getValue(this.locatorPosition), + }), + + // + clearMarker: forkJoin({ + canShow: this.getValue(this.showClearMarker), + position: this.getValue(this.clearMarkerPosition), + }), + + // + goMarkedPlace: forkJoin({ + canShow: this.getValue(this.showGoMarkedPlace), + position: this.getValue(this.goMarkedPlacePosition), + }), + + // + selectedLocation: forkJoin({ + canShow: this.getValue(this.showSelectedLocation), + position: this.getValue(this.selectedLocationPosition), + }), + }).pipe( + map((info) => { + return { + pos, + info, + }; + }), + map((data) => { + return ( + // + // ProgressBar ... + // (data.info.progressBar.canShow && + // data.info.progressBar.position === data.pos) || + // + // Locator ... + (data.info.locator.canShow && + data.info.locator.position === data.pos) || + // + // ClearMarker ... + (data.info.clearMarker.canShow && + data.info.clearMarker.position === data.pos) || + // + // Go Marked Place ... + (data.info.goMarkedPlace.canShow && + data.info.goMarkedPlace.position === data.pos) || + // + // Selected Location ... + (data.info.selectedLocation.canShow && + data.info.selectedLocation.position === data.pos) + ); + }) + ) + ) + ); + } + //#endregion + + // + //#region UI Handlers ... + async handleSeach(query: string) { + // + this.searchBarQueryChange.emit(query); + + // + if (this.isSearching) { + // + setTimeout( + async () => await this.handleSeach(query), + this.ACTION_WAITIONG_DURATION + ); + + // + return; + } + + // + if (isNullOrEmptyString(query)) { + // + this.searchResult = []; + this.isSearching = false; + this.searchPaneOpen = false; + this.searchResultListItems = []; + await this.setLoadingState(false); + return; + } + + // + query = toNormalString(query); + this.searchFilterSubject$.next(query); + } + + async handleSearchBarEvents(event: XSearchBarEventIdentifier) { + // + switch (event) { + // + case XSearchBarEvent.FOCUS: + // + setTimeout(() => { + // + this.searchPaneOpen = + this.hasChild(this.searchResult) && + this.hasChild(this.searchResultListItems); + + // + this.detectChanges(); + }, this.ACTION_WAITIONG_DURATION); + break; + + // + case XSearchBarEvent.BLUR: + // + setTimeout(() => { + // + this.searchPaneOpen = false; + this.detectChanges(); + }, this.ACTION_WAITIONG_DURATION); + break; + } + } + + async handleChangeLocation(item: XListItem) { + // + const zoom = this.IMPORTANT_ZOOM - 4; + const addMarker = await this.getValueAsync( + this.addMarkerOnSelectedLocation + ); + if (addMarker) { + await this.addMarker(item.data.lat, item.data.lon, zoom); + } else { + await this.setCenter(item.data.lat, item.data.lon, zoom); + } + } + + async handleLocationResultClickOutside(event: any) { + // + this.searchPaneOpen = false; + this.detectChanges(); + } + + async handleGoMarkedPlace() { + await this.goMarkedPlace(); + } + + async handleClearMarker() { + await this.clearMarker(); + } + + async handleLocate() { + await this.locate(); + } + + async handleSlectedLocationClicked(message: string) { + // + message = applyLocale(message, 'en-US'); + this.managerService.saveToClipboard(message); + } + //#endregion + + // + //#region Actions ... + async updateMap() { + // + // Recalling ... + if (!this.isInitialized) { + setTimeout(async () => { + await this.updateMap(); + }, this.ACTION_WAITIONG_DURATION); + + // + return; + } + + // + //#region Update ... + this.MAP.render(); + this.detectChanges(); + this.MAP.updateSize(); + this.MAP.renderSync(); + this.managerService.dispatchResizeEvent(); + if (this.mapView && this.mapView.nativeElement) { + // + const mapView = this.mapView.nativeElement as HTMLElement; + mapView.blur(); + mapView.focus(); + } + //#endregion + + // + setTimeout(async () => { + await this.updateMap(); + }, this.ACTION_WAITIONG_DURATION); + } + + async setZoom(zoom?: number | XStandardType) { + // + const desiredZoom = await this.getNormallizedZoom(zoom); + + // + if (!this.isInitialized) { + // + setTimeout(async () => { + await this.setZoom(zoom); + }, this.ACTION_WAITIONG_DURATION); + + // + return; + } + + // + this.map.getView().animate({ + zoom: desiredZoom, + duration: this.ANIMATION_DURATION, + }); + } + + async setCenter( + latitude?: number | XStandardType, + longitude?: number | XStandardType, + zoom?: number | XStandardType + ) { + // + const desiredZoom = await this.getNormallizedZoom(zoom); + const center = await this.getCenter(latitude, longitude); + + // + if (!this.isInitialized) { + // + setTimeout(async () => { + await this.setCenter(latitude, longitude, zoom); + }, this.ACTION_WAITIONG_DURATION); + + // + return; + } + + // + this.map.getView().animate({ + center, + zoom: desiredZoom, + duration: this.ANIMATION_DURATION, + }); + } + + async addMarker( + latitude?: number | XStandardType, + longitude?: number | XStandardType, + zoom?: number | XStandardType + ) { + // + if (!this.isInitialized) { + // + setTimeout(async () => { + await this.addMarker(latitude, longitude, zoom); + }, this.ACTION_WAITIONG_DURATION); + + // + return; + } + + // + const desiredZoom = await this.getNormallizedZoom(zoom); + const desiredLat = await this.getNormallizedLatitued(latitude); + const desiredLong = await this.getNormallizedLongitude(longitude); + + // + if (this.MARKER) { + // + this.MAP.removeLayer(this.MARKER); + this.MARKER = null; + this.actionProvider.next({ action: XMapAction.HasNotMarker }); + } + + // + this.MARKER_POSITION = { + zoom: desiredZoom.toString(), + latitude: desiredLat.toString(), + longitude: desiredLong.toString(), + }; + + // + const markerStyle = await this.getValueAsync(this.markerStyle); + + // + this.MARKER = new VectorLayer({ + source: new VectorSource({ + features: [ + new Feature({ + geometry: new Point( + transform([desiredLong, desiredLat], 'EPSG:4326', 'EPSG:3857') + ), + }), + ], + }), + style: markerStyle, + }); + + // + // Netify Selected Location ... + await this.updateSelectedLocation(latitude, longitude); + this.actionProvider.next({ action: XMapAction.HasMarker }); + + // + // Emit Location Selected Event ... + this.locationSelected.emit(this.MARKER_POSITION); + + // + this.MAP.addLayer(this.MARKER); + + // + setTimeout(async () => { + await this.setCenter(desiredLat, desiredLong, desiredZoom); + }, this.ACTION_WAITIONG_DURATION); + } + + async clearMarker() { + // + if (!this.MARKER) { + return; + } + + // + if (!this.isInitialized) { + // + setTimeout(async () => { + await this.clearMarker(); + }, this.ACTION_WAITIONG_DURATION); + + // + return; + } + + // + this.MAP.removeLayer(this.MARKER); + this.MARKER = null; + this.MARKER_POSITION = null; + + // + this.positionProvider.next(''); + this.actionProvider.next({ + action: XMapAction.HasNotMarker, + }); + } + + async goMarkedPlace() { + // + if (!this.hasMarker) { + return; + } + + // + if (!this.isInitialized) { + // + setTimeout(async () => { + await this.goMarkedPlace(); + }, this.ACTION_WAITIONG_DURATION); + + // + return; + } + + // + await this.setCenter( + +this.markerPosition.latitude, + +this.markerPosition.longitude + ); + } + + async locate() { + // + await this.setLoadingState(true); + const location = await this.getValueAsync( + applyFinally( + this.managerService.geoLocationService.getCurrentLocation().pipe( + catchError((err) => { + // + this.showError(err); + + // + return throwError(err); + }) + ), + async () => await this.setLoadingState(false) + ) + ); + + // + if (!isNullOrUndefined(location)) { + // + this.zoom = this.IMPORTANT_ZOOM; + this.latitude = location.coords.latitude; + this.longitude = location.coords.longitude; + + // + await this.addMarker( + location.coords.latitude, + location.coords.longitude, + this.zoom + ); + } + } + //#endregion + + // + //#region Private ... + private async prepareMapOptions() { + // + this.MAP = null; + this.MAP_OPTIONS = null; + this.showMapView = false; + + // + const zoom = 0; // await this.getNormallizedZoom(); + + // + const desiredLatitude = 0; // await this.getNormallizedLatitued(); + const desiredLongitude = 0; // await this.getNormallizedLongitude(); + const center = fromLonLat([desiredLatitude, desiredLongitude]); + + // + const view = new View({ + zoom, + center, + }); + + // + const layers: BaseLayer[] = []; + layers.push( + new TileLayer({ + source: new OSM(), + }) + ); + + // + const controls: Control[] = await this.getControls(); + + // + const interactions: Interaction[] = await this.getInteractions(); + + // + const overlays: Overlay[] = []; + + // + this.MAP_OPTIONS = { + view, + layers, + controls, + overlays, + target: this.componentId, + interactions, + keyboardEventTarget: this.componentId, + }; + } + + private async initializeMap() { + // + if (!isNullOrUndefined(this.MAP)) { + this.MAP = null; + } + + // + await this.prepareMapOptions(); + + // + this.MAP = new Map(this.options); + + // + //#region Attached Events ... + // + // Map Event Handler ... + this.MAP.on('rendercomplete', async (e: any) => { + // + this.isReady.emit(); + + // + this.IS_MAP_INITIALIZED = true; + + // + this.managerService.dispatchResizeEvent(); + Promise.resolve().then(() => this.managerService.dispatchResizeEvent()); + setTimeout( + () => this.managerService.dispatchResizeEvent(), + this.ACTION_WAITIONG_DURATION + ); + }); + + // + // Zoom End ... + this.MAP.on('moveend', async (e: any) => { + // + const zoom = this.map.getView().getZoom(); + const lonLat = this.map.getView().getCenter(); + const location = toMapLocation(lonLat); + location.zoom = zoom.toString(); + + // + this.zoom = zoom; + + // + this.viewChanged.emit(location); + }); + //#endregion + + // + this.showMapView = true; + await this.applyContentOffset(); + await this.updateMap(); + await this.applyCanSelect(); + + // + await this.applyLocationInfo(); + } + + private async getInteractions() { + // + const result: Interaction[] = []; + + // + const delta = this.ZOOM_DELTA; + const duration = this.ANIMATION_DURATION; + + // + const dragPan = true; + const pinchZoom = true; + const onFocusOnly = true; + const pinchRotate = true; + const keyboardPan = true; + const keyboardZoom = true; + const shiftDragZoom = true; + const mouseWheelZoom = true; + const doubleClickZoom = true; + const altShiftDragRotate = true; + + // + //#region Preperation ... + // + // DragRotate ... + const kinetic = new Kinetic(-0.005, 0.05, 100); + if (altShiftDragRotate) { + result.push(new DragRotate()); + } + + // + // DoubleClickZoom ... + if (doubleClickZoom) { + result.push( + new DoubleClickZoom({ + delta, + duration, + }) + ); + } + + // + // DragPan ... + if (dragPan) { + result.push( + new DragPan({ + onFocusOnly, + kinetic, + }) + ); + } + + // + // PinchRotate ... + if (pinchRotate) { + result.push(new PinchRotate()); + } + + // + // PinchZoom ... + if (pinchZoom) { + result.push( + new PinchZoom({ + duration, + }) + ); + } + + // + // KeyboardPan ... + if (keyboardPan) { + result.push(new KeyboardPan()); + } + + // + // KeyboardZoom ... + if (keyboardZoom) { + result.push( + new KeyboardZoom({ + delta, + duration, + }) + ); + } + + // + // MouseWheelZoom ... + if (mouseWheelZoom) { + result.push( + new MouseWheelZoom({ + duration, + onFocusOnly, + }) + ); + } + + // + // DragZoom ... + if (shiftDragZoom) { + result.push( + new DragZoom({ + duration, + }) + ); + } + //#endregion + + // + return result; + } + + private async getControls() { + // + const result: Control[] = []; + + // + //#region Adding Controls ... + // + // Zoom ... + const showZoom = await this.getValueAsync(this.showZoom); + if (showZoom) { + result.push(this.ZOOM_CONTROL); + } + + // + // Rotate ... + const showRotate = await this.getValueAsync(this.showRotate); + if (showRotate) { + result.push(this.ROTATE_CONTROL); + } + + // + // ZoomSlider ... + const showZoomSlider = await this.getValueAsync(this.showZoomSlider); + if (showZoomSlider) { + result.push(this.ZOOM_SLIDER_CONTROL); + } + //#endregion + + // + return result; + } + + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XMapAction.AddMarker: + // + if (model.payload) { + // + const result: XMapLocation = model.payload; + if (result) { + // + const lat = +result.latitude; + const long = +result.longitude; + const zoom = +result.zoom; + + // + await this.addMarker(lat, long, zoom); + } + } + break; + + // + case XMapAction.ClearMarker: + await this.clearMarker(); + break; + + // + case XMapAction.GoMarkedPlace: + await this.goMarkedPlace(); + break; + + // + case XMapAction.SetCenter: + // + if (model.payload) { + // + const result: XMapLocation = model.payload; + if (result) { + // + const zoom = +result.zoom; + const lat = +result.latitude; + const long = +result.longitude; + + // + await this.setCenter(lat, long, zoom); + } + } + break; + + // + case XMapAction.SetZoom: + // + if (model.payload) { + // + const zoom = +model.payload; + await this.setZoom(zoom); + } + break; + + // + case XMapAction.Locate: + await this.locate(); + break; + + // + case XMapAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + + private registerPluginHandler() { + // + // Register Query Change ... + this.applyTakeUntilWrapper( + this.searchFilterSubject$ + .asObservable() + .pipe(debounceTime(800), distinctUntilChanged()) + ).subscribe(async (query) => { + await this.handleSearchAction(query); + }); + + // + // Register LocationProvider ... + this.applyTakeUntilWrapper(this.positionProvider.asObservable()).subscribe( + (position) => { + // + this.currentPosition = position; + this.detectChanges(); + } + ); + } + + private async getNormallizedLatitued( + latitude?: number | XStandardType + ) { + const result = await this.getValueAsync(latitude || this.latitude || 0); + return result || 0; + } + + private async getNormallizedLongitude( + longitude?: number | XStandardType + ) { + const result = await this.getValueAsync(longitude || this.longitude || 0); + return result || 0; + } + + private async getNormallizedZoom(zoom?: number | XStandardType) { + // + let result = 0; + if (!zoom) { + result = await this.getValueAsync(this.zoom); + } else { + result = await this.getValueAsync(zoom); + } + + // + result = + result > this.IMPORTANT_ZOOM + ? this.IMPORTANT_ZOOM + : result < 0 + ? 0 + : result; + + // + return result || 0; + } + + private async getSummarySelectedLocationMessage( + latitude?: number | XStandardType, + longitude?: number | XStandardType + ) { + // + const desiredLat = await this.getNormallizedLatitued(latitude); + const desiredLong = await this.getNormallizedLongitude(longitude); + + // + const mLat = this.applyLocale(desiredLat.toString()); + const mLong = this.applyLocale(desiredLong.toString()); + + // + const result = `${mLat}, ${mLong}`; + return result; + } + + // + private async applyLocationInfo() { + // + if (!this.isInitialized) { + // + setTimeout( + async () => await this.applyLocationInfo(), + this.ACTION_WAITIONG_DURATION + ); + // + return; + } + + // + const normallizedZoom = await this.getNormallizedZoom(); + const latitude = await this.getNormallizedLatitued(); + const longitude = await this.getNormallizedLongitude(); + + // + const addMarker = await this.getValueAsync( + this.addMarkerOnSelectedLocation + ); + const zoom = addMarker ? this.IMPORTANT_ZOOM : normallizedZoom; + + // + if (this.longitude && this.latitude) { + // + setTimeout(async () => { + // + if (addMarker) { + await this.addMarker(latitude, longitude, zoom); + } else { + await this.setCenter(latitude, longitude, zoom); + } + }, this.ACTION_WAITIONG_DURATION); + + // + return; + } + + // + if (this.zoom) { + // + setTimeout(async () => { + await this.setZoom(normallizedZoom); + }, 1000); + + // + return; + } + } + + private async applyContentOffset() { + // + let result = 5; + + // + const showSerach = await this.getValueAsync(this.showSearchBar); + if (showSerach) { + // + const searchBarPosition = await this.getValueAsync( + this.searchBarPosition + ); + + // + if (searchBarPosition === XMapItemPosition.TOP && this.searchBar) { + // + const searchBarHeight = this.searchBar.getElementBoundaryHeight(); + if (searchBarHeight === 0) { + // + setTimeout( + async () => await this.applyContentOffset(), + this.ACTION_WAITIONG_DURATION + ); + + // + return; + } + + // + result += searchBarHeight; + } + } + + // + const canShowPluginBarAtTop = await this.getValueAsync( + this.canShowPlugins(this.ItemPositions.TOP) + ); + if (canShowPluginBarAtTop && this.pluginsBar) { + // + const pluginsBarHeight = + this.pluginsBar.nativeElement.getBoundingClientRect().height; + if (pluginsBarHeight === 0) { + // + setTimeout( + async () => await this.applyContentOffset(), + this.ACTION_WAITIONG_DURATION + ); + + // + return; + } + + // + result += pluginsBarHeight; + } + + // + this.setCssVar(XMapCSSVar.MapControlOffset, `${result}px`); + } + + private async applyCanSelect() { + // + if (!this.isInitialized) { + // + setTimeout( + async () => await this.applyCanSelect(), + this.ACTION_WAITIONG_DURATION + ); + + // + return; + } + + // + // Add Marker on Map Clicked ... + const canSelect = await this.getValueAsync(this.canSelect); + if (canSelect) { + this.MAP.on('click', async (args: any) => { + // + this.searchPaneOpen = false; + + // + const lonlat = transform(args.coordinate, 'EPSG:3857', 'EPSG:4326'); + + // + // tslint:disable-next-line:no-shadowed-variable + const lat = lonlat[1] as number; + + // + // tslint:disable-next-line:no-shadowed-variable + const long = lonlat[0] as number; + + // + // , this.IMPORTANT_ZOOM - 6 + await this.addMarker(lat, long); + }); + } + } + + // + //#region Plugin Handlers ... + private async applyZoom(forceUpdate = true) { + // + if (!this.isInitialized) { + // + setTimeout( + async () => await this.applyZoom(), + this.ACTION_WAITIONG_DURATION + ); + + // + return; + } + + // + const showZoom = await this.getValueAsync(this.showZoom); + if (showZoom) { + this.map.addControl(this.ZOOM_CONTROL); + } else { + this.map.removeControl(this.ZOOM_CONTROL); + } + + // + if (forceUpdate) { + await this.updateMap(); + } + } + + private async applyRotate(forceUpdate = true) { + // + if (!this.isInitialized) { + // + setTimeout( + async () => await this.applyRotate(), + this.ACTION_WAITIONG_DURATION + ); + + // + return; + } + + // + const showRotate = await this.getValueAsync(this.showRotate); + if (showRotate) { + this.map.addControl(this.ROTATE_CONTROL); + } else { + this.map.removeControl(this.ROTATE_CONTROL); + } + + // + if (forceUpdate) { + await this.updateMap(); + } + } + + private async applyZoomSlider(forceUpdate = true) { + // + if (!this.isInitialized) { + // + setTimeout( + async () => await this.applyZoomSlider(), + this.ACTION_WAITIONG_DURATION + ); + + // + return; + } + + // + const showZoomSlider = await this.getValueAsync(this.showZoomSlider); + if (showZoomSlider) { + this.map.addControl(this.ZOOM_SLIDER_CONTROL); + } else { + this.map.removeControl(this.ZOOM_SLIDER_CONTROL); + } + + // + if (forceUpdate) { + await this.updateMap(); + } + } + + // + //#region SelectedLocation Plugin ... + private async updateSelectedLocation( + latitude?: number | XStandardType, + longitude?: number | XStandardType + ) { + // + if (!this.isInitialized) { + // + setTimeout(async () => { + await this.updateSelectedLocation(latitude, longitude); + }, this.ACTION_WAITIONG_DURATION); + + // + return; + } + + // + const message = await this.getSummarySelectedLocationMessage( + latitude, + longitude + ); + this.positionProvider.next(message); + } + //#endregion + + // + //#region SearchBar Plugin ... + private async handleSearchAction(query: string) { + // + const url = this.searchPlaceProvider(query); + + // + this.searchResult = []; + this.searchPaneOpen = false; + this.searchResultListItems = []; + + // + await this.setLoadingState(true); + applyFinally(this.getPlaceResults(url), async () => { + this.isSearching = false; + await this.setLoadingState(false); + }).subscribe( + async (res) => { + // + this.searchResult = res; + await this.prepareSearchListItems(); + }, + () => { + this.showError(XExceptionIDs.ActionFailed); + } + ); + } + + private getPlaceResults(url: string): Observable { + // + const headers: HttpHeaders = new HttpHeaders(); + headers.set(XHeader.Accept, XContentType.AppJson); + + // + return applyFinally( + this.applyLoadingWrapper( + this.managerService.httpClient + .get(url, { headers }) + .pipe( + map((res) => { + return res.map((r) => { + // + const mPlace: XPlaceResult = { + display_name: r.display_name, + lat: +r.lat, + lon: +r.lon, + }; + + // + return mPlace; + }); + }), + tap((res) => { + if (!this.hasChild(res)) { + this.searchPaneOpen = false; + } + }) + ) + ), + () => this.setLoadingState(false) + ); + } + + private async prepareSearchListItems() { + // + if (!this.hasChild(this.searchResult)) { + // + this.searchPaneOpen = false; + await this.setLoadingState(false); + return; + } + + // + this.searchResultListItems = this.searchResult.map((r) => { + // + const li: XListItem = { + data: r, + }; + + // + return li; + }); + + // + this.searchPaneOpen = true; + await this.setLoadingState(false); + + // + this.detectChanges(); + } + //#endregion + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XMapComponent } from './x-map.component'; +import { XCardModule } from '../x-card/x-card.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XListModule } from '../x-list/x-list.module'; +import { XMapContainerComponent } from './x-map.typings'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XMapDialogService } from './x-map-dialog/x-map-dialog.service'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XMapDialogComponent } from './x-map-dialog/x-map-dialog.component'; + +@NgModule({ + imports: [ + XListModule, + XCardModule, + XIconModule, + CommonModule, + XSlotterModule, + XBaseComponentModule, + ], + exports: [ + XListModule, + XCardModule, + XIconModule, + CommonModule, + XMapComponent, + XSlotterModule, + XMapDialogComponent, + XBaseComponentModule, + ], + providers: [XMapDialogService], + entryComponents: [XMapDialogComponent], + declarations: [XMapComponent, XMapDialogComponent, XMapContainerComponent], +}) +export class XMapModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map.tools.ts + +import { XMapLocation } from './x-map.typings'; +import { isNullOrEmptyString, hasChild, isString, isArray, countChild } from 'x-framework-core'; + +/** + * get given position link for open on Google Maps ... + * + * @param model map result model ... + */ +export function getGoogleMapLink(model: XMapLocation) { + return `https://maps.google.com/?q=${model.latitude},${model.longitude}`; +} + +/** + * convert a point string to Map Result ... + * + * @param longLat the string which represent point ... + */ +export function fromLongLat(longLat: string) { + // + if (isNullOrEmptyString(longLat)) { + return null; + } + + // + const longLatArr = longLat.toString().split(','); + if (!hasChild(longLatArr) || longLatArr.length !== 2) { + return null; + } + + // + const result: XMapLocation = { + latitude: longLatArr[1], + longitude: longLatArr[0], + }; + + // + return result; +} + +export function toMapLocation(source: string | Array) { + // + if (isString(source)) { + return fromLongLat(source as string); + } + + // + if (!isArray(source) || countChild(source as Array) < 2) { + return null; + } + + // + const longLatArr = source as Array; + + // + const result: XMapLocation = { + latitude: longLatArr[1].toString(), + longitude: longLatArr[0].toString(), + }; + + // + return result; +} + +/** + * convert a point object to point string ... + * + * @param point the map selected object to convert it ... + */ +export function toLongLat(point: XMapLocation) { + return `${point.longitude},${point.latitude}`; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map.typings.ts + +import { + Input, + Inject, + NgZone, + Output, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XCardContainer, + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { + XSearchBarActionModel, + XSearchBarTypeIdentifier, + XComponentSearchBarContainer, + IXComponentSearchBarContainer, +} from '../x-search-bar/x-search-bar.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { Style, Circle, Stroke, Fill } from 'ol/style'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { IXBaseContainer } from '../classes/i-x-base.container'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export interface XPlaceResult { + display_name: string; + lat: number; + lon: number; +} + +export enum XMapItemPosition { + TOP = 'top', + BOTTOM = 'bottom', +} + +export type XMapItemPositionIdentifier = XMapItemPosition | string; + +export enum XMapAction { + // + Locate = 'locate', + + // + AddMarker = 'add_marker', + HasMarker = 'has_marker', + ClearMarker = 'clear-marker', + HasNotMarker = 'has_not_marker', + GoMarkedPlace = 'go-marked-place', + + // + SetCenter = 'set_center', + SetZoom = 'set_zoom', + + // + Refresh = 'refresh', +} + +export type XMapActionIdentifier = XMapAction | string; + +export interface XMapActionModel + extends XBaseActionModel {} + +export enum XMapCSSVar { + MapControlOffset = 'map-controls-offest', +} + +export type XMapCSSVarIdentification = XMapCSSVar | string; + +export interface XMapLocation { + zoom?: string; + latitude?: string; + longitude?: string; +} + +export type XMapStyle = Style; + +export const defaultMarkerStyle: XMapStyle = new Style({ + image: new Circle({ + radius: 10, + stroke: new Stroke({ + color: 'rgb(255,0,0)', + width: 5, + }), + fill: new Fill({ + color: 'rgba(255,0,0,.3)', + }), + }), + stroke: new Stroke({ + color: 'rgb(255,0,0)', + width: 5, + }), + fill: new Fill({ + color: 'rgba(255,165,0,.3)', + }), +}); + +export const defaultMapSearchPlaceProvider = (query: string) => { + return ( + 'https://nominatim.openstreetmap.org/search?q=' + + query + + '&format=json&limit=5' + ); +}; + +// +//#region Inside Card Container ... +type TXMapContainer = IXComponentCardContainer & IXComponentSearchBarContainer; +export interface IXMapContainer extends IXBaseContainer, TXMapContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + zoom: XStandardType; + latitude: XStandardType; + longitude: XStandardType; + + canSelect: XStandardType; + addMarkerOnSelectedLocation: XStandardType; + + // + isReady: EventEmitter; + viewChanged: EventEmitter; + locationSelected: EventEmitter; + // zoomChanged: EventEmitter; + + markerStyle: XStandardType; + + showProgressBar: XStandardType; + progressBarColor: XStandardType; + progressBarPosition: XStandardType; + + showSelectedLocation: XStandardType; + selectedLocationPosition: XStandardType; + + showGoMarkedPlace: XStandardType; + goMarkedPlacePosition: XStandardType; + + showClearMarker: XStandardType; + clearMarkerPosition: XStandardType; + + showLocator: XStandardType; + locatorPosition: XStandardType; + + showZoom: XStandardType; + showRotate: XStandardType; + showZoomSlider: XStandardType; + + // + searchBarPosition: XStandardType; + searchPlaceProvider: (query: string) => string; + searchResultListColor: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XMapContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly zoom: XStandardType = 0; + readonly latitude: XStandardType = 0; + readonly longitude: XStandardType = 0; + + readonly canSelect: XStandardType = true; + readonly addMarkerOnSelectedLocation: XStandardType = true; + + // + readonly isReady: EventEmitter = new EventEmitter(); + readonly viewChanged: EventEmitter = + new EventEmitter(); + readonly locationSelected: EventEmitter = + new EventEmitter(); + // zoomChanged: EventEmitter; + + readonly markerStyle: XStandardType = defaultMarkerStyle; + + readonly showProgressBar: XStandardType = true; + readonly progressBarColor: XStandardType = + XColorWithBrightness.Warning; + readonly progressBarPosition: XStandardType = + XMapItemPosition.TOP; + + readonly showSelectedLocation: XStandardType = true; + readonly selectedLocationPosition: XStandardType = + XMapItemPosition.TOP; + + readonly showGoMarkedPlace: XStandardType = true; + readonly goMarkedPlacePosition: XStandardType = + XMapItemPosition.TOP; + + readonly showClearMarker: XStandardType = true; + readonly clearMarkerPosition: XStandardType = + XMapItemPosition.TOP; + + readonly showLocator: XStandardType = true; + readonly locatorPosition: XStandardType = + XMapItemPosition.TOP; + + readonly showZoom: XStandardType = true; + readonly showRotate: XStandardType = true; + readonly showZoomSlider: XStandardType = false; + + // + readonly searchBarPosition: XStandardType = + XMapItemPosition.TOP; + readonly searchPlaceProvider: (query: string) => string = + defaultMapSearchPlaceProvider; + readonly searchResultListColor: XStandardType = + XColorWithBrightness.Light; + + // + //#region ComponentSearchBar ... + // + readonly showSearchBar: XStandardType = + this.searchBarPropertyProvider.showSearchBar; + + // + readonly searchBarActionProvider: Subject = + this.searchBarPropertyProvider.searchBarActionProvider; + readonly searchBarCssClass: XStandardType> = + this.searchBarPropertyProvider.searchBarCssClass; + + // + readonly searchBarColor: XStandardType = + this.searchBarPropertyProvider.searchBarColor; + readonly searchBarForegroundColor: XStandardType = + this.searchBarPropertyProvider.searchBarForegroundColor; + + // + readonly searchBarSearchInputColor: XStandardType = + this.searchBarPropertyProvider.searchBarSearchInputColor; + readonly searchBarForegroundSearchInputColor: XStandardType = + this.searchBarPropertyProvider.searchBarForegroundSearchInputColor; + + // + readonly searchBarType: XStandardType = + this.searchBarPropertyProvider.searchBarType; + readonly searchBarTitle: XStandardType = + this.searchBarPropertyProvider.searchBarTitle; + readonly searchBarValue: XStandardType = + this.searchBarPropertyProvider.searchBarValue; + readonly searchBarEnterKeyHint: XStandardType = + this.searchBarPropertyProvider.searchBarEnterKeyHint; + readonly searchBarKeyboardType: XStandardType = + this.searchBarPropertyProvider.searchBarKeyboardType; + readonly searchBarSpellcheck: XStandardType = + this.searchBarPropertyProvider.searchBarSpellcheck; + readonly searchBarDebounce: XStandardType = + this.searchBarPropertyProvider.searchBarDebounce; + + // + readonly searchBarShowCancelButton: XStandardType = + this.searchBarPropertyProvider.searchBarShowCancelButton; + readonly searchBarCancelButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonIcon; + readonly searchBarCancelButtonTitle: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonTitle; + readonly searchBarCancelButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonIconColor; + + // + readonly searchBarShowClearButton: XStandardType = + this.searchBarPropertyProvider.searchBarShowClearButton; + readonly searchBarClearButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarClearButtonIcon; + readonly searchBarClearButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarClearButtonIconColor; + + // + readonly searchBarSearchButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarSearchButtonIcon; + readonly searchBarSearchButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarSearchButtonIconColor; + + // + readonly searchBarQueryChange: EventEmitter = + this.searchBarPropertyProvider.searchBarQueryChange; + readonly searchBarOnEvent: EventEmitter = + this.searchBarPropertyProvider.searchBarOnEvent; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected propertyProvider: XCardContainer, + protected searchBarPropertyProvider: XComponentSearchBarContainer + ) { + super(propertyProvider); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export class XMapContainerComponent + extends XComponentCardContainerComponent + implements IXMapContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + zoom: XStandardType = this.propertyProvider.zoom; + + @Input() + latitude: XStandardType = this.propertyProvider.latitude; + + @Input() + longitude: XStandardType = this.propertyProvider.longitude; + + @Input() + canSelect: XStandardType = this.propertyProvider.canSelect; + + @Input() + addMarkerOnSelectedLocation: XStandardType = + this.propertyProvider.addMarkerOnSelectedLocation; + + // + @Output() + isReady: EventEmitter = new EventEmitter(); + + @Output() + viewChanged: EventEmitter = new EventEmitter(); + + @Output() + locationSelected: EventEmitter = new EventEmitter(); + // zoomChanged: EventEmitter; + + @Input() + markerStyle: XStandardType = this.propertyProvider.markerStyle; + + @Input() + showProgressBar: XStandardType = + this.propertyProvider.showProgressBar; + + @Input() + progressBarColor: XStandardType = + this.propertyProvider.progressBarColor; + + @Input() + progressBarPosition: XStandardType = + this.propertyProvider.progressBarPosition; + + @Input() + showSelectedLocation: XStandardType = + this.propertyProvider.showSelectedLocation; + + @Input() + selectedLocationPosition: XStandardType = + this.propertyProvider.selectedLocationPosition; + + @Input() + showGoMarkedPlace: XStandardType = + this.propertyProvider.showGoMarkedPlace; + + @Input() + goMarkedPlacePosition: XStandardType = + this.propertyProvider.goMarkedPlacePosition; + + @Input() + showClearMarker: XStandardType = + this.propertyProvider.showClearMarker; + + @Input() + clearMarkerPosition: XStandardType = + this.propertyProvider.clearMarkerPosition; + + @Input() + showLocator: XStandardType = this.propertyProvider.showLocator; + + @Input() + locatorPosition: XStandardType = + this.propertyProvider.locatorPosition; + + @Input() + showZoom: XStandardType = this.propertyProvider.showZoom; + + @Input() + showRotate: XStandardType = this.propertyProvider.showRotate; + + @Input() + showZoomSlider: XStandardType = this.propertyProvider.showZoomSlider; + + // + @Input() + searchBarPosition: XStandardType = + this.propertyProvider.searchBarPosition; + + @Input() + searchPlaceProvider: (query: string) => string = + this.propertyProvider.searchPlaceProvider; + + @Input() + searchResultListColor: XStandardType = + this.propertyProvider.searchResultListColor; + + // + //#region ComponentSearchBar ... + // + @Input() + showSearchBar: XStandardType = this.propertyProvider.showSearchBar; + + // + @Input() + searchBarActionProvider: Subject = + this.propertyProvider.searchBarActionProvider; + + @Input() + searchBarCssClass: XStandardType> = + this.propertyProvider.searchBarCssClass; + + // + @Input() + searchBarColor: XStandardType = + this.propertyProvider.searchBarColor; + + @Input() + searchBarForegroundColor: XStandardType = + this.propertyProvider.searchBarForegroundColor; + + // + @Input() + searchBarSearchInputColor: XStandardType = + this.propertyProvider.searchBarSearchInputColor; + + @Input() + searchBarForegroundSearchInputColor: XStandardType = + this.propertyProvider.searchBarForegroundSearchInputColor; + + // + @Input() + searchBarType: XStandardType = + this.propertyProvider.searchBarType; + + @Input() + searchBarTitle: XStandardType = + this.propertyProvider.searchBarTitle; + + @Input() + searchBarValue: XStandardType = this.propertyProvider.searchBarValue; + + @Input() + searchBarEnterKeyHint: XStandardType = + this.propertyProvider.searchBarEnterKeyHint; + + @Input() + searchBarKeyboardType: XStandardType = + this.propertyProvider.searchBarKeyboardType; + + @Input() + searchBarSpellcheck: XStandardType = + this.propertyProvider.searchBarSpellcheck; + + @Input() + searchBarDebounce: XStandardType = + this.propertyProvider.searchBarDebounce; + + // + @Input() + searchBarShowCancelButton: XStandardType = + this.propertyProvider.searchBarShowCancelButton; + + @Input() + searchBarCancelButtonIcon: XStandardType = + this.propertyProvider.searchBarCancelButtonIcon; + + @Input() + searchBarCancelButtonTitle: XStandardType = + this.propertyProvider.searchBarCancelButtonTitle; + + @Input() + searchBarCancelButtonIconColor: XStandardType = + this.propertyProvider.searchBarCancelButtonIconColor; + + // + @Input() + searchBarShowClearButton: XStandardType = + this.propertyProvider.searchBarShowClearButton; + + @Input() + searchBarClearButtonIcon: XStandardType = + this.propertyProvider.searchBarClearButtonIcon; + + @Input() + searchBarClearButtonIconColor: XStandardType = + this.propertyProvider.searchBarClearButtonIconColor; + + // + @Input() + searchBarSearchButtonIcon: XStandardType = + this.propertyProvider.searchBarSearchButtonIcon; + + @Input() + searchBarSearchButtonIconColor: XStandardType = + this.propertyProvider.searchBarSearchButtonIconColor; + + // + @Output() + searchBarQueryChange: EventEmitter = new EventEmitter(); + + @Output() + searchBarOnEvent: EventEmitter = new EventEmitter(); + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XMapContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps(container?: IXMapContainer): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + zoom: this.getValue(container ? container.zoom : this.zoom), + latitude: this.getValue( + container ? container.latitude : this.latitude + ), + longitude: this.getValue( + container ? container.longitude : this.longitude + ), + canSelect: this.getValue( + container ? container.canSelect : this.canSelect + ), + addMarkerOnSelectedLocation: this.getValue( + container + ? container.addMarkerOnSelectedLocation + : this.addMarkerOnSelectedLocation + ), + markerStyle: this.getValue( + container ? container.markerStyle : this.markerStyle + ), + showProgressBar: this.getValue( + container ? container.showProgressBar : this.showProgressBar + ), + progressBarColor: this.getValue( + container ? container.progressBarColor : this.progressBarColor + ), + progressBarPosition: this.getValue( + container + ? container.progressBarPosition + : this.progressBarPosition + ), + showSelectedLocation: this.getValue( + container + ? container.showSelectedLocation + : this.showSelectedLocation + ), + selectedLocationPosition: this.getValue( + container + ? container.selectedLocationPosition + : this.selectedLocationPosition + ), + showGoMarkedPlace: this.getValue( + container ? container.showGoMarkedPlace : this.showGoMarkedPlace + ), + goMarkedPlacePosition: this.getValue( + container + ? container.goMarkedPlacePosition + : this.goMarkedPlacePosition + ), + showClearMarker: this.getValue( + container ? container.showClearMarker : this.showClearMarker + ), + clearMarkerPosition: this.getValue( + container + ? container.clearMarkerPosition + : this.clearMarkerPosition + ), + showLocator: this.getValue( + container ? container.showLocator : this.showLocator + ), + locatorPosition: this.getValue( + container ? container.locatorPosition : this.locatorPosition + ), + showZoom: this.getValue( + container ? container.showZoom : this.showZoom + ), + showRotate: this.getValue( + container ? container.showRotate : this.showRotate + ), + showZoomSlider: this.getValue( + container ? container.showZoomSlider : this.showZoomSlider + ), + searchBarPosition: this.getValue( + container ? container.searchBarPosition : this.searchBarPosition + ), + searchPlaceProvider: this.getValue( + container + ? container.searchPlaceProvider + : this.searchPlaceProvider + ), + searchResultListColor: this.getValue( + container + ? container.searchResultListColor + : this.searchResultListColor + ), + showSearchBar: this.getValue( + container ? container.showSearchBar : this.showSearchBar + ), + searchBarCssClass: this.getValue( + container ? container.searchBarCssClass : this.searchBarCssClass + ), + searchBarColor: this.getValue( + container ? container.searchBarColor : this.searchBarColor + ), + searchBarForegroundColor: this.getValue( + container + ? container.searchBarForegroundColor + : this.searchBarForegroundColor + ), + searchBarSearchInputColor: this.getValue( + container + ? container.searchBarSearchInputColor + : this.searchBarSearchInputColor + ), + searchBarForegroundSearchInputColor: this.getValue( + container + ? container.searchBarForegroundSearchInputColor + : this.searchBarForegroundSearchInputColor + ), + searchBarType: this.getValue( + container ? container.searchBarType : this.searchBarType + ), + searchBarTitle: this.getValue( + container ? container.searchBarTitle : this.searchBarTitle + ), + searchBarValue: this.getValue( + container ? container.searchBarValue : this.searchBarValue + ), + searchBarEnterKeyHint: this.getValue( + container + ? container.searchBarEnterKeyHint + : this.searchBarEnterKeyHint + ), + searchBarKeyboardType: this.getValue( + container + ? container.searchBarKeyboardType + : this.searchBarKeyboardType + ), + searchBarSpellcheck: this.getValue( + container + ? container.searchBarSpellcheck + : this.searchBarSpellcheck + ), + searchBarDebounce: this.getValue( + container ? container.searchBarDebounce : this.searchBarDebounce + ), + searchBarShowCancelButton: this.getValue( + container + ? container.searchBarShowCancelButton + : this.searchBarShowCancelButton + ), + searchBarCancelButtonIcon: this.getValue( + container + ? container.searchBarCancelButtonIcon + : this.searchBarCancelButtonIcon + ), + searchBarCancelButtonTitle: this.getValue( + container + ? container.searchBarCancelButtonTitle + : this.searchBarCancelButtonTitle + ), + searchBarCancelButtonIconColor: this.getValue( + container + ? container.searchBarCancelButtonIconColor + : this.searchBarCancelButtonIconColor + ), + searchBarShowClearButton: this.getValue( + container + ? container.searchBarShowClearButton + : this.searchBarShowClearButton + ), + searchBarClearButtonIcon: this.getValue( + container + ? container.searchBarClearButtonIcon + : this.searchBarClearButtonIcon + ), + searchBarClearButtonIconColor: this.getValue( + container + ? container.searchBarClearButtonIconColor + : this.searchBarClearButtonIconColor + ), + searchBarSearchButtonIcon: this.getValue( + container + ? container.searchBarSearchButtonIcon + : this.searchBarSearchButtonIcon + ), + searchBarSearchButtonIconColor: this.getValue( + container + ? container.searchBarSearchButtonIconColor + : this.searchBarSearchButtonIconColor + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + isReady: this.isReady, + viewChanged: this.viewChanged, + locationSelected: this.locationSelected, + + // + searchBarActionProvider: container + ? container.searchBarActionProvider + : this.searchBarActionProvider, + + // + searchBarQueryChange: this.searchBarQueryChange, + searchBarOnEvent: this.searchBarOnEvent, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map-dialog\x-map-dialog.component.html + +
+ +
+ + +
+ + + +
+ +
+
+ + + + +

+ {{ getTitle() | async }} +

+
+
+ + + +
+ +
+
+
+
+
+
+ + + + +
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map-dialog\x-map-dialog.component.scss + +:host { + --x-map-height: 200px; +} + +::ng-deep.fullscreen-dialog { + // + width: 80vw !important; + max-width: 80vw !important; + + // + height: 80vh !important; + max-height: 80vh !important; + + // + &.mobile { + // + width: 100vw !important; + max-width: 100vw !important; + + // + height: 100vh !important; + max-height: 100vh !important; + } + + // + .mat-dialog-container { + padding: 0 !important; + } +} + +:host ::ng-deep x-map { + // + --map-height: var(--x-map-height); +} + +.x-map-dialog-container { + height: 100%; +} + +x-icon { + --size: 24px; +} + +.toolbar-area { + margin: 8px; +} + +.toolbar-item { + width: auto; + height: 48px; +} + +.toolbar-button { + width: 32px; + margin: { + left: 4px; + right: 4px; + } +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map-dialog\x-map-dialog.component.ts + +import { + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + ChangeDetectorRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XColor, + isFunction, + XResourceIDs, + XDialogResult, + isNullOrUndefined, + isNullOrEmptyString, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XMapAction, + XMapLocation, + XMapActionModel, + XMapItemPosition, + defaultMarkerStyle, + defaultMapSearchPlaceProvider, +} from '../x-map.typings'; +import { ViewChild } from '@angular/core'; +import { Subject, Subscription } from 'rxjs'; +import { concatMap, map } from 'rxjs/operators'; +import { + XSearchBarType, + XSearchBarShowButton, + XSearchBarActionModel, + XSearchBarEnterKeyHint, +} from '../../x-search-bar/x-search-bar.typings'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XBasePage } from '../../classes/x-base-page'; +import { XManagerService } from 'x-framework-services'; +import { XIconNames } from '../../x-icon/x-icon.typings'; +import { XSlotName } from '../../x-slotter/x-slotter.typings'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { XMapDialogConfig, XMapdialogCssVar } from './x-map-dialog.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-map-dialog', + templateUrl: './x-map-dialog.component.html', + styleUrls: ['./x-map-dialog.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XMapDialogComponent extends XBasePage { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly selectIcon = XIconNames.checked; + readonly backIcon = XIconNames.arrow_back; + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly MapItemPositions = Object.assign({}, XMapItemPosition); + + // + hasMarker: boolean; + selectedLocation: XMapLocation; + actionProvider = new Subject(); + + // + @ViewChild('mapDialogContainer') + mapDialogContainer: ElementRef; + + @ViewChild('mapDialogToolbar') + mapDialogToolbar: ElementRef; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + @Inject(MAT_DIALOG_DATA) public data: any, + public dialogRef: MatDialogRef + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + this.preventBack = true; + } + + async afterViewInit() { + super.afterViewInit(); + + // + await this.applyMapSize(); + } + + public registerViewHandlers(): void { + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider.subscribe( + (action) => { + switch (action.action) { + // + case XMapAction.HasMarker: + // + this.hasMarker = true; + this.detectChanges(); + break; + + // + case XMapAction.HasNotMarker: + this.hasMarker = false; + this.detectChanges(); + break; + } + } + ); + } + + public onDestroy(): void { + // + if (!isNullOrUndefined(this.ACTION_PROVIDER_SUBSCRIPTION)) { + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + } + //#endregion + + // + //#region UI Providers ... + /** + * retrieve map dialog config ... + */ + getData() { + return this.getValue(this.data as XMapDialogConfig); + } + + /** + * determines map dialog config exists or not ... + */ + hasData() { + return !isNullOrUndefined(this.getData()); + } + + /** + * determines dialog can select result or not ... + */ + dialogCanSelect() { + return ( + this.hasMarker && + !isNullOrUndefined(this.selectedLocation) && + !isNullOrEmptyString(this.selectedLocation.latitude) && + !isNullOrEmptyString(this.selectedLocation.longitude) + ); + } + + /** + * retrieve dialog title ... + */ + getTitle() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config.title)) + ); + } + + // + get markerStyle() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.markerStyle)), + map((style) => (style ? style : defaultMarkerStyle)) + ); + } + + get cssClass() { + return this.getData().pipe( + concatMap((config) => this.getArrayValue(config.cssClass)) + ); + } + + get progressBarColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.progressBarColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Warning)) + ); + } + + get canSelect() { + return this.getData().pipe( + concatMap((config) => + this.getValue(config.canSelect ? config.canSelect : true) + ) + ); + } + + // + get latitude() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.latitude)) + ); + } + + get longitude() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.longitude)) + ); + } + + // + get zoom() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.zoom)) + ); + } + + // + get showRotate() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showRotate)), + map((canShow) => (isNullOrUndefined(canShow) ? true : canShow)) + ); + } + + get showZoom() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showZoom)), + map((canShow) => (isNullOrUndefined(canShow) ? true : canShow)) + ); + } + + get showZoomSlider() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showZoomSlider)), + map((canShow) => (isNullOrUndefined(canShow) ? false : canShow)) + ); + } + + get showLocator() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showLocator)), + map((canShow) => (isNullOrUndefined(canShow) ? true : canShow)) + ); + } + + get showClearMarker() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showClearMarker)), + map((canShow) => (isNullOrUndefined(canShow) ? true : canShow)) + ); + } + + get showGoMarkedPlace() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showGoMarkedPlace)), + map((canShow) => (isNullOrUndefined(canShow) ? true : canShow)) + ); + } + + get showSelectedLocation() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showSelectedLocation)), + map((canShow) => (isNullOrUndefined(canShow) ? true : canShow)) + ); + } + + get showSearchBar() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showSearchBar)), + map((canShow) => (isNullOrUndefined(canShow) ? true : canShow)) + ); + } + + get searchResultListColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.searchResultListColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Light)) + ); + } + + // // + // get isInPage() { + // return this.getData().pipe( + // concatMap((config) => this.getOptionalValue(config.isInPage)) + // ); + // } + + // get wrapWithCard() { + // return this.getData().pipe( + // concatMap((config) => this.getOptionalValue(config.wrapWithCard)) + // ); + // } + + // get cardColor() { + // return this.getData().pipe( + // concatMap((config) => this.getValue(config.progressBarColor)), + // map((color) => (!isNullOrEmptyString(color) ? color : XColor.Light)) + // ); + // } + + // get cardForegroundColor() { + // return this.getData().pipe( + // concatMap((config) => this.getOptionalValue(config.cardForegroundColor)) + // ); + // } + + // get cardTitle() { + // return this.getData().pipe( + // concatMap((config) => this.getResourceValue(config.cardTitle)) + // ); + // } + + // get cardSubTitle() { + // return this.getData().pipe( + // concatMap((config) => this.getResourceValue(config.cardSubTitle)) + // ); + // } + + // get cardShowFooter() { + // return this.getData().pipe( + // concatMap((config) => this.getOptionalValue(config.cardShowFooter)) + // ); + // } + + // + get showProgressBar() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showProgressBar)), + map((result) => (isNullOrUndefined(result) ? true : result)) + ); + } + + // get progressBarPosition() { + // return this.getData().pipe( + // concatMap((config) => this.getOptionalValue(config.progressBarPosition)), + // map((result) => + // isNullOrEmptyString(result) ? XMapItemPosition.TOP : result + // ) + // ); + // } + + get locatorPosition() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.locatorPosition)), + map((result) => + isNullOrEmptyString(result) ? XMapItemPosition.TOP : result + ) + ); + } + + get clearMarkerPosition() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.clearMarkerPosition)), + map((result) => + isNullOrEmptyString(result) ? XMapItemPosition.TOP : result + ) + ); + } + + get goMarkedPlacePosition() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.goMarkedPlacePosition) + ), + map((result) => + isNullOrEmptyString(result) ? XMapItemPosition.TOP : result + ) + ); + } + + get selectedLocationPosition() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.selectedLocationPosition) + ), + map((result) => + isNullOrEmptyString(result) ? XMapItemPosition.TOP : result + ) + ); + } + + // + //#region Search ... + get searchPlaceProvider() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.searchPlaceProvider)), + map((provider) => + !provider || !isFunction(provider) + ? defaultMapSearchPlaceProvider + : provider + ) + ); + } + + get searchBarType() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.searchBarType)), + map((data) => (isNullOrEmptyString(data) ? XSearchBarType.Search : data)) + ); + } + + get searchBarValue() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.searchBarValue)) + ); + } + + get searchBarColor() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.searchBarColor)), + map((data) => + isNullOrEmptyString(data) ? XColorWithBrightness.Primary : data + ) + ); + } + + get searchBarTitle() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.searchBarTitle)), + map((data) => + !data || isNullOrEmptyString(data.toString()) + ? XResourceIDs.search + : data + ) + ); + } + + // get searchBarPosition() { + // return this.getData().pipe( + // concatMap((config) => this.getOptionalValue(config.searchBarPosition)), + // map((data) => (isNullOrEmptyString(data) ? XMapItemPosition.TOP : data)) + // ); + // } + + get searchBarCssClass() { + return this.getData().pipe( + concatMap((config) => this.getArrayValue(config.searchBarCssClass)) + ); + } + + get searchBarDebounce() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.searchBarDebounce)), + map((data) => + isNullOrUndefined(data) ? this.config.defaultSearchDebounceTime : data + ) + ); + } + + get searchBarClearButtonIcon() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarClearButtonIcon) + ), + map((data) => + !data || isNullOrEmptyString(data.toString()) ? XIconNames.clear : data + ) + ); + } + + get searchBarSearchInputColor() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarSearchInputColor) + ), + map((data) => + isNullOrEmptyString(data) ? XColorWithBrightness.Primary : data + ) + ); + } + + get searchBarSpellcheck() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.searchBarSpellcheck)) + ); + } + + get searchBarSearchButtonIcon() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarSearchButtonIcon) + ), + map((data) => + !data || isNullOrEmptyString(data.toString()) ? XIconNames.search : data + ) + ); + } + + get searchBarKeyboardType() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarKeyboardType) + ), + map((data) => (isNullOrEmptyString(data) ? XSearchBarType.Search : data)) + ); + } + + get searchBarEnterKeyHint() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarEnterKeyHint) + ), + map((data) => + isNullOrEmptyString(data) ? XSearchBarEnterKeyHint.Search : data + ) + ); + } + + get searchBarClearIconColor() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarClearButtonIconColor) + ), + map((data) => + isNullOrEmptyString(data) ? XColorWithBrightness.Light : data + ) + ); + } + + get searchBarActionProvider() { + return this.getData().pipe( + map((data) => + !isNullOrUndefined(data.searchBarActionProvider) + ? data.searchBarActionProvider + : new Subject() + ) + ); + } + + get searchBarSearchButtonIconColor() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarSearchButtonIconColor) + ), + map((data) => + isNullOrEmptyString(data) ? XColorWithBrightness.Light : data + ) + ); + } + + get searchBarForegroundColor() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarForegroundColor) + ) + ); + } + + get searchBarShowCancelButton() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarShowCancelButton) + ), + map((data) => + isNullOrEmptyString(data) ? XSearchBarShowButton.Never : data + ) + ); + } + + get searchBarCancelButtonIcon() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarCancelButtonIcon) + ), + map((data) => + !data || isNullOrEmptyString(data.toString()) ? XIconNames.cancel : data + ) + ); + } + + get searchBarCancelButtonTitle() { + return this.getData().pipe( + concatMap((config) => + this.getResourceValue(config.searchBarCancelButtonTitle) + ) + ); + } + + get searchBarForegroundSearchInputColor() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarForegroundSearchInputColor) + ) + ); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + async handleBack() { + this.dialogRef.close(); + } + + async handleSelect() { + // + if (!this.dialogCanSelect()) { + return; + } + + // + this.dialogRef.close({ + result: XDialogResult.Ok, + payload: this.selectedLocation, + }); + } + + async handleMapIsReady() { + // + const config = await this.getValueAsync(this.getData()); + if (!config || !config.isReady) { + return; + } + + // + config.isReady.emit(); + } + + async handleSearchBarQueryChange(event: string) { + // + const config = await this.getValueAsync(this.getData()); + if (!config || !config.searchBarQueryChange) { + return; + } + + // + config.searchBarQueryChange.emit(event); + } + + async handleMapLocationSelected(event: XMapLocation) { + // + this.selectedLocation = event; + + // + const config = await this.getValueAsync(this.getData()); + if (!config || !config.locationSelected) { + return; + } + + // + config.locationSelected.emit(event); + + // + this.detectChanges(); + } + //#endregion + + // + //#region Private ... + async applyMapSize() { + // + let result = 0; + + // + //#region DialogHeight ... + // + if (!this.mapDialogContainer) { + // + setTimeout(async () => await this.applyMapSize(), 500); + return; + } + const height = + this.mapDialogContainer.nativeElement.getBoundingClientRect().height; + if (height === 0) { + // + setTimeout(async () => await this.applyMapSize(), 500); + return; + } + + // + result += height; + //#endregion + + // + //#region ToolbarHeight ... + if (!this.mapDialogToolbar) { + // + setTimeout(async () => await this.applyMapSize(), 500); + return; + } + const toolbarHeight = + this.mapDialogToolbar.nativeElement.getBoundingClientRect().height; + if (toolbarHeight === 0) { + // + setTimeout(async () => await this.applyMapSize(), 500); + return; + } + + // + result -= toolbarHeight; + //#endregion + + // + this.setCssVar(XMapdialogCssVar.XMapHeight, `${result}px`); + this.detectChanges(); + + // + this.runTimedOut(() => { + this.managerService.dispatchResizeEvent(); + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map-dialog\x-map-dialog.service.ts + +import { + XColor, + toPromise, + XBaseService, + XResourceIDs, + XDialogResult, + isNullOrUndefined, +} from 'x-framework-core'; +import { + MatDialog, + MatDialogRef, + MatDialogConfig, +} from '@angular/material/dialog'; +import { XManagerService } from 'x-framework-services'; +import { XMapLocation } from '../x-map.typings'; +import { XMapDialogComponent } from './x-map-dialog.component'; +import { Injectable, Output, EventEmitter, Inject } from '@angular/core'; +import { XMapDialogResult, XMapDialogConfig } from './x-map-dialog.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XMapDialogService extends XBaseService { + // + //#region Props ... + private MOBILE_CLASS_NAME = 'mobile'; + private DIALOG_REF: MatDialogRef; + + // + isMobile$ = this.managerService.isMobileUi$; + + // tslint:disable-next-line:no-output-on-prefix + @Output() + onResult = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + private dialog: MatDialog, + private managerService: XManagerService + ) { + super(config); + } + //#endregion + + // + //#region Actions ... + /** + * Open Select Map Dialog ... + */ + async openDialog(config?: XMapDialogConfig) { + // + // Prevent Double Bubble ... + if (this.DIALOG_REF) { + return; + } + + // + // Prepare Config ... + if (!config) { + config = {}; + } + + // + //#region Default Config for Map Component ... + // + // Zoom ... + if (config && !config.zoom) { + config.zoom = 6; + } + + // + // canSelect ... + let selectable = false; + if (config && !isNullOrUndefined(config.canSelect)) { + // + selectable = await toPromise(config.canSelect); + config.canSelect = selectable; + } + + // + // Set Default Title ... + if (config && !config.title) { + // + if (selectable) { + config.title = XResourceIDs.map_dialog_select_title; + } else { + config.title = XResourceIDs.map_dialog_title; + } + } + + // + // Search Bar Color ... + if (config && !config.searchBarColor) { + config.searchBarColor = XColor.Primary; + } + + // + // Progress Bar Color ... + if (config && !config.progressBarColor) { + config.progressBarColor = XColor.Warning; + } + + // + // Search Result List Color ... + if (config && !config.searchResultListColor) { + config.searchResultListColor = XColor.Medium; + } + //#endregion + + // + // Prepare Dialog Config ... + const data: MatDialogConfig = { + data: config, + }; + + // + data.panelClass = 'fullscreen-dialog'; + + // + // Show Cropper inside a Dialog ... + this.DIALOG_REF = this.dialog.open(XMapDialogComponent, data); + this.DIALOG_REF.disableClose = true; + + // + // Attach Mobile Class ... + this.isMobile$.subscribe((isMobile) => { + // + if (isMobile) { + if (this.DIALOG_REF) { + this.DIALOG_REF.addPanelClass(this.MOBILE_CLASS_NAME); + } + } else { + if (this.DIALOG_REF) { + this.DIALOG_REF.removePanelClass(this.MOBILE_CLASS_NAME); + } + } + }); + + // + // Handle Backdrop ... + this.DIALOG_REF.backdropClick().subscribe(() => { + // + // Cancel Dialog ... + this.onResult.emit({ result: XDialogResult.Cancel }); + + // + this.closeDialog(); + }); + + // + // Handle Dialog Result ... + this.DIALOG_REF.afterClosed().subscribe((model: XMapDialogResult) => { + // + if (isNullOrUndefined(model) || model.result !== XDialogResult.Ok) { + // + // Cancel Dialog ... + this.onResult.emit({ result: XDialogResult.Cancel }); + } else { + // + // Handle Results ... + this.onResult.emit(model); + } + + // + this.closeDialog(); + }); + } + + /** + * Close Select Map Dialog ... + */ + closeDialog() { + // + // Prevent Go Forward ... + if (!this.DIALOG_REF) { + return; + } + + // + // Close Dialog ... + this.DIALOG_REF.close(); + this.DIALOG_REF = null; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map-dialog\x-map-dialog.typings.ts + +import { XDialogResult } from 'x-framework-core'; +import { XMapLocation, IXMapContainer } from '../x-map.typings'; +import { XBaseDialogConfig } from '../../classes/i-x-base-dialog.container'; + +export interface XMapDialogResult { + payload?: XMapLocation; + result: XDialogResult; +} + +export interface XMapDialogConfig + extends XBaseDialogConfig, + Pick< + Partial, + | 'zoom' + | 'isReady' + | 'latitude' + | 'cssClass' + | 'showZoom' + | 'longitude' + | 'canSelect' + | 'showRotate' + | 'showLocator' + | 'markerStyle' + | 'showSearchBar' + | 'searchBarType' + | 'actionProvider' + | 'searchBarValue' + | 'searchBarColor' + | 'searchBarTitle' + | 'searchBarColor' + | 'showZoomSlider' + | 'showProgressBar' + | 'showClearMarker' + | 'locatorPosition' + | 'progressBarColor' + | 'locationSelected' + | 'searchBarCssClass' + | 'showGoMarkedPlace' + | 'searchBarDebounce' + | 'searchPlaceProvider' + | 'clearMarkerPosition' + | 'searchBarSpellcheck' + | 'searchBarQueryChange' + | 'showSelectedLocation' + | 'goMarkedPlacePosition' + | 'searchBarKeyboardType' + | 'searchBarEnterKeyHint' + | 'searchResultListColor' + | 'searchBarActionProvider' + | 'searchBarClearButtonIcon' + | 'searchBarForegroundColor' + | 'selectedLocationPosition' + | 'searchBarForegroundColor' + | 'searchBarSearchButtonIcon' + | 'searchBarSearchInputColor' + | 'searchBarSearchInputColor' + | 'searchBarShowCancelButton' + | 'searchBarCancelButtonIcon' + | 'searchBarCancelButtonTitle' + | 'searchBarClearButtonIconColor' + | 'searchBarSearchButtonIconColor' + | 'searchBarForegroundSearchInputColor' + > {} + +export enum XMapdialogCssVar { + XMapHeight = 'x-map-height', +} + +export type XMapdialogCssVarIdentifier = XMapdialogCssVar | string; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-markdown\x-markdown.component.html + + + + + + + + +
+ +
+
+
+ + + +
+ +
+ + + + + + + + + + + +
+ + + + + +
+ + +
+
+ + + +
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-markdown\x-markdown.component.scss + +:host ::ng-deep a { + pointer-events: none; +} + +:host { + width: inherit; + height: inherit; +} + +textarea { + resize: none; +} + +.x-editor { + width: 100%; + min-height: 300px; + height: fit-content; +} + +.x-toolbar-wrapper { + margin-bottom: 0px; +} + +.x-preview { + text-align: justify; +} + +.x-preview-wrapper { + height: fit-content; + min-height: 300px; + border: 1px solid gray; + padding: { + left: 4px; + right: 4px; + } +} + +.x-toolbar-icon { + margin: { + left: 4px; + right: 4px; + } +} + +.no-border { + border: 0; + min-height: unset; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-markdown\x-markdown.component.ts + +import { + ViewChild, + Component, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + keys, + nameof, + insertText, + XStandardType, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XMarkdownMode, + XMarkdownAction, + XMarkdownActionModel, + XMarkdownToolbarItem, + XMarkdownModeIdentifier, + XMarkdownToolbarItemName, + XMarkdownToolbarConfigProps, + XMarkdownContainerComponent, + defaultXMarkdownToolbarConfig, +} from './x-markdown.typings'; +import { map } from 'rxjs/operators'; +import 'node_modules/prismjs/prism.js'; +import 'node_modules/marked/marked.min.js'; +import { Subject, Subscription } from 'rxjs'; +import { XSlotLayout } from '../x-slotter/x-slotter.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-markdown', + templateUrl: './x-markdown.component.html', + styleUrls: ['./x-markdown.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XMarkdownComponent extends XMarkdownContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private TOOLBAR_CONFIG: XMarkdownToolbarConfigProps; + + // + readonly SlotterLayouts = Object.assign({}, XSlotLayout); + + // + innerContent: string = this.content; + preparedMode: XMarkdownModeIdentifier; + toolbarItems = keys(defaultXMarkdownToolbarConfig); + + // + get preparedToolbarConfig() { + return this.TOOLBAR_CONFIG; + } + + // + @ViewChild('markdownEditor', { static: false }) + editor: ElementRef; + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.prepareMode(); + await this.prepareToolbarConfig(null); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Content Chaned ... + const isContentChanged = changeKeys.includes( + nameof('content') + ); + if (isContentChanged && this.content !== this.innerContent) { + this.innerContent = this.content; + this.detectChanges(); + } + //#endregion + + // + //#region Model Changed ... + const isModeChanged = changeKeys.includes( + nameof('mode') + ); + if (isModeChanged) { + await this.prepareMode(); + await this.prepareToolbarConfig(null); + } + //#endregion + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + // Register Action Provider ... + this.registerActionProvider(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + // + getSlotterLayout() { + return this.isMobileUi$.pipe( + map((isMobileUi) => + isMobileUi ? XSlotLayout.VERTICAL : XSlotLayout.HORIZONTAL + ) + ); + } + + // + //#region Mode Detectors ... + // + isEditMode() { + return this.getValueStream(this.mode).pipe( + map((preparedMode) => preparedMode === XMarkdownMode.EDIT) + ); + } + + // + isPreviewMode() { + return this.getValueStream(this.mode).pipe( + map((preparedMode) => preparedMode === XMarkdownMode.PREVIEW) + ); + } + + // + isBothMode() { + return this.getValueStream(this.mode).pipe( + map((preparedMode) => preparedMode === XMarkdownMode.BOTH) + ); + } + //#endregion + + // + //#region Toolbar Providers ... + hasToolbarItem(name: XMarkdownToolbarItemName | any) { + // + if (!this.TOOLBAR_CONFIG) { + return false; + } + + // + const itesNamess = keys(this.TOOLBAR_CONFIG); + const result = itesNamess.includes(name); + + // + return result; + } + + getToolbarItem(name: XMarkdownToolbarItemName | any) { + // + if (!this.hasToolbarItem(name)) { + return null; + } + + // + const item = this.TOOLBAR_CONFIG[name]; + const defaultItem = this.getDefaultItemConfig(name); + + // + const result: XMarkdownToolbarItem = { + ...defaultItem, + ...item, + }; + + // + return result; + } + //#endregion + //#endregion + + // + //#region Ui Handlers ... + handleContentChanged(content: string) { + // + if (!content) { + content = ''; + } + + // + this.innerContent = content; + this.contentChange.emit(content); + } + + async handleToolbarItemClicked(name: XMarkdownToolbarItemName) { + // + switch (name) { + // + case 'preview': + await this.handleToggleMode(); + break; + + // + case 'bold': + await this.handleAddContent('**', true); + break; + + // + case 'code': + await this.handleAddContent('`', true); + break; + + // + case 'heading': + await this.handleAddContent('# ', false); + break; + + // + case 'image': + await this.handleAddContent('![alt text](image url)', false); + break; + + // + case 'italic': + await this.handleAddContent('*', true); + break; + + // + case 'link': + await this.handleAddContent('[title](url)', false); + break; + + // + case 'ol': + await this.handleAddContent('1. ', false); + break; + + // + case 'ul': + await this.handleAddContent('- ', false); + break; + + // + case 'quote': + await this.handleAddContent('> ', false); + break; + } + + // + if (this.editor) { + this.editor.nativeElement.focus(); + } + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XMarkdownAction.Refresh: + this.detectChanges(); + break; + + // + case XMarkdownAction.FireToolbarButtons: + // + if (!isNullOrEmptyString(model.payload)) { + await this.handleToolbarItemClicked(model.payload); + } + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async prepareMode() { + this.preparedMode = await this.getValueAsync(this.mode); + await this.changeMode(this.preparedMode); + } + + private async prepareToolbarConfig( + config: XStandardType + ) { + // + if (!config) { + this.TOOLBAR_CONFIG = defaultXMarkdownToolbarConfig; + return; + } + + // + const cfg = await this.getValueAsync(config); + + // + this.TOOLBAR_CONFIG = { + ...cfg, + }; + + // + this.detectChanges(); + } + + private getDefaultItemConfig(name: XMarkdownToolbarItemName) { + return defaultXMarkdownToolbarConfig[name]; + } + + private async changeMode(mode: XMarkdownModeIdentifier) { + // + this.mode = mode; + this.preparedMode = mode; + + // + this.detectChanges(); + } + + private async handleToggleMode() { + // + const mode = await this.getValueAsync(this.mode); + + // + switch (mode) { + case XMarkdownMode.BOTH: + await this.changeMode(XMarkdownMode.EDIT); + break; + + case XMarkdownMode.EDIT: + await this.changeMode(XMarkdownMode.BOTH); + break; + } + } + + private getCurrentPosition() { + // + if (!this.editor) { + return { + start: -1, + end: -1, + }; + } + + // + const start = this.editor.nativeElement.selectionStart; + const end = this.editor.nativeElement.selectionEnd; + + // + const result = { + start: start ? +start : -1, + end: end ? +end : -1, + }; + + // + return result; + } + + private async handleAddContent(content: string, double: boolean = false) { + // + if (!content) { + return; + } + + // + const pos = this.getCurrentPosition(); + + // + if (!pos) { + return; + } + + // + let selectedContent: string; + if (pos.start === pos.end) { + selectedContent = ''; + } else { + // + selectedContent = this.innerContent.substring(pos.start, pos.end); + this.innerContent = this.innerContent.replace(selectedContent, ''); + } + + // + let insertedContent: string; + if (isNullOrEmptyString(selectedContent)) { + insertedContent = double ? `${content}${content}` : `${content}`; + } else { + insertedContent = double + ? `${content}${selectedContent}${content}` + : `${content}${selectedContent}`; + } + + // + const startPos = pos.start > -1 ? pos.start : this.innerContent.length; + this.innerContent = insertText( + this.innerContent || '', + insertedContent, + startPos + ); + this.contentChange.emit(this.innerContent); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-markdown\x-markdown.module.ts + +import { NgModule } from '@angular/core'; +import { MarkdownModule } from 'ngx-markdown'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XIconModule } from './../x-icon/x-icon.module'; +import { XMarkdownComponent } from './x-markdown.component'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XMarkdownComponent], + imports: [ + CommonModule, + XIconModule, + XCardModule, + XSlotterModule, + XBaseComponentModule, + MarkdownModule.forRoot(), + ], + exports: [ + XIconModule, + XCardModule, + MarkdownModule, + XSlotterModule, + XMarkdownComponent, + XBaseComponentModule, + ], +}) +export class XMarkdownModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-markdown\x-markdown.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XIconIdentifier, XIconNames } from '../x-icon/x-icon.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XMarkdownMode { + EDIT = 'edit', + PREVIEW = 'preview', + BOTH = 'both', +} + +export type XMarkdownModeIdentifier = XMarkdownMode | string; + +export enum XMarkdownToolbarItemSlot { + START = 'start', + END = 'end', +} + +export type XMarkdownToolbarItemSlotIdentifier = + | XMarkdownToolbarItemSlot + | string; + +export type XMarkdownToolbarItemName = + | 'image' + | 'link' + | 'ul' + | 'ol' + | 'quote' + | 'code' + | 'bold' + | 'italic' + | 'heading' + | 'preview' + | 'guide'; + +export interface XMarkdownToolbarItem { + icon?: XIconIdentifier; + title?: XResourceIdentifier; + slot?: XMarkdownToolbarItemSlotIdentifier; +} + +export type XMarkdownToolbarConfigProps = { + [key in XMarkdownToolbarItemName]?: XMarkdownToolbarItem; +}; + +export const defaultXMarkdownToolbarConfig: XMarkdownToolbarConfigProps = { + heading: { + icon: XIconNames.heading, + title: XResourceIDs.x_markdown_heading, + slot: XMarkdownToolbarItemSlot.START, + }, + bold: { + icon: XIconNames.bold, + title: XResourceIDs.x_markdown_bold, + slot: XMarkdownToolbarItemSlot.START, + }, + italic: { + icon: XIconNames.italic, + title: XResourceIDs.x_markdown_italic, + slot: XMarkdownToolbarItemSlot.START, + }, + quote: { + icon: XIconNames.quote, + title: XResourceIDs.x_markdown_quote, + slot: XMarkdownToolbarItemSlot.START, + }, + code: { + icon: XIconNames.code, + title: XResourceIDs.x_markdown_code, + slot: XMarkdownToolbarItemSlot.START, + }, + ol: { + icon: XIconNames.ol, + title: XResourceIDs.x_markdown_ol, + slot: XMarkdownToolbarItemSlot.START, + }, + ul: { + icon: XIconNames.ul, + title: XResourceIDs.x_markdown_ul, + slot: XMarkdownToolbarItemSlot.START, + }, + link: { + icon: XIconNames.link, + title: XResourceIDs.x_markdown_link, + slot: XMarkdownToolbarItemSlot.START, + }, + image: { + icon: XIconNames.image, + title: XResourceIDs.x_markdown_image, + slot: XMarkdownToolbarItemSlot.START, + }, + preview: { + icon: XIconNames.preview, + title: XResourceIDs.x_markdown_preview, + slot: XMarkdownToolbarItemSlot.END, + }, + guide: { + icon: XIconNames.guide, + title: XResourceIDs.x_markdown_guide, + slot: XMarkdownToolbarItemSlot.END, + }, +}; + +export enum XMarkdownAction { + // + FireToolbarButtons = 'fire_toolbar_buttons', + + // + Refresh = 'refresh', +} + +export type XMarkdownActionIdentifier = XMarkdownAction | string; + +export interface XMarkdownActionModel + extends XBaseActionModel {} + +// +//#region Inside Card Container ... +export interface IXMarkdownContainer extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + content: string; + contentChange: EventEmitter; + + // + mode: XStandardType; + + // + editorBoxColor: XStandardType; + previewBoxColor: XStandardType; + + // + //#region Toolbar ... + hasToolbar: XStandardType; + toolbarCssClass: XStandardType>; + toolbarColor: XStandardType; + toolbarForegroundColor: XStandardType; + toolbarConfig: XStandardType; + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +export class XMarkdownContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly content: string = ''; + readonly contentChange = new EventEmitter(); + + // + readonly mode: XStandardType = XMarkdownMode.BOTH; + + // + readonly editorBoxColor: XStandardType = + XColorWithBrightness.Transparent; + readonly previewBoxColor: XStandardType = + XColorWithBrightness.Transparent; + + // + //#region Toolbar ... + readonly hasToolbar: XStandardType = true; + readonly toolbarCssClass: XStandardType>; + readonly toolbarColor: XStandardType = + XColorWithBrightness.Light; + readonly toolbarForegroundColor: XStandardType = false; + readonly toolbarConfig: XStandardType; + //#endregion + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XMarkdownContainerComponent + extends XComponentCardContainerComponent + implements IXMarkdownContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + content: string = this.propertyProvider.content; + + @Output() + contentChange: EventEmitter = new EventEmitter(); + + // + @Input() + mode: XStandardType = this.propertyProvider.mode; + + // + @Input() + editorBoxColor: XStandardType = + this.propertyProvider.editorBoxColor; + + @Input() + previewBoxColor: XStandardType = + this.propertyProvider.previewBoxColor; + + // + //#region Toolbar ... + @Input() + hasToolbar: XStandardType = this.propertyProvider.hasToolbar; + + @Input() + toolbarCssClass: XStandardType> = + this.propertyProvider.toolbarCssClass; + + @Input() + toolbarColor: XStandardType = + this.propertyProvider.toolbarColor; + + @Input() + toolbarForegroundColor: XStandardType = + this.propertyProvider.toolbarForegroundColor; + + @Input() + toolbarConfig: XStandardType = + this.propertyProvider.toolbarConfig; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XMarkdownContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXMarkdownContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + content: this.getValue( + container ? container.content : this.content + ), + + // + mode: this.getValue(container ? container.mode : this.mode), + + // + editorBoxColor: this.getValue( + container ? container.editorBoxColor : this.editorBoxColor + ), + previewBoxColor: this.getValue( + container ? container.previewBoxColor : this.previewBoxColor + ), + + // + hasToolbar: this.getValue( + container ? container.hasToolbar : this.hasToolbar + ), + toolbarCssClass: this.getArrayValue( + container ? container.toolbarCssClass : this.toolbarCssClass + ), + toolbarColor: this.getValue( + container ? container.toolbarColor : this.toolbarColor + ), + toolbarForegroundColor: this.getValue( + container + ? container.toolbarForegroundColor + : this.toolbarForegroundColor + ), + toolbarConfig: this.getValue( + container ? container.toolbarConfig : this.toolbarConfig + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + contentChange: this.contentChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + + +
+ + + + + +
+ + {{ getTitle(key) | async }} + +
+ + + +
+ + + + {{ getContent(key, preparedModel[key]) | async }} + + + + + + + + + + + +
+ + +
+ + +
+ + +
+ +
+
+
+
+
+
+
+ + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter.component.scss + +:host { + // + --header-highlight-color: var(--x-color-light-shade); + + // + --header-background-color: var(--x-color-light); + --header-foreground-color: var(--x-color-light-contrast); + + // + --content-background-color: var(--x-color-light); + --content-foreground-color: var(--x-color-light-contrast); + + // + --panel-background-color: var(--x-color-light); + --panel-foreground-color: var(--x-color-light-contrast); +} + +:host ::ng-deep .x-card { + .x-actions { + display: none; + } + + .x-content { + padding: 0px !important; + margin: 0px; + } +} + +:host ::ng-deep .x-expandable-panel { + // + &.mobile { + .mat-expansion-panel-header { + margin: { + top: 2px; + bottom: 6px; + } + } + } + + .mat-expansion-panel { + // + box-shadow: unset; + color: var(--panel-foreground-color); + background-color: var(--panel-background-color); + + // + .mat-expansion-panel-body { + color: var(--panel-foreground-color); + background-color: var(--panel-background-color); + } + + // + .mat-expansion-panel-header { + // + color: var(--header-foreground-color); + background-color: var(--header-background-color); + + // + // Header HighLight ... + &.cdk-keyboard-focused, + &.cdk-program-focused, + &:not(.mat-expanded):hover { + background-color: var(--header-highlight-color); + } + + &.mobile { + .mat-content { + flex-direction: column; + } + + .x-model-presenter-title { + .x-title-span { + max-width: calc(80%); + } + } + } + + .x-expandable-panel-header-title { + // + flex-grow: 0; + margin-left: 4px !important; + + // + .x-model-presenter-title { + width: 100%; + word-wrap: unset; + white-space: pre; + } + + // + .x-model-presenter-title.hasLabel { + .x-title-span::after { + content: " : "; + } + } + } + + .x-expandable-panel-header-description { + // + overflow: hidden; + margin-left: 4px !important; + + // + .x-model-presenter-content { + width: 100%; + } + + .x-end-bottom-slot { + flex: 0 0 auto !important; + } + + .x-start-top-slot { + overflow: hidden; + } + + .x-start-top-slot-inner-container { + max-width: calc(90%); + } + } + } + + // + .mat-expansion-panel-content { + color: var(--content-foreground-color); + background-color: var(--content-background-color); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter.component.ts + +import { + Component, + QueryList, + ContentChildren, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + tap, + map, + filter, + mergeMap, + switchMap, + concatMap, +} from 'rxjs/operators'; +import { + keys, + nameof, + isDate, + isArray, + toArray, + isNumber, + isString, + isObject, + toXString, + isFunction, + parseXColor, + isSameObject, + XOneOrManyType, + XModelProperty, + toNormalString, + getPropertyValue, + setPropertyValue, + isNullOrUndefined, + isNullOrEmptyString, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XModelPresenterAction, + XModelPresenterCssVars, + XModelPresenterActionModel, + XModelPresenterStartupState, + XModelPresenterContainerComponent, +} from './x-model-presenter.typings'; +import { + XExpandablePanelAction, + XExpandablePanelActionModel, +} from '../x-expandable-panel/x-expandable-panel.typings'; +import { XSlotLayout, XSlotName } from '../x-slotter/x-slotter.typings'; +import { of, from, Subject, forkJoin, Observable, Subscription } from 'rxjs'; +import { XModelPresenterItemComponent } from './x-model-presenter-item/x-model-presenter-item.component'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-model-presenter', + templateUrl: './x-model-presenter.component.html', + styleUrls: ['./x-model-presenter.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XModelPresenterComponent< + T = any +> extends XModelPresenterContainerComponent { + // + //#region Props ... + private MODEL: T; + private keyPrefix = ''; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + + // + get prefix() { + return this.keyPrefix; + } + + get preparedModel() { + return this.MODEL; + } + + // + @ContentChildren(XModelPresenterItemComponent) + items: QueryList; + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.prepareModel(); + await this.handleStartupState(); + } + + async afterViewInit() { + super.afterViewInit(); + + // + // prepare KeyPrefix ... + const keyPrefix = this.element.nativeElement + ? (this.element.nativeElement as HTMLElement).getAttribute('keyPrefix') + : ''; + if (!isNullOrEmptyString(keyPrefix)) { + this.keyPrefix += `${keyPrefix}`; + } + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + // + //#region Model Changed ... + const isModelChanged = changeKeys.includes( + nameof('model') + ); + + // + if (isModelChanged) { + await this.prepareModel(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + const isPanelActionProviderChanged = changeKeys.includes( + nameof('panelActionProvider') + ); + if (isActionProviderChanged || isPanelActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Color Changes ... + // + //#region Color Changed ... + const isColorChanged = changeKeys.includes( + nameof('color') + ); + if (isColorChanged) { + await this.applyPanelColor(); + } + //#endregion + + // + //#region Header Highlight Color Changed ... + const isHeaderHighlightColorChanged = changeKeys.includes( + nameof('headerHighlightColor') + ); + if (isHeaderHighlightColorChanged) { + await this.applyHeaderHighlightColor(); + } + //#endregion + + // + //#region Header Color Changed ... + const isHeaderColorChanged = changeKeys.includes( + nameof('headerColor') + ); + if (isHeaderColorChanged) { + await this.applyHeaderColor(); + } + //#endregion + + // + //#region Content Color Changed ... + const isContentColorChanged = changeKeys.includes( + nameof('contentColor') + ); + if (isContentColorChanged) { + await this.applyContentColor(); + } + //#endregion + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + keys(model: T) { + // + let modelKeys = keys(model); + + // + if (!this.hasChild(this.sortOrderModelKeys)) { + return modelKeys; + } + + // + const sortOrderArray = toArray(this.sortOrderModelKeys) as string[]; + const result: string[] = [ + ...sortOrderArray.filter((k) => modelKeys.includes(k)), + ...modelKeys.filter((k) => !sortOrderArray.includes(k)), + ]; + + // + return result; + } + + canShowKey(key: string) { + // + return forkJoin({ + key: this.getValue(key), + model: this.getValue(this.model), + prefix: this.getValue(this.keyPrefix), + hiddenModelKeys: this.getArrayValue(this.hiddenModelKeys), + hideOptionalValues: this.getValue(this.hideOptionalValues), + availableModelKeys: this.getArrayValue(this.availableModelKeys), + }).pipe( + map((provider) => { + // + const value = provider.model[provider.key]; + + // + const modelHasValue = + !isNullOrUndefined(provider.model) && + !isNullOrUndefined(value) && + !isNullOrEmptyString(toXString(provider.model[provider.key])); + const hasChildHiddens = this.hasChild(provider.hiddenModelKeys); + const hasChildAvailables = this.hasChild(provider.availableModelKeys); + + // + if (!hasChildAvailables && !hasChildHiddens) { + // + if (!modelHasValue && provider.hideOptionalValues) { + return false; + } + + // + return true; + } + + // + const isInHiddens = provider.hiddenModelKeys.includes( + provider.key as any + ); + const isInAvailables = provider.availableModelKeys.includes( + provider.key as any + ); + + // + if ( + (!hasChildAvailables && !isInHiddens) || + (isInAvailables && !hasChildHiddens) || + (isInAvailables && !isInHiddens) || + (!isInAvailables && + !isInHiddens && + isNumber(provider.key) && + !isNullOrEmptyString(provider.prefix)) + ) { + // + if (!modelHasValue && provider.hideOptionalValues) { + return false; + } + + // + return true; + } + + // + return false; + }) + ); + } + + canShowLabelKey(key: string) { + return this.getArrayValue(this.hiddenLabelModelKeys).pipe( + map((hiddenLabelsModelKeys) => { + // + const hasChildHiddenLabels = this.hasChild(hiddenLabelsModelKeys); + + // + if (!hasChildHiddenLabels) { + return true; + } + + // + const isInHiddens = hiddenLabelsModelKeys.includes(key as any); + return hasChildHiddenLabels && !isInHiddens; + }) + ); + } + + getTitle(key: string): Observable { + return this.getValue(this.modelKeyProvider).pipe( + concatMap((provider) => { + return isNullOrUndefined(provider) || !isFunction(provider) + ? this.getValue(key) + : this.getValue(provider(key as any)); + }) + ); + } + + hasContentItem(key: string) { + return of(this.items).pipe( + concatMap((items) => { + return isNullOrUndefined(items) + ? of(false) + : this.getArrayValue(items.toArray()).pipe( + switchMap((items) => + from(items).pipe( + mergeMap((item) => + this.getValue(item.name).pipe( + map((name) => ({ name, item })) + ) + ), + filter( + (itemData) => + toNormalString(itemData.name) === toNormalString(key) + ), + map((dataObject) => !isNullOrUndefined(dataObject)) + ) + ) + ); + }) + ); + } + + getContentItem( + key: string, + value: any + ): Observable { + return this.hasContentItem(key).pipe( + concatMap((isExists) => { + return !isExists + ? of(null) + : of(this.items).pipe( + concatMap((items) => { + return isNullOrUndefined(items) + ? of(null) + : this.getArrayValue(items.toArray()).pipe( + switchMap((items) => + from(items).pipe( + mergeMap((item) => + this.getValue(item.name).pipe( + map((name) => ({ name, item })) + ) + ), + filter( + (itemData) => + toNormalString(itemData.name) === + toNormalString(key) + ), + tap((dataObject) => (dataObject.item.value = value)), + map((dataObject) => dataObject.item) + ) + ) + ); + }) + ); + }) + ); + } + + getContent(key: string, value: any): Observable { + return this.getValue(this.modelValueProvider).pipe( + concatMap((provider) => { + return isNullOrUndefined(provider) || !isFunction(provider) + ? this.getValue(value) + : this.getValue(provider(key as any, value)); + }) + ); + } + + canDescribe(key: string, value: any) { + return of(value).pipe( + map( + (eVal) => + isDate(eVal) || + isFunction(eVal) || + (!isArray(eVal) && !isObject(eVal)) + ) + ); + } + + getModelPresenterTitleClasses(key: string) { + return [`x-model-presenter-${key}-title`]; + } + + getModelPresenterContentClasses(key: string) { + return [`x-model-presenter-${key}-content`]; + } + + // + //#region Neste ... + getKeyPrefix(key: string) { + // + const result = isNullOrEmptyString(this.keyPrefix) + ? `${key}` + : `${this.keyPrefix}.${key}`; + + // + return result; + } + + getHiddenModelKeys(key: string) { + return this.prepareNestedCollection(key, this.hiddenModelKeys); + } + + getSortOrderModelKeys(key: string) { + return this.prepareNestedCollection(key, this.sortOrderModelKeys); + } + + getAvailableModelKeys(key: string) { + return this.prepareNestedCollection(key, this.availableModelKeys); + } + + getHiddenLabelModelKeys(key: string) { + return this.prepareNestedCollection(key, this.hiddenLabelModelKeys); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + //#endregion + + // + //#region Actions ... + async expandAll() { + this.panelActionProvider.next({ + action: XExpandablePanelAction.ExpandAll, + }); + } + + async collapseAll() { + this.panelActionProvider.next({ + action: XExpandablePanelAction.CollapseAll, + }); + } + + async setModelProperty(property: XModelProperty) { + // + if (!property || isNullOrEmptyString(property.key)) { + return; + } + + // + const model = this.MODEL; + const nodeValue = getPropertyValue(model, property.prefix); + const modelKeys = isNullOrUndefined(nodeValue) ? [] : keys(nodeValue); + if (!this.hasChild(modelKeys)) { + return; + } + + // + const isExists = modelKeys.includes(property.key as string); + if (!isExists) { + return; + } + + // + // normalize value ... + const normalValue = isString(property.value) + ? !isNullOrEmptyString(property.value) && + !isNullOrUndefined(property.value) + ? property.value.toString().trim() + : undefined + : isNullOrUndefined(property.value) + ? undefined + : property.value; + + // + if (nodeValue[property.key] === normalValue) { + return; + } + + // + const setterPropertyPath = isNullOrEmptyString(property.prefix) + ? `${property.key as string}` + : `${property.prefix}.${property.key as string}`; + + // + setPropertyValue(model, setterPropertyPath, normalValue); + + // + this.MODEL = { + ...model, + }; + this.detectChanges(); + this.onModelChanged.emit(model); + } + + async getModelProperty(key: keyof T | string) { + // + if (isNullOrEmptyString(key)) { + return null; + } + + // + const model = await this.getValueAsync(this.model); + const modelKeys = await this.getValueAsync(this.keys(model)); + if (!this.hasChild(modelKeys)) { + return null; + } + + // + const isExists = modelKeys.includes(key); + if (!isExists) { + return null; + } + + // + const result = model[key as any]; + return result; + } + + async getModelViewProperty(key: keyof T | string) { + // + if (isNullOrEmptyString(key)) { + return null; + } + + // + const model = await this.getValueAsync(this.model); + const modelKeys = await this.getValueAsync(this.keys(model)); + if (!this.hasChild(modelKeys)) { + return null; + } + + // + const isExists = modelKeys.includes(key); + if (!isExists) { + return null; + } + + // + const result = await this.getValueAsync( + this.getContent(key, model[key as any]) + ); + return result; + } + + async setModel(model: T) { + // + if (!model || isSameObject(model as any, this.MODEL as any)) { + return; + } + + // + this.model = { + ...model, + }; + this.MODEL = { + ...model, + }; + + // + await this.prepareModel(); + this.onModelChanged.emit(model); + } + + async getModel() { + // + const result = await this.getValueAsync(this.model); + return result; + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + if (!this.panelActionProvider) { + this.panelActionProvider = new Subject(); + } + + // + let modelProperty: XModelProperty = null; + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XModelPresenterAction.Refresh: + this.detectChanges(); + break; + + // + case XModelPresenterAction.ExpandAll: + await this.expandAll(); + break; + + // + case XModelPresenterAction.CollapseAll: + await this.collapseAll(); + break; + + // + case XModelPresenterAction.ClearProperty: + // + modelProperty = model.payload as XModelProperty; + if ( + !model.payload || + isNullOrUndefined(model) || + isNullOrUndefined(model.payload) || + isNullOrEmptyString(model.action) + ) { + return; + } + + // + modelProperty.value = null; + + // + await this.setModelProperty(modelProperty); + break; + + // + case XModelPresenterAction.SetProperty: + // + modelProperty = model.payload as XModelProperty; + if ( + !model.payload || + isNullOrUndefined(model) || + isNullOrUndefined(model.payload) || + isNullOrEmptyString(model.action) + ) { + return; + } + + // + await this.setModelProperty(modelProperty); + break; + + // + case XModelPresenterAction.SetModel: + // + const resetModel = model.payload as T; + if (!model || !model.payload || !resetModel) { + return; + } + + // + await this.setModel(resetModel); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async prepareModel() { + // + this.MODEL = null; + this.detectChanges(); + + // + if (!this.model) { + return; + } + + // + this.MODEL = await this.getValueAsync(this.model); + this.detectChanges(); + } + + private prepareNestedCollection( + key: string, + collection: XOneOrManyType + ) { + // + return forkJoin({ + key: this.getValue(key), + prefix: this.getValue(this.getKeyPrefix(key)), + model: this.getValue(this.preparedModel[key]), + collection: this.getArrayValue(collection), + }).pipe( + map((provider) => { + // + const allAvailableCollectionItems = provider.collection; + + // + const keySelector = isNullOrUndefined(provider.prefix) + ? `${provider.key}.` + : `${provider.prefix}.`; + + // + const canFilter = + !isNumber(provider.key) && + !provider.prefix.includes(`${provider.key}.`); + const result = canFilter + ? allAvailableCollectionItems + .filter((k) => k.includes(`${provider.key}.`)) + .map((k) => k.replace(`${provider.key}.`, '')) + : allAvailableCollectionItems; + + // + return result; + }) + ); + } + + private async handleStartupState() { + // + const state = await this.getValueAsync(this.startupState); + if (isNullOrEmptyString(state)) { + return; + } + + // + switch (state) { + // + case XModelPresenterStartupState.CollapseAll: + await this.collapseAll(); + break; + + // + case XModelPresenterStartupState.ExpandAll: + await this.expandAll(); + break; + } + } + + // + //#region Color Appliers ... + private async applyPanelColor() { + // + const color = await this.getValueAsync(this.color); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XModelPresenterCssVars.PanelForegroundColor, + `var(${fgColor})` + ); + this.setCssVar( + XModelPresenterCssVars.PanelBackgroundColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + private async applyHeaderColor() { + // + const color = await this.getValueAsync(this.headerColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XModelPresenterCssVars.HeaderForegroundColor, + `var(${fgColor})` + ); + this.setCssVar( + XModelPresenterCssVars.HeaderBackgroundColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + private async applyHeaderHighlightColor() { + // + const color = await this.getValueAsync(this.headerHighlightColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XModelPresenterCssVars.HeaderHighlighColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + private async applyContentColor() { + // + const color = await this.getValueAsync(this.contentColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XModelPresenterCssVars.ContentForegroundColor, + `var(${fgColor})` + ); + this.setCssVar( + XModelPresenterCssVars.ContentBackgroundColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XModelPresenterComponent } from './x-model-presenter.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XExpandablePanelModule } from '../x-expandable-panel/x-expandable-panel.module'; +import { XModelPresenterItemComponent } from './x-model-presenter-item/x-model-presenter-item.component'; + +@NgModule({ + declarations: [XModelPresenterComponent, XModelPresenterItemComponent], + imports: [ + XCardModule, + CommonModule, + XBaseComponentModule, + XExpandablePanelModule, + ], + exports: [ + XCardModule, + CommonModule, + XBaseComponentModule, + XExpandablePanelModule, + XModelPresenterComponent, + XModelPresenterItemComponent, + ], +}) +export class XModelPresenterModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XModelFieldsType, + XColorIdentifier, + XModelKeyProvider, + XModelValueProvider, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; +import { XExpandablePanelActionModel } from '../x-expandable-panel/x-expandable-panel.typings'; + +export enum XModelPresenterStartupState { + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', +} + +export type XModelPresenterStartupStateIdentifier = + | XModelPresenterStartupState + | string; + +export enum XModelPresenterAction { + // + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', + + // + ClearProperty = 'clear_property', + SetProperty = 'set_property', + + // + SetModel = 'set_model', + + // + Refresh = 'refresh', +} + +export type XModelPresenterActionIdentifier = XModelPresenterAction | string; + +export interface XModelPresenterActionModel + extends XBaseActionModel {} + +export enum XModelPresenterCssVars { + // + HeaderHighlighColor = 'header-highlight-color', + + // + HeaderForegroundColor = 'header-foreground-color', + HeaderBackgroundColor = 'header-background-color', + + // + ContentForegroundColor = 'content-foreground-color', + ContentBackgroundColor = 'content-background-color', + + // + PanelForegroundColor = 'panel-foreground-color', + PanelBackgroundColor = 'panel-background-color', +} + +export type XModelPresenterCssVarsIdentifier = XModelPresenterCssVars | string; + +// +//#region Inside Card Container ... +export interface IXModelPresenterContainer + extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + headerColor: XStandardType; + headerHighlightColor: XStandardType; + contentColor: XStandardType; + labelColor: XStandardType; + valueColor: XStandardType; + + // + showLabels: XStandardType; + propertyActionTemplate: TemplateRef; + startupState: XStandardType; + + // + panelCssClass: XStandardType>; + panelActionProvider: Subject; + + // + model: XStandardType; + + // + hideOptionalValues: XStandardType; + availableModelKeys: XModelFieldsType; + hiddenModelKeys: XModelFieldsType; + hiddenLabelModelKeys: XModelFieldsType; + sortOrderModelKeys: XModelFieldsType; + modelKeyProvider: XModelKeyProvider; + modelValueProvider: XModelValueProvider; + + // + onModelChanged: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XModelPresenterContainer + extends XComponentCardContainer + implements Readonly> +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly headerColor: XStandardType = + XColorWithBrightness.Light; + readonly headerHighlightColor: XStandardType = + XColorWithBrightness.LightShade; + readonly contentColor: XStandardType = + XColorWithBrightness.Dark; + readonly labelColor: XStandardType = + XColorWithBrightness.TertiaryShade; + readonly valueColor: XStandardType = + XColorWithBrightness.Dark; + + // + readonly showLabels: XStandardType = true; + readonly propertyActionTemplate: TemplateRef; + readonly startupState: XStandardType = + XModelPresenterStartupState.CollapseAll; + + // + readonly panelCssClass: XStandardType>; + readonly panelActionProvider: Subject = + new Subject(); + + // + readonly model: XStandardType; + + // + readonly hideOptionalValues: XStandardType = false; + readonly availableModelKeys: XModelFieldsType; + readonly hiddenModelKeys: XModelFieldsType; + readonly hiddenLabelModelKeys: XModelFieldsType; + readonly sortOrderModelKeys: XModelFieldsType; + readonly modelKeyProvider: XModelKeyProvider; + readonly modelValueProvider: XModelValueProvider; + + // + readonly onModelChanged: EventEmitter = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XModelPresenterContainerComponent + extends XComponentCardContainerComponent + implements IXModelPresenterContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Input() + headerColor: XStandardType = + this.propertyProvider.headerColor; + + @Input() + headerHighlightColor: XStandardType = + this.propertyProvider.headerHighlightColor; + + @Input() + contentColor: XStandardType = + this.propertyProvider.contentColor; + + @Input() + labelColor: XStandardType = + this.propertyProvider.labelColor; + + @Input() + valueColor: XStandardType = + this.propertyProvider.valueColor; + + // + @Input() + showLabels: XStandardType = this.propertyProvider.showLabels; + + @Input() + propertyActionTemplate: TemplateRef = + this.propertyProvider.propertyActionTemplate; + + @Input() + startupState: XStandardType = + this.propertyProvider.startupState; + + // + @Input() + panelCssClass: XStandardType> = + this.propertyProvider.panelCssClass; + + @Input() + panelActionProvider: Subject = + this.propertyProvider.panelActionProvider; + + // + @Input() + model: XStandardType = this.propertyProvider.model; + + // + @Input() + hideOptionalValues: XStandardType = + this.propertyProvider.hideOptionalValues; + + @Input() + availableModelKeys: XModelFieldsType = + this.propertyProvider.availableModelKeys; + + @Input() + hiddenModelKeys: XModelFieldsType = this.propertyProvider.hiddenModelKeys; + + @Input() + hiddenLabelModelKeys: XModelFieldsType = + this.propertyProvider.hiddenLabelModelKeys; + + @Input() + sortOrderModelKeys: XModelFieldsType = + this.propertyProvider.sortOrderModelKeys; + + @Input() + modelKeyProvider: XModelKeyProvider = + this.propertyProvider.modelKeyProvider; + + @Input() + modelValueProvider: XModelValueProvider = + this.propertyProvider.modelValueProvider; + + // + @Output() + onModelChanged: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XModelPresenterContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXModelPresenterContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + headerColor: this.getValue( + container ? container.headerColor : this.headerColor + ), + headerHighlightColor: this.getValue( + container + ? container.headerHighlightColor + : this.headerHighlightColor + ), + contentColor: this.getValue( + container ? container.contentColor : this.contentColor + ), + labelColor: this.getValue( + container ? container.labelColor : this.labelColor + ), + valueColor: this.getValue( + container ? container.valueColor : this.valueColor + ), + showLabels: this.getValue( + container ? container.showLabels : this.showLabels + ), + propertyActionTemplate: this.getValue( + container + ? container.propertyActionTemplate + : this.propertyActionTemplate + ), + startupState: this.getValue( + container ? container.startupState : this.startupState + ), + panelCssClass: this.getValue( + container ? container.panelCssClass : this.panelCssClass + ), + model: this.getValue(container ? container.model : this.model), + hideOptionalValues: this.getValue( + container ? container.hideOptionalValues : this.hideOptionalValues + ), + availableModelKeys: this.getValue( + container ? container.availableModelKeys : this.availableModelKeys + ), + hiddenModelKeys: this.getValue( + container ? container.hiddenModelKeys : this.hiddenModelKeys + ), + hiddenLabelModelKeys: this.getValue( + container + ? container.hiddenLabelModelKeys + : this.hiddenLabelModelKeys + ), + sortOrderModelKeys: this.getValue( + container ? container.sortOrderModelKeys : this.sortOrderModelKeys + ), + modelKeyProvider: this.getValue( + container ? container.modelKeyProvider : this.modelKeyProvider + ), + modelValueProvider: this.getValue( + container ? container.modelValueProvider : this.modelValueProvider + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + panelActionProvider: container + ? container.panelActionProvider + : this.panelActionProvider, + + // + onModelChanged: this.onModelChanged, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter-item\x-model-presenter-item.component.html + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter-item\x-model-presenter-item.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter-item\x-model-presenter-item.component.ts + +import { + Input, + Inject, + NgZone, + Renderer2, + Component, + ViewChild, + ElementRef, + TemplateRef, + ChangeDetectorRef, +} from '@angular/core'; +import { + XModelPresenterItemAction, + XModelPresenterItemModel, +} from './x-model-presenter-item.typings'; +import { Subject, Subscription } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseComponent } from '../../classes/x-base-component'; +import { XStandardType, XOneOrManyType, nameof } from 'x-framework-core'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from './../../config/x-framework-components.config'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-model-presenter-item', + templateUrl: './x-model-presenter-item.component.html', + styleUrls: ['./x-model-presenter-item.component.scss'], + // changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XModelPresenterItemComponent extends XBaseComponent { + // + //#region Props ... + // + @ViewChild('content', { static: false }) + content: TemplateRef; + + // + @Input() + name: XStandardType; + + @Input() + value: XStandardType; + + // + @Input() + cssClass: XStandardType>; + + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + @Input() + actionProvider = new Subject(); + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Private ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XModelPresenterItemAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter-item\x-model-presenter-item.typings.ts + +import { XBaseActionModel } from '../../classes/x-base-action.model'; + +export enum XModelPresenterItemAction { + Refresh = 'refresh', +} +export type XModelPresenterItemActionIdentifier = XModelPresenterItemAction | string; + +export interface XModelPresenterItemModel + extends XBaseActionModel {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-page.component.html + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-page.component.scss + +:host { + height: 100%; + + --side-menu-width: 200px; + --side-menu-min-width: 100px; + --side-menu-max-width: 200px; + + --side-menu-content-height-gutter: 0px; + --side-menu-content-offset-top: 0px; + --side-menu-content-offset-bottom: 0px; + + --content-height-gutter: 0px; + --content-offset-top: 0px; + --content-offset-bottom: 0px; + + ion-refresher { + padding-top: 90px; + } + + ion-content { + // + height: calc(100% - var(--content-height-gutter)); + overflow: auto; + + // + --offset-top: var(--content-offset-top) !important; + --offset-bottom: var(--content-offset-bottom) !important; + + // + --padding-top: 0; + --padding-end: 0; + --padding-start: 0; + --padding-bottom: 0; + } +} + +:host::ng-deep { + ion-header, + ion-footer { + z-index: 10000 !important; + } +} + +.x-menu { + // + --width: var(--side-menu-width) !important; + --min-width: var(--side-menu-min-width) !important; + --max-width: var(--side-menu-max-width) !important; + + // + ion-content { + // + height: calc(100% - var(--side-menu-content-height-gutter)) !important; + overflow: auto !important; + + // + --offset-top: var(--side-menu-content-offset-top) !important; + --offset-bottom: var(--side-menu-content-offset-bottom) !important; + + // + --padding-top: 0; + --padding-end: 0; + --padding-start: 0; + --padding-bottom: 0; + } +} + +.split-pane-visible > .split-pane-side { + border: 0 !important; + width: var(--side-menu-width) !important; + min-width: var(--side-menu-min-width) !important; + max-width: var(--side-menu-max-width) !important; +} + +.x-footer-offset, +.x-toolbar-offset { + visibility: hidden; +} + +.x-page-side-wrapper { + touch-action: pan-y; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-page.component.ts + +import { + XMenuState, + XMenuAction, + XPageCSSVars, + XPageLoadingEvent, +} from './x-page.typings'; +import { map } from 'rxjs/operators'; +import { IonContent } from '@ionic/angular'; +import { XFabItem } from '../x-fab/x-fab.typings'; +import { isNullOrUndefined, nameof, XBehaviourEvent } from 'x-framework-core'; +import { XPageContainerComponent } from './x-page.container'; +import { XFooterComponent } from './x-footer/x-footer.component'; +import { XToolbarComponent } from './x-toolbar/x-toolbar.component'; +import { Component, ViewChild, ChangeDetectionStrategy } from '@angular/core'; +import { XToolbarAction } from './x-toolbar/x-toolbar.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-page', + templateUrl: './x-page.component.html', + styleUrls: ['./x-page.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XPageComponent extends XPageContainerComponent { + // + //#region Props ... + // + readonly MenuStates = Object.assign({}, XMenuState); + + // + scrollYOffset = 0; + scrollXOffset = 0; + + // + //#region View Childs ... + // + //#region Toolbar ... + private TOOLBAR: XToolbarComponent; + + @ViewChild('toolbar', { static: false }) + set toolbar(v: XToolbarComponent) { + // + if (!isNullOrUndefined(v)) { + // + this.TOOLBAR = v; + this.applyContentSize(); + } + } + + get toolbar() { + return this.TOOLBAR; + } + //#endregion + + // + //#region Footer ... + private FOOTER: XFooterComponent; + + @ViewChild('footer', { static: false }) + set footer(v: XFooterComponent) { + // + if (!isNullOrUndefined(v)) { + // + this.FOOTER = v; + this.applyContentSize(); + } + } + + get footer() { + return this.FOOTER; + } + //#endregion + //#endregion + + // + //#region Side Props ... + // + private IS_SIDE_OPENED = false; + + get isSideOpened() { + return this.IS_SIDE_OPENED; + } + + private CAN_CLOSE_MENU = true; + + get canCloseMenu() { + return this.CAN_CLOSE_MENU; + } + //#endregion + //#endregion + + // + //#region LifeCycle ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region CssVars ... + // + //#region SideMenuWidth ... + const isSideMenuWidthChanged = changeKeys.includes( + nameof('sideMenuWidth') + ); + const isSideMenuMinWidthChanged = changeKeys.includes( + nameof('sideMenuMinWidth') + ); + const isSideMenuMaxWidthChanged = changeKeys.includes( + nameof('sideMenuMaxWidth') + ); + if ( + isSideMenuWidthChanged || + isSideMenuMinWidthChanged || + isSideMenuMaxWidthChanged + ) { + await this.applySideMenuWidth(); + } + //#endregion + //#endregion + } + + async afterViewInit() { + super.afterViewInit(); + + // + this.handleMenuStartState(); + await this.applyContentSize(); + } + //#endregion + + // + //#region Register Handlers ... + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerResizeHandler(); + this.registerMenuActionProvider(); + this.registerToolbarActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + //#endregion + + // + //#region UI Handlers ... + async handleChangeSplitPane(event: CustomEvent | any) { + // + const canUseMenu = await this.canUseMenu(); + if (!canUseMenu) { + return; + } + + // + const isSideOpened = event.detail.visible; + this.IS_SIDE_OPENED = isSideOpened; + this.splitterStateChanged.emit(isSideOpened); + + // + if (isSideOpened) { + // + this.CAN_CLOSE_MENU = false; + this.menuState = XMenuState.Opened; + await this.openMenu(); + } else { + // + this.CAN_CLOSE_MENU = true; + this.menuState = XMenuState.Closed; + await this.closeMenu(); + } + } + + async handleMenuStateChanged(state: XMenuState) { + // + const canUseMenu = await this.canUseMenu(); + if (!canUseMenu) { + return; + } + + // + this.menuState = state; + + // + this.menuStateChange.emit(state); + } + + async handleRefresh(event: any) { + // + const loaderEvent: XPageLoadingEvent = { + type: 'RefreshLoader', + event, + }; + + // + this.refreshPage.emit(loaderEvent); + } + + async handleLoadMore(event: any) { + // + const loaderEvent: XPageLoadingEvent = { + type: 'InfinityScrollerLoader', + event, + }; + + // + this.loadMore.emit(loaderEvent); + } + + async cancelLoading(event: any) { + // + if (!event) { + return; + } + + // + event.target.complete(); + } + + async handleScrollStarted(event: XBehaviourEvent) { + // + this.scrolledEvent.emit(event); + + // + this.closeActions(); + } + + async handleScrollingEvent(event: XBehaviourEvent) { + // + this.scrolledEvent.emit(event); + + // + this.scrollYOffset = event.event.detail.scrollTop as number; + this.scrollXOffset = event.event.detail.scrollLeft as number; + + // + await this.applyCaptureOffsets(); + } + + isMenuOpen() { + // + return this.getValue(this.menuState).pipe( + map((menuState) => { + return ( + menuState === XMenuState.WillOpen || menuState === XMenuState.Opened + ); + }) + ); + } + //#endregion + + // + //#region Actions ... + async openMenu() { + // + const canUseMenu = await this.canUseMenu(); + if (!canUseMenu || this.IS_SIDE_OPENED) { + return; + } + + // + this.menuController.open(); + } + + async closeMenu() { + // + const canUseMenu = await this.canUseMenu(); + if (!canUseMenu || !this.CAN_CLOSE_MENU) { + return; + } + + // + this.menuController.close(); + } + + async toggleMenu() { + // + const canUseMenu = await this.canUseMenu(); + if (!canUseMenu) { + return; + } + + // + if (this.isMenuOpen()) { + await this.closeMenu(); + } else { + await this.openMenu(); + } + } + + async scrollToTop(duration?: number) { + // + await this.waitForElement('ion-content[id="x-menu-wrapper"]').then( + (el: any) => { + el.scrollToTop(duration); + } + ); + } + + async scrollToBottom(duration?: number) { + // + await this.waitForElement('ion-content[id="x-menu-wrapper"]').then( + (el: any) => { + el.scrollToBottom(duration); + } + ); + } + + async scrollByPoint(x?: number, y?: number, duration?: number) { + // + await this.waitForElement('ion-content[id="x-menu-wrapper"]').then( + (el: any) => { + el.scrollByPoint(x, y, duration); + } + ); + } + + async scrollToPoint(x?: number, y?: number, duration?: number) { + // + await this.waitForElement('ion-content[id="x-menu-wrapper"]').then( + (el: any) => { + el.scrollToPoint(x, y, duration); + } + ); + } + + closeActions(delay: number = 1000) { + // + if (!this.actions) { + return; + } + + // + setTimeout(() => { + // + if (!this.actions) { + return; + } + + // + this.actions = { + ...this.actions, + activated: false, + } as XFabItem; + + // + this.detectChanges(); + }, delay); + } + + openActions(delay: number = 1000) { + // + if (!this.actions) { + return; + } + + // + setTimeout(() => { + // + if (!this.actions) { + return; + } + + // + this.actions = { + ...this.actions, + activated: true, + } as XFabItem; + + // + this.detectChanges(); + }, delay); + } + //#endregion + + // + //#region Private ... + private async canUseMenu() { + // + if (!this.toolbarHasMenu || !this.toolbarShowMenu) { + return false; + } + + // + const hasMenu = await this.toPromise(this.getValue(this.toolbarHasMenu)); + const showMenu = await this.toPromise(this.getValue(this.toolbarShowMenu)); + + // + if (!hasMenu || !showMenu) { + return false; + } + + // + return true; + } + + private async handleMenuStartState() { + // + const canUseMenu = await this.canUseMenu(); + if (!canUseMenu || this.isSideOpened) { + return; + } + + // + switch (this.menuState) { + // + case XMenuState.WillOpen: + case XMenuState.Opened: + await this.openMenu(); + break; + + // + case XMenuState.WillClose: + case XMenuState.Closed: + await this.closeMenu(); + break; + } + } + + private registerMenuActionProvider() { + // + this.applyTakeUntilWrapper( + this.menuActionProvider.asObservable() + ).subscribe((action) => { + // + switch (action) { + case XMenuAction.Open: + this.openMenu(); + break; + + case XMenuAction.Close: + this.closeMenu(); + break; + + case XMenuAction.Toggle: + this.toggleMenu(); + break; + } + }); + } + + private registerToolbarActionProvider() { + // + if (isNullOrUndefined(this.toolbarActionProvider)) { + return; + } + + // + this.applyTakeUntilWrapper( + this.toolbarActionProvider.asObservable() + ).subscribe(async (event) => { + // + if (isNullOrUndefined(event)) { + return; + } + + // + if (event.action === XToolbarAction.ToggleMenu) { + await this.toggleMenu(); + } + }); + } + + private async registerResizeHandler() { + // + this.applyTakeUntilWrapper(this.ruler.change()).subscribe(async () => { + await this.applyContentSize(); + }); + } + + private async getToolbarHeight() { + // + if (!this.toolbar) { + return 0; + } + + // + const toolbarElm = this.toolbar.getElement(); + + // + if (!toolbarElm) { + return 0; + } + + // + return toolbarElm.getBoundingClientRect().height; + } + + private async getFooterHeight() { + // + if (!this.footer) { + return 0; + } + + // + const footerElm = this.footer.getElement(); + + // + if (!footerElm) { + return 0; + } + + // + return footerElm.getBoundingClientRect().height; + } + + private async applyContentSize() { + // + const showToolbar = await this.getValueAsync(this.showToolbar); + const toolbarHeight = await this.getToolbarHeight(); + + // + const showFooter = await this.getValueAsync(this.showFooter); + const footerHeight = await this.getFooterHeight(); + + // + if ( + (showToolbar && toolbarHeight === 0) || + (showFooter && footerHeight === 0) + ) { + // + setTimeout(() => this.applyContentSize(), 100); + return; + } + + // + const topValue = showToolbar ? `-${toolbarHeight}px` : '0px'; + const bottomValue = showFooter ? `-${footerHeight}px` : '0px'; + + // + this.setCssVar(XPageCSSVars.ContentOffsetTop, topValue); + this.setCssVar(XPageCSSVars.SideMenuContentOffsetTop, topValue); + + // + this.setCssVar(XPageCSSVars.ContentOffsetBottom, bottomValue); + this.setCssVar(XPageCSSVars.SideMenuContentOffsetBottom, bottomValue); + + // + await this.applyCaptureOffsets(); + + // + this.detectChanges(); + } + + private async applySideMenuWidth() { + // + const sideMenuWidth = (await this.getValueAsync(this.sideMenuWidth)) || 200; + const sideMenuMinWidth = + (await this.getValueAsync(this.sideMenuMinWidth)) || 100; + const sideMenuMaxWidth = + (await this.getValueAsync(this.sideMenuMaxWidth)) || 200; + + // + this.setCssVar(XPageCSSVars.SideMenuWidth, `${sideMenuWidth}px`); + this.setCssVar(XPageCSSVars.SideMenuMinWidth, `${sideMenuMinWidth}px`); + this.setCssVar(XPageCSSVars.SideMenuMaxWidth, `${sideMenuMaxWidth}px`); + + // + this.detectChanges(); + } + + private async applyCaptureOffsets() { + // + const showToolbar = await this.getValueAsync(this.showToolbar); + const toolbarHeight = await this.getToolbarHeight(); + + // + const showFooter = await this.getValueAsync(this.showFooter); + const footerHeight = await this.getFooterHeight(); + + // + if ( + (showToolbar && toolbarHeight === 0) || + (showFooter && footerHeight === 0) + ) { + // + setTimeout(() => this.applyCaptureOffsets(), 100); + return; + } + + // + this.captureTopOffset = -1 * this.scrollYOffset + -1 * toolbarHeight; + + // + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-page.container.ts + +import { + Input, + NgZone, + Output, + Inject, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XSpinner, + XStandardType, + XOneOrManyType, + XBehaviourEvent, + XColorIdentifier, + XSpinnerIdentifier, + XBehaviourEventType, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XPageSize, + XSideSlot, + XSideType, + XMenuState, + XMenuAction, + XPageLoadingEvent, + XMenuSlotIdentifier, + XLogoSlotIdentifier, + XPageSizeIdentifier, + XSideSlotIdentifier, + XSideTypeIdentifier, + XBackSlotIdentifier, + XTitleSlotIdentifier, + XContentSlotIdentifier, +} from './x-page.typings'; +import { forkJoin } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { + XToolbarFooterContainer, + IXToolbarFooterContainer, +} from '../classes/i-x-toolbar-footer'; +import { Observable, Subject } from 'rxjs'; +import { MenuController } from '@ionic/angular'; +import { XBasePage } from '../classes/x-base-page'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { IXBaseContainer } from '../classes/i-x-base.container'; +import { XFooterActionModel } from './x-footer/x-footer.typings'; +import { XFabActionModel, XFabItem } from '../x-fab/x-fab.typings'; +import { XToolbarActionModel } from './x-toolbar/x-toolbar.typings'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; + +// +//#region Container ... +export interface IXPageContainer + extends IXBaseContainer, + IXToolbarFooterContainer { + // + cssClass: XStandardType>; + + // + color: XStandardType; + + // + enableCaptureMode: XStandardType; + downloadCapturedImage: XStandardType; + captureTopOffset: XStandardType; + captureBottomOffset: XStandardType; + imageCaptured: EventEmitter; + + // + provideScrollEvents: XStandardType; + scrollStartClasses: XOneOrManyType; + scrollEndClasses: XOneOrManyType; + scrollingClasses: XOneOrManyType; + scrollBehaviour: XOneOrManyType; + scrollEventActionProvider: Subject; + scrolledEvent: EventEmitter; + + // + toggleMenuWhen: XStandardType; + hasSide: XStandardType; + sideSlot: XStandardType; + sideType: XStandardType; + sideColor: XStandardType; + sideMenuWidth: XStandardType; + sideMenuMinWidth: XStandardType; + sideMenuMaxWidth: XStandardType; + menuState: XStandardType; + menuActionProvider: Subject; + splitterStateChanged: EventEmitter; + menuStateChange: EventEmitter; + + // + showPageRefresher: XStandardType; + refreshPage: EventEmitter; + refreshingText: XStandardType; + refreshPullingText: XStandardType; + refreshPullingIcon: XStandardType; + refreshingSpinner: XStandardType; + + // + showPageInfinityScroller: XStandardType; + infinityScrollDisabled: XStandardType; + infinityScrollThreshold: XStandardType; + infinityScrollerLoadingText: XStandardType; + infinityScrollerLoadingSpinner: XStandardType; + loadMore: EventEmitter; + + // + actions: XStandardType; + actionsActionProvider: Subject; +} + +@Injectable({ + providedIn: 'root', +}) +export class XPageContainer + extends XToolbarFooterContainer + implements Readonly +{ + // + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = + XColorWithBrightness.LightShade; + + // + readonly enableCaptureMode: XStandardType = false; + readonly downloadCapturedImage: XStandardType = true; + readonly captureTopOffset: XStandardType = 0; + readonly captureBottomOffset: XStandardType = 0; + readonly imageCaptured: EventEmitter = new EventEmitter< + string | Blob + >(); + + // + readonly provideScrollEvents: XStandardType; + readonly scrollStartClasses: XOneOrManyType; + readonly scrollEndClasses: XOneOrManyType; + readonly scrollingClasses: XOneOrManyType; + readonly scrollBehaviour: XOneOrManyType; + readonly scrollEventActionProvider: Subject = + new Subject(); + readonly scrolledEvent: EventEmitter = + new EventEmitter(); + + // + readonly toggleMenuWhen: XStandardType = + XPageSize.Medium; + readonly hasSide: XStandardType = true; + readonly sideSlot: XStandardType = XSideSlot.Start; + readonly sideType: XStandardType = XSideType.Overlay; + readonly sideColor: XStandardType = + XColorWithBrightness.PrimaryShade; + readonly sideMenuWidth: XStandardType = 200; + readonly sideMenuMinWidth: XStandardType = 100; + readonly sideMenuMaxWidth: XStandardType = 200; + readonly menuState: XStandardType = XMenuState.Opened; + readonly menuActionProvider = new Subject(); + readonly splitterStateChanged = new EventEmitter(); + readonly menuStateChange = new EventEmitter(); + + // + readonly showPageRefresher: XStandardType = false; + readonly refreshPage: EventEmitter = + new EventEmitter(); + readonly refreshingText: XStandardType; + readonly refreshPullingText: XStandardType; + readonly refreshPullingIcon: XStandardType; + readonly refreshingSpinner: XStandardType = + XSpinner.Circular; + + // + readonly showPageInfinityScroller: XStandardType = false; + readonly infinityScrollDisabled: XStandardType = false; + readonly infinityScrollThreshold: XStandardType = '200px'; + readonly infinityScrollerLoadingText: XStandardType; + readonly infinityScrollerLoadingSpinner: XStandardType = + XSpinner.Circular; + readonly loadMore: EventEmitter = + new EventEmitter(); + + // + readonly actions: XStandardType; + readonly actionsActionProvider: Subject = + new Subject(); +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XPageContainerComponent + extends XBasePage + implements IXPageContainer +{ + // + //#region Props ... + // + //#region Page ... + // + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + // + @Input() + enableCaptureMode: XStandardType = + this.propertyProvider.enableCaptureMode; + + @Input() + downloadCapturedImage: XStandardType = + this.propertyProvider.downloadCapturedImage; + + @Input() + captureTopOffset: XStandardType = + this.propertyProvider.captureTopOffset; + + @Input() + captureBottomOffset: XStandardType = + this.propertyProvider.captureBottomOffset; + + @Output() + imageCaptured: EventEmitter = new EventEmitter(); + + // + @Input() + provideScrollEvents: XStandardType = + this.propertyProvider.provideScrollEvents; + + @Input() + scrollStartClasses: XOneOrManyType = + this.propertyProvider.scrollStartClasses; + + @Input() + scrollEndClasses: XOneOrManyType = + this.propertyProvider.scrollEndClasses; + + @Input() + scrollingClasses: XOneOrManyType = + this.propertyProvider.scrollingClasses; + + @Input() + scrollBehaviour: XOneOrManyType = + this.propertyProvider.scrollBehaviour; + + @Input() + scrollEventActionProvider: Subject = + this.propertyProvider.scrollEventActionProvider; + + @Output() + scrolledEvent: EventEmitter = new EventEmitter(); + + // + @Input() + toggleMenuWhen: XStandardType = + this.propertyProvider.toggleMenuWhen; + + @Input() + hasSide: XStandardType = this.propertyProvider.hasSide; + + @Input() + sideSlot: XStandardType = this.propertyProvider.sideSlot; + + @Input() + sideType: XStandardType = this.propertyProvider.sideType; + + @Input() + sideColor: XStandardType = this.propertyProvider.sideColor; + + @Input() + sideMenuWidth: XStandardType = this.propertyProvider.sideMenuWidth; + + @Input() + sideMenuMinWidth: XStandardType = + this.propertyProvider.sideMenuMinWidth; + + @Input() + sideMenuMaxWidth: XStandardType = + this.propertyProvider.sideMenuMaxWidth; + + @Input() + menuState: XStandardType = this.propertyProvider.menuState; + + @Input() + menuActionProvider: Subject = + this.propertyProvider.menuActionProvider; + + @Output() + splitterStateChanged: EventEmitter = new EventEmitter(); + + @Output() + menuStateChange: EventEmitter = new EventEmitter(); + + // + @Input() + showPageRefresher: XStandardType = + this.propertyProvider.showPageRefresher; + + @Output() + refreshPage: EventEmitter = new EventEmitter(); + + @Input() + refreshingText: XStandardType = this.propertyProvider.refreshingText; + + @Input() + refreshPullingText: XStandardType = + this.propertyProvider.refreshPullingText; + + @Input() + refreshPullingIcon: XStandardType = + this.propertyProvider.refreshPullingIcon; + + @Input() + refreshingSpinner: XStandardType = + this.propertyProvider.refreshingSpinner; + + // + @Input() + showPageInfinityScroller: XStandardType = + this.propertyProvider.showPageInfinityScroller; + + @Input() + infinityScrollDisabled: XStandardType = + this.propertyProvider.infinityScrollDisabled; + + @Input() + infinityScrollThreshold: XStandardType = + this.propertyProvider.infinityScrollThreshold; + + @Input() + infinityScrollerLoadingText: XStandardType = + this.propertyProvider.infinityScrollerLoadingText; + + @Input() + infinityScrollerLoadingSpinner: XStandardType = + this.propertyProvider.infinityScrollerLoadingSpinner; + + @Output() + loadMore: EventEmitter = new EventEmitter(); + + // + @Input() + actions: XStandardType = this.propertyProvider.actions; + + @Input() + actionsActionProvider: Subject = + this.propertyProvider.actionsActionProvider; + //#endregion + + // + //#region Toolbar ... + // + @Input() + showToolbar: XStandardType = this.propertyProvider.showToolbar; + + // + @Input() + toolbarActionProvider: Subject = + this.propertyProvider.toolbarActionProvider; + + @Input() + toolbarCssClass: XStandardType> = + this.propertyProvider.toolbarCssClass; + + // + @Input() + toolbarColor: XStandardType = + this.propertyProvider.toolbarColor; + + // + @Input() + toolbarShowProgressBar: XStandardType = + this.propertyProvider.toolbarShowProgressBar; + + @Input() + toolbarProgressBarColor: XStandardType = + this.propertyProvider.toolbarProgressBarColor; + + // + @Input() + toolbarShowTitle: XStandardType = + this.propertyProvider.toolbarShowTitle; + + @Input() + toolbarTitleSlot: XStandardType = + this.propertyProvider.toolbarTitleSlot; + + @Input() + toolbarTitle: XStandardType = this.propertyProvider.toolbarTitle; + + // + @Input() + toolbarShowSubTitle: XStandardType = + this.propertyProvider.toolbarShowSubTitle; + + @Input() + toolbarSubTitle: XStandardType = + this.propertyProvider.toolbarSubTitle; + + // + @Input() + toolbarShowLogo: XStandardType = + this.propertyProvider.toolbarShowLogo; + + @Input() + toolbarLogoSlot: XStandardType = + this.propertyProvider.toolbarLogoSlot; + + @Input() + toolbarLogoUrl: XStandardType = this.propertyProvider.toolbarLogoUrl; + + // + @Input() + toolbarHasBack: XStandardType = this.propertyProvider.toolbarHasBack; + + @Input() + toolbarShowBack: XStandardType = + this.propertyProvider.toolbarShowBack; + + @Input() + toolbarBackSlot: XStandardType = + this.propertyProvider.toolbarBackSlot; + + @Input() + toolbarBackHandler?: () => XStandardType = () => { + this.managerService.historyService.back(); + }; + + @Input() + toolbarDefaultHref: string = this.propertyProvider.toolbarDefaultHref; + + // + @Input() + toolbarHasMenu: XStandardType = this.propertyProvider.toolbarHasMenu; + + @Input() + toolbarShowMenu: XStandardType = + this.propertyProvider.toolbarShowMenu; + + @Input() + toolbarAutoHideMenu: XStandardType = + this.propertyProvider.toolbarAutoHideMenu; + + @Input() + toolbarMenuSlot: XStandardType = + this.propertyProvider.toolbarMenuSlot; + + // + @Input() + toolbarStartSlotButtonsTemplate: TemplateRef = + this.propertyProvider.toolbarStartSlotButtonsTemplate; + + @Input() + toolbarEndSlotButtonsTemplate: TemplateRef = + this.propertyProvider.toolbarEndSlotButtonsTemplate; + + // + @Input() + toolbarContentSlot: XStandardType = + this.propertyProvider.toolbarContentSlot; + + @Input() + toolbarContentTemplate: TemplateRef = + this.propertyProvider.toolbarContentTemplate; + //#endregion + + // + //#region Footer ... + // + @Input() + showFooter: XStandardType = this.propertyProvider.showFooter; + + // + @Input() + footerActionProvider: Subject = + this.propertyProvider.footerActionProvider; + + @Input() + footerCssClass: XStandardType> = + this.propertyProvider.footerCssClass; + + // + @Input() + footerColor: XStandardType = + this.propertyProvider.footerColor; + + @Input() + footerContentTemplate: TemplateRef = + this.propertyProvider.footerContentTemplate; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + public menuController: MenuController, + protected propertyProvider: XPageContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXPageContainer + ): Observable { + return forkJoin({ + // + //#region Page ... + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + + // + color: this.getValue(container ? container.color : this.color), + + // + enableCaptureMode: this.getValue( + container ? container.enableCaptureMode : this.enableCaptureMode + ), + downloadCapturedImage: this.getValue( + container ? container.downloadCapturedImage : this.downloadCapturedImage + ), + captureTopOffset: this.getValue( + container ? container.captureTopOffset : this.captureTopOffset + ), + captureBottomOffset: this.getValue( + container ? container.captureBottomOffset : this.captureBottomOffset + ), + + // + provideScrollEvents: this.getValue( + container ? container.provideScrollEvents : this.provideScrollEvents + ), + scrollStartClasses: this.getValue( + container ? container.scrollStartClasses : this.scrollStartClasses + ), + scrollEndClasses: this.getValue( + container ? container.scrollEndClasses : this.scrollEndClasses + ), + scrollingClasses: this.getValue( + container ? container.scrollingClasses : this.scrollingClasses + ), + scrollBehaviour: this.getValue( + container ? container.scrollBehaviour : this.scrollBehaviour + ), + + // + toggleMenuWhen: this.getValue( + container ? container.toggleMenuWhen : this.toggleMenuWhen + ), + hasSide: this.getValue(container ? container.hasSide : this.hasSide), + sideSlot: this.getValue(container ? container.sideSlot : this.sideSlot), + sideType: this.getValue(container ? container.sideType : this.sideType), + sideColor: this.getValue( + container ? container.sideColor : this.sideColor + ), + sideMenuWidth: this.getValue( + container ? container.sideMenuWidth : this.sideMenuWidth + ), + sideMenuMinWidth: this.getValue( + container ? container.sideMenuMinWidth : this.sideMenuMinWidth + ), + sideMenuMaxWidth: this.getValue( + container ? container.sideMenuMaxWidth : this.sideMenuMaxWidth + ), + menuState: this.getValue( + container ? container.menuState : this.menuState + ), + + // + showPageRefresher: this.getValue( + container ? container.showPageRefresher : this.showPageRefresher + ), + refreshingText: this.getValue( + container ? container.refreshingText : this.refreshingText + ), + refreshPullingText: this.getValue( + container ? container.refreshPullingText : this.refreshPullingText + ), + refreshPullingIcon: this.getValue( + container ? container.refreshPullingIcon : this.refreshPullingIcon + ), + refreshingSpinner: this.getValue( + container ? container.refreshingSpinner : this.refreshingSpinner + ), + + // + showPageInfinityScroller: this.getValue( + container + ? container.showPageInfinityScroller + : this.showPageInfinityScroller + ), + infinityScrollDisabled: this.getValue( + container + ? container.infinityScrollDisabled + : this.infinityScrollDisabled + ), + infinityScrollThreshold: this.getValue( + container + ? container.infinityScrollThreshold + : this.infinityScrollThreshold + ), + infinityScrollerLoadingText: this.getValue( + container + ? container.infinityScrollerLoadingText + : this.infinityScrollerLoadingText + ), + infinityScrollerLoadingSpinner: this.getValue( + container + ? container.infinityScrollerLoadingSpinner + : this.infinityScrollerLoadingSpinner + ), + + // + actions: this.getValue(container ? container.actions : this.actions), + //#endregion + + // + //#region Toolbar ... + showToolbar: this.getValue( + container ? container.showToolbar : this.showToolbar + ), + toolbarCssClass: this.getValue( + container ? container.toolbarCssClass : this.toolbarCssClass + ), + toolbarColor: this.getValue( + container ? container.toolbarColor : this.toolbarColor + ), + toolbarShowProgressBar: this.getValue( + container + ? container.toolbarShowProgressBar + : this.toolbarShowProgressBar + ), + toolbarProgressBarColor: this.getValue( + container + ? container.toolbarProgressBarColor + : this.toolbarProgressBarColor + ), + toolbarShowTitle: this.getValue( + container ? container.toolbarShowTitle : this.toolbarShowTitle + ), + toolbarTitleSlot: this.getValue( + container ? container.toolbarTitleSlot : this.toolbarTitleSlot + ), + toolbarTitle: this.getValue( + container ? container.toolbarTitle : this.toolbarTitle + ), + toolbarShowSubTitle: this.getValue( + container ? container.toolbarShowSubTitle : this.toolbarShowSubTitle + ), + toolbarSubTitle: this.getValue( + container ? container.toolbarSubTitle : this.toolbarSubTitle + ), + toolbarShowLogo: this.getValue( + container ? container.toolbarShowLogo : this.toolbarShowLogo + ), + toolbarLogoSlot: this.getValue( + container ? container.toolbarLogoSlot : this.toolbarLogoSlot + ), + toolbarLogoUrl: this.getValue( + container ? container.toolbarLogoUrl : this.toolbarLogoUrl + ), + toolbarHasBack: this.getValue( + container ? container.toolbarHasBack : this.toolbarHasBack + ), + toolbarShowBack: this.getValue( + container ? container.toolbarShowBack : this.toolbarShowBack + ), + toolbarBackSlot: this.getValue( + container ? container.toolbarBackSlot : this.toolbarBackSlot + ), + toolbarBackHandler: this.getValue( + container ? container.toolbarBackHandler : this.toolbarBackHandler + ), + toolbarDefaultHref: this.getValue( + container ? container.toolbarDefaultHref : this.toolbarDefaultHref + ), + toolbarHasMenu: this.getValue( + container ? container.toolbarHasMenu : this.toolbarHasMenu + ), + toolbarShowMenu: this.getValue( + container ? container.toolbarShowMenu : this.toolbarShowMenu + ), + toolbarAutoHideMenu: this.getValue( + container ? container.toolbarAutoHideMenu : this.toolbarAutoHideMenu + ), + toolbarMenuSlot: this.getValue( + container ? container.toolbarMenuSlot : this.toolbarMenuSlot + ), + toolbarStartSlotButtonsTemplate: this.getValue( + container + ? container.toolbarStartSlotButtonsTemplate + : this.toolbarStartSlotButtonsTemplate + ), + toolbarEndSlotButtonsTemplate: this.getValue( + container + ? container.toolbarEndSlotButtonsTemplate + : this.toolbarEndSlotButtonsTemplate + ), + toolbarContentSlot: this.getValue( + container ? container.toolbarContentSlot : this.toolbarContentSlot + ), + toolbarContentTemplate: this.getValue( + container + ? container.toolbarContentTemplate + : this.toolbarContentTemplate + ), + //#endregion + + // + //#region Footer ... + showFooter: this.getValue( + container ? container.showFooter : this.showFooter + ), + footerCssClass: this.getValue( + container ? container.footerCssClass : this.footerCssClass + ), + footerColor: this.getValue( + container ? container.footerColor : this.footerColor + ), + footerContentTemplate: this.getValue( + container ? container.footerContentTemplate : this.footerContentTemplate + ), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + toolbarActionProvider: container + ? container.toolbarActionProvider + : this.toolbarActionProvider, + + // + footerActionProvider: container + ? container.footerActionProvider + : this.footerActionProvider, + + // + menuActionProvider: container + ? container.menuActionProvider + : this.menuActionProvider, + + // + scrollEventActionProvider: container + ? container.scrollEventActionProvider + : this.scrollEventActionProvider, + + // + actionsActionProvider: container + ? container.actionsActionProvider + : this.actionsActionProvider, + + // + imageCaptured: this.imageCaptured, + splitterStateChanged: this.splitterStateChanged, + menuStateChange: this.menuStateChange, + scrolledEvent: this.scrolledEvent, + refreshPage: this.refreshPage, + loadMore: this.loadMore, + }; + }) + ); + } + //#endregion +} + +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-page.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XPageComponent } from './x-page.component'; +import { XFabModule } from './../x-fab/x-fab.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XButtonModule } from '../x-button/x-button.module'; +import { XFooterComponent } from './x-footer/x-footer.component'; +import { XToolbarComponent } from './x-toolbar/x-toolbar.component'; +import { XNavigatorComponent } from './x-navigator/x-navigator.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XScreenCaptureModule } from '../x-screen-capture/x-screen-capture.module'; +import { XExpandableListModule } from '../x-expandable-list/x-expandable-list.module'; +import { XExpandablePanelModule } from '../x-expandable-panel/x-expandable-panel.module'; + +@NgModule({ + imports: [ + XFabModule, + XIconModule, + CommonModule, + XButtonModule, + XBaseComponentModule, + XScreenCaptureModule, + XExpandableListModule, + XExpandablePanelModule, + ], + declarations: [ + XPageComponent, + XFooterComponent, + XToolbarComponent, + XNavigatorComponent, + ], + exports: [ + XFabModule, + XIconModule, + CommonModule, + XButtonModule, + XPageComponent, + XFooterComponent, + XToolbarComponent, + XNavigatorComponent, + XBaseComponentModule, + XScreenCaptureModule, + XExpandableListModule, + XExpandablePanelModule, + ], +}) +export class XPageModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-page.typings.ts + + +export enum XMenuState { + WillOpen, + Opened, + WillClose, + Closed, +} + +export enum XMenuAction { + Open, + Close, + Toggle, +} + +export enum XPageSize { + ExtraSmall = 'xs', + Small = 'sm', + Medium = 'md', + Large = 'lg', + ExtraLarge = 'xl', +} + +export type XPageSizeIdentifier = XPageSize | string; + +export enum XSideType { + Overlay = 'overlay', + Reveal = 'reveal', + Push = 'push', +} + +export type XSideTypeIdentifier = XSideType | string; + +export enum XBackSlot { + Start = 'start', + End = 'end', +} + +export type XBackSlotIdentifier = XBackSlot | string; + +export enum XMenuSlot { + Start = 'start', + End = 'end', +} + +export type XMenuSlotIdentifier = XMenuSlot | string; + +export enum XLogoSlot { + Start = 'start', + End = 'end', +} + +export type XLogoSlotIdentifier = XLogoSlot | string; + +export enum XTitleSlot { + Start = 'start', + End = 'end', +} + +export type XTitleSlotIdentifier = XTitleSlot | string; + +export enum XContentSlot { + Start = 'start', + Middle = 'middle', + End = 'end', +} + +export type XContentSlotIdentifier = XContentSlot | string; + +export enum XSideSlot { + Start = 'start', + End = 'end', +} + +export type XSideSlotIdentifier = XSideSlot | string; + +export interface XPageLoadingEvent { + type: 'InfinityScrollerLoader' | 'RefreshLoader'; + event: any; +} + +export enum XPageCSSVars { + SideMenuWidth = 'side-menu-width', + SideMenuMinWidth = 'side-menu-min-width', + SideMenuMaxWidth = 'side-menu-max-width', + SideMenuContentHeightGutter = 'side-menu-content-height-gutter', + SideMenuContentOffsetTop = 'side-menu-content-offset-top', + SideMenuContentOffsetBottom = 'side-menu-content-offset-bottom', + ContentHeightGutter = 'content-height-gutter', + ContentOffsetTop = 'content-offset-top', + ContentOffsetBottom = 'content-offset-bottom', +} + +export type XPageCSSVarsIdentifier = XPageCSSVars | string; + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-footer\x-footer.component.html + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-footer\x-footer.component.scss + +:host { + position: absolute; + bottom: 0; + left: 0; + right: 0; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-footer\x-footer.component.ts + +import { + XFooterAction, + XFooterActionModel, + XFooterContainerComponent, +} from './x-footer.typings'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-footer', + templateUrl: './x-footer.component.html', + styleUrls: ['./x-footer.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XFooterComponent extends XFooterContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.actionProvider.asObservable() + ).subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XFooterAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-footer\x-footer.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, + XBaseComponentContainer, + IXBaseComponentContainer, + XBaseComponentContainerComponent, +} from '../../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +export enum XFooterAction { + Refresh = 'refresh', +} + +export type XFooterActionIdentifier = XFooterAction | string; + +export interface XFooterActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXFooterContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + contentTemplate: TemplateRef; +} + +@Injectable({ + providedIn: 'root' +}) +export class XFooterContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly contentTemplate: TemplateRef; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XFooterContainerComponent + extends XBaseContainerComponent + implements IXFooterContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + contentTemplate: TemplateRef = this.propertyProvider.contentTemplate; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XFooterContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXFooterContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + color: this.getValue(container ? container.color : this.color), + contentTemplate: this.getValue( + container ? container.contentTemplate : this.contentTemplate + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentFooterContainer = { + [TKey in keyof IXFooterContainer as `footer${Capitalize< + string & TKey + >}`]: IXFooterContainer[TKey]; +}; + +export interface IXComponentFooterContainer + extends TXComponentFooterContainer, + IXBaseComponentContainer { + showFooter: XStandardType; +} + +@Injectable({ + providedIn: 'root' +}) +export class XComponentFooterContainer + extends XBaseComponentContainer + implements Readonly +{ + // + //#region Props ... + // + readonly showFooter: XStandardType = true; + + // + readonly footerActionProvider: Subject = + this.propertyProvider.actionProvider; + readonly footerCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly footerColor: XStandardType = + this.propertyProvider.color; + readonly footerContentTemplate: TemplateRef = + this.propertyProvider.contentTemplate; + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XFooterContainer) { + super(); + } + //#endregion +} + +@Injectable({ + providedIn: 'root' +}) +@Component({ + template: ` + + `, +}) +export abstract class XComponentFooterContainerComponent + extends XBaseComponentContainerComponent + implements IXComponentFooterContainer +{ + // + //#region Props ... + // + @Input() + showFooter: XStandardType = this.propertyProvider.showFooter; + + // + @Input() + footerActionProvider: Subject = + this.propertyProvider.footerActionProvider; + + @Input() + footerCssClass: XStandardType> = + this.propertyProvider.footerCssClass; + + // + @Input() + footerColor: XStandardType = + this.propertyProvider.footerColor; + + @Input() + footerContentTemplate: TemplateRef = + this.propertyProvider.footerContentTemplate; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XComponentFooterContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXComponentFooterContainer + ): Observable { + return forkJoin({ + // + showFooter: this.getValue( + container ? container.showFooter : this.showFooter + ), + footerCssClass: this.getValue( + container ? container.footerCssClass : this.footerCssClass + ), + footerColor: this.getValue( + container ? container.footerColor : this.footerColor + ), + footerContentTemplate: this.getValue( + container ? container.footerContentTemplate : this.footerContentTemplate + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + footerActionProvider: container + ? container.footerActionProvider + : this.footerActionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-navigator\x-navigator.component.html + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-navigator\x-navigator.component.scss + +.nav-item > * { + display: inline-block; + position: relative; + margin: 0; + padding: 0; +} + +.x-nav-item-title { + max-width: 150px; + margin: 4px; +} + +.nav-item-parent { + .x-nav-item-title { + font-size: 10pt; + font-weight: bold; + } +} + +.nav-item-child { + .x-nav-item-title { + font-size: 9pt; + font-weight: normal; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-navigator\x-navigator.component.ts + +import { + XNavigatorAction, + XNavigatorActionModel, + XNavigatorItemIconSlot, + XNavigatorContainerComponent, + XNavigatorListItem, +} from './x-navigator.typings'; +import { map } from 'rxjs/operators'; +import { Observable, Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { XStandardType, XOneOrManyType, nameof } from 'x-framework-core'; +import { XExpandableListItem } from '../../x-expandable-list/x-expandable-list.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-navigator', + templateUrl: './x-navigator.component.html', + styleUrls: ['./x-navigator.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XNavigatorComponent extends XNavigatorContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private EXPANDABLE_LIST_ITEMS: XOneOrManyType>; + + get preparedItems() { + return this.EXPANDABLE_LIST_ITEMS; + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.prepareItems(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Items Changed ... + const isItemsChanged = changeKeys.includes( + nameof('items') + ); + if (isItemsChanged) { + await this.prepareItems(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + isEndSlotted() { + return this.getValue(this.iconSlot).pipe( + map((iconSlot) => iconSlot === XNavigatorItemIconSlot.End) + ); + } + + isStartSlotted() { + return this.getValue(this.iconSlot).pipe( + map((iconSlot) => iconSlot === XNavigatorItemIconSlot.Start) + ); + } + //#endregion + + // + //#region UI Actions ... + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.actionProvider.asObservable() + ).subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XNavigatorAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async prepareItems() { + // + this.EXPANDABLE_LIST_ITEMS = null; + this.detectChanges(); + + // + const items = await this.getArrayValueAsync(this.items); + const preparedItems = await Promise.all( + items.map(async (i) => await this.toExpandableListItem(i)) + ); + + // + this.EXPANDABLE_LIST_ITEMS = [ + ...preparedItems + ]; + + // + this.detectChanges(); + } + + private async toExpandableListItem(item: XNavigatorListItem) { + // + const result: XExpandableListItem = { + // + ...item, + data: '', + childs: [ + ...(await Promise.all( + (item.childs ? await this.getArrayValueAsync(item.childs) : []).map( + async (c) => await this.toExpandableListItem(c) + ) + )), + ], + }; + + // + return result; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-navigator\x-navigator.tools.ts + +import { + XPage, + isArray, + toArray, + hasChild, + mergeRoutes, + XOneOrManyType, +} from 'x-framework-core'; +import { XNavigatorListItem } from './x-navigator.typings'; + +export function extractParent(source: XOneOrManyType): any { + // + if (!source) { + return null; + } + + // + const isArrayOfPages = isArray(source); + const page = source as XPage; + const pages = source as XPage[]; + + // + if (isArrayOfPages) { + // + // Handle Array of Pages ... + return pages.filter((p) => hasChild(p.childs)); + } else { + // + // Handle On Page ... + return extractParent(page.childs); + } +} + +export function extractChilds(source: XOneOrManyType) { + // + if (!source) { + return null; + } + + // + const isArrayOfPages = isArray(source); + const page = source as XPage; + const pages = source as XPage[]; + + // + if (isArrayOfPages) { + // + // Handle Array of Pages ... + return pages.filter((p) => !hasChild(p.childs)); + } else { + // + // Handle On Page ... + return extractParent(page.childs); + } +} + +export function toFlat(pages: XOneOrManyType) { + // + if (!pages || !hasChild(pages)) { + return []; + } + + // + let result: XPage[] = []; + + // + const pPages = toArray(pages); + pPages.forEach((p) => { + // + result.push(p); + + // + if (p.childs && hasChild(p.childs)) { + // + const childsFlat = toFlat(p.childs); + result = result.concat(childsFlat); + } + }); + + // + return result; +} + +export function toNavigatiorListItem(page: XPage) { + // + if (!page) { + return null; + } + + // + const pageRoute = mergeRoutes(page.route); + + // + const result: XNavigatorListItem = { + id: page.id ? `${page.id}_(${pageRoute})` : `${page.name}_(${pageRoute})`, + opened: false, + hidden: false, + disabled: false, + icon: page.icon, + href: page.route, + replaceUrl: true, + title: page.title, + description: page.description, + childs: hasChild(page.childs) + ? toArray(page.childs).map((c) => toNavigatiorListItem(c)) + : null, + }; + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-navigator\x-navigator.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../../x-icon/x-icon.typings'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; +import { XExpandableListItem } from '../../x-expandable-list/x-expandable-list.typings'; + +export type XNavigatorListItemBase = Pick< + XExpandableListItem, + | 'id' + | 'href' + | 'title' + | 'opened' + | 'hidden' + | 'disabled' + | 'replaceUrl' + | 'description' +>; +export interface XNavigatorListItem extends XNavigatorListItemBase { + icon?: XStandardType; + childs?: XStandardType>; +} + +export enum XNavigatorItemIconSlot { + Start = 'start', + End = 'end', +} + +export type XNavigatorItemIconSlotIdentifier = XNavigatorItemIconSlot | string; + +export enum XNavigatorStartupState { + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', +} + +export type XNavigatorStartupStateIdentifier = XNavigatorStartupState | string; + +export enum XNavigatorAction { + Refresh = 'refresh', +} + +export type XNavigatorActionIdentifier = XNavigatorAction | string; + +export interface XNavigatorActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXNavigatorContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + itemCssClass: XStandardType>; + items: XStandardType>; + + // + closeSiblings: XStandardType; + iconSlot: XStandardType; + startupState: XStandardType; + + // + showChildIcon: XStandardType; + showChildDetailIcon: XStandardType; + showParentIcon: XStandardType; + showParentDetailIcon: XStandardType; + disableParentRouting: XStandardType; + + // + childColor: XStandardType; + childActiveColor: XStandardType; + parentColor: XStandardType; + parentActiveColor: XStandardType; + highlightColor: XStandardType; + dividerColor: XStandardType; + + // + itemSelected: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XNavigatorContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly itemCssClass: XStandardType>; + readonly items: XStandardType>; + + // + readonly closeSiblings: XStandardType = true; + readonly iconSlot: XStandardType = + XNavigatorItemIconSlot.Start; + readonly startupState: XStandardType = + XNavigatorStartupState.ExpandAll; + + // + readonly showChildIcon: XStandardType = true; + readonly showChildDetailIcon: XStandardType = true; + readonly showParentIcon: XStandardType = true; + readonly showParentDetailIcon: XStandardType = true; + readonly disableParentRouting: XStandardType = true; + + // + readonly childColor: XStandardType = + XColorWithBrightness.PrimaryShade; + readonly childActiveColor: XStandardType = + XColorWithBrightness.Dark; + readonly parentColor: XStandardType = + XColorWithBrightness.TertiaryTint; + readonly parentActiveColor: XStandardType = + XColorWithBrightness.Dark; + readonly highlightColor: XStandardType = + XColorWithBrightness.Warning; + readonly dividerColor: XStandardType = + XColorWithBrightness.Dark; + + // + readonly itemSelected: EventEmitter = + new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XNavigatorContainerComponent + extends XBaseContainerComponent + implements IXNavigatorContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + itemCssClass: XStandardType> = + this.propertyProvider.itemCssClass; + + @Input() + items: XStandardType> = + this.propertyProvider.items; + + // + @Input() + closeSiblings: XStandardType = this.propertyProvider.closeSiblings; + + @Input() + iconSlot: XStandardType = + this.propertyProvider.iconSlot; + + @Input() + startupState: XStandardType = + this.propertyProvider.startupState; + + // + @Input() + showChildIcon: XStandardType = this.propertyProvider.showChildIcon; + + @Input() + showChildDetailIcon: XStandardType = + this.propertyProvider.showChildDetailIcon; + + @Input() + showParentIcon: XStandardType = this.propertyProvider.showParentIcon; + + @Input() + showParentDetailIcon: XStandardType = + this.propertyProvider.showParentDetailIcon; + + @Input() + disableParentRouting: XStandardType = + this.propertyProvider.disableParentRouting; + + // + @Input() + childColor: XStandardType = + this.propertyProvider.childColor; + + @Input() + childActiveColor: XStandardType = + this.propertyProvider.childActiveColor; + + @Input() + parentColor: XStandardType = + this.propertyProvider.parentColor; + + @Input() + parentActiveColor: XStandardType = + this.propertyProvider.parentActiveColor; + + @Input() + highlightColor: XStandardType = + this.propertyProvider.highlightColor; + + @Input() + dividerColor: XStandardType = + this.propertyProvider.dividerColor; + + // + @Output() + itemSelected: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XNavigatorContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXNavigatorContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + itemCssClass: this.getValue( + container ? container.itemCssClass : this.itemCssClass + ), + items: this.getValue(container ? container.items : this.items), + closeSiblings: this.getValue( + container ? container.closeSiblings : this.closeSiblings + ), + iconSlot: this.getValue(container ? container.iconSlot : this.iconSlot), + startupState: this.getValue( + container ? container.startupState : this.startupState + ), + showChildIcon: this.getValue( + container ? container.showChildIcon : this.showChildIcon + ), + showChildDetailIcon: this.getValue( + container ? container.showChildDetailIcon : this.showChildDetailIcon + ), + showParentIcon: this.getValue( + container ? container.showParentIcon : this.showParentIcon + ), + showParentDetailIcon: this.getValue( + container ? container.showParentDetailIcon : this.showParentDetailIcon + ), + disableParentRouting: this.getValue( + container ? container.disableParentRouting : this.disableParentRouting + ), + childColor: this.getValue( + container ? container.childColor : this.childColor + ), + childActiveColor: this.getValue( + container ? container.childActiveColor : this.childActiveColor + ), + parentColor: this.getValue( + container ? container.parentColor : this.parentColor + ), + parentActiveColor: this.getValue( + container ? container.parentActiveColor : this.parentActiveColor + ), + highlightColor: this.getValue( + container ? container.highlightColor : this.highlightColor + ), + dividerColor: this.getValue( + container ? container.dividerColor : this.dividerColor + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + itemSelected: this.itemSelected, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-toolbar\x-toolbar.component.html + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + +
+ + + + + + + + + + + +

+ {{ getValue(title) | async }} +

+ + +
+ {{ getValue(subTitle) | async }} +
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-toolbar\x-toolbar.component.scss + +.x-toolbar-slot, +.x-toolbar-start-slot, +.x-toolbar-middle-slot, +.x-toolbar-end-slot { + visibility: inherit; +} + +.x-toolbar-start-slot, +.x-toolbar-middle-slot, +.x-toolbar-end-slot { + margin: { + left: 4px; + right: 4px; + } +} + +.x-toolbar-slot-container { + display: flex; +} + +.x-toolbar-slot { + display: flex; + flex: 1; +} + +.x-toolbar-slot > *, +.x-toolbar-slot-container > * { + margin: { + left: 2px; + right: 2px; + } + + width: max-content; +} + +ion-thumbnail { + width: 48px !important; + height: 48px !important; +} + +ion-img { + object-fit: contain !important; +} + +:host { + --menu-button-transform: rotate(90deg); + --menu-button-transform-duration: 500ms; +} + +:host.menu-opened { + ion-menu-button { + // + transform: var(--menu-button-transform); + -ms-transform: var(--menu-button-transform); + -webkit-transform: var(--menu-button-transform); + + // + transition: transform; + -webkit-transition: transform; + + // + transition-duration: var(--menu-button-transform-duration); + -webkit-transition-duration: var(--menu-button-transform-duration); + } +} + +ion-menu-button { + // + transition-duration: var(--menu-button-transform-duration); + -webkit-transition-duration: var(--menu-button-transform-duration); +} + +.x-toolbar-title-wrapper { + padding-inline-end: 16px; + padding-inline-start: 0; + + display: block; + margin: { + top: auto; + bottom: auto; + } +} + +.x-toolbar-title, +.x-toolbar-sub-title { + display: block !important; +} + +.x-toolbar-back { +} + +.mobile { + max-width: 150px; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-toolbar\x-toolbar.component.ts + +import { + XParam, + nameof, + isNullOrEmptyString, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XToolbarAction, + XToolbarActionModel, + XToolbarContainerComponent, +} from './x-toolbar.typings'; +import { map } from 'rxjs/operators'; +import { Subject, Subscription } from 'rxjs'; +import { XIconNames } from '../../x-icon/x-icon.typings'; +import { XMenuSlot, XBackSlot } from './../x-page.typings'; +import { XButtonType } from '../../x-button/x-button.typings'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { XLogoSlot, XTitleSlot, XContentSlot } from '../x-page.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-toolbar', + templateUrl: './x-toolbar.component.html', + styleUrls: ['./x-toolbar.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XToolbarComponent extends XToolbarContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly IconNames = Object.assign({}, XIconNames); + readonly ButtonTypes = Object.assign({}, XButtonType); + readonly ColorNames = Object.assign({}, XColorWithBrightness); + //#endregion + + // + //#region LifeCycle ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + + //#endregion + + // + //#region Reading Query Params ... + //#endregion + + // + //#region Providers ... + getBackHref() { + // + const isContainReturnUrl = this.managerService.isContainQueryParam( + XParam.ReturnUrl + ); + + // + return !isNullOrEmptyString(this.defaultHref) + ? this.defaultHref + : !isContainReturnUrl + ? null + : this.managerService.getQueryParamValOfType(XParam.ReturnUrl); + } + + canBack() { + // + return this.getValue(this.hasBack).pipe( + map((hasBack) => { + // + if ( + !hasBack && + this.managerService.isContainQueryParam(XParam.HasBack) + ) { + hasBack = + hasBack || + this.managerService.getQueryParamValOfType(XParam.HasBack); + } + + // + return hasBack; + }) + ); + } + + // + //#region Slot Providers ... + isEndSlotBack() { + return this.getValue(this.backSlot).pipe( + map((slot) => slot === XBackSlot.End) + ); + } + + isStartSlotBack() { + return this.getValue(this.backSlot).pipe( + map((slot) => slot === XBackSlot.Start) + ); + } + + isEndSlotMenu() { + return this.getValue(this.menuSlot).pipe( + map((slot) => slot === XMenuSlot.End) + ); + } + + isStartSlotMenu() { + return this.getValue(this.menuSlot).pipe( + map((slot) => slot === XMenuSlot.Start) + ); + } + + isEndSlotLogo() { + return this.getValue(this.logoSlot).pipe( + map((slot) => slot === XLogoSlot.End) + ); + } + + isStartSlotLogo() { + return this.getValue(this.logoSlot).pipe( + map((slot) => slot === XLogoSlot.Start) + ); + } + + isEndSlotTitle() { + return this.getValue(this.titleSlot).pipe( + map((slot) => slot === XTitleSlot.End) + ); + } + + isStartSlotTitle() { + return this.getValue(this.titleSlot).pipe( + map((slot) => slot === XTitleSlot.Start) + ); + } + + isEndSlotContent() { + return this.getValue(this.contentSlot).pipe( + map((slot) => slot === XContentSlot.End) + ); + } + + isMiddleSlotContent() { + return this.getValue(this.contentSlot).pipe( + map((slot) => slot === XContentSlot.Middle) + ); + } + + isStartSlotContent() { + return this.getValue(this.contentSlot).pipe( + map((slot) => slot === XContentSlot.Start) + ); + } + //#endregion + //#endregion + + // + //#region Handlers ... + handleMenuButtonClicked() { + this.actionProvider.next({ action: XToolbarAction.ToggleMenu }); + } + + async handleBackButtonClicked(event: any) { + // + if (this.backHandler) { + // + if (event && event.preventDefault) { + event.preventDefault(); + } + + // + await this.getValueAsync(this.backHandler()); + return; + } + + // + const hasReturnUrl = this.managerService.isContainQueryParam( + XParam.ReturnUrl + ); + const hasDefaultHref = !isNullOrEmptyString(this.defaultHref); + const returnUrl = hasReturnUrl + ? this.managerService.getQueryParamValOfType(XParam.ReturnUrl) + : hasDefaultHref + ? this.defaultHref + : ''; + + // + if (!isNullOrEmptyString(returnUrl)) { + // + await this.managerService.navigateByUrl(returnUrl); + return; + } + + // + this.managerService.historyService.back(); + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.actionProvider.asObservable() + ).subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XToolbarAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-toolbar\x-toolbar.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XBackSlot, + XLogoSlot, + XMenuSlot, + XTitleSlot, + XContentSlot, + XBackSlotIdentifier, + XLogoSlotIdentifier, + XMenuSlotIdentifier, + XTitleSlotIdentifier, + XContentSlotIdentifier, +} from '../x-page.typings'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, + XBaseComponentContainer, + IXBaseComponentContainer, + XBaseComponentContainerComponent, +} from '../../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +export enum XToolbarAction { + Refresh = 'refresh', + ToggleMenu = 'ToggleMenu', +} + +export type XToolbarActionIdentifier = XToolbarAction | string; + +export interface XToolbarActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXToolbarContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + + // + showProgressBar: XStandardType; + progressBarColor: XStandardType; + + // + showTitle: XStandardType; + titleSlot: XStandardType; + title: XStandardType; + + // + showSubTitle: XStandardType; + subTitle: XStandardType; + + // + showLogo: XStandardType; + logoSlot: XStandardType; + logoUrl: XStandardType; + + // + hasBack: XStandardType; + showBack: XStandardType; + backSlot: XStandardType; + backHandler?: () => XStandardType; + defaultHref: string; + + // + hasMenu: XStandardType; + showMenu: XStandardType; + autoHideMenu: XStandardType; + menuSlot: XStandardType; + + // + startSlotButtonsTemplate: TemplateRef; + endSlotButtonsTemplate: TemplateRef; + + // + contentSlot: XStandardType; + contentTemplate: TemplateRef; +} + +@Injectable({ + providedIn: 'root', +}) +export class XToolbarContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = + XColorWithBrightness.Primary; + + // + readonly showProgressBar: XStandardType = false; + readonly progressBarColor: XStandardType = + XColorWithBrightness.Warning; + + // + readonly showTitle: XStandardType = true; + readonly titleSlot: XStandardType = XTitleSlot.End; + readonly title: XStandardType; + + // + readonly showSubTitle: XStandardType = false; + readonly subTitle: XStandardType; + + // + readonly showLogo: XStandardType = false; + readonly logoSlot: XStandardType = XLogoSlot.Start; + readonly logoUrl: XStandardType; + + // + readonly hasBack: XStandardType = true; + readonly showBack: XStandardType = false; + readonly backSlot: XStandardType = XBackSlot.Start; + readonly backHandler?: () => XStandardType; + readonly defaultHref: string; + + // + readonly hasMenu: XStandardType = true; + readonly showMenu: XStandardType = false; + readonly autoHideMenu: XStandardType = true; + readonly menuSlot: XStandardType = XMenuSlot.Start; + + // + readonly startSlotButtonsTemplate: TemplateRef; + readonly endSlotButtonsTemplate: TemplateRef; + + // + readonly contentSlot: XStandardType = + XContentSlot.End; + readonly contentTemplate: TemplateRef; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XToolbarContainerComponent + extends XBaseContainerComponent + implements IXToolbarContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + // + @Input() + showProgressBar: XStandardType = + this.propertyProvider.showProgressBar; + + @Input() + progressBarColor: XStandardType = + this.propertyProvider.progressBarColor; + + // + @Input() + showTitle: XStandardType = this.propertyProvider.showTitle; + + @Input() + titleSlot: XStandardType = + this.propertyProvider.titleSlot; + + @Input() + title: XStandardType = this.propertyProvider.title; + + // + @Input() + showSubTitle: XStandardType = this.propertyProvider.showSubTitle; + + @Input() + subTitle: XStandardType = this.propertyProvider.subTitle; + + // + @Input() + showLogo: XStandardType = this.propertyProvider.showLogo; + + @Input() + logoSlot: XStandardType = this.propertyProvider.logoSlot; + + @Input() + logoUrl: XStandardType = this.propertyProvider.logoUrl; + + // + @Input() + hasBack: XStandardType = this.propertyProvider.hasBack; + + @Input() + showBack: XStandardType = this.propertyProvider.showBack; + + @Input() + backSlot: XStandardType = this.propertyProvider.backSlot; + + @Input() + backHandler?: () => XStandardType = this.propertyProvider.backHandler; + + @Input() + defaultHref: string = this.propertyProvider.defaultHref; + + // + @Input() + hasMenu: XStandardType = this.propertyProvider.hasMenu; + + @Input() + showMenu: XStandardType = this.propertyProvider.showMenu; + + @Input() + autoHideMenu: XStandardType = this.propertyProvider.autoHideMenu; + + @Input() + menuSlot: XStandardType = this.propertyProvider.menuSlot; + + // + @Input() + startSlotButtonsTemplate: TemplateRef = + this.propertyProvider.startSlotButtonsTemplate; + + @Input() + endSlotButtonsTemplate: TemplateRef = + this.propertyProvider.endSlotButtonsTemplate; + + // + @Input() + contentSlot: XStandardType = + this.propertyProvider.contentSlot; + + @Input() + contentTemplate: TemplateRef = this.propertyProvider.contentTemplate; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XToolbarContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXToolbarContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + color: this.getValue(container ? container.color : this.color), + showProgressBar: this.getValue( + container ? container.showProgressBar : this.showProgressBar + ), + progressBarColor: this.getValue( + container ? container.progressBarColor : this.progressBarColor + ), + showTitle: this.getValue( + container ? container.showTitle : this.showTitle + ), + titleSlot: this.getValue( + container ? container.titleSlot : this.titleSlot + ), + title: this.getValue(container ? container.title : this.title), + showSubTitle: this.getValue( + container ? container.showSubTitle : this.showSubTitle + ), + subTitle: this.getValue(container ? container.subTitle : this.subTitle), + showLogo: this.getValue(container ? container.showLogo : this.showLogo), + logoSlot: this.getValue(container ? container.logoSlot : this.logoSlot), + logoUrl: this.getValue(container ? container.logoUrl : this.logoUrl), + hasBack: this.getValue(container ? container.hasBack : this.hasBack), + showBack: this.getValue(container ? container.showBack : this.showBack), + backSlot: this.getValue(container ? container.backSlot : this.backSlot), + backHandler: this.getValue( + container ? container.backHandler : this.backHandler + ), + defaultHref: this.getValue( + container ? container.defaultHref : this.defaultHref + ), + hasMenu: this.getValue(container ? container.hasMenu : this.hasMenu), + showMenu: this.getValue(container ? container.showMenu : this.showMenu), + autoHideMenu: this.getValue( + container ? container.autoHideMenu : this.autoHideMenu + ), + menuSlot: this.getValue(container ? container.menuSlot : this.menuSlot), + startSlotButtonsTemplate: this.getValue( + container + ? container.startSlotButtonsTemplate + : this.startSlotButtonsTemplate + ), + endSlotButtonsTemplate: this.getValue( + container + ? container.endSlotButtonsTemplate + : this.endSlotButtonsTemplate + ), + contentSlot: this.getValue( + container ? container.contentSlot : this.contentSlot + ), + contentTemplate: this.getValue( + container ? container.contentTemplate : this.contentTemplate + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentToolbarContainer = { + [TKey in keyof IXToolbarContainer as `toolbar${Capitalize< + string & TKey + >}`]: IXToolbarContainer[TKey]; +}; + +export interface IXComponentToolbarContainer + extends TXComponentToolbarContainer, + IXBaseComponentContainer { + showToolbar: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XComponentToolbarContainer + extends XBaseComponentContainer + implements Readonly +{ + // + //#region Props ... + // + readonly showToolbar: XStandardType = true; + + // + readonly toolbarActionProvider: Subject = + this.propertyProvider.actionProvider; + readonly toolbarCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly toolbarColor: XStandardType = + this.propertyProvider.color; + + // + readonly toolbarShowProgressBar: XStandardType = + this.propertyProvider.showProgressBar; + readonly toolbarProgressBarColor: XStandardType = + this.propertyProvider.progressBarColor; + + // + readonly toolbarShowTitle: XStandardType = + this.propertyProvider.showTitle; + readonly toolbarTitleSlot: XStandardType = + this.propertyProvider.titleSlot; + readonly toolbarTitle: XStandardType = this.propertyProvider.title; + + // + readonly toolbarShowSubTitle: XStandardType = + this.propertyProvider.showSubTitle; + readonly toolbarSubTitle: XStandardType = + this.propertyProvider.subTitle; + + // + readonly toolbarShowLogo: XStandardType = + this.propertyProvider.showLogo; + readonly toolbarLogoSlot: XStandardType = + this.propertyProvider.logoSlot; + readonly toolbarLogoUrl: XStandardType = + this.propertyProvider.logoUrl; + + // + readonly toolbarHasBack: XStandardType = + this.propertyProvider.hasBack; + readonly toolbarShowBack: XStandardType = + this.propertyProvider.showBack; + readonly toolbarBackSlot: XStandardType = + this.propertyProvider.backSlot; + readonly toolbarBackHandler?: () => XStandardType = + this.propertyProvider.backHandler; + readonly toolbarDefaultHref: string = this.propertyProvider.defaultHref; + + // + readonly toolbarHasMenu: XStandardType = + this.propertyProvider.hasMenu; + readonly toolbarShowMenu: XStandardType = + this.propertyProvider.showMenu; + readonly toolbarAutoHideMenu: XStandardType = + this.propertyProvider.autoHideMenu; + readonly toolbarMenuSlot: XStandardType = + this.propertyProvider.menuSlot; + + // + readonly toolbarStartSlotButtonsTemplate: TemplateRef = + this.propertyProvider.startSlotButtonsTemplate; + readonly toolbarEndSlotButtonsTemplate: TemplateRef = + this.propertyProvider.endSlotButtonsTemplate; + + // + readonly toolbarContentSlot: XStandardType = + this.propertyProvider.contentSlot; + readonly toolbarContentTemplate: TemplateRef = + this.propertyProvider.contentTemplate; + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XToolbarContainer) { + super(); + } + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +@Component({ + template: ` + + `, +}) +export abstract class XComponentToolbarContainerComponent + extends XBaseComponentContainerComponent + implements IXComponentToolbarContainer +{ + // + //#region Props ... + // + @Input() + showToolbar: XStandardType = this.propertyProvider.showToolbar; + + // + @Input() + toolbarActionProvider: Subject = + this.propertyProvider.toolbarActionProvider; + + @Input() + toolbarCssClass: XStandardType> = + this.propertyProvider.toolbarCssClass; + + // + @Input() + toolbarColor: XStandardType = + this.propertyProvider.toolbarColor; + + // + @Input() + toolbarShowProgressBar: XStandardType = + this.propertyProvider.toolbarShowProgressBar; + + @Input() + toolbarProgressBarColor: XStandardType = + this.propertyProvider.toolbarProgressBarColor; + + // + @Input() + toolbarShowTitle: XStandardType = + this.propertyProvider.toolbarShowTitle; + + @Input() + toolbarTitleSlot: XStandardType = + this.propertyProvider.toolbarTitleSlot; + + @Input() + toolbarTitle: XStandardType = this.propertyProvider.toolbarTitle; + + // + @Input() + toolbarShowSubTitle: XStandardType = + this.propertyProvider.toolbarShowSubTitle; + + @Input() + toolbarSubTitle: XStandardType = + this.propertyProvider.toolbarSubTitle; + + // + @Input() + toolbarShowLogo: XStandardType = + this.propertyProvider.toolbarShowLogo; + + @Input() + toolbarLogoSlot: XStandardType = + this.propertyProvider.toolbarLogoSlot; + + @Input() + toolbarLogoUrl: XStandardType = this.propertyProvider.toolbarLogoUrl; + + // + @Input() + toolbarHasBack: XStandardType = this.propertyProvider.toolbarHasBack; + + @Input() + toolbarShowBack: XStandardType = + this.propertyProvider.toolbarShowBack; + + @Input() + toolbarBackSlot: XStandardType = + this.propertyProvider.toolbarBackSlot; + + @Input() + toolbarBackHandler?: () => XStandardType = + this.propertyProvider.toolbarBackHandler; + + @Input() + toolbarDefaultHref: string = this.propertyProvider.toolbarDefaultHref; + + // + @Input() + toolbarHasMenu: XStandardType = this.propertyProvider.toolbarHasMenu; + + @Input() + toolbarShowMenu: XStandardType = + this.propertyProvider.toolbarShowMenu; + + @Input() + toolbarAutoHideMenu: XStandardType = + this.propertyProvider.toolbarAutoHideMenu; + + @Input() + toolbarMenuSlot: XStandardType = + this.propertyProvider.toolbarMenuSlot; + + // + @Input() + toolbarStartSlotButtonsTemplate: TemplateRef = + this.propertyProvider.toolbarStartSlotButtonsTemplate; + + @Input() + toolbarEndSlotButtonsTemplate: TemplateRef = + this.propertyProvider.toolbarEndSlotButtonsTemplate; + + // + @Input() + toolbarContentSlot: XStandardType = + this.propertyProvider.toolbarContentSlot; + + @Input() + toolbarContentTemplate: TemplateRef = + this.propertyProvider.toolbarContentTemplate; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XComponentToolbarContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXComponentToolbarContainer + ): Observable { + return forkJoin({ + // + showToolbar: this.getValue( + container ? container.showToolbar : this.showToolbar + ), + // + toolbarCssClass: this.getValue( + container ? container.toolbarCssClass : this.toolbarCssClass + ), + toolbarColor: this.getValue( + container ? container.toolbarColor : this.toolbarColor + ), + toolbarShowProgressBar: this.getValue( + container + ? container.toolbarShowProgressBar + : this.toolbarShowProgressBar + ), + toolbarProgressBarColor: this.getValue( + container + ? container.toolbarProgressBarColor + : this.toolbarProgressBarColor + ), + toolbarShowTitle: this.getValue( + container ? container.toolbarShowTitle : this.toolbarShowTitle + ), + toolbarTitleSlot: this.getValue( + container ? container.toolbarTitleSlot : this.toolbarTitleSlot + ), + toolbarTitle: this.getValue( + container ? container.toolbarTitle : this.toolbarTitle + ), + toolbarShowSubTitle: this.getValue( + container ? container.toolbarShowSubTitle : this.toolbarShowSubTitle + ), + toolbarSubTitle: this.getValue( + container ? container.toolbarSubTitle : this.toolbarSubTitle + ), + toolbarShowLogo: this.getValue( + container ? container.toolbarShowLogo : this.toolbarShowLogo + ), + toolbarLogoSlot: this.getValue( + container ? container.toolbarLogoSlot : this.toolbarLogoSlot + ), + toolbarLogoUrl: this.getValue( + container ? container.toolbarLogoUrl : this.toolbarLogoUrl + ), + toolbarHasBack: this.getValue( + container ? container.toolbarHasBack : this.toolbarHasBack + ), + toolbarShowBack: this.getValue( + container ? container.toolbarShowBack : this.toolbarShowBack + ), + toolbarBackSlot: this.getValue( + container ? container.toolbarBackSlot : this.toolbarBackSlot + ), + toolbarBackHandler: this.getValue( + container ? container.toolbarBackHandler : this.toolbarBackHandler + ), + toolbarDefaultHref: this.getValue( + container ? container.toolbarDefaultHref : this.toolbarDefaultHref + ), + toolbarHasMenu: this.getValue( + container ? container.toolbarHasMenu : this.toolbarHasMenu + ), + toolbarShowMenu: this.getValue( + container ? container.toolbarShowMenu : this.toolbarShowMenu + ), + toolbarAutoHideMenu: this.getValue( + container ? container.toolbarAutoHideMenu : this.toolbarAutoHideMenu + ), + toolbarMenuSlot: this.getValue( + container ? container.toolbarMenuSlot : this.toolbarMenuSlot + ), + toolbarStartSlotButtonsTemplate: this.getValue( + container + ? container.toolbarStartSlotButtonsTemplate + : this.toolbarStartSlotButtonsTemplate + ), + toolbarEndSlotButtonsTemplate: this.getValue( + container + ? container.toolbarEndSlotButtonsTemplate + : this.toolbarEndSlotButtonsTemplate + ), + toolbarContentSlot: this.getValue( + container ? container.toolbarContentSlot : this.toolbarContentSlot + ), + toolbarContentTemplate: this.getValue( + container + ? container.toolbarContentTemplate + : this.toolbarContentTemplate + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + toolbarActionProvider: container + ? container.toolbarActionProvider + : this.toolbarActionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-progress-bar\x-progress-bar.component.html + +
+ +
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-progress-bar\x-progress-bar.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-progress-bar\x-progress-bar.component.ts + +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { XProgressBarAction, XProgressBarActionModel, XProgressBarContainerComponent, XProgressBarType } from './x-progress-bar.typings'; +import { Subject, Subscription } from 'rxjs'; +import { nameof } from 'x-framework-core'; + +@Component({ + selector: 'x-progress-bar', + templateUrl: './x-progress-bar.component.html', + styleUrls: ['./x-progress-bar.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XProgressBarComponent extends XProgressBarContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly PresentTypes = Object.assign({}, XProgressBarType); + //#endregion + + // + //#region LifeCycle ... + onChange (changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy () { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Handlers ... + registerViewHandlers () { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Reading Query Params ... + //#endregion + + // + //#region Providers ... + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider () { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.actionProvider.asObservable() + ).subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XProgressBarAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider () { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-progress-bar\x-progress-bar.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XProgressBarComponent } from './x-progress-bar.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XProgressBarComponent], + imports: [CommonModule, XBaseComponentModule], + exports: [XBaseComponentModule, XProgressBarComponent], +}) +export class XProgressBarModule { } + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-progress-bar\x-progress-bar.typings.ts + +import { + Subject, + forkJoin, + Observable, +} from 'rxjs'; +import { + Input, + NgZone, + Inject, + Renderer2, + Component, + Injectable, + ElementRef, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseComponentContainer, + XBaseContainerComponent, + IXBaseComponentContainer, +} from '../classes/i-x-base.container'; +import { XManagerService } from 'x-framework-services'; +import { ViewportRuler } from '@angular/cdk/scrolling'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region Prop Definitions ... +export enum XProgressBarType { + Buffer = 'buffer', + Determinate = 'determinate', + Indeterminate = 'indeterminate', +} + +export type XProgressBarTypeIdentifier = XProgressBarType | string; +//#endregion + +// +//#region Action Provider +export enum XProgressBarAction { + Refresh = 'refresh', +} + +export type XProgressBarActionIdentifier = XProgressBarAction | string; + + +export interface XProgressBarActionModel + extends XBaseActionModel { } +//#endregion + +// +//#region Container ... +export interface IXProgressBarContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + + // + type: XStandardType; + + // + value: XStandardType; + buffer: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XProgressBarContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider = new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Warning; + + // + readonly type: XStandardType = XProgressBarType.Indeterminate; + + // + readonly value: XStandardType; + readonly buffer: XStandardType; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XProgressBarContainerComponent + extends XBaseContainerComponent + implements IXProgressBarContainer { + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = + this.propertyProvider.color; + + // + @Input() + type: XStandardType = + this.propertyProvider.type; + + // + @Input() + value: XStandardType = + this.propertyProvider.value; + + @Input() + buffer: XStandardType = + this.propertyProvider.buffer; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XProgressBarContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps ( + container?: IXProgressBarContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + + // + color: this.getValue(container ? container.color : this.color), + + // + type: this.getValue(container ? container.type : this.type), + + // + value: this.getValue(container ? container.value : this.value), + buffer: this.getValue(container ? container.buffer : this.buffer), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentProgressBarContainer = { + [TKey in keyof IXProgressBarContainer as `progressBar${Capitalize< + string & TKey + >}`]: IXProgressBarContainer[TKey]; +}; + +export interface IXComponentProgressBarContainer + extends TXComponentProgressBarContainer, + IXBaseComponentContainer { + showProgressBar: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XComponentProgressBarContainer + extends XBaseComponentContainer + implements Readonly { + // + //#region Props ... + // + readonly showProgressBar: XStandardType = false; + + // + readonly progressBarActionProvider = + this.propertyProvider.actionProvider; + readonly progressBarCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly progressBarColor: XStandardType = + this.propertyProvider.color; + + // + readonly progressBarType: XStandardType = + this.propertyProvider.type; + + // + readonly progressBarValue: XStandardType = + this.propertyProvider.value; + readonly progressBarBuffer: XStandardType = + this.propertyProvider.buffer; + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XProgressBarContainer) { + super(); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-screen-capture\x-screen-capture.component.html + +
+ +
+ +
+ + +
+
+
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-screen-capture\x-screen-capture.component.scss + +.x-overlay { + top: 0px; + left: 0px; + width: 100vw; + height: 100vh; + position: absolute; + + &.enabled { + background-color: rgba(204, 204, 204, 0.6); + } +} + +.x-rectangle { + position: absolute; + + &.drawing { + border: 1px solid #ff0000; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-screen-capture\x-screen-capture.component.ts + +import { + ViewChild, + Component, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { nameof } from 'x-framework-core'; +import { XScreenCaptureDimensions } from './x-screen-capture.typings'; +import { XScreenCaptureContainerComponent } from './x-screen-capture.container'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-screen-capture', + templateUrl: './x-screen-capture.component.html', + styleUrls: ['./x-screen-capture.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XScreenCaptureComponent extends XScreenCaptureContainerComponent { + // + //#region Props ... + // + rect: HTMLElement; + captureZone: HTMLElement; + + // + isDrawing = false; + + // + clippingDimensions: XScreenCaptureDimensions = { + x: 0, + y: 0, + width: 0, + height: 0, + }; + + // + mouse = { + x: 0, + y: 0, + startX: 0, + startY: 0, + }; + + // + //#region View Childs ... + // + private RECT: ElementRef; + + // + @ViewChild('rect', { static: false }) + set rectangle(v: ElementRef) { + // + this.RECT = v; + this.rect = v.nativeElement; + } + + // + get rectangle() { + return this.RECT; + } + + // + private OVERLAY: ElementRef; + + // + @ViewChild('over', { static: false }) + set overlay(v: ElementRef) { + // + this.OVERLAY = v; + this.captureZone = v.nativeElement; + } + + // + get overlay() { + return this.OVERLAY; + } + + // + private CONTENT: ElementRef; + + // + @ViewChild('content', { static: false }) + set content(v: ElementRef) { + this.CONTENT = v; + } + + // + get content() { + return this.CONTENT; + } + //#endregion + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + const isEnableChanged = changeKeys.includes( + nameof('enable') + ); + if (isEnableChanged) { + // + const enable = await this.getValueAsync(this.enable); + this.reAssignElements(enable); + this.detectChanges(); + } + } + + async afterViewInit() { + super.afterViewInit(); + + // + this.reAssignElements(); + } + //#endregion + + // + //#region UI Handlers ... + async handleMouseMoved(event: MouseEvent) { + this.drawRect(event); + } + + async handleMouseStarted(event: MouseEvent) { + this.startCapture(event); + } + + async handleMouseFinished(event: MouseEvent) { + this.endCapture(event); + } + //#endregion + + // + //#region Private ... + private reAssignElements(enabled?: boolean) { + // + if (!this.OVERLAY || !this.RECT || !this.CONTENT) { + return; + } + + // + this.rect = this.RECT.nativeElement; + this.captureZone = this.OVERLAY.nativeElement; + + // + if (enabled === true) { + this.captureZone.style.cursor = 'crosshair'; + } else { + this.captureZone.style.cursor = 'default'; + } + } + + private setRectangle() { + // + this.rect.style.left = this.clippingDimensions.x + 'px'; + this.rect.style.top = this.clippingDimensions.y + 'px'; + this.rect.style.width = this.clippingDimensions.width + 'px'; + this.rect.style.height = this.clippingDimensions.height + 'px'; + } + + private startCapture(e: any) { + // + if (!this.captureZone) { + // + this.reAssignElements(); + } + + // + this.setMousePosition(e); + + // + this.isDrawing = true; + this.detectChanges(); + + // + this.mouse.startX = this.mouse.x; + this.mouse.startY = this.mouse.y; + this.clippingDimensions = { + x: this.mouse.x, + y: this.mouse.y, + width: 0, + height: 0, + }; + + // + this.setRectangle(); + this.captureZone.style.cursor = 'crosshair'; + this.detectChanges(); + } + + private drawRect(e: any) { + // + if (!this.captureZone) { + return; + } + + // + if (this.isDrawing) { + // + this.setMousePosition(e); + + // + this.clippingDimensions = { + x: + this.mouse.x - this.mouse.startX < 0 + ? this.mouse.x + : this.mouse.startX, + y: + this.mouse.y - this.mouse.startY < 0 + ? this.mouse.y + : this.mouse.startY, + width: Math.abs(this.mouse.x - this.mouse.startX), + height: Math.abs(this.mouse.y - this.mouse.startY), + }; + + // + this.setRectangle(); + } + } + + private setMousePosition(e: any) { + // + if (!this.captureZone) { + return; + } + + // + // tslint:disable-next-line: deprecation + const ev = e || window.event; // Moz || IE + if (ev.pageX) { + // Moz + // + this.mouse.x = ev.pageX + window.pageXOffset; + this.mouse.y = ev.pageY + window.pageYOffset; + } else if (ev.clientX) { + // IE + // + this.mouse.x = ev.clientX + document.body.scrollLeft; + this.mouse.y = ev.clientY + document.body.scrollTop; + } + } + + private async endCapture(ev: MouseEvent) { + // + if (!ev || !this.captureZone) { + return; + } + + // + this.captureZone.style.cursor = 'default'; + this.isDrawing = false; + this.detectChanges(); + + // + const captureDocument = document.body; + + // + const topOffset = await this.getValueAsync(this.topOffset); + if (topOffset !== 0) { + this.clippingDimensions = { + ...this.clippingDimensions, + y: this.clippingDimensions.y + topOffset, + }; + } + + // + const bottomOffset = await this.getValueAsync(this.bottomOffset); + if (bottomOffset !== 0) { + this.clippingDimensions = { + ...this.clippingDimensions, + y: this.clippingDimensions.y + bottomOffset, + }; + } + + // + const canDownload = await this.getOptionalValueAsync( + this.downloadCapturedImage + ); + if (canDownload === true) { + // + const blob = await this.screenCaptureService.captureAndDownload( + captureDocument, + null, + this.clippingDimensions + ); + + // + this.imageCaptured.emit(blob); + } else { + // + const blob = await this.screenCaptureService.capture( + captureDocument, + null, + this.clippingDimensions + ); + + // + this.imageCaptured.emit(blob); + } + + // + this.clippingDimensions = { + x: 0, + y: 0, + width: 0, + height: 0, + }; + + // + this.setRectangle(); + + // + this.reAssignElements(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-screen-capture\x-screen-capture.container.ts + +import { + Input, + Inject, + NgZone, + Output, + Renderer2, + Component, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { forkJoin, Observable } from 'rxjs'; +import { XStandardType } from 'x-framework-core'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XScreenCaptureService } from './x-screen-capture.service'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region Container ... +export interface IXScreenCaptureContainer extends IXBaseContainer { + enable: XStandardType; + topOffset: XStandardType; + bottomOffset: XStandardType; + downloadCapturedImage: XStandardType; + imageCaptured: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XScreenCaptureContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly enable: XStandardType = false; + readonly topOffset: XStandardType = 0; + readonly bottomOffset: XStandardType = 0; + readonly downloadCapturedImage: XStandardType = true; + readonly imageCaptured: EventEmitter = new EventEmitter< + string | Blob + >(); + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +@Component({ + template: ` + + `, +}) +export abstract class XScreenCaptureContainerComponent + extends XBaseContainerComponent + implements IXScreenCaptureContainer +{ + // + //#region Props ... + @Input() + enable: XStandardType = this.propertyProvider.enable; + + @Input() + topOffset: XStandardType = this.propertyProvider.topOffset; + + @Input() + bottomOffset: XStandardType = this.propertyProvider.bottomOffset; + + @Input() + downloadCapturedImage: XStandardType = + this.propertyProvider.downloadCapturedImage; + + @Output() + imageCaptured: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + public screenCaptureService: XScreenCaptureService, + protected propertyProvider: XScreenCaptureContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXScreenCaptureContainer + ): Observable { + return forkJoin({ + // + enable: this.getValue(container ? container.enable : this.enable), + topOffset: this.getValue( + container ? container.topOffset : this.topOffset + ), + bottomOffset: this.getValue( + container ? container.bottomOffset : this.bottomOffset + ), + downloadCapturedImage: this.getValue( + container ? container.downloadCapturedImage : this.downloadCapturedImage + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + imageCaptured: this.imageCaptured, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-screen-capture\x-screen-capture.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XScreenCaptureComponent } from './x-screen-capture.component'; + +@NgModule({ + declarations: [XScreenCaptureComponent], + imports: [CommonModule, XBaseComponentModule], + exports: [XBaseComponentModule, XScreenCaptureComponent], +}) +export class XScreenCaptureModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-screen-capture\x-screen-capture.service.ts + +import { + XMimeType, + blobToImage, + XBaseService, + generateUuid, + canvasToBlob, + dataUrlToImage, + canvasToDataUrl, + clipImageToCanvas, + downloadArrayBufferedFile, +} from 'x-framework-core'; +import { + XScreenCaptureType, + XScreenCaptureOptions, + XScreenCaptureDimensions, + XScreenCaptureTypeIdentifier, +} from './x-screen-capture.typings'; +import * as domToImage from 'dom-to-image'; +import { Injectable, Inject } from '@angular/core'; +import { XNotificationService } from 'x-framework-services'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XScreenCaptureService extends XBaseService { + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + private notificationService: XNotificationService + ) { + super(config); + } + //#endregion + + /** + * Capture Element with Options as specified type ... + * + * @param screen an instance of HTMLElement ... + */ + async capture( + screen: HTMLElement, + type?: XScreenCaptureTypeIdentifier, + options?: XScreenCaptureOptions, + clippingDimensions?: XScreenCaptureDimensions + ) { + // + if (!screen) { + return null; + } + + // + if (!type) { + type = XScreenCaptureType.PNG; + } + + // + if (!options) { + options = {}; + } + + // + this.playCaptureNotification(); + + // + try { + // + switch (type) { + // + // PNG ... + case XScreenCaptureType.PNG: + // + const pngDataUrl = await domToImage.default.toPng(screen, options); + if (!clippingDimensions) { + return pngDataUrl; + } else { + // + const pngImage = await dataUrlToImage(pngDataUrl); + if (!pngImage) { + return null; + } + + // + const pngClippedCanvas = await clipImageToCanvas( + pngImage, + clippingDimensions.x, + clippingDimensions.y, + clippingDimensions.width, + clippingDimensions.height + ); + if (!pngClippedCanvas) { + return null; + } + + // + return canvasToDataUrl(pngClippedCanvas, XMimeType.ImagePNG); + } + + // + // JPeg ... + case XScreenCaptureType.JPeg: + // + const jPegDataUrl = await domToImage.default.toJpeg(screen, options); + if (!clippingDimensions) { + return jPegDataUrl; + } else { + // + const jPegImage = await dataUrlToImage(jPegDataUrl); + if (!jPegImage) { + return null; + } + + // + const jPegClippedCanvas = await clipImageToCanvas( + jPegImage, + clippingDimensions.x, + clippingDimensions.y, + clippingDimensions.width, + clippingDimensions.height + ); + if (!jPegClippedCanvas) { + return null; + } + + // + return canvasToDataUrl(jPegClippedCanvas, XMimeType.ImageJPEG); + } + + // + // SVG ... + case XScreenCaptureType.SVG: + // + const svgDataUrl = await domToImage.default.toSvg(screen, options); + if (!clippingDimensions) { + return svgDataUrl; + } else { + // + const svgImage = await dataUrlToImage(svgDataUrl); + if (!svgImage) { + return null; + } + + // + const svgClippedCanvas = await clipImageToCanvas( + svgImage, + clippingDimensions.x, + clippingDimensions.y, + clippingDimensions.width, + clippingDimensions.height + ); + if (!svgClippedCanvas) { + return null; + } + + // + return canvasToDataUrl(svgClippedCanvas, XMimeType.ImageSVG); + } + + // + // To Blob ... + case XScreenCaptureType.Blob: + // + const blob = await domToImage.default.toBlob(screen, options); + if (!clippingDimensions) { + return blob; + } else { + // + const blobImage = await blobToImage(blob); + if (!blobImage) { + return null; + } + + // + const blobClippedCanvas = await clipImageToCanvas( + blobImage, + clippingDimensions.x, + clippingDimensions.y, + clippingDimensions.width, + clippingDimensions.height + ); + if (!blobClippedCanvas) { + return null; + } + + // + return await canvasToBlob(blobClippedCanvas); + } + } + } catch {} + + // + return null; + } + + /** + * Capture Element with Options as Blob object and Download it ... + * + * @param screen an instance of HTMLElement ... + * @param fileName downloaded file name ... + */ + async captureAndDownload( + screen: HTMLElement, + options?: XScreenCaptureOptions, + clippingDimensions?: XScreenCaptureDimensions, + fileName?: string + ) { + // + this.playCaptureNotification(); + + // + const fName = fileName ? fileName : `${generateUuid()}.png`; + + // + try { + // + const blob = await domToImage.default.toBlob(screen, options); + + // + if (!blob) { + return null; + } + + // + if (!clippingDimensions) { + // + downloadArrayBufferedFile(fName, blob); + return blob; + } else { + // + const blobImage = await blobToImage(blob); + if (!blobImage) { + return null; + } + + // + const blobClippedCanvas = await clipImageToCanvas( + blobImage, + clippingDimensions.x, + clippingDimensions.y, + clippingDimensions.width, + clippingDimensions.height + ); + if (!blobClippedCanvas) { + return null; + } + + // + const clippedBlob = await canvasToBlob(blobClippedCanvas); + if (!clippedBlob) { + return null; + } + + // + downloadArrayBufferedFile(fName, clippedBlob); + return clippedBlob; + } + } catch {} + + // + return null; + } + + /** + * play Capture Notification Sound ... + */ + playCaptureNotification() { + this.notificationService.playCaptureNotification(); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-screen-capture\x-screen-capture.typings.ts + +import { Options } from 'dom-to-image'; + +// tslint:disable-next-line:no-empty-interface +export interface XScreenCaptureOptions extends Options {} + +export enum XScreenCaptureType { + SVG = 'svg', + PNG = 'png', + Blob = 'blob', + JPeg = 'jpeg', +} + +export type XScreenCaptureTypeIdentifier = XScreenCaptureType | string; + +export interface XScreenCaptureDimensions { + x: number; + y: number; + width: number; + height: number; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-search-bar\x-search-bar.component.html + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-search-bar\x-search-bar.component.scss + +:host { + --clear-icon-color: var(--x-color-danger); + --cancel-icon-color: var(--x-color-primary); + --search-icon-color: var(--x-color-primary); +} + +:host::ng-deep { + .searchbar-clear-icon { + color: var(--clear-icon-color) !important; + } + + .searchbar-search-icon { + color: var(--search-icon-color) !important; + } + + .searchbar-cancel-button { + color: var(--cancel-icon-color) !important; + } +} + +.x-search-bar { + padding: { + left: 3px; + right: 3px; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-search-bar\x-search-bar.component.ts + +import { + nameof, + parseXColor, + isNullOrEmptyString, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XSearchBarEvent, + XSearchBarAction, + XSearchBarCssVars, + XSearchBarActionModel, + XSearchBarContainerComponent, +} from './x-search-bar.typings'; +import { filter } from 'rxjs/operators'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-search-bar', + templateUrl: './x-search-bar.component.html', + styleUrls: ['./x-search-bar.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XSearchBarComponent extends XSearchBarContainerComponent { + // + //#region Props ... + // + protected applyThemeColors: boolean = true; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + readonly SearchBarEvents = Object.assign({}, XSearchBarEvent); + //#endregion + + // + //#region LifeCycles ... + afterViewInit() { + super.afterViewInit(); + + // + // Handle Start Value for start searching ... + this.getValue(this.value) + .pipe(filter((query) => !isNullOrEmptyString(query))) + .subscribe((query) => { + this.queryChange.emit(query); + }); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Action Provider ... + const isActionProviderChaged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChaged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Colors ... + // + const isCancelButtonIconColorChanged = changeKeys.includes( + nameof('cancelButtonIconColor') + ); + if (isCancelButtonIconColorChanged) { + await this.applyCancelButtonIconColor(); + } + + // + const isClearButtonIconColorChanged = changeKeys.includes( + nameof('clearButtonIconColor') + ); + if (isClearButtonIconColorChanged) { + await this.applyClearButtonIconColor(); + } + + // + const isSearchButtonIconColorChanged = changeKeys.includes( + nameof('searchButtonIconColor') + ); + if (isSearchButtonIconColorChanged) { + await this.applySearchButtonIconColor(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Register Handlers ... + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + //#endregion + + // + //#region UI Handlers ... + // + handleSearchQueryValueChange(event: CustomEvent | any) { + // + const query = + event && event.detail && event.detail.value + ? event.detail.value.toString().trim().toLowerCase() + : ''; + + // + this.queryChange.emit(query); + } + //#endregion + + // + //#region Actions ... + //#endregion + + // + //#region Private ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XSearchBarAction.ClearValue: + // + this.value = ''; + this.detectChanges(); + break; + + // + case XSearchBarAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + + // + //#region Color Appliers ... + private async applyCancelButtonIconColor() { + // + const color = await this.getValueAsync(this.cancelButtonIconColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XSearchBarCssVars.CancelIconColor, `var(${bgColor})`); + this.setCssVar( + XSearchBarCssVars.CancelIconColor, + `var(${bgColor})`, + document.body + ); + + // + this.detectChanges(); + } + + private async applyClearButtonIconColor() { + // + const color = await this.getValueAsync(this.clearButtonIconColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XSearchBarCssVars.ClearIconColor, `var(${bgColor})`); + this.setCssVar( + XSearchBarCssVars.ClearIconColor, + `var(${bgColor})`, + document.body + ); + + // + this.detectChanges(); + } + + private async applySearchButtonIconColor() { + // + const color = await this.getValueAsync(this.searchButtonIconColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XSearchBarCssVars.SearchIconColor, `var(${bgColor})`); + this.setCssVar( + XSearchBarCssVars.SearchIconColor, + `var(${bgColor})`, + document.body + ); + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-search-bar\x-search-bar.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XSearchBarComponent } from './x-search-bar.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XSearchBarComponent], + imports: [ + CommonModule, + XBaseComponentModule, + ], + exports: [ + CommonModule, + XSearchBarComponent, + XBaseComponentModule, + ], +}) +export class XSearchBarModule { } + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-search-bar\x-search-bar.typings.ts + +import { + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseComponentContainer, + XBaseContainerComponent, + IXBaseComponentContainer, + XBaseComponentContainerComponent, +} from '../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XSearchBarAction { + ClearValue = 'clear_value', + Refresh = 'refresh', +} + +export type XSearchBarActionIdentifier = XSearchBarAction | string; + +export interface XSearchBarActionModel + extends XBaseActionModel {} + +export enum XSearchBarType { + None = 'none', + Text = 'text', + Tel = 'tel', + Url = 'url', + Email = 'email', + Numeric = 'numeric', + Decimal = 'decimal', + Search = 'search', +} + +export type XSearchBarTypeIdentifier = XSearchBarType | string; + +export enum XSearchBarEnterKeyHint { + Done = 'done', + Go = 'go', + Next = 'next', + Previous = 'previous', + Search = 'search', + Send = 'send', + Enter = 'enter', +} + +export type XSearchBarEnterKeyHintIdentifier = XSearchBarEnterKeyHint | string; + +export enum XSearchBarShowButton { + Always = 'always', + Focus = 'focus', + Never = 'never', +} + +export type XSearchBarShowButtonIdentifier = XSearchBarShowButton | string; + +export enum XSearchBarCssVars { + // + ClearIconColor = 'clear-icon-color', + CancelIconColor = 'cancel-icon-color', + SearchIconColor = 'search-icon-color', +} + +export type XSearchBarCssVarsIdentifier = XSearchBarCssVars | string; + +export enum XSearchBarEvent { + FOCUS = 'focus', + BLUR = 'blur', + CANCEL = 'cancel', + CLEAR = 'clear', + INPUT = 'input', +} + +export type XSearchBarEventIdentifier = XSearchBarEvent | string; + +// +//#region Container ... +export interface IXSearchBarContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + searchInputColor: XStandardType; + foregroundSearchInputColor: XStandardType; + + // + type: XStandardType; + title: XStandardType; + value: XStandardType; + enterKeyHint: XStandardType; + keyboardType: XStandardType; + spellcheck: XStandardType; + debounce: XStandardType; + + // + showCancelButton: XStandardType; + cancelButtonIcon: XStandardType; + cancelButtonTitle: XStandardType; + cancelButtonIconColor: XStandardType; + + // + showClearButton: XStandardType; + clearButtonIcon: XStandardType; + clearButtonIconColor: XStandardType; + + // + searchButtonIcon: XStandardType; + searchButtonIconColor: XStandardType; + + // + queryChange: EventEmitter; + onEvent: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XSearchBarContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly searchInputColor: XStandardType = + XColorWithBrightness.Light; + readonly foregroundSearchInputColor: XStandardType = false; + + // + readonly type: XStandardType = + XSearchBarType.Search; + readonly title: XStandardType = XResourceIDs.search; + readonly value: XStandardType; + readonly enterKeyHint: XStandardType = + XSearchBarEnterKeyHint.Search; + readonly keyboardType: XStandardType = + XSearchBarType.Search; + readonly spellcheck: XStandardType = false; + readonly debounce: XStandardType = + this.config.defaultSearchDebounceTime || 500; + + // + readonly showCancelButton: XStandardType = + XSearchBarShowButton.Never; + readonly cancelButtonIcon: XStandardType; + readonly cancelButtonTitle: XStandardType; + readonly cancelButtonIconColor: XStandardType = + XColorWithBrightness.Primary; + + // + readonly showClearButton: XStandardType = + XSearchBarShowButton.Always; + readonly clearButtonIcon: XStandardType; + readonly clearButtonIconColor: XStandardType = + XColorWithBrightness.Danger; + + // + readonly searchButtonIcon: XStandardType; + readonly searchButtonIconColor: XStandardType = + XColorWithBrightness.Primary; + + // + readonly queryChange: EventEmitter = new EventEmitter(); + readonly onEvent: EventEmitter = + new EventEmitter(); + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig + ) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XSearchBarContainerComponent + extends XBaseContainerComponent + implements IXSearchBarContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Input() + searchInputColor: XStandardType = + this.propertyProvider.searchInputColor; + + @Input() + foregroundSearchInputColor: XStandardType = + this.propertyProvider.foregroundSearchInputColor; + + // + @Input() + type: XStandardType = this.propertyProvider.type; + + @Input() + title: XStandardType = this.propertyProvider.title; + + @Input() + value: XStandardType = this.propertyProvider.value; + + @Input() + enterKeyHint: XStandardType = + this.propertyProvider.enterKeyHint; + + @Input() + keyboardType: XStandardType = + this.propertyProvider.keyboardType; + + @Input() + spellcheck: XStandardType = this.propertyProvider.spellcheck; + + @Input() + debounce: XStandardType = this.propertyProvider.debounce; + + // + @Input() + showCancelButton: XStandardType = + this.propertyProvider.showCancelButton; + + @Input() + cancelButtonIcon: XStandardType = + this.propertyProvider.cancelButtonIcon; + + @Input() + cancelButtonTitle: XStandardType = + this.propertyProvider.cancelButtonTitle; + + @Input() + cancelButtonIconColor: XStandardType = + this.propertyProvider.cancelButtonIconColor; + + // + @Input() + showClearButton: XStandardType = + this.propertyProvider.showClearButton; + + @Input() + clearButtonIcon: XStandardType = + this.propertyProvider.clearButtonIcon; + + @Input() + clearButtonIconColor: XStandardType = + this.propertyProvider.clearButtonIconColor; + + // + @Input() + searchButtonIcon: XStandardType = + this.propertyProvider.searchButtonIcon; + + @Input() + searchButtonIconColor: XStandardType = + this.propertyProvider.searchButtonIconColor; + + // + @Output() + queryChange: EventEmitter = new EventEmitter(); + + @Output() + onEvent: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XSearchBarContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXSearchBarContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + searchInputColor: this.getValue( + container ? container.searchInputColor : this.searchInputColor + ), + foregroundSearchInputColor: this.getValue( + container + ? container.foregroundSearchInputColor + : this.foregroundSearchInputColor + ), + type: this.getValue(container ? container.type : this.type), + title: this.getValue(container ? container.title : this.title), + value: this.getValue(container ? container.value : this.value), + enterKeyHint: this.getValue( + container ? container.enterKeyHint : this.enterKeyHint + ), + keyboardType: this.getValue( + container ? container.keyboardType : this.keyboardType + ), + spellcheck: this.getValue( + container ? container.spellcheck : this.spellcheck + ), + debounce: this.getValue(container ? container.debounce : this.debounce), + showCancelButton: this.getValue( + container ? container.showCancelButton : this.showCancelButton + ), + cancelButtonIcon: this.getValue( + container ? container.cancelButtonIcon : this.cancelButtonIcon + ), + cancelButtonTitle: this.getValue( + container ? container.cancelButtonTitle : this.cancelButtonTitle + ), + cancelButtonIconColor: this.getValue( + container ? container.cancelButtonIconColor : this.cancelButtonIconColor + ), + showClearButton: this.getValue( + container ? container.showCancelButton : this.showCancelButton + ), + clearButtonIcon: this.getValue( + container ? container.clearButtonIcon : this.clearButtonIcon + ), + clearButtonIconColor: this.getValue( + container ? container.clearButtonIconColor : this.clearButtonIconColor + ), + searchButtonIcon: this.getValue( + container ? container.searchButtonIcon : this.searchButtonIcon + ), + searchButtonIconColor: this.getValue( + container ? container.searchButtonIconColor : this.searchButtonIconColor + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + onEvent: this.onEvent, + queryChange: this.queryChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentSearchBarContainer = { + [TKey in keyof IXSearchBarContainer as `searchBar${Capitalize< + string & TKey + >}`]: IXSearchBarContainer[TKey]; +}; + +export interface IXComponentSearchBarContainer + extends TXComponentSearchBarContainer, + IXBaseComponentContainer { + // + showSearchBar: XStandardType; + + // + searchBarActionProvider: Subject; + searchBarCssClass: XStandardType>; + + // + searchBarColor: XStandardType; + searchBarForegroundColor: XStandardType; + + // + searchBarSearchInputColor: XStandardType; + searchBarForegroundSearchInputColor: XStandardType; + + // + searchBarType: XStandardType; + searchBarTitle: XStandardType; + searchBarValue: XStandardType; + searchBarEnterKeyHint: XStandardType; + searchBarKeyboardType: XStandardType; + searchBarSpellcheck: XStandardType; + searchBarDebounce: XStandardType; + + // + searchBarShowCancelButton: XStandardType; + searchBarCancelButtonIcon: XStandardType; + searchBarCancelButtonTitle: XStandardType; + searchBarCancelButtonIconColor: XStandardType; + + // + searchBarShowClearButton: XStandardType; + searchBarClearButtonIcon: XStandardType; + searchBarClearButtonIconColor: XStandardType; + + // + searchBarSearchButtonIcon: XStandardType; + searchBarSearchButtonIconColor: XStandardType; + + // + searchBarQueryChange: EventEmitter; + searchBarOnEvent: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XComponentSearchBarContainer + extends XBaseComponentContainer + implements Readonly +{ + // + //#region Props ... + // + readonly showSearchBar: XStandardType = true; + + // + readonly searchBarActionProvider: Subject = + this.propertyProvider.actionProvider; + readonly searchBarCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly searchBarColor: XStandardType = + this.propertyProvider.color; + readonly searchBarForegroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + readonly searchBarSearchInputColor: XStandardType = + this.propertyProvider.searchInputColor; + readonly searchBarForegroundSearchInputColor: XStandardType = + this.propertyProvider.foregroundSearchInputColor; + + // + readonly searchBarType: XStandardType = + this.propertyProvider.type; + readonly searchBarTitle: XStandardType = + this.propertyProvider.title; + readonly searchBarValue: XStandardType = this.propertyProvider.value; + readonly searchBarEnterKeyHint: XStandardType = + this.propertyProvider.enterKeyHint; + readonly searchBarKeyboardType: XStandardType = + this.propertyProvider.keyboardType; + readonly searchBarSpellcheck: XStandardType = + this.propertyProvider.spellcheck; + readonly searchBarDebounce: XStandardType = + this.propertyProvider.debounce; + + // + readonly searchBarShowCancelButton: XStandardType = + this.propertyProvider.showCancelButton; + readonly searchBarCancelButtonIcon: XStandardType = + this.propertyProvider.cancelButtonIcon; + readonly searchBarCancelButtonTitle: XStandardType = + this.propertyProvider.cancelButtonTitle; + readonly searchBarCancelButtonIconColor: XStandardType = + this.propertyProvider.cancelButtonIconColor; + + // + readonly searchBarShowClearButton: XStandardType = + this.propertyProvider.showClearButton; + readonly searchBarClearButtonIcon: XStandardType = + this.propertyProvider.clearButtonIcon; + readonly searchBarClearButtonIconColor: XStandardType = + this.propertyProvider.clearButtonIconColor; + + // + readonly searchBarSearchButtonIcon: XStandardType = + this.propertyProvider.searchButtonIcon; + readonly searchBarSearchButtonIconColor: XStandardType = + this.propertyProvider.searchButtonIconColor; + + // + readonly searchBarQueryChange: EventEmitter = + this.propertyProvider.queryChange; + readonly searchBarOnEvent: EventEmitter = + this.propertyProvider.onEvent; + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XSearchBarContainer) { + super(); + } + //#endregion +} + +@Component({ + template: ``, +}) +export abstract class XComponentSearchBarContainerComponent + extends XBaseComponentContainerComponent + implements IXComponentSearchBarContainer +{ + // + //#region Props ... + // + @Input() + showSearchBar: XStandardType = this.propertyProvider.showSearchBar; + + // + @Input() + searchBarActionProvider: Subject = + this.propertyProvider.searchBarActionProvider; + + @Input() + searchBarCssClass: XStandardType> = + this.propertyProvider.searchBarCssClass; + + // + @Input() + searchBarColor: XStandardType = + this.propertyProvider.searchBarColor; + + @Input() + searchBarForegroundColor: XStandardType = + this.propertyProvider.searchBarForegroundColor; + + // + @Input() + searchBarSearchInputColor: XStandardType = + this.propertyProvider.searchBarSearchInputColor; + + @Input() + searchBarForegroundSearchInputColor: XStandardType = + this.propertyProvider.searchBarForegroundSearchInputColor; + + // + @Input() + searchBarType: XStandardType = + this.propertyProvider.searchBarType; + + @Input() + searchBarTitle: XStandardType = + this.propertyProvider.searchBarTitle; + + @Input() + searchBarValue: XStandardType = this.propertyProvider.searchBarValue; + + @Input() + searchBarEnterKeyHint: XStandardType = + this.propertyProvider.searchBarEnterKeyHint; + + @Input() + searchBarKeyboardType: XStandardType = + this.propertyProvider.searchBarKeyboardType; + + @Input() + searchBarSpellcheck: XStandardType = + this.propertyProvider.searchBarSpellcheck; + + @Input() + searchBarDebounce: XStandardType = + this.propertyProvider.searchBarDebounce; + + // + @Input() + searchBarShowCancelButton: XStandardType = + this.propertyProvider.searchBarShowCancelButton; + + @Input() + searchBarCancelButtonIcon: XStandardType = + this.propertyProvider.searchBarCancelButtonIcon; + + @Input() + searchBarCancelButtonTitle: XStandardType = + this.propertyProvider.searchBarCancelButtonTitle; + + @Input() + searchBarCancelButtonIconColor: XStandardType = + this.propertyProvider.searchBarCancelButtonIconColor; + + // + @Input() + searchBarShowClearButton: XStandardType = + this.propertyProvider.searchBarShowClearButton; + + @Input() + searchBarClearButtonIcon: XStandardType = + this.propertyProvider.searchBarClearButtonIcon; + + @Input() + searchBarClearButtonIconColor: XStandardType = + this.propertyProvider.searchBarClearButtonIconColor; + + // + @Input() + searchBarSearchButtonIcon: XStandardType = + this.propertyProvider.searchBarSearchButtonIcon; + + @Input() + searchBarSearchButtonIconColor: XStandardType = + this.propertyProvider.searchBarSearchButtonIconColor; + + // + @Output() + searchBarQueryChange: EventEmitter = new EventEmitter(); + + @Output() + searchBarOnEvent: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XComponentSearchBarContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXComponentSearchBarContainer + ): Observable { + return forkJoin({ + // + showSearchBar: this.getValue( + container ? container.showSearchBar : this.showSearchBar + ), + + // + searchBarCssClass: this.getArrayValue( + container ? container.searchBarCssClass : this.searchBarCssClass + ), + + // + searchBarColor: this.getValue( + container ? container.searchBarColor : this.searchBarColor + ), + searchBarForegroundColor: this.getValue( + container + ? container.searchBarForegroundColor + : this.searchBarForegroundColor + ), + searchBarSearchInputColor: this.getValue( + container + ? container.searchBarSearchInputColor + : this.searchBarSearchInputColor + ), + searchBarForegroundSearchInputColor: this.getValue( + container + ? container.searchBarForegroundSearchInputColor + : this.searchBarForegroundSearchInputColor + ), + searchBarType: this.getValue( + container ? container.searchBarType : this.searchBarType + ), + searchBarTitle: this.getValue( + container ? container.searchBarTitle : this.searchBarTitle + ), + searchBarValue: this.getValue( + container ? container.searchBarValue : this.searchBarValue + ), + searchBarEnterKeyHint: this.getValue( + container ? container.searchBarEnterKeyHint : this.searchBarEnterKeyHint + ), + searchBarKeyboardType: this.getValue( + container ? container.searchBarKeyboardType : this.searchBarKeyboardType + ), + searchBarSpellcheck: this.getValue( + container ? container.searchBarSpellcheck : this.searchBarSpellcheck + ), + searchBarDebounce: this.getValue( + container ? container.searchBarDebounce : this.searchBarDebounce + ), + searchBarShowCancelButton: this.getValue( + container + ? container.searchBarShowCancelButton + : this.searchBarShowCancelButton + ), + searchBarCancelButtonIcon: this.getValue( + container + ? container.searchBarCancelButtonIcon + : this.searchBarCancelButtonIcon + ), + searchBarCancelButtonTitle: this.getValue( + container + ? container.searchBarCancelButtonTitle + : this.searchBarCancelButtonTitle + ), + searchBarCancelButtonIconColor: this.getValue( + container + ? container.searchBarCancelButtonIconColor + : this.searchBarCancelButtonIconColor + ), + searchBarShowClearButton: this.getValue( + container + ? container.searchBarShowClearButton + : this.searchBarShowClearButton + ), + searchBarClearButtonIcon: this.getValue( + container + ? container.searchBarClearButtonIcon + : this.searchBarClearButtonIcon + ), + searchBarClearButtonIconColor: this.getValue( + container + ? container.searchBarClearButtonIconColor + : this.searchBarClearButtonIconColor + ), + searchBarSearchButtonIcon: this.getValue( + container + ? container.searchBarSearchButtonIcon + : this.searchBarSearchButtonIcon + ), + searchBarSearchButtonIconColor: this.getValue( + container + ? container.searchBarSearchButtonIconColor + : this.searchBarSearchButtonIconColor + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + searchBarActionProvider: container + ? container.searchBarActionProvider + : this.searchBarActionProvider, + + // + searchBarOnEvent: this.searchBarOnEvent, + searchBarQueryChange: this.searchBarQueryChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slide.animation.providers.ts + +import { XSlideAnimationProvider } from './x-slider.typings'; + +// +//#region Animations ... +export const Coverflow: XSlideAnimationProvider = { + coverflowEffect: { + rotate: 50, + stretch: 0, + depth: 100, + modifier: 1, + slideShadows: true + }, + on: { + beforeInit() { + const swiper = this; + swiper.classNames.push( + `${swiper.params.containerModifierClass}coverflow` + ); + swiper.classNames.push(`${swiper.params.containerModifierClass}3d`); + + swiper.params.watchSlidesProgress = true; + swiper.originalParams.watchSlidesProgress = true; + }, + setTranslate() { + const swiper = this; + const { + width: swiperWidth, + height: swiperHeight, + slides, + $wrapperEl, + slidesSizesGrid + } = swiper; + const params = swiper.params.coverflowEffect; + const isHorizontal = swiper.isHorizontal(); + const transform$$1 = swiper.translate; + const center = isHorizontal + ? -transform$$1 + swiperWidth / 2 + : -transform$$1 + swiperHeight / 2; + const rotate = isHorizontal ? params.rotate : -params.rotate; + const translate = params.depth; + // Each slide offset from center + for (let i = 0, length = slides.length; i < length; i += 1) { + const $slideEl = slides.eq(i); + const slideSize = slidesSizesGrid[i]; + const slideOffset = $slideEl[0].swiperSlideOffset; + const offsetMultiplier = + ((center - slideOffset - slideSize / 2) / slideSize) * + params.modifier; + + let rotateY = isHorizontal ? rotate * offsetMultiplier : 0; + let rotateX = isHorizontal ? 0 : rotate * offsetMultiplier; + // var rotateZ = 0 + let translateZ = -translate * Math.abs(offsetMultiplier); + + let translateY = isHorizontal ? 0 : params.stretch * offsetMultiplier; + let translateX = isHorizontal ? params.stretch * offsetMultiplier : 0; + + // Fix for ultra small values + if (Math.abs(translateX) < 0.001) { + translateX = 0; + } + if (Math.abs(translateY) < 0.001) { + translateY = 0; + } + if (Math.abs(translateZ) < 0.001) { + translateZ = 0; + } + if (Math.abs(rotateY) < 0.001) { + rotateY = 0; + } + if (Math.abs(rotateX) < 0.001) { + rotateX = 0; + } + + const slideTransform = + `translate3d(${translateX}px,${translateY}px,${translateZ}px)` + + ` rotateX(${rotateX}deg) rotateY(${rotateY}deg)`; + + $slideEl.transform(slideTransform); + $slideEl[0].style.zIndex = -Math.abs(Math.round(offsetMultiplier)) + 1; + if (params.slideShadows) { + // Set shadows + let $shadowBeforeEl = isHorizontal + ? $slideEl.find('.swiper-slide-shadow-left') + : $slideEl.find('.swiper-slide-shadow-top'); + let $shadowAfterEl = isHorizontal + ? $slideEl.find('.swiper-slide-shadow-right') + : $slideEl.find('.swiper-slide-shadow-bottom'); + if ($shadowBeforeEl.length === 0) { + $shadowBeforeEl = swiper.$( + `
` + ); + // $slideEl.append($shadowBeforeEl); + } + if ($shadowAfterEl.length === 0) { + $shadowAfterEl = swiper.$( + `
` + ); + // $slideEl.append($shadowAfterEl); + } + if ($shadowBeforeEl.length) { + $shadowBeforeEl[0].style.opacity = + offsetMultiplier > 0 ? offsetMultiplier : 0; + } + if ($shadowAfterEl.length) { + $shadowAfterEl[0].style.opacity = + -offsetMultiplier > 0 ? -offsetMultiplier : 0; + } + } + } + + // Set correct perspective for IE10 + if ( + swiper.support.pointerEvents || + swiper.support.prefixedPointerEvents + ) { + const ws = $wrapperEl[0].style; + ws.perspectiveOrigin = `${center}px 50%`; + } + }, + setTransition(duration) { + const swiper = this; + swiper.slides + .transition(duration) + .find( + '.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left' + ) + .transition(duration); + } + } +}; + +// +export const Cube: XSlideAnimationProvider = { + cubeEffect: { + shadow: true, + slideShadows: true, + shadowOffset: 20, + shadowScale: 0.94 + }, + on: { + beforeInit() { + const swiper = this; + swiper.classNames.push(`${swiper.params.containerModifierClass}cube`); + swiper.classNames.push(`${swiper.params.containerModifierClass}3d`); + + const overwriteParams = { + slidesPerView: 1, + slidesPerColumn: 1, + slidesPerGroup: 1, + watchSlidesProgress: true, + resistanceRatio: 0, + spaceBetween: 0, + centeredSlides: false, + virtualTranslate: true + }; + + this.params = Object.assign(this.params, overwriteParams); + this.originalParams = Object.assign(this.originalParams, overwriteParams); + }, + setTranslate() { + const swiper = this; + const { + $el, + $wrapperEl, + slides, + width: swiperWidth, + height: swiperHeight, + rtlTranslate: rtl, + size: swiperSize + } = swiper; + const params = swiper.params.cubeEffect; + const isHorizontal = swiper.isHorizontal(); + const isVirtual = swiper.virtual && swiper.params.virtual.enabled; + let wrapperRotate = 0; + let $cubeShadowEl; + if (params.shadow) { + if (isHorizontal) { + $cubeShadowEl = $wrapperEl.find('.swiper-cube-shadow'); + if ($cubeShadowEl.length === 0) { + $cubeShadowEl = swiper.$('
'); + $wrapperEl.append($cubeShadowEl); + } + $cubeShadowEl.css({ height: `${swiperWidth}px` }); + } else { + $cubeShadowEl = $el.find('.swiper-cube-shadow'); + if ($cubeShadowEl.length === 0) { + $cubeShadowEl = swiper.$('
'); + $el.append($cubeShadowEl); + } + } + } + + for (let i = 0; i < slides.length; i += 1) { + const $slideEl = slides.eq(i); + let slideIndex = i; + if (isVirtual) { + slideIndex = parseInt($slideEl.attr('data-swiper-slide-index'), 10); + } + let slideAngle = slideIndex * 90; + let round = Math.floor(slideAngle / 360); + if (rtl) { + slideAngle = -slideAngle; + round = Math.floor(-slideAngle / 360); + } + const progress = Math.max(Math.min($slideEl[0].progress, 1), -1); + let tx = 0; + let ty = 0; + let tz = 0; + if (slideIndex % 4 === 0) { + tx = -round * 4 * swiperSize; + tz = 0; + } else if ((slideIndex - 1) % 4 === 0) { + tx = 0; + tz = -round * 4 * swiperSize; + } else if ((slideIndex - 2) % 4 === 0) { + tx = swiperSize + round * 4 * swiperSize; + tz = swiperSize; + } else if ((slideIndex - 3) % 4 === 0) { + tx = -swiperSize; + tz = 3 * swiperSize + swiperSize * 4 * round; + } + if (rtl) { + tx = -tx; + } + + if (!isHorizontal) { + ty = tx; + tx = 0; + } + + const transform$$1 = `rotateX(${ + isHorizontal ? 0 : -slideAngle + }deg) rotateY(${ + isHorizontal ? slideAngle : 0 + }deg) translate3d(${tx}px, ${ty}px, ${tz}px)`; + if (progress <= 1 && progress > -1) { + wrapperRotate = slideIndex * 90 + progress * 90; + if (rtl) { + wrapperRotate = -slideIndex * 90 - progress * 90; + } + } + $slideEl.transform(transform$$1); + if (params.slideShadows) { + // Set shadows + let shadowBefore = isHorizontal + ? $slideEl.find('.swiper-slide-shadow-left') + : $slideEl.find('.swiper-slide-shadow-top'); + let shadowAfter = isHorizontal + ? $slideEl.find('.swiper-slide-shadow-right') + : $slideEl.find('.swiper-slide-shadow-bottom'); + if (shadowBefore.length === 0) { + shadowBefore = swiper.$( + `
` + ); + $slideEl.append(shadowBefore); + } + if (shadowAfter.length === 0) { + shadowAfter = swiper.$( + `
` + ); + $slideEl.append(shadowAfter); + } + if (shadowBefore.length) { + shadowBefore[0].style.opacity = Math.max(-progress, 0); + } + if (shadowAfter.length) { + shadowAfter[0].style.opacity = Math.max(progress, 0); + } + } + } + $wrapperEl.css({ + '-webkit-transform-origin': `50% 50% -${swiperSize / 2}px`, + '-moz-transform-origin': `50% 50% -${swiperSize / 2}px`, + '-ms-transform-origin': `50% 50% -${swiperSize / 2}px`, + 'transform-origin': `50% 50% -${swiperSize / 2}px` + }); + + if (params.shadow) { + if (isHorizontal) { + $cubeShadowEl.transform( + `translate3d(0px, ${swiperWidth / 2 + + params.shadowOffset}px, ${-swiperWidth / + 2}px) rotateX(90deg) rotateZ(0deg) scale(${params.shadowScale})` + ); + } else { + const shadowAngle = + Math.abs(wrapperRotate) - + Math.floor(Math.abs(wrapperRotate) / 90) * 90; + const multiplier = + 1.5 - + (Math.sin((shadowAngle * 2 * Math.PI) / 360) / 2 + + Math.cos((shadowAngle * 2 * Math.PI) / 360) / 2); + const scale1 = params.shadowScale; + const scale2 = params.shadowScale / multiplier; + const offset$$1 = params.shadowOffset; + $cubeShadowEl.transform( + `scale3d(${scale1}, 1, ${scale2}) translate3d(0px, ${swiperHeight / + 2 + + offset$$1}px, ${-swiperHeight / 2 / scale2}px) rotateX(-90deg)` + ); + } + } + + const zFactor = + swiper.browser.isSafari || swiper.browser.isUiWebView + ? -swiperSize / 2 + : 0; + $wrapperEl.transform( + `translate3d(0px,0,${zFactor}px) rotateX(${ + swiper.isHorizontal() ? 0 : wrapperRotate + }deg) rotateY(${swiper.isHorizontal() ? -wrapperRotate : 0}deg)` + ); + }, + setTransition(duration) { + const swiper = this; + const { $el, slides } = swiper; + slides + .transition(duration) + .find( + '.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left' + ) + .transition(duration); + if (swiper.params.cubeEffect.shadow && !swiper.isHorizontal()) { + $el.find('.swiper-cube-shadow').transition(duration); + } + } + } +}; + +// +export const Fade: XSlideAnimationProvider = { + on: { + beforeInit() { + const swiper = this; + swiper.classNames.push(`${swiper.params.containerModifierClass}fade`); + const overwriteParams = { + slidesPerView: 1, + slidesPerColumn: 1, + slidesPerGroup: 1, + watchSlidesProgress: true, + spaceBetween: 0, + virtualTranslate: true + }; + swiper.params = Object.assign(swiper.params, overwriteParams); + swiper.params = Object.assign(swiper.originalParams, overwriteParams); + }, + setTranslate() { + const swiper = this; + const { slides } = swiper; + for (let i = 0; i < slides.length; i += 1) { + const $slideEl = swiper.slides.eq(i); + const offset$$1 = $slideEl[0].swiperSlideOffset; + let tx = -offset$$1; + if (!swiper.params.virtualTranslate) { + tx -= swiper.translate; + } + let ty = 0; + if (!swiper.isHorizontal()) { + ty = tx; + tx = 0; + } + const slideOpacity = swiper.params.fadeEffect.crossFade + ? Math.max(1 - Math.abs($slideEl[0].progress), 0) + : 1 + Math.min(Math.max($slideEl[0].progress, -1), 0); + $slideEl + .css({ + opacity: slideOpacity + }) + .transform(`translate3d(${tx}px, ${ty}px, 0px)`); + } + }, + setTransition(duration) { + const swiper = this; + const { slides, $wrapperEl } = swiper; + slides.transition(duration); + if (swiper.params.virtualTranslate && duration !== 0) { + let eventTriggered = false; + slides.transitionEnd(() => { + if (eventTriggered) { + return; + } + if (!swiper || swiper.destroyed) { + return; + } + eventTriggered = true; + swiper.animating = false; + const triggerEvents = ['webkitTransitionEnd', 'transitionend']; + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < triggerEvents.length; i += 1) { + $wrapperEl.trigger(triggerEvents[i]); + } + }); + } + } + } +}; + +// +export const Flip: XSlideAnimationProvider = { + on: { + beforeInit() { + const swiper = this; + swiper.classNames.push(`${swiper.params.containerModifierClass}flip`); + swiper.classNames.push(`${swiper.params.containerModifierClass}3d`); + const overwriteParams = { + slidesPerView: 1, + slidesPerColumn: 1, + slidesPerGroup: 1, + watchSlidesProgress: true, + spaceBetween: 0, + virtualTranslate: true + }; + swiper.params = Object.assign(swiper.params, overwriteParams); + swiper.originalParams = Object.assign( + swiper.originalParams, + overwriteParams + ); + }, + setTranslate() { + const swiper = this; + const { $, slides, rtlTranslate: rtl } = swiper; + for (let i = 0; i < slides.length; i += 1) { + const $slideEl = slides.eq(i); + let progress = $slideEl[0].progress; + if (swiper.params.flipEffect.limitRotation) { + progress = Math.max(Math.min($slideEl[0].progress, 1), -1); + } + const offset$$1 = $slideEl[0].swiperSlideOffset; + const rotate = -180 * progress; + let rotateY = rotate; + let rotateX = 0; + let tx = -offset$$1; + let ty = 0; + if (!swiper.isHorizontal()) { + ty = tx; + tx = 0; + rotateX = -rotateY; + rotateY = 0; + } else if (rtl) { + rotateY = -rotateY; + } + + $slideEl[0].style.zIndex = + -Math.abs(Math.round(progress)) + slides.length; + + if (swiper.params.flipEffect.slideShadows) { + // Set shadows + let shadowBefore = swiper.isHorizontal() + ? $slideEl.find('.swiper-slide-shadow-left') + : $slideEl.find('.swiper-slide-shadow-top'); + let shadowAfter = swiper.isHorizontal() + ? $slideEl.find('.swiper-slide-shadow-right') + : $slideEl.find('.swiper-slide-shadow-bottom'); + if (shadowBefore.length === 0) { + shadowBefore = swiper.$( + `
` + ); + $slideEl.append(shadowBefore); + } + if (shadowAfter.length === 0) { + shadowAfter = swiper.$( + `
` + ); + $slideEl.append(shadowAfter); + } + if (shadowBefore.length) { + shadowBefore[0].style.opacity = Math.max(-progress, 0); + } + if (shadowAfter.length) { + shadowAfter[0].style.opacity = Math.max(progress, 0); + } + } + $slideEl.transform( + `translate3d(${tx}px, ${ty}px, 0px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)` + ); + } + }, + setTransition(duration) { + const swiper = this; + const { slides, activeIndex, $wrapperEl } = swiper; + slides + .transition(duration) + .find( + '.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left' + ) + .transition(duration); + if (swiper.params.virtualTranslate && duration !== 0) { + let eventTriggered = false; + // eslint-disable-next-line + slides.eq(activeIndex).transitionEnd(function onTransitionEnd() { + if (eventTriggered) { + return; + } + if (!swiper || swiper.destroyed) { + return; + } + + eventTriggered = true; + swiper.animating = false; + const triggerEvents = ['webkitTransitionEnd', 'transitionend']; + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < triggerEvents.length; i += 1) { + $wrapperEl.trigger(triggerEvents[i]); + } + }); + } + } + } +}; +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slider.component.html + + + + + + + + +
+ +
+
+
+ + + +
+ + + + + +
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slider.component.scss + +:host { + --paginator: var(--x-color-dark-tint); + --paginator-active: var(--x-color-secondary); +} + +ion-slides { + --bullet-background: var(--paginator); + --bullet-background-active: var(--paginator-active); +} + +.no-pager { + padding-bottom: 0 !important; +} + +.slider { + padding-bottom: 30px !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slider.component.ts + +import { + Component, + QueryList, + ViewChild, + ContentChildren, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XSliderAction, + XSliderConfig, + XSliderAnimation, + XSliderActionModel, + defaultXSliderConfig, + XSliderContainerComponent, + XSliderAnimationIdentifier, + defaultXSliderPaginationConfig, +} from './x-slider.typings'; +import { map } from 'rxjs/operators'; +import { IonSlides } from '@ionic/angular'; +import { Subject, Subscription } from 'rxjs'; +import { XSlideComponent } from './x-slide/x-slide.component'; +import { Coverflow, Cube, Fade, Flip } from './x-slide.animation.providers'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-slider', + templateUrl: './x-slider.component.html', + styleUrls: ['./x-slider.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XSliderComponent extends XSliderContainerComponent { + // + //#region Props ... + // + private LAST_ACTIVE_INDEX = 0; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + //#region Slider Config ... + // + private SLIDER_CONFIG: any; + + // + get sliderConfig() { + // + if (!this.SLIDER_CONFIG) { + this.SLIDER_CONFIG = this.getConfiguration(); + } + + // + return this.SLIDER_CONFIG; + } + //#endregion + + // + //#region Slider ... + // + private SLIDER: IonSlides; + + // + @ViewChild('slider', { static: false }) + set slider(s: IonSlides) { + this.SLIDER = s; + this.handleConfigurationChanged(); + } + + // + get slider() { + return this.SLIDER; + } + //#endregion + + // + //#region Slides ... + // + private SLIDES: QueryList; + + // + @ContentChildren(XSlideComponent) + set slides(s: QueryList) { + this.SLIDES = s; + this.handleConfigurationChanged(); + } + + // + get slides() { + return this.SLIDES; + } + //#endregion + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + const isConfigChanged = changeKeys.includes( + nameof('options') + ); + const isAnimationChanged = changeKeys.includes( + nameof('animation') + ); + + // + if (isConfigChanged || isAnimationChanged) { + await this.handleConfigurationChanged(); + } + } + + afterContentInit() { + super.afterContentInit(); + + // + if (this.slider) { + this.update(); + } + + // + this.managerService.dispatchResizeEvent(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Ui Action Providers ... + hasPager() { + return this.getValue(this.options).pipe( + map((options) => options.pagination) + ); + } + //#endregion + + // + //#region UI Handlers ... + async handleSlideChanged() { + // + if (isNullOrUndefined(this.slider)) { + return; + } + + // + const index = await this.slider.getActiveIndex(); + if (index === this.LAST_ACTIVE_INDEX) { + return; + } + + // + this.LAST_ACTIVE_INDEX = index; + this.onSlideChange.emit(index); + } + //#endregion + + // + //#region Provided Actions ... + length() { + return this.slider.length(); + } + + getActiveIndex() { + return this.slider.getActiveIndex(); + } + + getPreviousIndex() { + return this.slider.getPreviousIndex(); + } + + getSwiper() { + return this.slider.getSwiper(); + } + + isBeginning() { + return this.slider.isBeginning(); + } + + isEnd() { + return this.slider.isEnd(); + } + + lockSwipeToNext(lock: boolean) { + return this.slider.lockSwipeToNext(lock); + } + + lockSwipeToPrev(lock: boolean) { + return this.slider.lockSwipeToPrev(lock); + } + + lockSwipes(lock: boolean) { + return this.slider.lockSwipes(lock); + } + + slideNext(speed?: number, runCallbacks?: boolean) { + return this.slider.slideNext(speed, runCallbacks); + } + + slidePrev(speed?: number, runCallbacks?: boolean) { + return this.slider.slidePrev(speed, runCallbacks); + } + + slideTo(index: number, speed?: number, runCallbacks?: boolean) { + return this.slider.slideTo(index, speed, runCallbacks); + } + + startAutoplay() { + return this.slider.startAutoplay(); + } + + stopAutoplay() { + return this.slider.stopAutoplay(); + } + + update() { + return this.slider.update(); + } + + updateAutoHeight() { + return this.slider.updateAutoHeight(); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XSliderAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async handleConfigurationChanged() { + // + const anim = await this.getValueAsync(this.animation); + const options = await this.getValueAsync(this.options); + + // + this.SLIDER_CONFIG = this.getConfiguration(anim, options); + } + + private getConfiguration( + animation?: XSliderAnimationIdentifier, + config?: XSliderConfig + ) { + // + let result: XSliderConfig; + + // + if (isNullOrEmptyString(animation)) { + animation = XSliderAnimation.Coverflow; + } + + // + if (!config) { + config = defaultXSliderConfig; + } + + // + result = { + ...defaultXSliderConfig, + ...config, + speed: + config.speed && config.speed <= 15000 && config.speed >= 500 + ? config.speed + : defaultXSliderConfig.speed, + initialSlide: + this.slides && + config.initialSlide <= this.slides.length && + config.initialSlide >= 0 + ? config.initialSlide + : defaultXSliderConfig.initialSlide, + }; + + // + switch (animation) { + case XSliderAnimation.Coverflow: + result = { + ...result, + ...Coverflow, + }; + break; + + case XSliderAnimation.Cube: + result = { + ...result, + ...Cube, + }; + break; + + case XSliderAnimation.Fade: + result = { + ...result, + ...Fade, + }; + break; + + case XSliderAnimation.Flip: + result = { + ...result, + ...Flip, + }; + break; + } + + // + if (result.pagination) { + result.pagination = defaultXSliderPaginationConfig; + } + + // + return result; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slider.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XSliderComponent } from './x-slider.component'; +import { XSlideComponent } from './x-slide/x-slide.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XSliderComponent, XSlideComponent], + imports: [XCardModule, CommonModule, XBaseComponentModule, XIconModule], + exports: [ + XIconModule, + XCardModule, + XSlideComponent, + XSliderComponent, + XBaseComponentModule, + ], +}) +export class XSliderModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slider.typings.ts + +import { + Input, + Inject, + NgZone, + Output, + Renderer2, + Component, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XOneOrManyType, XStandardType } from 'x-framework-core'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XSliderAnimation { + Coverflow = 'coverflow', + Cube = 'cube', + Fade = 'fade', + Flip = 'flip', +} + +export type XSliderAnimationIdentifier = XSliderAnimation | string; + +export interface XSlideAnimationProvider { + [key: string]: any; + on: { + [key: string]: any; + beforeInit?: () => void; + setTranslate?: () => void; + setTransition?: (duration?: number) => void; + }; +} + +export interface XSliderPaginationConfig { + el: string; + type: string; + clickable: boolean; +} + +export type XSliderPagination = XSliderPaginationConfig | boolean; + +export const defaultXSliderPaginationConfig: XSliderPaginationConfig = { + el: '.swiper-pagination', + type: 'bullets', + clickable: true, +}; + +export interface XSliderConfig { + lazy: boolean; + zoom: boolean; + autoplay: boolean; + initialSlide: number; + grabCursor: boolean; + autoHeight: boolean; + slidesPerView: number; + loadPrevNext: boolean; + preventClicks: boolean; + pagination: XSliderPagination; + speed: number; + on?: any; +} + +export const defaultXSliderConfig: XSliderConfig = { + lazy: true, + zoom: true, + autoplay: true, + initialSlide: 0, + grabCursor: true, + autoHeight: true, + slidesPerView: 1, + loadPrevNext: true, + preventClicks: false, + speed: 400, + pagination: true, +}; + +export enum XSliderAction { + Refresh = 'refresh', +} + +export type XSliderActionIdentifier = XSliderAction | string; + +export interface XSliderActionModel + extends XBaseActionModel {} + +// +//#region Inside Card Container ... +export interface IXSliderContainer extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + options: XStandardType; + animation: XStandardType; + + // + onSlideChange: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XSliderContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly options: XStandardType = defaultXSliderConfig; + readonly animation: XStandardType = + XSliderAnimation.Coverflow; + + // + readonly onSlideChange = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XSliderContainerComponent + extends XComponentCardContainerComponent + implements IXSliderContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + options: XStandardType = this.propertyProvider.options; + + @Input() + animation: XStandardType = + this.propertyProvider.animation; + + // + @Output() + onSlideChange: EventEmitter = this.propertyProvider.onSlideChange; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XSliderContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXSliderContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + options: this.getValue( + container ? container.options : this.options + ), + animation: this.getValue( + container ? container.animation : this.animation + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + onSlideChange: container + ? container.onSlideChange + : this.onSlideChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slide\x-slide.component.html + + + +
+ +
+
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slide\x-slide.component.scss + +.full-width { + width: 100%; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slide\x-slide.component.ts + +import { + Component, + ViewChild, + TemplateRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XSlideAction, + XSlideActionModel, + XSlideContainerComponent, +} from './x-slide.typings'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-slide', + templateUrl: './x-slide.component.html', + styleUrls: ['./x-slide.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XSlideComponent extends XSlideContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + @ViewChild('content', { static: true }) + template!: TemplateRef; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XSlideAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slide\x-slide.typings.ts + +import { + Input, + NgZone, + Inject, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../../classes/i-x-base.container'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +export enum XSlideAction { + Refresh = 'refresh', +} + +export type XSlideActionIdentifier = XSlideAction | string; + +export interface XSlideActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXSlideContainer extends IXBaseContainer { + actionProvider: Subject; +} + +@Injectable({ + providedIn: 'root' +}) +export class XSlideContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + actionProvider: Subject = new Subject(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XSlideContainerComponent + extends XBaseContainerComponent + implements IXSlideContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XSlideContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXSlideContainer + ): Observable { + return forkJoin({ + // + }).pipe( + map((data) => { + return { + // // + // ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slotter.component.html + +
+ + +
+ + +
+ +
+
+
+ + +
+ +
+ +
+
+
+ + +
+ +
+ +
+
+
+
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slotter.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slotter.component.ts + +import { + Component, + QueryList, + ContentChildren, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XSlotName, + XSlotLayout, + XSlotterAction, + XSlotterActionModel, + XSlotterContainerComponent, +} from './x-slotter.typings'; +import { map } from 'rxjs/operators'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; +import { XSlotComponent } from './x-slot/x-slot.component'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-slotter', + templateUrl: './x-slotter.component.html', + styleUrls: ['./x-slotter.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XSlotterComponent extends XSlotterContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + //#region Slots Props ... + // + private SLOTS: QueryList; + + // + @ContentChildren(XSlotComponent) + set slots(s: QueryList) { + // + this.SLOTS = s; + this.prepareSlots(); + } + + // + get slots() { + return this.SLOTS; + } + + // + startOrTopSlots: XSlotComponent[] = []; + cneterSlots: XSlotComponent[] = []; + endOrBottomSlots: XSlotComponent[] = []; + //#endregion + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Ui Providers ... + hasSlots() { + return this.slots && this.slots.length > 0; + } + + getHasCenterSlot() { + return this.applyTakeUntilWrapper(this.getValue(this.hasCenterSlot)); + } + + getHasStartOrTopSlot() { + return this.applyTakeUntilWrapper(this.getValue(this.hasStartOrTopSlot)); + } + + getHasEndOrBottomSlot() { + return this.applyTakeUntilWrapper(this.getValue(this.hasEndOrBottomSlot)); + } + + isLayoutHorizontal() { + return this.getValue(this.layout).pipe( + map((layout) => layout === XSlotLayout.HORIZONTAL) + ); + } + + isLayoutVertical() { + return this.getValue(this.layout).pipe( + map((layout) => layout === XSlotLayout.VERTICAL) + ); + } + + getLayout() { + return this.getValue(this.layout).pipe( + map((layout) => { + return layout === XSlotLayout.VERTICAL ? 'column' : 'row'; + }) + ); + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XSlotterAction.Refresh: + await this.prepareSlots(); + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async prepareSlots() { + // + this.startOrTopSlots = []; + this.cneterSlots = []; + this.endOrBottomSlots = []; + + // + this.detectChanges(); + + // + if (!this.hasSlots()) { + return; + } + + // + this.slots.forEach(async (slot) => { + // + const name = await this.getValueAsync(slot.name); + switch (name) { + case XSlotName.START: + this.startOrTopSlots.push(slot); + break; + + case XSlotName.TOP: + this.startOrTopSlots.push(slot); + break; + + case XSlotName.CENTER: + this.cneterSlots.push(slot); + break; + + case XSlotName.END: + this.endOrBottomSlots.push(slot); + break; + + case XSlotName.BOTTOM: + this.endOrBottomSlots.push(slot); + break; + } + + // + this.detectChanges(); + }); + + // + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slotter.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XSlotterComponent } from './x-slotter.component'; +import { XSlotComponent } from './x-slot/x-slot.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XSlotterComponent, XSlotComponent], + imports: [CommonModule, XBaseComponentModule], + exports: [XBaseComponentModule, XSlotterComponent, XSlotComponent] +}) +export class XSlotterModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slotter.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XOneOrManyType, XStandardType } from 'x-framework-core'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XSlotName { + START = 'start', + END = 'end', + CENTER = 'center', + TOP = 'top', + BOTTOM = 'bottom', +} + +export type XSlotNameIdentifier = XSlotName | string; + +export enum XSlotLayout { + HORIZONTAL = 'horizontal', + VERTICAL = 'vertical', +} + +export type XSlotLayoutIdentifier = XSlotLayout | string; + +export enum XSlotterAction { + Refresh = 'refresh', +} + +export type XSlotterActionIdentifier = XSlotterAction | string; + +export interface XSlotterActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXSlotterContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + layout: XStandardType; + + // + hasStartOrTopSlot: XStandardType; + startTopSlotCssClass: XStandardType>; + + // + hasCenterSlot: XStandardType; + centerSlotCssClass: XStandardType>; + + // + hasEndOrBottomSlot: XStandardType; + endBottomSlotCssClass: XStandardType>; +} + +@Injectable({ + providedIn: 'root' +}) +export class XSlotterContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject; + readonly cssClass: XStandardType>; + + // + readonly layout: XStandardType = + XSlotLayout.HORIZONTAL; + + // + readonly hasStartOrTopSlot: XStandardType = true; + readonly startTopSlotCssClass: XStandardType>; + + // + readonly hasCenterSlot: XStandardType = true; + readonly centerSlotCssClass: XStandardType>; + + // + readonly hasEndOrBottomSlot: XStandardType = true; + readonly endBottomSlotCssClass: XStandardType>; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XSlotterContainerComponent + extends XBaseContainerComponent + implements IXSlotterContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + layout: XStandardType = this.propertyProvider.layout; + + // + @Input() + hasStartOrTopSlot: XStandardType = + this.propertyProvider.hasStartOrTopSlot; + + @Input() + startTopSlotCssClass: XStandardType> = + this.propertyProvider.startTopSlotCssClass; + + // + @Input() + hasCenterSlot: XStandardType = this.propertyProvider.hasCenterSlot; + + @Input() + centerSlotCssClass: XStandardType> = + this.propertyProvider.centerSlotCssClass; + + // + @Input() + hasEndOrBottomSlot: XStandardType = + this.propertyProvider.hasEndOrBottomSlot; + + @Input() + endBottomSlotCssClass: XStandardType> = + this.propertyProvider.endBottomSlotCssClass; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XSlotterContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXSlotterContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + layout: this.getValue(container ? container.layout : this.layout), + hasStartOrTopSlot: this.getValue( + container ? container.hasStartOrTopSlot : this.hasStartOrTopSlot + ), + startTopSlotCssClass: this.getValue( + container ? container.startTopSlotCssClass : this.startTopSlotCssClass + ), + hasCenterSlot: this.getValue( + container ? container.hasCenterSlot : this.hasCenterSlot + ), + centerSlotCssClass: this.getValue( + container ? container.centerSlotCssClass : this.centerSlotCssClass + ), + hasEndOrBottomSlot: this.getValue( + container ? container.hasEndOrBottomSlot : this.hasEndOrBottomSlot + ), + endBottomSlotCssClass: this.getValue( + container ? container.endBottomSlotCssClass : this.endBottomSlotCssClass + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slot\x-slot.component.html + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slot\x-slot.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slot\x-slot.component.ts + +import { + XSlotAction, + XSlotActionModel, + XSlotContainerComponent, +} from './x-slot.typings'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ViewChild, TemplateRef } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-slot', + templateUrl: './x-slot.component.html', + styleUrls: ['./x-slot.component.scss'], + // changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XSlotComponent extends XSlotContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + @ViewChild('content', { static: false }) + content: TemplateRef; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Private ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XSlotAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slot\x-slot.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XSlotNameIdentifier } from '../x-slotter.typings'; +import { XOneOrManyType, XStandardType } from 'x-framework-core'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +export enum XSlotAction { + Refresh = 'refresh', +} + +export type XSlotActionIdentifier = XSlotAction | string; + +export interface XSlotActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXSlotContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + name: XStandardType; +} + +@Injectable({ + providedIn: 'root' +}) +export class XSlotContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider = new Subject(); + readonly cssClass: XStandardType>; + + // + readonly name: XStandardType; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XSlotContainerComponent + extends XBaseContainerComponent + implements IXSlotContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + name: XStandardType = this.propertyProvider.name; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XSlotContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXSlotContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + name: this.getValue(container ? container.name : this.name), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-spinner\x-spinner.component.html + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-spinner\x-spinner.component.scss + +:host { + display: inline-block; + position: relative; + width: 28px; + height: 28px; +} + +ion-spinner { + display: inline-block; + position: relative; + width: 28px; + height: 28px; +} + +.x-spinner { + direction: ltr !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-spinner\x-spinner.component.ts + +import { + XSpinnerAction, + XSpinnerActionModel, + XSpinnerContainerComponent, +} from './x-spinner.typings'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-spinner', + templateUrl: './x-spinner.component.html', + styleUrls: ['./x-spinner.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XSpinnerComponent extends XSpinnerContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XSpinnerAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-spinner\x-spinner.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XSpinnerComponent } from './x-spinner.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XSpinnerComponent], + imports: [CommonModule, XBaseComponentModule], + exports: [XBaseComponentModule, XSpinnerComponent] +}) +export class XSpinnerModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-spinner\x-spinner.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { + XSpinner, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XSpinnerIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XSpinnerAction { + Refresh = 'refresh', +} + +export type XSpinnerActionIdentifier = XSpinnerAction | string; + +export interface XSpinnerActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXSpinnerContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + name: XStandardType; + duration: XStandardType; + color: XStandardType; + paused: XStandardType; +} + +@Injectable({ + providedIn: 'root' +}) +export class XSpinnerContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject; + readonly cssClass: XStandardType>; + + // + readonly name: XStandardType = XSpinner.Circular; + readonly duration: XStandardType = undefined; + readonly color: XStandardType = + XColorWithBrightness.SecondaryShade; + readonly paused: XStandardType = false; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XSpinnerContainerComponent + extends XBaseContainerComponent + implements IXSpinnerContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + name: XStandardType = this.propertyProvider.name; + + @Input() + duration: XStandardType = this.propertyProvider.duration; + + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + paused: XStandardType = this.propertyProvider.paused; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XSpinnerContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXSpinnerContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + + // + name: this.getValue(container ? container.name : this.name), + duration: this.getValue(container ? container.duration : this.duration), + color: this.getValue(container ? container.color : this.color), + paused: this.getValue(container ? container.paused : this.paused), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-stepper.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + + +
+ + + + + {{ applyLocale(index + 1) }} + + + {{ applyLocale(index + 1) }} + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+ + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-stepper.component.scss + +:host { + // + // TODO: add and handle cssVariable and color applier ... + --icon-background-color: var(--x-color-tertiary); + --icon-foreground-color: var(--x-color-tertiary-contrast); + --title-foreground-color: var(--x-color-secondary); + --header-background-color: var(--x-color-light); + --header-foreground-color: var(--x-color-light-contrast); +} + +:host(::ng-deep.disableHeader) { + .mat-step-header { + pointer-events: none; + } +} + +:host ::ng-deep .mat-step-header, +:host ::ng-deep .mat-stepper-vertical, +:host ::ng-deep .mat-stepper-horizontal, +:host ::ng-deep .mat-horizontal-stepper-header-container { + background-color: transparent !important; +} + +:host ::ng-deep .mat-step-icon { + color: var(--icon-foreground-color) !important; + background-color: var(--icon-background-color) !important; +} + +:host ::ng-deep .mat-step-label, +:host ::ng-deep .mat-step-header { + color: var(--header-foreground-color) !important; + background-color: var(--header-background-color) !important; +} + +:host ::ng-deep .mat-step-label { + color: var(--title-foreground-color) !important; +} + +:host ::ng-deep .mat-vertical-stepper-header { + padding: { + top: 4px; + bottom: 4px; + } +} + +// +$transition: opacity; +$duration: 500ms; +$delay: 0; +button { + // + transition: $transition; + -webkit-transition: $transition; + + // + transition-delay: $delay; + -webkit-transition-delay: $delay; + + // + transition-duration: $duration; + -webkit-transition-duration: $duration; +} + +.x-step-actions { + button { + margin: { + left: 4px; + right: 4px; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-stepper.component.ts + +import { + ViewChild, + Component, + QueryList, + ContentChildren, +} from '@angular/core'; +import { + nameof, + mergeList, + parseXColor, + XStandardType, + isNullOrEmptyString, + XColorWithBrightness, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XStepperType, + XStepperAction, + XStepperCssVars, + XStepperActionModel, + XStepperLabelPosition, + XStepperContainerComponent, +} from './x-stepper.typings'; +import { + StepperOrientation, + StepperSelectionEvent, + STEPPER_GLOBAL_OPTIONS, +} from '@angular/cdk/stepper'; +import { concatMap, map } from 'rxjs/operators'; +import { MatStepper } from '@angular/material/stepper'; +import { Observable, Subject, Subscription } from 'rxjs'; +import { XStepComponent } from './x-step/x-step.component'; +import { XButtonType } from '../x-button/x-button.typings'; +import { XSlotName } from '../x-slotter/x-slotter.typings'; +import { XNPRButtonType } from '../classes/i-x-next-prev-reset.container'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-stepper', + templateUrl: './x-stepper.component.html', + styleUrls: ['./x-stepper.component.scss'], + providers: [ + { + provide: STEPPER_GLOBAL_OPTIONS, + useValue: { showError: true }, + }, + ], +}) +export class XStepperComponent extends XStepperContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly NPRButtonTypes = Object.assign({}, XNPRButtonType); + + // + @ViewChild('stepper', { static: false }) + private stepper: MatStepper; + + // + selected: string; + // applyThemeColors = true; + + // + //#region Steps Props ... + // + private STEPS: QueryList; + + // + @ContentChildren(XStepComponent) + set steps(s: QueryList) { + // + if (!s) { + return; + } + + // + this.STEPS = s; + this.detectChanges(); + } + + // + get steps() { + return this.STEPS; + } + //#endregion + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Colors Changed ... + // + const isIconColorChanged = changeKeys.includes( + nameof('iconColor') + ); + if (isIconColorChanged) { + await this.applyIconColor(); + } + + // + const isTitleColorChanged = changeKeys.includes( + nameof('titleColor') + ); + if (isTitleColorChanged) { + await this.applyTitleColor(); + } + + // + const isHeaderColorChanged = changeKeys.includes( + nameof('headerColor') + ); + if (isHeaderColorChanged) { + await this.applyeaderColor(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerInstanceHandlers() { + super.registerInstanceHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Task Handlers ... + //#endregion + + // + //#region UI Providers ... + // + //#region TypeDetectors ... + getOrientation() { + return this.getValue(this.type).pipe( + map((result) => result as StepperOrientation) + ); + } + + isTypeHorizontal() { + return this.getValue(this.type).pipe( + map((type) => type === XStepperType.HORIZONTAL) + ); + } + + isTypeVertical() { + return this.getValue(this.type).pipe( + map((type) => type === XStepperType.VERTICAL) + ); + } + //#endregion + + // + getLabelPositionValue(): Observable<'bottom' | 'end'> { + return this.getValue(this.labelPosition).pipe( + map((position) => + position === XStepperLabelPosition.END ? 'end' : 'bottom' + ) + ); + } + + // + hasSteps() { + return this.steps && this.steps.length > 0 ? true : false; + } + + hasActions(step: XStepComponent) { + return this.getValue(step.hasNext).pipe( + concatMap((hasNext) => + this.getValue(step.hasPrev).pipe( + map((hasPrev) => { + return { + hasNext, + hasPrev, + }; + }) + ) + ), + concatMap((hasNextPrev) => + this.getValue(step.hasReset).pipe( + map((hasReset) => { + return { + ...hasNextPrev, + hasReset, + }; + }) + ) + ), + map((info) => info.hasNext || info.hasPrev || info.hasReset) + ); + } + + // + //#region Button Providers ... + hasStepAction(step: XStepComponent, type: XNPRButtonType) { + // + switch (type) { + // + case this.NPRButtonTypes.Next: + return this.getOptionalValue(this.hasNext).pipe( + concatMap((cHas) => + this.getOptionalValue(step.hasNext).pipe( + map((sHas) => cHas || sHas) + ) + ) + ); + + // + case this.NPRButtonTypes.Prev: + return this.getOptionalValue(this.hasPrev).pipe( + concatMap((cHas) => + this.getOptionalValue(step.hasPrev).pipe( + map((sHas) => cHas || sHas) + ) + ) + ); + + // + case this.NPRButtonTypes.Reset: + return this.getOptionalValue(this.hasReset).pipe( + concatMap((cHas) => + this.getOptionalValue(step.hasReset).pipe( + map((sHas) => cHas || sHas) + ) + ) + ); + } + } + + getButtonIcon(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtons(step, type).pipe( + concatMap((btns) => + btns.s && btns.s.icon + ? this.getValue(btns.s.icon) + : this.getValue(btns.c.icon) + ) + ); + } + + getButtonType(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtons(step, type).pipe( + concatMap((btns) => + btns.s && btns.s.type + ? this.getValue(btns.s.type) + : this.getValue(btns.c.type) + ), + map((btnType) => + !isNullOrEmptyString(btnType) ? btnType : XButtonType.Raised + ) + ); + } + + getButtonTitle(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtons(step, type).pipe( + concatMap((btns) => + btns.s && btns.s.title + ? this.getResourceValue(btns.s.title) + : this.getResourceValue(btns.c.title) + ) + ); + } + + getButtonColor(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtons(step, type).pipe( + concatMap((btns) => + btns.s && btns.s.color + ? this.getValue(btns.s.color) + : this.getValue(btns.c.color) + ), + map((btnColor) => + !isNullOrEmptyString(btnColor) ? btnColor : XColorWithBrightness.Primary + ) + ); + } + + getButtonIconColor(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtons(step, type).pipe( + concatMap((btns) => + btns.s && btns.s.iconColor + ? this.getValue(btns.s.iconColor) + : this.getValue(btns.c.iconColor) + ), + map((btnColor) => + !isNullOrEmptyString(btnColor) ? btnColor : XColorWithBrightness.Primary + ) + ); + } + + getButtonDisabled(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtonDisabled(step, type); + } + + getButtonCssClass(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtons(step, type).pipe( + concatMap((btns) => + this.getArrayValue(btns.s.cssClass).pipe( + concatMap((sCss) => + this.getArrayValue(btns.c.cssClass).pipe( + map((cCss) => mergeList(sCss, cCss)) + ) + ) + ) + ) + ); + } + + getButtonIconPosition(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtons(step, type).pipe( + concatMap((btns) => + btns.s && btns.s.iconPosition + ? this.getValue(btns.s.iconPosition) + : this.getValue(btns.c.iconPosition) + ) + ); + } + + getButtonForegroundColor(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtons(step, type).pipe( + concatMap((btns) => + btns.s && btns.s.foregroundColor + ? this.getOptionalValue(btns.s.foregroundColor) + : this.getOptionalValue(btns.c.foregroundColor) + ) + ); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + async handleSelectionChanged(event: StepperSelectionEvent) { + // + if (this.uiDisabled || this.loading || !this.hasSteps()) { + return; + } + + const index = event.selectedIndex; + const stepId = await this.getValueAsync(this.steps.toArray()[index].id); + this.selectedChange.emit(stepId); + } + + async handleNPRStep( + stepper: MatStepper, + step: XStepComponent, + type: XNPRButtonType + ) { + // + switch (type) { + // + case this.NPRButtonTypes.Next: + await this.handleNextStep(stepper, step); + break; + + // + case this.NPRButtonTypes.Prev: + await this.handlePrevStep(stepper, step); + break; + + // + case this.NPRButtonTypes.Reset: + await this.handleReset(stepper, step); + break; + } + } + + async handleNextStep( + stepper: MatStepper, + step: XStepComponent, + force?: boolean + ) { + // + if ( + this.uiDisabled || + this.loading || + !stepper || + !step || + step.loading || + step.uiDisabled || + !this.hasSteps() + ) { + return; + } + + // + let nextId: string; + const stepId = await this.getValueAsync(step.id); + const stepIndex = await this.getStepIndex(stepId); + if (stepIndex === -1) { + return; + } + if (stepIndex < this.steps.length - 1) { + nextId = await this.getValueAsync(this.steps.toArray()[stepIndex + 1].id); + } + + // + const button = await this.getValueAsync(step.nextButton); + if (button.clicked) { + button.clicked.emit(); + } + + if (isNullOrEmptyString(nextId)) { + return; + } + + // + if (!force) { + // + const isUiBusy = this.uiDisabled || this.loading; + const canNextStep = await this.getValueAsync(this.canNext); + const disableNextStep = await this.getValueAsync(step.disableNext); + + // + if (isUiBusy || disableNextStep || !canNextStep) { + return; + } + + // + const isConfirmedType = this.nextConfirmer + ? this.nextConfirmer() + : this.getValue(true); + const isConfirmed = await this.getValueAsync(isConfirmedType); + + // + if (!isConfirmed) { + return; + } + + // + const isStepNextConfitmedType = step.nextConfirmer + ? step.nextConfirmer() + : this.getValue(true); + const isStepNextConfirmed = await this.getValueAsync( + isStepNextConfitmedType + ); + if (!isStepNextConfirmed) { + return; + } + } + + // + this.selected = nextId; + this.selectedChange.emit(nextId); + stepper.next(); + this.onNext.emit(stepId); + this.detectChanges(); + } + + async handlePrevStep( + stepper: MatStepper, + step: XStepComponent, + force?: boolean + ) { + // + if ( + this.uiDisabled || + this.loading || + !stepper || + !step || + step.loading || + step.uiDisabled || + !this.hasSteps() + ) { + return; + } + + // + let prevId: string; + const stepId = await this.getValueAsync(step.id); + const stepIndex = await this.getStepIndex(stepId); + if (stepIndex === -1) { + return; + } + prevId = await this.getValueAsync(this.steps.toArray()[stepIndex].id); + + // + const button = await this.getValueAsync(step.prevButton); + if (button.clicked) { + button.clicked.emit(); + } + + if (isNullOrEmptyString(prevId)) { + return; + } + + // + if (!force) { + const isUiBusy = this.uiDisabled || this.loading; + const canPrevStep = await this.getValueAsync(this.canPrev); + const disablePrevStep = await this.getValueAsync(step.disablePrev); + if (isUiBusy || disablePrevStep || !canPrevStep) { + return; + } + + // + const isConfirmedType = this.prevConfirmer + ? this.prevConfirmer() + : this.getValue(true); + const isConfirmed = await this.getValueAsync(isConfirmedType); + + // + if (!isConfirmed) { + return; + } + + // + const isStepPrevConfitmedType = step.prevConfirmer + ? step.prevConfirmer() + : this.getValue(true); + const isStepPrevConfirmed = await this.getValueAsync( + isStepPrevConfitmedType + ); + if (!isStepPrevConfirmed) { + return; + } + } + + // + this.selected = prevId; + this.selectedChange.emit(prevId); + stepper.previous(); + this.onPrev.emit(stepId); + this.detectChanges(); + } + + async handleReset( + stepper: MatStepper, + step: XStepComponent, + force?: boolean + ) { + // + if ( + this.uiDisabled || + this.loading || + !stepper || + !step || + step.loading || + step.uiDisabled || + !this.hasSteps() + ) { + return; + } + + // + const stepId = await this.getValueAsync(step.id); + const firstId = await this.getValueAsync(this.steps.toArray()[0].id); + + // + const button = await this.getValueAsync(step.resetButton); + if (button.clicked) { + button.clicked.emit(); + } + + // + if (!force) { + const isUiBusy = this.uiDisabled || this.loading; + const canResetStep = await this.getValueAsync(this.canReset); + const disableResetStep = await this.getValueAsync(step.disableReset); + if (isUiBusy || disableResetStep || !canResetStep) { + return; + } + + // + const isConfirmedType = this.resetConfirmer + ? this.resetConfirmer() + : this.getValue(true); + const isConfirmed = await this.getValueAsync(isConfirmedType); + + // + if (!isConfirmed) { + return; + } + + // + const isStepResetConfitmedType = step.resetConfirmer + ? step.resetConfirmer() + : this.getValue(true); + const isStepResetConfirmed = await this.getValueAsync( + isStepResetConfitmedType + ); + if (!isStepResetConfirmed) { + return; + } + } + + // + this.selected = firstId; + this.selectedChange.emit(firstId); + stepper.reset(); + this.onReset.emit(stepId); + this.detectChanges(); + } + + async handleToStep(stepper: MatStepper, step: XStepComponent) { + // + if ( + this.uiDisabled || + this.loading || + !step || + step.loading || + step.uiDisabled || + !this.hasSteps() || + this.selected === (await this.getValueAsync(step.id)) + ) { + return; + } + + // + const stepId = await this.getValueAsync(step.id); + const stepIndex = await this.getStepIndex(stepId); + + // + this.selected = stepId; + this.selectedChange.emit(stepId); + stepper.selectedIndex = stepIndex; + this.detectChanges(); + } + //#endregion + + // + //#region Actions ... + async next(force?: boolean) { + // + if (!this.selected || !this.stepper || !this.hasSteps()) { + return; + } + + // + const stepId = await this.getValueAsync(this.selected); + if (!stepId) { + return; + } + + // + const step = await this.getStep(stepId); + if (!step) { + return; + } + + // + await this.handleNextStep(this.stepper, step, force); + } + + async prev(force?: boolean) { + // + if (!this.selected || !this.stepper || !this.hasSteps()) { + return; + } + + // + const stepId = await this.getValueAsync(this.selected); + if (!stepId) { + return; + } + + // + const step = await this.getStep(stepId); + if (!step) { + return; + } + + // + await this.handlePrevStep(this.stepper, step, force); + } + + async reset(force?: boolean) { + // + if (!this.selected || !this.stepper || !this.hasSteps()) { + return; + } + + // + const stepId = await this.getValueAsync(this.selected); + if (!stepId) { + return; + } + + // + const step = await this.getStep(stepId); + if (!step) { + return; + } + + // + await this.handleReset(this.stepper, step, force); + } + + async to(id: XStandardType, force?: boolean) { + // + if (!id || !this.hasSteps() || !this.stepper) { + return; + } + + // + const stepId = await this.getValueAsync(id); + if (!stepId) { + return; + } + + // + const stepIndex = await this.getStepIndex(stepId); + if (stepIndex === -1) { + return; + } + + // + const step = await this.getStep(stepId); + if (!step) { + return; + } + + // + const selectedIndex = await this.getStepIndex(this.selected); + if (selectedIndex === -1 || selectedIndex === stepIndex) { + return; + } + + // + if (stepIndex > selectedIndex) { + // + // next confirm ... + // + if (!force) { + // + const isUiBusy = this.uiDisabled || this.loading; + const canNextStep = await this.getValueAsync(this.canNext); + const disableNextStep = await this.getValueAsync(step.disableNext); + + // + if (isUiBusy || disableNextStep || !canNextStep) { + return; + } + + // + const isStepNextConfitmedType = step.nextConfirmer + ? step.nextConfirmer() + : this.getValue(true); + const isStepNextConfirmed = await this.getValueAsync( + isStepNextConfitmedType + ); + if (!isStepNextConfirmed) { + return; + } + } + + // + this.selected = stepId; + this.selectedChange.emit(stepId); + } else if (stepIndex < selectedIndex) { + // + // prev Confirm ... + // + if (!force) { + const isUiBusy = this.uiDisabled || this.loading; + const canPrevStep = await this.getValueAsync(this.canPrev); + const disablePrevStep = await this.getValueAsync(step.disablePrev); + if (isUiBusy || disablePrevStep || !canPrevStep) { + return; + } + + // + const isStepPrevConfitmedType = step.prevConfirmer + ? step.prevConfirmer() + : this.getValue(true); + const isStepPrevConfirmed = await this.getValueAsync( + isStepPrevConfitmedType + ); + if (!isStepPrevConfirmed) { + return; + } + } + + // + this.selected = stepId; + this.selectedChange.emit(stepId); + } + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XStepperAction.NEXT: + await this.next(model.force); + break; + + // + case XStepperAction.PREV: + await this.prev(model.force); + break; + + // + case XStepperAction.RESET: + await this.reset(model.force); + break; + + // + case XStepperAction.TO: + if (model.payload) { + await this.to(model.payload, model.force); + } + break; + + // + case XStepperAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private getActionButtons(step: XStepComponent, type: XNPRButtonType) { + // + switch (type) { + // + case this.NPRButtonTypes.Next: + return this.getValue(this.nextButton).pipe( + concatMap((cButton) => + this.getValue(step.nextButton).pipe( + map((sButton) => ({ c: cButton, s: sButton })) + ) + ) + ); + + // + case this.NPRButtonTypes.Prev: + return this.getValue(this.prevButton).pipe( + concatMap((cButton) => + this.getValue(step.prevButton).pipe( + map((sButton) => ({ c: cButton, s: sButton })) + ) + ) + ); + + // + case this.NPRButtonTypes.Reset: + return this.getValue(this.resetButton).pipe( + concatMap((cButton) => + this.getValue(step.resetButton).pipe( + map((sButton) => ({ c: cButton, s: sButton })) + ) + ) + ); + } + } + + private getActionButtonDisabled(step: XStepComponent, type: XNPRButtonType) { + // + switch (type) { + // + case this.NPRButtonTypes.Next: + return this.getOptionalValue(this.disableNext).pipe( + concatMap((cDisable) => + this.getOptionalValue(step.disableNext).pipe( + map( + (sDisable) => + this.uiDisabled || cDisable || step.uiDisabled || sDisable + ) + ) + ) + ); + + // + case this.NPRButtonTypes.Prev: + return this.getOptionalValue(this.disablePrev).pipe( + concatMap((cDisable) => + this.getOptionalValue(step.disablePrev).pipe( + map( + (sDisable) => + this.uiDisabled || cDisable || step.uiDisabled || sDisable + ) + ) + ) + ); + + // + case this.NPRButtonTypes.Reset: + return this.getOptionalValue(this.disableReset).pipe( + concatMap((cDisable) => + this.getOptionalValue(step.disableReset).pipe( + map( + (sDisable) => + this.uiDisabled || cDisable || step.uiDisabled || sDisable + ) + ) + ) + ); + } + } + + private async getStep(id: string) { + // + if (!id || !this.hasSteps()) { + return null; + } + + // + return this.steps.toArray().find(async (p) => { + const pId = await this.getValueAsync(p.id); + return pId === id; + }); + } + + private async getStepIndex(id: string) { + // + if (!id || !this.hasSteps()) { + return -1; + } + + // + let result = -1; + const steps = this.steps.toArray(); + if (!this.hasChild(steps)) { + return result; + } + + // + for (let i = 0; i < steps.length; i++) { + // + const step = steps[i]; + const stepId = await this.getValueAsync(step.id); + if (stepId === id) { + result = i; + } + } + + // + return result; + } + + // + //#region Color Appliers ... + private async applyIconColor() { + // + const color = await this.getValueAsync(this.iconColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XStepperCssVars.IconForegroundColor, `var(${fgColor})`); + this.setCssVar(XStepperCssVars.IconBackgroundColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + private async applyTitleColor() { + // + const color = await this.getValueAsync(this.titleColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XStepperCssVars.TitleForegroundColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + private async applyeaderColor() { + // + const color = await this.getValueAsync(this.headerColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XStepperCssVars.HeaderForegroundColor, `var(${fgColor})`); + this.setCssVar(XStepperCssVars.HeaderBackgroundColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-stepper.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule } from '@angular/forms'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XFormModule } from './../x-form/x-form.module'; +import { XStepperComponent } from './x-stepper.component'; +import { XStepComponent } from './x-step/x-step.component'; +import { XButtonModule } from '../x-button/x-button.module'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XStepperComponent, XStepComponent], + imports: [ + XIconModule, + XCardModule, + XFormModule, + CommonModule, + XButtonModule, + XSlotterModule, + ReactiveFormsModule, + XBaseComponentModule, + ], + exports: [ + XIconModule, + XCardModule, + XFormModule, + CommonModule, + XButtonModule, + XStepComponent, + XSlotterModule, + XStepperComponent, + ReactiveFormsModule, + XBaseComponentModule, + ], +}) +export class XStepperModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-stepper.typings.ts + +import { + Input, + NgZone, + Inject, + Output, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { concatMap, map } from 'rxjs/operators'; +import { + XNextPrevResetAndHostContainer, + IXNextPrevResetAndHostContainer, + XNextPrevResetAndHostContainerComponent, +} from '../classes/i-x-next-prev-reset.container'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XStepperType { + HORIZONTAL = 'horizontal', + VERTICAL = 'vertical', +} + +export type XStepperTypeIdentifier = XStepperType | string; + +export enum XStepperLabelPosition { + END = 'end', + BOTTOM = 'bottom', +} + +export type XStepperLabelPositionIdentifier = XStepperLabelPosition | string; + +export enum XStepperAction { + NEXT = 'next', + PREV = 'prev', + RESET = 'reset', + TO = 'to', + + // + Refresh = 'refresh', +} + +export type XStepperActionIdentifier = XStepperAction | string; + +export interface XStepperActionModel + extends XBaseActionModel { + force?: boolean; +} + +export enum XStepperCssVars { + // + IconForegroundColor = 'icon-foreground-color', + IconBackgroundColor = 'icon-background-color', + + // + TitleForegroundColor = 'title-foreground-color', + + // + HeaderForegroundColor = 'header-foreground-color', + HeaderBackgroundColor = 'header-background-color', +} + +export type XStepperCssVarsIdentifier = XStepperCssVars | string; + +export function defaultActionConfirmer() { + return true; +} + +// +//#region Inside Card Container ... +export interface IXStepperContainer extends IXNextPrevResetAndHostContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + type: XStandardType; + showLabel: XStandardType; + labelPosition: XStandardType; + + // + selectableHeaders: XStandardType; + + // + selectedIndex: XStandardType; + selectedChange: EventEmitter; + + // + iconColor: XStandardType; + titleColor: XStandardType; + headerColor: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XStepperContainer + extends XNextPrevResetAndHostContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly type: XStandardType = + XStepperType.HORIZONTAL; + readonly showLabel: XStandardType = true; + readonly labelPosition: XStandardType = + XStepperLabelPosition.END; + + // + readonly selectableHeaders: XStandardType = false; + + // + readonly selectedIndex: XStandardType; + readonly selectedChange: EventEmitter = new EventEmitter(); + + // + readonly iconColor: XStandardType = + XColorWithBrightness.Tertiary; + readonly titleColor: XStandardType = + XColorWithBrightness.Secondary; + readonly headerColor: XStandardType = + XColorWithBrightness.Light; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XStepperContainerComponent + extends XNextPrevResetAndHostContainerComponent + implements IXStepperContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + type: XStandardType = this.propertyProvider.type; + + @Input() + showLabel: XStandardType = this.propertyProvider.showLabel; + + @Input() + labelPosition: XStandardType = + this.propertyProvider.labelPosition; + + // + @Input() + selectableHeaders: XStandardType = + this.propertyProvider.selectableHeaders; + + // + @Input() + selectedIndex: XStandardType = this.propertyProvider.selectedIndex; + + @Output() + selectedChange: EventEmitter = new EventEmitter(); + + // + @Input() + iconColor: XStandardType = this.propertyProvider.iconColor; + + @Input() + titleColor: XStandardType = + this.propertyProvider.titleColor; + + @Input() + headerColor: XStandardType = + this.propertyProvider.headerColor; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XStepperContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXStepperContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + type: this.getValue(container ? container.type : this.type), + showLabel: this.getValue( + container ? container.showLabel : this.showLabel + ), + labelPosition: this.getValue( + container ? container.labelPosition : this.labelPosition + ), + selectableHeaders: this.getValue( + container ? container.selectableHeaders : this.selectableHeaders + ), + selectedIndex: this.getValue( + container ? container.selectedIndex : this.selectedIndex + ), + iconColor: this.getValue( + container ? container.iconColor : this.iconColor + ), + titleColor: this.getValue( + container ? container.titleColor : this.titleColor + ), + headerColor: this.getValue( + container ? container.headerColor : this.headerColor + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + selectedChange: this.selectedChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-step\x-step.component.html + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-step\x-step.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-step\x-step.component.ts + +import { + XStepAction, + XStepActionModel, + XStepContainerComponent, +} from './x-step.typings'; +import { Subject, Subscription } from 'rxjs'; +import { XModalButtonRole, nameof } from 'x-framework-core'; +import { Component, ViewChild, TemplateRef } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-step', + templateUrl: './x-step.component.html', + styleUrls: ['./x-step.component.scss'], +}) +export class XStepComponent extends XStepContainerComponent { + // + //#region Props ... + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + //#region ViewChilds ... + // + @ViewChild('label', { static: false }) + label!: TemplateRef; + + // + @ViewChild('content', { static: false }) + content!: TemplateRef; + + // + @ViewChild('actions', { static: false }) + actions!: TemplateRef; + //#endregion + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerInstanceHandlers() { + super.registerInstanceHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XStepAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private defaultStepperResetConfirmer() { + // + return new Promise((resolver) => { + // + this.managerService.dialogService.presentAreYouSureYesNoDialog( + () => resolver(true), + XModalButtonRole.Destructive, + () => resolver(false) + ); + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-step\x-step.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { concatMap, map } from 'rxjs/operators'; +import { + XNextPrevResetContainer, + IXNextPrevResetContainer, + XNextPrevResetContainerComponent, +} from '../../classes/i-x-next-prev-reset.container'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { forkJoin, Subject, Observable } from 'rxjs'; +import { XManagerService } from 'x-framework-services'; +import { generateUuid, XStandardType } from 'x-framework-core'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +export enum XStepAction { + Refresh = 'refresh', +} + +export type XStepActionIdentifier = XStepAction | string; + +export interface XStepActionModel + extends XBaseActionModel { + force?: boolean; +} + +// +//#region Container ... +export interface IXStepContainer extends IXNextPrevResetContainer { + actionProvider: Subject; + id: XStandardType; + hasCustomActions: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XStepContainer + extends XNextPrevResetContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + readonly id: XStandardType; // = generateUuid('XStepComponent'); + readonly hasCustomActions: XStandardType = false; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XStepContainerComponent + extends XNextPrevResetContainerComponent + implements IXStepContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + id: XStandardType = generateUuid('XStepComponent'); + + @Input() + hasCustomActions: XStandardType = + this.propertyProvider.hasCustomActions; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XStepContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXStepContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + id: this.getValue(container ? container.id : this.id), + hasCustomActions: this.getValue( + container ? container.hasCustomActions : this.hasCustomActions + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tab-navigator\x-tab-navigator.component.html + +
+ + + + + + + + + + + {{ getResourceValue(item.title) | async }} + + + + + {{ getItemBadgeNumber(item) | async }} + + + + + +
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tab-navigator\x-tab-navigator.component.scss + +:host { + // + --highlight-foreground-color: var(--x-color-success-contrast); + --highlight-background-color: var(--x-color-success); + + // + --selected-foreground-color: var(--x-color-secondary-contrast); + --selected-background-color: var(--x-color-secondary); + + // + --badge-foreground-color: var(--x-color-dark-contrast); + --badge-background-color: var(--x-color-dark); + + // + --icon-color: var(--x-color-dark); + + // + --foreground-color: var(--x-color-light-contrast); + --background-color: var(--x-color-light); +} + +x-icon { + font-size: 12px; + color: var(--icon-color); +} + +ion-tab-button { + --color: var(--foreground-color); + --color-focused: var(--highlight-foreground-color); + --color-selected: var(--selected-foreground-color); +} + +ion-tab-button::part(native) { + color: var(--foreground-color); + + &:hover { + color: var(--highlight-foreground-color); + } +} + +ion-badge { + color: var(--badge-foreground-color); + background-color: var(--badge-background-color); +} + +.tab-selected { + background-color: var(--selected-background-color) !important; + + x-icon { + color: var(--selected-foreground-color) !important; + } +} + +.x-tab-label { + font-size: 9px !important; + + &:hover { + color: var(--highlight-foreground-color); + } +} + +.x-nav-button:hover { + color: var(--highlight-foreground-color); + background-color: var(--highlight-background-color); + + x-icon { + color: var(--highlight-foreground-color) !important; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tab-navigator\x-tab-navigator.component.ts + +import { + nameof, + parseXColor, + isNullOrEmptyString, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XTabNavigatorItem, + XTabNavigatorAction, + XTabNavigatorCssVars, + XTabNavigatorItemLayout, + XTabNavigatorActionModel, + XTabNavigatorContainerComponent, +} from './x-tab-navigator.typings'; +import { map, concatMap } from 'rxjs/operators'; +import { of, Observable, Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-tab-navigator', + templateUrl: './x-tab-navigator.component.html', + styleUrls: ['./x-tab-navigator.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XTabNavigatorComponent extends XTabNavigatorContainerComponent { + // + //#region Props ... + // + private PREPARED_ITEMS: XTabNavigatorItem[]; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + get preparedItems() { + return this.PREPARED_ITEMS; + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.prepareItems(); + await this.applyDefaultColors(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Items Changed ... + const isItemsChanged = changeKeys.includes( + nameof('items') + ); + if (isItemsChanged) { + await this.prepareItems(); + } + //#endregion + + // + //#region Colors Changed ... + // + // Color ... + const isColorChanged = changeKeys.includes( + nameof('color') + ); + if (isColorChanged) { + await this.applyColor(); + } + + // + // Icon Color ... + const isIconColorChanged = changeKeys.includes( + nameof('iconColor') + ); + if (isIconColorChanged) { + await this.applyIconColor(); + } + + // + // HighLight Color ... + const isHighLightColorChanged = changeKeys.includes( + nameof('highlightColor') + ); + if (isHighLightColorChanged) { + await this.applyHighLightedColor(); + } + + // + // Selected Color ... + const isSelectedColorChanged = changeKeys.includes( + nameof('selectedColor') + ); + if (isSelectedColorChanged) { + await this.applySelectedColor(); + } + + // + // Badge Color ... + const isBadgeColorChanged = changeKeys.includes( + nameof('badgeColor') + ); + if (isBadgeColorChanged) { + await this.applyBadgeColor(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Ui Providers ... + isDisabled(item: XTabNavigatorItem) { + return (this.getOptionalValue(item.disabled) as Observable).pipe( + map((disabled) => disabled || this.uiDisabled) + ); + } + + getItemRoute(item: XTabNavigatorItem) { + return this.getArrayValue(item.href).pipe( + map((href) => { + // + if (!this.hasChild(href)) { + return undefined; + } + + // + if (href.length === 1) { + return href[0]; + } + + // + const result = href.join('/').replace('//', '/'); + return result; + }) + ); + } + + getItemTooltip(item: XTabNavigatorItem) { + return this.getResourceValue(item.description).pipe( + concatMap((val) => { + if (isNullOrEmptyString(val.toString())) { + return this.getResourceValue(item.title); + } else { + return of(val); + } + }) + ); + } + + hideTabTitle(item: XTabNavigatorItem) { + return this.getValue(this.hideTabLabelsOnSmallScreens).pipe( + concatMap((hideOnMobile) => { + return hideOnMobile + ? this.isMobileUi$ + : this.getValue(item.layout).pipe( + map((layout) => layout === XTabNavigatorItemLayout.LABEL_HIDE) + ); + }) + ); + } + + hasBadgeNumber(item: XTabNavigatorItem) { + return this.getValue(item.badge).pipe(map((badge) => badge > 0)); + } + + getItemBadgeNumber(item: XTabNavigatorItem) { + return (this.getOptionalValue(item.badge) as Observable).pipe( + map((badge) => { + if (!badge) { + return this.applyLocale(0); + } else { + return this.applyLocale(badge); + } + }) + ); + } + //#endregion + + // + //#region Private ... + private async prepareItems() { + // + const items = await this.getValueAsync(this.items); + this.PREPARED_ITEMS = items; + + // + this.detectChanges(); + } + + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XTabNavigatorAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + //#region Color Appliers ... + private async applyDefaultColors() { + // + await this.applyColor(); + await this.applyIconColor(); + await this.applyBadgeColor(); + await this.applySelectedColor(); + await this.applyHighLightedColor(); + } + + // + // Color ... + private async applyColor() { + // + const color = await this.getValueAsync(this.color); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XTabNavigatorCssVars.ForegroundColor, `var(${fgColor})`); + this.setCssVar(XTabNavigatorCssVars.BackgroundColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + // + // Icon Color ... + private async applyIconColor() { + // + const color = await this.getValueAsync(this.iconColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XTabNavigatorCssVars.IconColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + // + // HighLight Color ... + private async applyHighLightedColor() { + // + const color = await this.getValueAsync(this.highlightColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XTabNavigatorCssVars.HighlightedForegroundColor, + `var(${fgColor})` + ); + this.setCssVar( + XTabNavigatorCssVars.HighlightedBackgroundColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + // + // Selected Color ... + private async applySelectedColor() { + // + const color = await this.getValueAsync(this.selectedColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XTabNavigatorCssVars.SelectedForegroundColor, + `var(${fgColor})` + ); + this.setCssVar( + XTabNavigatorCssVars.SelectedBackgroundColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + // + // Badge Color ... + private async applyBadgeColor() { + // + const color = await this.getValueAsync(this.badgeColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XTabNavigatorCssVars.BadgeForegroundColor, + `var(${fgColor})` + ); + this.setCssVar( + XTabNavigatorCssVars.BadgeBackgroundColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tab-navigator\x-tab-navigator.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XTabNavigatorComponent } from './x-tab-navigator.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XTabNavigatorComponent], + imports: [CommonModule, XBaseComponentModule, XIconModule], + exports: [XIconModule, XBaseComponentModule, XTabNavigatorComponent] +}) +export class XTabNavigatorModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tab-navigator\x-tab-navigator.tools.ts + +import { + XTabNavigatorItem, + XTabNavigatorItemLayout, +} from './x-tab-navigator.typings'; +import { XPage } from 'x-framework-core'; + +export function toTabNavigatiorItem(page: XPage) { + // + if (!page) { + return null; + } + + // + const result: XTabNavigatorItem = { + id: page.id ? page.id : page.name, + title: page.title, + description: page.description, + href: page.baseRoute, + disabled: false, + icon: page.icon, + badge: 0, + layout: XTabNavigatorItemLayout.ICON_TOP, + }; + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tab-navigator\x-tab-navigator.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XColorWithBrightness, + XRouterTargetIdentifier, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XNavigatorListItem } from '../x-page/x-navigator/x-navigator.typings'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export interface XTabNavigatorItem + extends Pick< + XNavigatorListItem, + 'id' | 'title' | 'description' | 'href' | 'disabled' + > { + // + icon: XStandardType; + badge?: XStandardType; + // + download?: XStandardType; + routerTarget?: XStandardType; + // + layout: XStandardType; +} + +export enum XTabNavigatorItemLayout { + ICON_TOP = 'icon-top', + ICON_END = 'icon-end', + ICON_HIDE = 'icon-hide', + ICON_START = 'icon-start', + LABEL_HIDE = 'label-hide', + ICON_BOTTOM = 'icon-bottom', +} + +export type XTabNavigatorItemLayoutIdentifier = + | XTabNavigatorItemLayout + | string; + +export enum XTabNavigatorNavigatorSlot { + TOP = 'top', + BOTTOM = 'bottom', +} + +export type XTabNavigatorNavigatorSlotIdentifier = + | XTabNavigatorNavigatorSlot + | string; + +export enum XTabNavigatorAction { + Refresh = 'refresh', +} + +export type XTabNavigatorActionIdentifier = XTabNavigatorAction | string; + +export interface XTabNavigatorActionModel + extends XBaseActionModel {} + +export enum XTabNavigatorCssVars { + // + HighlightedForegroundColor = 'highlight-foreground-color', + HighlightedBackgroundColor = 'highlight-background-color', + + // + SelectedForegroundColor = 'selected-foreground-color', + SelectedBackgroundColor = 'selected-background-color', + + // + BadgeForegroundColor = 'badge-foreground-color', + BadgeBackgroundColor = 'badge-background-color', + + // + IconColor = 'icon-color', + + // + ForegroundColor = 'foreground-color', + BackgroundColor = 'background-color', +} + +// +//#region Container ... +export interface IXTabNavigatorContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + navigatorSlot: XStandardType; + hideTabLabelsOnSmallScreens: XStandardType; + items: XStandardType; + + // + color: XStandardType; + foregroundColor: XStandardType; + iconColor: XStandardType; + badgeColor: XStandardType; + highlightColor: XStandardType; + selectedColor: XStandardType; +} + +@Injectable({ + providedIn: 'root' +}) +export class XTabNavigatorContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly navigatorSlot: XStandardType = + XTabNavigatorNavigatorSlot.BOTTOM; + readonly hideTabLabelsOnSmallScreens: XStandardType = true; + readonly items: XStandardType = []; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + readonly iconColor: XStandardType = + XColorWithBrightness.Dark; + readonly badgeColor: XStandardType = + XColorWithBrightness.WarningTint; + readonly highlightColor: XStandardType = + XColorWithBrightness.WarningShade; + readonly selectedColor: XStandardType = + XColorWithBrightness.TertiaryShade; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XTabNavigatorContainerComponent + extends XBaseContainerComponent + implements IXTabNavigatorContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + navigatorSlot: XStandardType = + this.propertyProvider.navigatorSlot; + + @Input() + hideTabLabelsOnSmallScreens: XStandardType = + this.propertyProvider.hideTabLabelsOnSmallScreens; + + @Input() + items: XStandardType = this.propertyProvider.items; + + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + @Input() + iconColor: XStandardType = this.propertyProvider.iconColor; + + @Input() + badgeColor: XStandardType = + this.propertyProvider.badgeColor; + + @Input() + highlightColor: XStandardType = + this.propertyProvider.highlightColor; + + @Input() + selectedColor: XStandardType = + this.propertyProvider.selectedColor; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XTabNavigatorContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXTabNavigatorContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + navigatorSlot: this.getValue( + container ? container.navigatorSlot : this.navigatorSlot + ), + hideTabLabelsOnSmallScreens: this.getValue( + container + ? container.hideTabLabelsOnSmallScreens + : this.hideTabLabelsOnSmallScreens + ), + items: this.getValue(container ? container.items : this.items), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + iconColor: this.getValue( + container ? container.iconColor : this.iconColor + ), + badgeColor: this.getValue( + container ? container.badgeColor : this.badgeColor + ), + highlightColor: this.getValue( + container ? container.highlightColor : this.highlightColor + ), + selectedColor: this.getValue( + container ? container.selectedColor : this.selectedColor + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-table\x-table.component.html + + + + + + + + +
+ +
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ {{ title }} +
+
+ + + + + + + + +
+ {{ value }} +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + {{ + preparedPaginatorOptions.pageInfoProvider( + managerService.currentLocale, + currentPage, + pageSize, + totalPages, + totalItems + ) + }} + + + + +
+ + +
+ + {{ applyLocale(pageSize) }} + +
+
+
+ + + + + + + + + + + + + +
+
+ + + + + + + + + + + + +
+ +
+ + + + + + + +
+
+ + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-table\x-table.component.scss + +::ng-deep :root, +:host { + // + --divider-color: var(--x-color-dark); + + // + --highlight-color: var(--x-color-warning-shade-contrast); + --highlight-background-color: var(--x-color-warning-shade); + + // + --odd-rows-background-color: inherit; + --even-rows-background-color: inherit; + + // + --check-box-border-color: var(--x-color-dark); + --check-box-check-mark-color: var(--x-color-dark); +} + +x-card { + width: inherit; + height: inherit; +} + +.x-table { + width: inherit; + height: inherit; +} + +mat-row, +td.mat-cell, +mat-header-row, +mat-footer-row, +th.mat-header-cell, +td.mat-footer-cell { + border-bottom-color: var(--divider-color) !important; +} + +.mat-paginator, +.mat-paginator-page-size .mat-select-trigger { + color: unset !important; +} + +.mat-paginator { + background-color: unset !important; +} + +.x-table-paginator-wrapper { + width: 100%; +} + +.x-page-select-icon { + margin: { + left: 4px; + right: 4px; + } +} + +.x-table-cell, +.x-table-header { + flex: 1 !important; +} + +.x-header-title { + width: fit-content; +} + +.x-table-selection { + flex: none !important; +} + +.x-table-cell { + color: inherit !important; + background-color: inherit !important; +} + +.x-table-row { + &:hover { + color: var(--highlight-color) !important; + background-color: var(--highlight-background-color) !important; + } + + &:not(:hover):nth-child(odd) { + background-color: var(--odd-rows-background-color) !important; + } + + &:not(:hover):nth-child(even) { + background-color: var(--even-rows-background-color) !important; + } +} + +.x-check-box { + --border-color: var(--check-box-border-color, inherit) !important; + --checkmark-color: var(--check-box-check-mark-color, inherit) !important; + --background-checked: auto !important; + --border-color-checked: auto !important; +} + +.x-selection-master { + visibility: hidden !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-table\x-table.component.ts + +import { + nameof, + XColor, + toArray, + XLocale, + isNumber, + hasChild, + isFunction, + applyLocale, + XResourceIDs, + XModalButtonRole, + XPickerColumnOption, +} from 'x-framework-core'; +import { + XTableAction, + XTableActionModel, + XTableColumnOption, + XTableItemPosition, + XTableActionButton, + XTableActionOptions, + XTablePaginatorOptions, + XTableContainerComponent, + X_TABLE_SELECTION_HEADER_IDENTIFIER, +} from './x-table.typings'; +import { Subject, Subscription } from 'rxjs'; +import { map, concatMap } from 'rxjs/operators'; +import { MatSort } from '@angular/material/sort'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import { SelectionModel } from '@angular/cdk/collections'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatTableDataSource } from '@angular/material/table'; +import { Component, ViewChild, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-table', + templateUrl: './x-table.component.html', + styleUrls: ['./x-table.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XTableComponent extends XTableContainerComponent { + // + //#region Props ... + private MODAL: any; + private SORT: MatSort; + private DATA: any[] = []; + private PAGE_SIZE: number; + private TOTAL_PAGES: number; + private TOTAL_ITEMS: number; + private CURRENT_PAGE: number; + private PAGINATOR: MatPaginator; + private IS_ROWS_SELECTABLE: boolean; + private DISPLAYED_COLUMNS: string[] = []; + private ACTION_OPTIONS: XTableActionOptions; + private SELECTION_SUBSCRIPTION: Subscription; + private SELECTION = new SelectionModel(); + private COLUMNS_DATA: XTableColumnOption[]; + private PAGINATOR_OPTIONS: XTablePaginatorOptions; + private PAGINATOR_INIT_SUBSCRIPTION: Subscription; + private PAGINATOR_PAGE_SUBSCRIPTION: Subscription; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private DATA_SOURCE = new MatTableDataSource(this.DATA); + + // + pageNumberInput = false; + selectionRowTitle = X_TABLE_SELECTION_HEADER_IDENTIFIER; + + // + //#region Getters ... + get data() { + return this.DATA; + } + + get isRowsSelectable() { + return this.IS_ROWS_SELECTABLE; + } + + get columnsData() { + return this.COLUMNS_DATA; + } + + get dataSource() { + return this.DATA_SOURCE; + } + + get displayedColumns() { + return this.DISPLAYED_COLUMNS; + } + + get selection() { + return this.SELECTION; + } + + get preparedPaginatorOptions() { + return this.PAGINATOR_OPTIONS; + } + + get pageSize() { + return this.PAGE_SIZE; + } + + get currentPage() { + return this.CURRENT_PAGE; + } + + get totalPages() { + return this.TOTAL_PAGES; + } + + get totalItems() { + return this.TOTAL_ITEMS; + } + + get selectedItems() { + return this.isRowsSelectable && this.selection + ? this.selection.selected + : []; + } + + get preparedActionOptions() { + return this.ACTION_OPTIONS; + } + //#endregion + + // + //#region Sort Props ... + @ViewChild(MatSort, { static: false }) + set sort(v: MatSort) { + if (!v) { + return; + } + + // + this.SORT = v; + this.DATA_SOURCE.sort = v; + } + + get sort() { + return this.SORT; + } + //#endregion + + // + //#region Paginator Props ... + @ViewChild(MatPaginator, { static: false }) + set paginator(v: MatPaginator) { + if (!v) { + return; + } + + // + this.PAGINATOR = v; + this.DATA_SOURCE.paginator = v; + this.registerPaginatorHandlers(); + } + + get paginator() { + return this.PAGINATOR; + } + //#endregion + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.preparePaginatorOptions(); + } + + async afterViewInit() { + super.afterViewInit(); + + // + await this.prepareUIData(); + await this.prepareSelection(); + await this.prepareActionOptions(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + let isLoadingChanged = changeKeys.includes( + nameof('loading') + ); + let isUiDisabledChanged = changeKeys.includes( + nameof('uiDisabled') + ); + if (isLoadingChanged || isUiDisabledChanged) { + await this.prepareActionOptions(); + } + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + // Handle Prepare Data Source ... + if ( + changeKeys.includes(nameof('properties')) || + changeKeys.includes(nameof('selectableRows')) + ) { + await this.prepareDataSource(); + } + + // + // Handle Prepare Selection ... + if ( + changeKeys.includes(nameof('allowMultiSelect')) || + changeKeys.includes(nameof('initialSelectedValues')) + ) { + await this.prepareSelection(); + } + + // + // Handle Paginator Options ... + if (changeKeys.includes(nameof('paginatorOptions'))) { + await this.preparePaginatorOptions(); + } + + // + // Handle Action Options ... + if (changeKeys.includes(nameof('actionOptions'))) { + await this.prepareActionOptions(); + } + + // + // Handle UI Data ... + if (changeKeys.includes(nameof('selectableRows'))) { + await this.prepareUIData(); + } + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + isSelectionRow(col: XTableColumnOption) { + return this.getValue(col.name).pipe( + map((colName) => colName === X_TABLE_SELECTION_HEADER_IDENTIFIER) + ); + } + + isSearchbarTopPositioned() { + return this.getValue(this.searchBarPosition).pipe( + map((pos) => pos === XTableItemPosition.TOP) + ); + } + + isPaginatorTopPositioned() { + return this.getValue(this.paginatorPosition).pipe( + map((pos) => pos === XTableItemPosition.TOP) + ); + } + + isActionBarTopPositioned() { + return this.getValue(this.actionBarPosition).pipe( + map((pos) => pos === XTableItemPosition.TOP) + ); + } + + getHeaderClass(col: XTableColumnOption) { + return this.getValue(col.name).pipe( + concatMap((colName) => + this.getValue(this.headerCellCssClass).pipe( + map((cssClass) => { + return { colName, cssClass }; + }) + ) + ), + map((info) => { + // + let result: string[] = []; + + // + const className = `x-table-header-${info.colName.toString()}`; + const cssClasses = toArray(info.cssClass); + + // + result = [className].concat(cssClasses); + + // + return result; + }) + ); + } + + getCellClass(col: XTableColumnOption) { + return this.getValue(col.name).pipe( + concatMap((colName) => + this.getValue(this.cellCssClass).pipe( + map((cssClass) => { + return { colName, cssClass }; + }) + ) + ), + map((info) => { + // + let result: string[] = []; + + // + const className = `x-table-cell-${info.colName.toString()}`; + const cssClasses = toArray(info.cssClass); + + // + result = [className].concat(cssClasses); + + // + return result; + }) + ); + } + + getColumnName(col: XTableColumnOption) { + return this.getValue(col.name).pipe(map(name => name.toString())); + } + //#endregion + + // + //#region UI Handlers ... + async handleRowClicked(row: any) { + // + const isClickable = await this.getValueAsync(this.clickableRows); + const isSelectable = await this.getValueAsync(this.selectableRows); + + // + if (!isClickable && !this.selectableRows) { + return; + } + + // + if (isSelectable && this.selection) { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + this.selection.toggle(row); + } + + // + if (isClickable) { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + this.rowClicked.emit(row); + } + } + + async handleRowSelected(event: CustomEvent | any, row: any) { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + const isSelectable = await this.getValueAsync(this.selectableRows); + if (!isSelectable) { + return; + } + + // + const selected = event.detail.checked as boolean; + + // + if (!selected) { + return; + } + + // + this.rowSelected.emit(row); + } + + async handleFilterData(event: CustomEvent | any) { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + const query = + event && event.detail && event.detail.value + ? event.detail.value.toString().trim().toLowerCase() + : ''; + + // + await this.filterData(query); + } + + async handlePageChange(event: CustomEvent | any) { + // + const selectedPage = (event.detail.value as number) || undefined; + if (!selectedPage) { + return; + } + + // + await this.toPage(selectedPage); + } + + async handlePageSizeChange() { + // + if (!this.preparedPaginatorOptions || !this.pageSize) { + return; + } + + // + const pageSizes = await this.getValueAsync( + this.preparedPaginatorOptions.pageSizes + ); + if (!hasChild(pageSizes)) { + return; + } + + const options: XPickerColumnOption[] = pageSizes.map((ps) => { + // + const opt: XPickerColumnOption = { + text: this.applyLocale(ps.toString()), + value: ps, + disabled: ps === this.pageSize, + }; + + // + return opt; + }); + + // + if (this.MODAL) { + await this.managerService.dialogService.pickerController.dismiss(); + this.MODAL = null; + } + + // + this.MODAL = await this.managerService.dialogService.presentPicker({ + buttons: [ + { + text: this.resourceProvider('accept'), + role: XModalButtonRole.Selected, + handler: (value) => { + if (!value) { + return; + } + + // + const pageSize = value.pageSize.value; + this.changePageSize(pageSize); + this.detectChanges(); + }, + }, + { + text: this.resourceProvider('cancel'), + role: XModalButtonRole.Cancel, + }, + ], + columns: [{ name: 'pageSize', options }], + }); + + // + await this.MODAL.onWillDismiss(); + this.MODAL = null; + } + + async handleActionBarItemClick( + action: XTableActionButton, + selectedItems: any[] + ) { + // + if (action && action.handler && isFunction(action.handler)) { + await this.getValueAsync(action.handler(selectedItems)); + } + } + //#endregion + + // + //#region Actions ... + isAllSelected() { + const numSelected = this.selection.selected.length; + const numRows = this.dataSource.data.length; + return numSelected === numRows; + } + + isAllFilteredSelected() { + // + if ( + !this.dataSource || + !hasChild(this.dataSource.filteredData) || + this.selection.isEmpty() || + !hasChild(this.selection.selected) + ) { + return false; + } + + // + const filteredData = this.dataSource.filteredData; + const result = this.selection.selected.every((s) => + filteredData.includes(s) + ); + + // + return result; + } + + isSelected(item: any) { + // + if ( + !item || + !this.data || + !this.selection || + !this.isRowsSelectable || + !this.data.includes(item) + ) { + return false; + } + + // + return this.selection.isSelected(item); + } + + toggleSelectAll() { + this.isAllSelected() + ? this.selection.clear() + : this.dataSource.data.forEach((row) => this.selection.select(row)); + } + + selectAll() { + // + if ( + !this.isRowsSelectable || + !this.selection || + !this.dataSource || + !this.dataSource.data + ) { + return; + } + + // + this.dataSource.data.forEach((i) => this.selection.select(i)); + } + + selectAllFilteredData() { + // + if ( + !this.isRowsSelectable || + !this.selection || + !this.dataSource || + !this.dataSource.data || + !this.dataSource.filteredData + ) { + return; + } + + // + this.dataSource.filteredData.forEach((i) => this.selection.select(i)); + } + + deselectAll() { + // + if ( + !this.isRowsSelectable || + !this.selection || + !this.dataSource || + !this.dataSource.data || + !this.dataSource.filteredData + ) { + return; + } + + // + this.dataSource.filteredData.forEach((i) => this.selection.deselect(i)); + } + + deselectAllFilteredData() { + // + if (!this.isRowsSelectable || !this.selection) { + return; + } + + // + this.selection.clear(); + } + + inverseSelection() { + // + if ( + !this.isRowsSelectable || + !this.selection || + !this.dataSource || + !this.dataSource.data + ) { + return; + } + + // + this.dataSource.data.forEach((i) => this.selection.toggle(i)); + } + + async filterData(query: string) { + // + this.dataSource.filter = query.trim().toLowerCase(); + this.prepareActionOptions(); + + // + await this.firstPage(); + } + + async firstPage() { + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + this.paginator.firstPage(); + + // + this.detectChanges(); + } + + async lastPage() { + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + this.paginator.lastPage(); + + // + this.detectChanges(); + } + + async nextPage() { + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + this.paginator.nextPage(); + + // + this.detectChanges(); + } + + async previousPage() { + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + this.paginator.previousPage(); + + // + this.detectChanges(); + } + + async toPage(index: number) { + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + index = index === 0 ? 1 : index; + + // + this.paginator.pageIndex = index - 1; + this.paginator.page.next({ + pageIndex: index - 1, + pageSize: this.pageSize, + length: this.totalItems, + }); + + // + this.detectChanges(); + } + + async changePageSize(size: number) { + // + if (!size || !this.paginator) { + return; + } + + // + this.paginator.pageSize = size; + this.paginator.page.emit({ + pageIndex: 0, + pageSize: size, + length: this.totalItems, + }); + + // + this.detectChanges(); + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XTableAction.Refresh: + this.detectChanges(); + break; + + // + case XTableAction.DE_SELECT_ALL: + this.deselectAll(); + break; + + // + case XTableAction.INVERSE_SELECTION: + this.inverseSelection(); + break; + + // + case XTableAction.SELECT_ALL: + this.selectAll(); + break; + + // + case XTableAction.TOGGLE_SELECT_ALL: + this.toggleSelectAll(); + break; + + // + case XTableAction.FIRST_PAGE: + await this.firstPage(); + break; + + // + case XTableAction.LAST_PAGE: + await this.lastPage(); + break; + + // + case XTableAction.NEXT_PAGE: + await this.nextPage(); + break; + + // + case XTableAction.PREVIOUS_PAGE: + await this.previousPage(); + break; + + // + case XTableAction.TO_PAGE: + if (model.payload && isNumber(model.payload)) { + await this.toPage(model.payload as number); + } + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async prepareUIData() { + // + this.IS_ROWS_SELECTABLE = await this.getValueAsync(this.selectableRows); + + // + this.detectChanges(); + } + + private async prepareDataSource() { + // + this.DATA = []; + this.COLUMNS_DATA = []; + this.DISPLAYED_COLUMNS = []; + if (!this.properties) { + return; + } + + // + const dataSource = await this.getValueAsync(this.properties.items); + this.DATA = dataSource; + + // + const isSelectableRows = await this.getValueAsync(this.selectableRows); + const baseColumnsData = !isSelectableRows + ? [] + : [ + { + name: X_TABLE_SELECTION_HEADER_IDENTIFIER, + } as XTableColumnOption, + ]; + this.COLUMNS_DATA = baseColumnsData.concat(this.properties.columns); + + // + const displayedColumns = await Promise.all( + this.COLUMNS_DATA.map( + async (cd) => (await this.getValueAsync(cd.name)) as string + ) + ); + this.DISPLAYED_COLUMNS = displayedColumns; + + // + this.DATA_SOURCE.data = this.DATA; + + // + this.detectChanges(); + } + + private async prepareSelection() { + // + const isMultipleSelection = await this.getValueAsync(this.allowMultiSelect); + const initialSelectedValues = await this.getValueAsync( + this.initialSelectedValues + ); + + // + this.SELECTION = new SelectionModel( + isMultipleSelection, + initialSelectedValues + ); + + // + // notify event emitter when has initial selected values ... + if (hasChild(initialSelectedValues)) { + this.rowSelectionChanged.emit(initialSelectedValues); + } + + // + this.registerSelectionChangeHandler(); + + // + this.detectChanges(); + } + + private registerSelectionChangeHandler() { + // + // Unsubscribe Last ... + if (this.SELECTION_SUBSCRIPTION) { + this.SELECTION_SUBSCRIPTION.unsubscribe(); + this.SELECTION_SUBSCRIPTION = null; + } + + // + // Subscribe New ... + this.SELECTION_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.SELECTION.changed.asObservable() + ).subscribe(async () => { + // + await this.prepareActionOptions(); + this.rowSelectionChanged.emit(this.SELECTION.selected); + }); + } + + private async preparePaginatorOptions() { + // + const options = await this.getValueAsync(this.paginatorOptions); + const defaultOptions = this.defaultPaginatorOptions(); + const preparedOptions: XTablePaginatorOptions = { + ...defaultOptions, + ...options, + }; + + // + if (options) { + // + if (!options.first) { + preparedOptions.first = undefined; + } else { + // + const first = options.first; + + // + if (first.disabled) { + const firstDisabled = await this.getValueAsync(first.disabled); + preparedOptions.first.disabled = + defaultOptions.first.disabled && firstDisabled; + } else { + preparedOptions.first.disabled = defaultOptions.first.disabled; + } + + // + if (!first.icon) { + preparedOptions.first.icon = defaultOptions.first.icon; + } + + // + if (!first.title) { + preparedOptions.first.title = defaultOptions.first.title; + } + } + + // + if (!options.last) { + preparedOptions.last = undefined; + } else { + // + const last = options.last; + + // + if (last.disabled) { + const lastDisabled = await this.getValueAsync(last.disabled); + preparedOptions.last.disabled = + defaultOptions.last.disabled && lastDisabled; + } else { + preparedOptions.last.disabled = defaultOptions.last.disabled; + } + + // + if (!last.icon) { + preparedOptions.last.icon = defaultOptions.last.icon; + } + + // + if (!last.title) { + preparedOptions.last.title = defaultOptions.last.title; + } + } + + // + if (!options.next) { + preparedOptions.next = undefined; + } else { + // + const next = options.next; + + // + if (next.disabled) { + const nextDisabled = await this.getValueAsync(next.disabled); + preparedOptions.next.disabled = + defaultOptions.next.disabled && nextDisabled; + } else { + preparedOptions.next.disabled = defaultOptions.next.disabled; + } + + // + if (!next.icon) { + preparedOptions.next.icon = defaultOptions.next.icon; + } + + // + if (!next.title) { + preparedOptions.next.title = defaultOptions.next.title; + } + } + + // + if (!options.prev) { + preparedOptions.prev = undefined; + } else { + // + const prev = options.prev; + + // + if (prev.disabled) { + const prevDisabled = await this.getValueAsync(prev.disabled); + preparedOptions.prev.disabled = + defaultOptions.prev.disabled && prevDisabled; + } else { + preparedOptions.prev.disabled = defaultOptions.prev.disabled; + } + + // + if (!prev.icon) { + preparedOptions.prev.icon = defaultOptions.prev.icon; + } + + // + if (!prev.title) { + preparedOptions.prev.title = defaultOptions.prev.title; + } + } + } + + // + this.PAGINATOR_OPTIONS = preparedOptions; + + // + this.detectChanges(); + } + + private async registerPaginatorHandlers() { + // + if (!this.paginator) { + return; + } + + // + //#region Handle Paginator Ready ... + // + // Unsubscribe Last ... + if (this.PAGINATOR_INIT_SUBSCRIPTION) { + this.PAGINATOR_INIT_SUBSCRIPTION.unsubscribe(); + this.PAGINATOR_INIT_SUBSCRIPTION = null; + } + + // + // Subscribe New ... + this.PAGINATOR_INIT_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.paginator.initialized + ).subscribe(async () => { + // + await this.preparePaginatorOptions(); + + // + //#region Initial Page Info Reciever ... + // + this.PAGE_SIZE = this.paginator.pageSize; + this.TOTAL_ITEMS = this.paginator.length; + this.PAGE_SIZE = this.paginator.pageSize; + this.CURRENT_PAGE = this.paginator.pageIndex + 1; + + // + const totalPagesFloat = this.paginator.length / this.paginator.pageSize; + this.TOTAL_PAGES = Math.floor(totalPagesFloat); + + // + if ( + totalPagesFloat > this.TOTAL_PAGES && + totalPagesFloat < this.TOTAL_PAGES + 1 + ) { + this.TOTAL_PAGES++; + } + //#endregion + + // + this.detectChanges(); + }); + //#endregion + + // + //#region Handle Page Changes ... + // + // Unsubscribe last ... + if (this.PAGINATOR_PAGE_SUBSCRIPTION) { + this.PAGINATOR_PAGE_SUBSCRIPTION.unsubscribe(); + this.PAGINATOR_PAGE_SUBSCRIPTION = null; + } + + // + // Subscribe New ... + this.PAGINATOR_PAGE_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.paginator.page.asObservable() + ).subscribe(async (res) => { + // + // ReNew Pagination Options ... + await this.preparePaginatorOptions(); + + // + // Handle Current Page ... + this.PAGE_SIZE = res.pageSize; + this.TOTAL_ITEMS = res.length; + this.PAGE_SIZE = res.pageSize; + this.CURRENT_PAGE = res.pageIndex + 1; + this.pageChanged.emit(this.CURRENT_PAGE); + + // + //#region Handle Calculation of Total pages ... + const totalPagesFloat = res.length / res.pageSize; + this.TOTAL_PAGES = Math.floor(totalPagesFloat); + + // + if ( + totalPagesFloat > this.TOTAL_PAGES && + totalPagesFloat < this.TOTAL_PAGES + 1 + ) { + this.TOTAL_PAGES++; + } + //#endregion + + // + this.detectChanges(); + }); + //#endregion + } + + private defaultPaginatorOptions() { + // + const options: XTablePaginatorOptions = { + pageSizes: this.config.pageSizes, + pageSize: this.config.defaultPageSize, + first: { + title: 'first', + icon: XIconNames.first, + disabled: !this.paginator || !this.paginator.hasPreviousPage(), + }, + last: { + title: 'last', + icon: XIconNames.last, + disabled: !this.paginator || !this.paginator.hasNextPage(), + }, + prev: { + title: 'previous', + icon: XIconNames.previous, + disabled: !this.paginator || !this.paginator.hasPreviousPage(), + }, + next: { + title: 'next', + icon: XIconNames.next, + disabled: !this.paginator || !this.paginator.hasNextPage(), + }, + pageInfoProvider: ( + locale: XLocale, + currentPage: number, + pageSize: number, + totalPages: number + ) => { + // + const info = `${currentPage}/${totalPages}`; + return applyLocale(info, locale); + }, + }; + + // + return options; + } + + private async prepareActionOptions() { + // + let preparedOptions: XTableActionOptions = {}; + const options = await this.getValueAsync(this.actionOptions); + const defaultOptions = this.defaultActionOptions(); + + // + if (!options) { + preparedOptions = defaultOptions; + } else { + // + if (options.start) { + preparedOptions.start = [...options.start, ...defaultOptions.start]; + } else { + preparedOptions.start = [...defaultOptions.start]; + } + + // + if (options.end) { + preparedOptions.end = [...defaultOptions.end, ...options.end]; + } else { + preparedOptions.end = [...defaultOptions.end]; + } + } + + // + this.ACTION_OPTIONS = preparedOptions; + + // + this.detectChanges(); + } + + private defaultActionOptions() { + // + const options: XTableActionOptions = { + start: [ + { + title: XResourceIDs.delete, + icon: XIconNames.remove, + color: XColor.Danger, + disabled: + this.uiDisabled || + this.loading || + !this.selection || + !hasChild(this.data) || + !hasChild(this.selection.selected), + hidden: !this.isRowsSelectable, + handler: (items: any) => { + // + this.managerService.dialogService.presentActionModal({ + header: XResourceIDs.are_you_sure, + buttons: [], + keyboardClose: true, + backdropDismiss: true, + hasOk: true, + okHandler: () => { + this.removeItems.emit(items); + }, + hasCancel: true, + }); + }, + }, + ], + end: [ + { + title: XResourceIDs.select_all, + icon: XIconNames.select_all, + color: XColor.Tertiary, + disabled: + this.uiDisabled || + this.loading || + !this.selection || + !hasChild(this.data) || + this.isAllSelected(), + hidden: !this.isRowsSelectable, + handler: () => { + this.selectAll(); + }, + }, + { + title: XResourceIDs.select_all_group, + icon: XIconNames.select_all_group, + color: XColor.Tertiary, + disabled: + this.uiDisabled || + this.loading || + !hasChild(this.dataSource.filteredData) || + this.isAllFilteredSelected(), + hidden: + !this.dataSource || + !this.isRowsSelectable || + !this.dataSource.filter, + handler: () => { + this.selectAllFilteredData(); + }, + }, + { + title: XResourceIDs.deselect_all, + icon: XIconNames.deselect_all, + color: XColor.Tertiary, + disabled: + this.uiDisabled || + this.loading || + !this.selection || + !hasChild(this.data) || + !hasChild(this.selection.selected), + hidden: !this.isRowsSelectable, + handler: () => { + this.deselectAll(); + }, + }, + { + title: XResourceIDs.deselect_all_group, + icon: XIconNames.deselect_all_group, + color: XColor.Tertiary, + disabled: + this.uiDisabled || + this.loading || + !this.selection || + !hasChild(this.data) || + !this.isAllFilteredSelected() || + !hasChild(this.selection.selected), + hidden: + !this.isRowsSelectable || + !this.dataSource.filter || + !hasChild(this.dataSource.filteredData), + handler: () => { + this.deselectAllFilteredData(); + }, + }, + { + title: XResourceIDs.inverse_selection, + icon: XIconNames.inverse_selection, + color: XColor.Tertiary, + disabled: + this.uiDisabled || + this.loading || + !this.selection || + !hasChild(this.data) || + !hasChild(this.selection.selected), + hidden: !this.isRowsSelectable, + handler: () => { + this.inverseSelection(); + }, + }, + ], + }; + + // + return options; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-table\x-table.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XTableComponent } from './x-table.component'; +import { XCardModule } from '../x-card/x-card.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XEmptyModule } from '../x-empty/x-empty.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XTableComponent], + imports: [ + XCardModule, + XIconModule, + XEmptyModule, + CommonModule, + XBaseComponentModule, + ], + exports: [ + XCardModule, + XIconModule, + XEmptyModule, + XTableComponent, + XBaseComponentModule, + ], +}) +export class XTableModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-table\x-table.typings.ts + +import { + Inject, + Input, + NgZone, + Output, + Component, + Renderer2, + Injectable, + ElementRef, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XLocale, + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XCardEmptyContainer, + IXCardEmptyContainer, + XCardEmptyContainerComponent, +} from '../classes/i-x-card-empty.container'; +import { concatMap, map } from 'rxjs/operators'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export type XTableColumn = keyof T; + +export type XTableColumnIdentifier = XTableColumn | string; + +export interface XTableColumnOption { + // + hidden?: XStandardType; + name: XTableColumnIdentifier; + sortable?: XStandardType; + icon?: XStandardType; + customColumn?: XStandardType; + title: XStandardType; + description?: XStandardType; +} + +export interface XTableConfig { + // + items: XStandardType; + columns: XTableColumnOption[]; +} + +export type XSortDirection = 'asc' | 'desc'; + +export enum XTableItemPosition { + TOP = 'top', + BOTTOM = 'bottom', +} + +export type XTableItemPositionIdentifier = XTableItemPosition | string; + +export const X_TABLE_SELECTION_HEADER_IDENTIFIER = 'selection'; + +export interface XTableActionButton { + hidden?: XStandardType; + disabled?: XStandardType; + icon?: XStandardType; + color?: XStandardType; + title?: XStandardType; + handler?: (item?: any) => XStandardType; +} + +export interface XTablePaginatorOptions { + pageSizes?: XStandardType; + pageSize?: XStandardType; + first?: XTableActionButton; + last?: XTableActionButton; + next?: XTableActionButton; + prev?: XTableActionButton; + pageInfoProvider?: ( + locale: XLocale, + currentPage: number, + pageSize: number, + totalPages: number, + totalItems: number + ) => string; +} + +export interface XTableActionOptions { + end?: XTableActionButton[]; + start?: XTableActionButton[]; +} + +export enum XTableAction { + // + Refresh = 'refresh', + + // + TOGGLE_SELECT_ALL = 'toggle-select-all', + SELECT_ALL = 'select-all', + DE_SELECT_ALL = 'deselect-all', + INVERSE_SELECTION = 'inverse-selection', + FIRST_PAGE = 'first-page', + LAST_PAGE = 'last-page', + NEXT_PAGE = 'next-page', + PREVIOUS_PAGE = 'previous-page', + TO_PAGE = 'to-page', +} + +export type XTableActionIdentifier = XTableAction | string; + +export interface XTableActionModel + extends XBaseActionModel {} + +// +//#region Inside Card/Empty Container ... +export interface IXTableContainer extends IXCardEmptyContainer { + // + //#region Other Props ... + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + canClearSort: XStandardType; + cellCssClass: XStandardType>; + properties: XTableConfig; + cellTemplate: TemplateRef; + customCellTemplate: TemplateRef; + //#endregion + + // + //#region Header Props ... + // + showHeader: XStandardType; + stickyHeader: XStandardType; + headerCellCssClass: XStandardType>; + + // + headerColor: XStandardType; + headerForegroundColor: XStandardType; + //#endregion + + // + //#region Click Props ... + clickableRows: XStandardType; + rowClicked: EventEmitter; + //#endregion + + // + //#region Selection props ... + selectableRows: XStandardType; + rowSelected: EventEmitter; + rowSelectionChanged: EventEmitter; + allowMultiSelect: XStandardType; + initialSelectedValues: XStandardType; + //#endregion + + // + //#region Filter/Search Props ... + showSearchBar: XStandardType; + searchBarPosition: XStandardType; + searchBarColor: XStandardType; + searchTitle: XStandardType; + //#endregion + + // + //#region Paginator Props ... + showPaginator: XStandardType; + paginatorPosition: XStandardType; + paginatorColor: XStandardType; + paginatorOptions: XStandardType; + pageChanged: EventEmitter; + //#endregion + + // + //#region Action Props ... + showActionBar: XStandardType; + actionBarPosition: XStandardType; + actionBarColor: XStandardType; + actionOptions: XStandardType; + removeItems: EventEmitter; + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +export class XTableContainer + extends XCardEmptyContainer + implements Readonly +{ + // + //#region Props ... + // + //#region Other Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + readonly canClearSort: XStandardType = true; + readonly cellCssClass: XStandardType>; + readonly properties: XTableConfig; + readonly cellTemplate: TemplateRef; + readonly customCellTemplate: TemplateRef; + //#endregion + + // + //#region Header Props ... + // + readonly showHeader: XStandardType = true; + readonly stickyHeader: XStandardType = true; + readonly headerCellCssClass: XStandardType>; + + // + readonly headerColor: XStandardType = + XColorWithBrightness.Dark; + readonly headerForegroundColor: XStandardType = false; + //#endregion + + // + //#region Click Props ... + readonly clickableRows: XStandardType = false; + readonly rowClicked: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Selection props ... + readonly selectableRows: XStandardType = false; + readonly rowSelected: EventEmitter = new EventEmitter(); + readonly rowSelectionChanged: EventEmitter = new EventEmitter(); + readonly allowMultiSelect: XStandardType = true; + readonly initialSelectedValues: XStandardType = []; + //#endregion + + // + //#region Filter/Search Props ... + readonly showSearchBar: XStandardType = false; + readonly searchBarPosition: XStandardType = + XTableItemPosition.TOP; + readonly searchBarColor: XStandardType = + XColorWithBrightness.Light; + readonly searchTitle: XStandardType = + XResourceIDs.search; + //#endregion + + // + //#region Paginator Props ... + readonly showPaginator: XStandardType; + readonly paginatorPosition: XStandardType = + XTableItemPosition.BOTTOM; + readonly paginatorColor: XStandardType = + XColorWithBrightness.Light; + readonly paginatorOptions: XStandardType; + readonly pageChanged: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Action Props ... + readonly showActionBar: XStandardType; + readonly actionBarPosition: XStandardType = + XTableItemPosition.TOP; + readonly actionBarColor: XStandardType = + XColorWithBrightness.Light; + readonly actionOptions: XStandardType; + readonly removeItems: EventEmitter = new EventEmitter(); + //#endregion + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XTableContainerComponent + extends XCardEmptyContainerComponent + implements IXTableContainer +{ + // + //#region Props ... + // + //#region Other Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + canClearSort: XStandardType = this.propertyProvider.canClearSort; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + @Input() + cellCssClass: XStandardType> = + this.propertyProvider.cellCssClass; + + @Input() + properties: XTableConfig = this.propertyProvider.properties; + + @Input() + cellTemplate: TemplateRef = this.propertyProvider.cellTemplate; + + @Input() + customCellTemplate: TemplateRef = + this.propertyProvider.customCellTemplate; + //#endregion + + // + //#region Header Props ... + @Input() + showHeader: XStandardType = this.propertyProvider.showHeader; + + @Input() + stickyHeader: XStandardType = this.propertyProvider.stickyHeader; + + @Input() + headerColor: XStandardType = + this.propertyProvider.headerColor; + + @Input() + headerForegroundColor: XStandardType = + this.propertyProvider.headerForegroundColor; + + @Input() + headerCellCssClass: XStandardType> = + this.propertyProvider.headerCellCssClass; + //#endregion + + // + //#region Click Props ... + @Input() + clickableRows: XStandardType = this.propertyProvider.clickableRows; + + @Output() + rowClicked: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Selection props ... + @Input() + selectableRows: XStandardType = this.propertyProvider.selectableRows; + + @Output() + rowSelected: EventEmitter = new EventEmitter(); + + @Output() + rowSelectionChanged: EventEmitter = new EventEmitter(); + + @Input() + allowMultiSelect: XStandardType = + this.propertyProvider.allowMultiSelect; + + @Input() + initialSelectedValues: XStandardType = + this.propertyProvider.initialSelectedValues; + //#endregion + + // + //#region Filter/Search Props ... + @Input() + showSearchBar: XStandardType = this.propertyProvider.showSearchBar; + + @Input() + searchBarPosition: XStandardType = + this.propertyProvider.searchBarPosition; + + @Input() + searchBarColor: XStandardType = + this.propertyProvider.searchBarColor; + + @Input() + searchTitle: XStandardType = + this.propertyProvider.searchTitle; + //#endregion + + // + //#region Paginator Props ... + @Input() + showPaginator: XStandardType = this.propertyProvider.showPaginator; + + @Input() + paginatorPosition: XStandardType = + this.propertyProvider.paginatorPosition; + + @Input() + paginatorColor: XStandardType = + this.propertyProvider.paginatorColor; + + @Input() + paginatorOptions: XStandardType = + this.propertyProvider.paginatorOptions; + + @Output() + pageChanged: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Action Props ... + @Input() + showActionBar: XStandardType = this.propertyProvider.showActionBar; + + @Input() + actionBarPosition: XStandardType = + this.propertyProvider.actionBarPosition; + + @Input() + actionBarColor: XStandardType = + this.propertyProvider.actionBarColor; + + @Input() + actionOptions: XStandardType = + this.propertyProvider.actionOptions; + + @Output() + removeItems: EventEmitter = new EventEmitter(); + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XTableContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXTableContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + canClearSort: this.getValue( + container ? container.canClearSort : this.canClearSort + ), + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + cellCssClass: this.getValue( + container ? container.cellCssClass : this.cellCssClass + ), + properties: this.getValue( + container ? container.properties : this.properties + ), + cellTemplate: this.getValue( + container ? container.cellTemplate : this.cellTemplate + ), + customCellTemplate: this.getValue( + container ? container.customCellTemplate : this.customCellTemplate + ), + showHeader: this.getValue( + container ? container.showHeader : this.showHeader + ), + stickyHeader: this.getValue( + container ? container.stickyHeader : this.stickyHeader + ), + headerColor: this.getValue( + container ? container.headerColor : this.headerColor + ), + headerForegroundColor: this.getValue( + container + ? container.headerForegroundColor + : this.headerForegroundColor + ), + headerCellCssClass: this.getValue( + container ? container.headerCellCssClass : this.headerCellCssClass + ), + clickableRows: this.getValue( + container ? container.clickableRows : this.clickableRows + ), + selectableRows: this.getValue( + container ? container.selectableRows : this.selectableRows + ), + allowMultiSelect: this.getValue( + container ? container.allowMultiSelect : this.allowMultiSelect + ), + initialSelectedValues: this.getValue( + container + ? container.initialSelectedValues + : this.initialSelectedValues + ), + showSearchBar: this.getValue( + container ? container.showSearchBar : this.showSearchBar + ), + searchBarPosition: this.getValue( + container ? container.searchBarPosition : this.searchBarPosition + ), + searchBarColor: this.getValue( + container ? container.searchBarColor : this.searchBarColor + ), + searchTitle: this.getValue( + container ? container.searchTitle : this.searchTitle + ), + showPaginator: this.getValue( + container ? container.showPaginator : this.showPaginator + ), + paginatorPosition: this.getValue( + container ? container.paginatorPosition : this.paginatorPosition + ), + paginatorColor: this.getValue( + container ? container.paginatorColor : this.paginatorColor + ), + paginatorOptions: this.getValue( + container ? container.paginatorOptions : this.paginatorOptions + ), + showActionBar: this.getValue( + container ? container.showActionBar : this.showActionBar + ), + actionBarPosition: this.getValue( + container ? container.actionBarPosition : this.actionBarPosition + ), + actionBarColor: this.getValue( + container ? container.actionBarColor : this.actionBarColor + ), + actionOptions: this.getValue( + container ? container.actionOptions : this.actionOptions + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + rowSelectionChanged: this.rowSelectionChanged, + pageChanged: this.pageChanged, + rowClicked: this.rowClicked, + rowSelected: this.rowSelected, + removeItems: this.removeItems, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tabs.component.html + + + + + + + + +
+ +
+
+
+ + + +
+ + + +
+ +
+
+ + + + +
+
+
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tabs.component.scss + +:host { + // + --tab-header-color: var(--x-color-primary); + + // + --tab-indicator-color: var(--x-color-secondary); + + // + --tab-paginator-foreground-color: var(--x-color-secondary-contrast); + --tab-paginator-background-color: var(--x-color-secondary); + + // + --active-tab-foreground-color: var(--x-color-secondary-contrast); + --active-tab-background-color: var(--x-color-secondary); +} + +:host ::ng-deep.mat-ink-bar { + background-color: var(--tab-indicator-color) !important; +} + +.x-wrapper { + // + width: inherit; + height: inherit; +} + +x-card { + // + width: inherit; + height: inherit; +} + +:host ::ng-deep .x-wrapper { + .mat-tab-header { + display: flex; + } + + &.hide-header { + .mat-tab-header { + display: none; + } + } +} + +:host ::ng-deep.swipable { + // + cursor: grab; + + // + -webkit-user-select: none; /* Chrome all / Safari all */ + -moz-user-select: none; /* Firefox all */ + -ms-user-select: none; /* IE 10+ */ + user-select: none; /* Likely future */ +} + +:host ::ng-deep.mat-tab-label-active { + opacity: 1 !important; + + // + color: var(--active-tab-foreground-color) !important; + background-color: var(--active-tab-background-color) !important; + + .mat-tab-label-content { + // + color: var(--active-tab-foreground-color) !important; + background-color: var(--active-tab-background-color) !important; + + * { + // + color: var(--active-tab-foreground-color) !important; + background-color: var(--active-tab-background-color) !important; + } + } +} + +:host ::ng-deep .mat-tab-header-pagination { + // + background-color: var(--tab-paginator-background-color) !important; +} + +:host ::ng-deep.mat-tab-header-pagination-chevron { + border-color: var(--tab-paginator-foreground-color) !important; +} + +:host ::ng-deep .mat-tab-labels { + // + background-color: var(--tab-header-color) !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tabs.component.ts + +import { + QueryList, + Component, + ContentChildren, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + parseXColor, + isNullOrEmptyString, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XTabAlign, + XTabsAction, + XTabsCssVars, + XTabChangeEvent, + XTabsActionModel, + XTabsHeaderPosition, + XTabsContainerComponent, +} from './x-tabs.typings'; +import { map, concatMap } from 'rxjs/operators'; +import { XTabComponent } from './x-tab/x-tab.component'; +import { MatTabChangeEvent } from '@angular/material/tabs'; +import { of, Subject, Subscription, Observable } from 'rxjs'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-tabs', + templateUrl: './x-tabs.component.html', + styleUrls: ['./x-tabs.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XTabsComponent extends XTabsContainerComponent { + // + //#region Props ... + // + private TABS: QueryList; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + canPrev$ = new Subject(); + canNext$ = new Subject(); + + // + get tabs() { + return this.TABS; + } + + // + @ContentChildren(XTabComponent) + set tabs(s: QueryList) { + this.TABS = s; + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.handleSelectedIndex(); + await this.applyDefaultColors(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + const hasSelectedIndex = changeKeys.includes( + nameof('selectedIndex') + ); + if (hasSelectedIndex) { + await this.handleSelectedIndex(); + } + + // + //#region Color Changed ... + // + // Tab Header Color ... + const isTabHeaderColorChanged = changeKeys.includes( + nameof('headerColor') + ); + if (isTabHeaderColorChanged) { + await this.applyTabHeaderColor(); + } + + // + // Active Tab Color ... + const isActiveTabColorChanged = changeKeys.includes( + nameof('activeTabColor') + ); + if (isActiveTabColorChanged) { + await this.applyActiveTabColor(); + } + + // + // Tab Indicator Color ... + const isTabIndicatorColorChanged = changeKeys.includes( + nameof('tabIndicatorColor') + ); + if (isTabIndicatorColorChanged) { + await this.applyTabIndicatorColor(); + } + + // + // Tab Paginator Color ... + const isTabPaginatorColorChanged = changeKeys.includes( + nameof('tabPaginatorColor') + ); + if (isTabPaginatorColorChanged) { + await this.applyTabPaginatorColor(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Task Handlers ... + //#endregion + + // + //#region UI Providers ... + isDynamicHeight() { + return this.getValue(this.dynamicHeight).pipe( + map((isDynamic) => (isDynamic ? '' : undefined)) + ); + } + + isStretched() { + return this.getValue(this.alignTabs).pipe( + map((align) => (align === XTabAlign.STRETCH ? '' : undefined)) + ); + } + + getTabAlignValue() { + return this.isStretched().pipe( + concatMap((isStretched) => { + if (isStretched) { + return of(undefined); + } else { + return this.getValue(this.alignTabs); + } + }) + ); + } + + swipeDisabled() { + return this.getValue(this.swipable).pipe( + map((swipable) => !swipable || this.uiDisabled) + ); + } + + getHeaderPosition(): Observable<'above' | 'below'> { + return this.getValue(this.headerPosition).pipe( + map((position) => + position === XTabsHeaderPosition.ABOVE ? 'above' : 'below' + ) + ); + } + //#endregion + + // + //#region UI Handlers ... + async handleSelectedTabChanged(event: MatTabChangeEvent) { + // + const mEvent: XTabChangeEvent = { + index: event.index, + tab: this.tabs.toArray()[event.index], + }; + + // + const canLoop = await this.canLoop(); + + // + if (event.index === 0) { + // + this.canPrev$.next(false || canLoop); + this.canNext$.next(true); + } else if (event.index === this.tabs.length - 1) { + // + this.canPrev$.next(true); + this.canNext$.next(false || canLoop); + } else if (event.index > 0 && event.index < this.tabs.length) { + // + this.canPrev$.next(true); + this.canNext$.next(true); + } + + // + this.selectedTabChange.emit(mEvent); + this.selectedIndexChange.emit(mEvent.index); + } + + async handleSwipeEnd() { + await this.prev(); + } + + async handleSwipeStart() { + await this.next(); + } + //#endregion + + // + //#region Actions ... + length() { + return this.tabs ? this.tabs.length : 0; + } + + getTab(index: number) { + return this.checkIndex(index) ? this.tabs.toArray()[index] : null; + } + + async next() { + // + const isLocked = await this.isLocked(); + if (isLocked) { + return; + } + + // + const current = this.selectedIndex; + const canLoop = await this.canLoop(); + const next = this.checkIndex(current + 1) + ? current + 1 + : canLoop + ? 0 + : current; + + // + this.selectedIndex = next; + this.detectChanges(); + } + + async prev() { + // + const isLocked = await this.isLocked(); + if (isLocked) { + return; + } + + // + const current = this.selectedIndex; + const canLoop = await this.canLoop(); + const prev = this.checkIndex(current - 1) + ? current - 1 + : canLoop + ? this.tabs.length - 1 + : current; + + // + this.selectedIndex = prev; + this.detectChanges(); + } + + async first() { + // + const isLocked = await this.isLocked(); + if (isLocked) { + return; + } + + // + this.selectedIndex = 0; + this.detectChanges(); + } + + async last() { + // + const isLocked = await this.isLocked(); + if (isLocked) { + return; + } + + // + this.selectedIndex = this.tabs.length - 1; + this.detectChanges(); + } + + async to(index: number) { + // + const isLocked = await this.isLocked(); + if (isLocked) { + return; + } + + // + if (!this.checkIndex(index)) { + return; + } + + // + this.selectedIndex = index; + this.detectChanges(); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XTabsAction.FIRST: + await this.first(); + break; + + // + case XTabsAction.LAST: + await this.last(); + break; + + // + case XTabsAction.NEXT: + await this.next(); + break; + + // + case XTabsAction.PREV: + await this.prev(); + break; + + // + case XTabsAction.TO: + if (model.payload) { + await this.to(model.payload); + } + break; + + // + case XTabsAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private checkIndex(index: number) { + return this.tabs && index < this.tabs.length && index >= 0; + } + + private async canLoop() { + // + const result = await this.getValueAsync(this.loopTabs); + return result || false; + } + + private async isLocked() { + // + const result = await this.getValueAsync(this.lock); + return result || false; + } + + private async handleSelectedIndex() { + // + const startIndex = await this.getValueAsync(this.selectedIndex); + + // + if (!this.checkIndex(startIndex)) { + // + this.selectedIndex = 0; + return; + } + + // + this.selectedIndex = startIndex; + } + + // + //#region Color Appliers ... + private async applyDefaultColors() { + // + await this.applyActiveTabColor(); + await this.applyTabHeaderColor(); + await this.applyTabPaginatorColor(); + await this.applyTabIndicatorColor(); + } + + private async applyTabHeaderColor() { + // + const color = await this.getValueAsync(this.headerColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XTabsCssVars.TabHeaderColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + private async applyActiveTabColor() { + // + const color = await this.getValueAsync(this.activeTabColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XTabsCssVars.ActiveTabForegroundColor, `var(${fgColor})`); + this.setCssVar(XTabsCssVars.ActiveTabBackgroundColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + private async applyTabIndicatorColor() { + // + const color = await this.getValueAsync(this.tabIndicatorColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XTabsCssVars.TabIndicatorColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + private async applyTabPaginatorColor() { + // + const color = await this.getValueAsync(this.tabPaginatorColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XTabsCssVars.TabPaginatorForegroundColor, `var(${fgColor})`); + this.setCssVar(XTabsCssVars.TabPaginatorBackgroundColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tabs.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XTabsComponent } from './x-tabs.component'; +import { XCardModule } from '../x-card/x-card.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XTabComponent } from './x-tab/x-tab.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XTabsContainerComponent } from './x-tabs.typings'; + +@NgModule({ + declarations: [XTabsComponent, XTabComponent, XTabsContainerComponent], + imports: [CommonModule, XBaseComponentModule, XIconModule, XCardModule], + exports: [ + XIconModule, + XCardModule, + XTabComponent, + XTabsComponent, + XBaseComponentModule + ] +}) +export class XTabsModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tabs.typings.ts + +import { + Input, + Inject, + NgZone, + Output, + Renderer2, + Component, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { XManagerService } from 'x-framework-services'; +import { XTabComponent } from './x-tab/x-tab.component'; +import { MatTabChangeEvent } from '@angular/material/tabs'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XTabsHeaderPosition { + ABOVE = 'above', + BELOW = 'below', +} + +export type XTabsHeaderPositionIdentifier = XTabsHeaderPosition | string; + +export interface XTabChangeEvent extends Pick { + tab: XTabComponent; +} + +export enum XTabAlign { + START = 'start', + CENTER = 'center', + END = 'end', + STRETCH = 'stretch', +} + +export type XTabAlignIdentifier = XTabAlign | string; + +export enum XTabsAction { + FIRST = 'first', + LAST = 'last', + PREV = 'prev', + NEXT = 'next', + TO = 'to', + + // + Refresh = 'refresh', +} + +export type XTabsActionIdentifier = XTabsAction | string; + +export interface XTabsActionModel + extends XBaseActionModel {} + +export enum XTabsCssVars { + // + TabHeaderColor = 'tab-header-color', + + // + TabIndicatorColor = 'tab-indicator-color', + + // + ActiveTabForegroundColor = 'active-tab-foreground-color', + ActiveTabBackgroundColor = 'active-tab-background-color', + + // + TabPaginatorForegroundColor = 'tab-paginator-foreground-color', + TabPaginatorBackgroundColor = 'tab-paginator-background-color', +} + +export type XTabsCssVarsIdentifier = XTabsCssVars | string; + +// +//#region Inside Card Container ... +export interface IXTabsContainer extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + selectedIndex: number; + selectedIndexChange: EventEmitter; + + // + showHeader: XStandardType; + disablePagination: XStandardType; + alignTabs: XStandardType; + dynamicHeight: XStandardType; + animationDuration: XStandardType; + disableRipple: XStandardType; + headerPosition: XStandardType; + + // + loopTabs: XStandardType; + swipable: XStandardType; + animationDone: EventEmitter; + + // + lock: XStandardType; + + // + color: XStandardType; + headerColor: XStandardType; + activeTabColor: XStandardType; + tabIndicatorColor: XStandardType; + tabPaginatorColor: XStandardType; + + // + selectedTabChange: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XTabsContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly selectedIndex: number = 0; + readonly selectedIndexChange: EventEmitter = + new EventEmitter(); + + // + readonly showHeader: XStandardType = true; + readonly disablePagination: XStandardType = false; + readonly alignTabs: XStandardType = XTabAlign.START; + readonly dynamicHeight: XStandardType = false; + readonly disableRipple: XStandardType = false; + readonly headerPosition: XStandardType = + XTabsHeaderPosition.ABOVE; + + // + readonly loopTabs: XStandardType = true; + readonly swipable: XStandardType = false; + readonly animationDuration: XStandardType = '500ms'; + + // + readonly lock: XStandardType = false; + + // + readonly color: XStandardType = + XColorWithBrightness.Transparent; + readonly headerColor: XStandardType = + XColorWithBrightness.Primary; + readonly activeTabColor: XStandardType = + XColorWithBrightness.Secondary; + readonly tabIndicatorColor: XStandardType = + XColorWithBrightness.SecondaryTint; + readonly tabPaginatorColor: XStandardType = + XColorWithBrightness.SecondaryShade; + + // + readonly animationDone: EventEmitter = new EventEmitter(); + readonly selectedTabChange: EventEmitter = + new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export class XTabsContainerComponent + extends XComponentCardContainerComponent + implements IXTabsContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + selectedIndex: number = this.propertyProvider.selectedIndex; + + @Output() + selectedIndexChange: EventEmitter = new EventEmitter(); + + // + @Input() + showHeader: XStandardType = this.propertyProvider.showHeader; + + @Input() + disablePagination: XStandardType = + this.propertyProvider.disablePagination; + + @Input() + alignTabs: XStandardType = + this.propertyProvider.alignTabs; + + @Input() + dynamicHeight: XStandardType = this.propertyProvider.dynamicHeight; + + @Input() + disableRipple: XStandardType = this.propertyProvider.disableRipple; + + @Input() + headerPosition: XStandardType = + this.propertyProvider.headerPosition; + + // + @Input() + loopTabs: XStandardType = this.propertyProvider.loopTabs; + + @Input() + swipable: XStandardType = this.propertyProvider.swipable; + + @Input() + animationDuration: XStandardType = + this.propertyProvider.animationDuration; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + headerColor: XStandardType = + this.propertyProvider.headerColor; + + @Input() + activeTabColor: XStandardType = + this.propertyProvider.activeTabColor; + + @Input() + tabIndicatorColor: XStandardType = + this.propertyProvider.tabIndicatorColor; + + @Input() + tabPaginatorColor: XStandardType = + this.propertyProvider.tabPaginatorColor; + + // + @Output() + animationDone: EventEmitter = new EventEmitter(); + + @Output() + selectedTabChange: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XTabsContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXTabsContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + selectedIndex: this.getValue( + container ? container.selectedIndex : this.selectedIndex + ), + showHeader: this.getValue( + container ? container.showHeader : this.showHeader + ), + disablePagination: this.getValue( + container ? container.disablePagination : this.disablePagination + ), + alignTabs: this.getValue( + container ? container.alignTabs : this.alignTabs + ), + dynamicHeight: this.getValue( + container ? container.dynamicHeight : this.dynamicHeight + ), + animationDuration: this.getValue( + container ? container.animationDuration : this.animationDuration + ), + disableRipple: this.getValue( + container ? container.disableRipple : this.disableRipple + ), + headerPosition: this.getValue( + container ? container.headerPosition : this.headerPosition + ), + loopTabs: this.getValue( + container ? container.loopTabs : this.loopTabs + ), + swipable: this.getValue( + container ? container.swipable : this.swipable + ), + lock: this.getValue(container ? container.lock : this.lock), + color: this.getValue(container ? container.color : this.color), + headerColor: this.getValue( + container ? container.headerColor : this.headerColor + ), + activeTabColor: this.getValue( + container ? container.activeTabColor : this.activeTabColor + ), + tabIndicatorColor: this.getValue( + container ? container.tabIndicatorColor : this.tabIndicatorColor + ), + tabPaginatorColor: this.getValue( + container ? container.tabPaginatorColor : this.tabPaginatorColor + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + animationDone: this.animationDone, + selectedTabChange: this.selectedTabChange, + selectedIndexChange: this.selectedIndexChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tab\x-tab.component.html + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tab\x-tab.component.scss + +.full-width { + width: 100%; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tab\x-tab.component.ts + +import { + Component, + ViewChild, + TemplateRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XTabAction, + XTabActionModel, + XTabContainerComponent, +} from './x-tab.typings'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-tab', + templateUrl: './x-tab.component.html', + styleUrls: ['./x-tab.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XTabComponent extends XTabContainerComponent { + // + //#region Props ... + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + @ViewChild('label', { static: false }) + label!: TemplateRef; + + // + @ViewChild('content', { static: false }) + content!: TemplateRef; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XTabAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tab\x-tab.typings.ts + +import { + Input, + Inject, + NgZone, + Renderer2, + ElementRef, + ChangeDetectorRef, +} from '@angular/core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../../classes/i-x-base.container'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { Component, Injectable } from '@angular/core'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +export enum XTabAction { + Refresh = 'refresh', +} + +export type XTabActionIdentifier = XTabAction | string; + +export interface XTabActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXTabContainer extends IXBaseContainer { + actionProvider: Subject; +} + +@Injectable({ + providedIn: 'root' +}) +export class XTabContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XTabContainerComponent + extends XBaseContainerComponent + implements IXTabContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XTabContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps(container?: IXTabContainer): Observable { + return forkJoin({ + // + }).pipe( + map((data) => { + return { + // + // ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-theme\x-theme-typings.ts + +// +//#region Form Interfaces ... +export interface XThemeBaseFormModel { + name: string; + } + + export interface XThemeDarkFormModel { + // + //#region Dark ... + darkXColorPrimary: string; + darkXColorPrimaryContrast: string; + darkXColorPrimaryShade: string; + darkXColorPrimaryShadeContrast: string; + darkXColorPrimaryTint: string; + darkXColorPrimaryTintContrast: string; + darkXColorSecondary: string; + darkXColorSecondaryContrast: string; + darkXColorSecondaryShade: string; + darkXColorSecondaryShadeContrast: string; + darkXColorSecondaryTint: string; + darkXColorSecondaryTintContrast: string; + darkXColorTertiary: string; + darkXColorTertiaryContrast: string; + darkXColorTertiaryShade: string; + darkXColorTertiaryShadeContrast: string; + darkXColorTertiaryTint: string; + darkXColorTertiaryTintContrast: string; + darkXColorSuccess: string; + darkXColorSuccessContrast: string; + darkXColorSuccessShade: string; + darkXColorSuccessShadeContrast: string; + darkXColorSuccessTint: string; + darkXColorSuccessTintContrast: string; + darkXColorWarning: string; + darkXColorWarningContrast: string; + darkXColorWarningShade: string; + darkXColorWarningShadeContrast: string; + darkXColorWarningTint: string; + darkXColorWarningTintContrast: string; + darkXColorDanger: string; + darkXColorDangerContrast: string; + darkXColorDangerShade: string; + darkXColorDangerShadeContrast: string; + darkXColorDangerTint: string; + darkXColorDangerTintContrast: string; + darkXColorDark: string; + darkXColorDarkContrast: string; + darkXColorDarkShade: string; + darkXColorDarkShadeContrast: string; + darkXColorDarkTint: string; + darkXColorDarkTintContrast: string; + darkXColorMedium: string; + darkXColorMediumContrast: string; + darkXColorMediumShade: string; + darkXColorMediumShadeContrast: string; + darkXColorMediumTint: string; + darkXColorMediumTintContrast: string; + darkXColorLight: string; + darkXColorLightContrast: string; + darkXColorLightShade: string; + darkXColorLightShadeContrast: string; + darkXColorLightTint: string; + darkXColorLightTintContrast: string; + //#endregion + } + + export interface XThemeLightFormModel { + // + //#region Light ... + lightXColorPrimary: string; + lightXColorPrimaryContrast: string; + lightXColorPrimaryShade: string; + lightXColorPrimaryShadeContrast: string; + lightXColorPrimaryTint: string; + lightXColorPrimaryTintContrast: string; + lightXColorSecondary: string; + lightXColorSecondaryContrast: string; + lightXColorSecondaryShade: string; + lightXColorSecondaryShadeContrast: string; + lightXColorSecondaryTint: string; + lightXColorSecondaryTintContrast: string; + lightXColorTertiary: string; + lightXColorTertiaryContrast: string; + lightXColorTertiaryShade: string; + lightXColorTertiaryShadeContrast: string; + lightXColorTertiaryTint: string; + lightXColorTertiaryTintContrast: string; + lightXColorSuccess: string; + lightXColorSuccessContrast: string; + lightXColorSuccessShade: string; + lightXColorSuccessShadeContrast: string; + lightXColorSuccessTint: string; + lightXColorSuccessTintContrast: string; + lightXColorWarning: string; + lightXColorWarningContrast: string; + lightXColorWarningShade: string; + lightXColorWarningShadeContrast: string; + lightXColorWarningTint: string; + lightXColorWarningTintContrast: string; + lightXColorDanger: string; + lightXColorDangerContrast: string; + lightXColorDangerShade: string; + lightXColorDangerShadeContrast: string; + lightXColorDangerTint: string; + lightXColorDangerTintContrast: string; + lightXColorDark: string; + lightXColorDarkContrast: string; + lightXColorDarkShade: string; + lightXColorDarkShadeContrast: string; + lightXColorDarkTint: string; + lightXColorDarkTintContrast: string; + lightXColorMedium: string; + lightXColorMediumContrast: string; + lightXColorMediumShade: string; + lightXColorMediumShadeContrast: string; + lightXColorMediumTint: string; + lightXColorMediumTintContrast: string; + lightXColorLight: string; + lightXColorLightContrast: string; + lightXColorLightShade: string; + lightXColorLightShadeContrast: string; + lightXColorLightTint: string; + lightXColorLightTintContrast: string; + //#endregion + } + + export interface XThemeFormModel + extends XThemeBaseFormModel, + XThemeDarkFormModel, + XThemeLightFormModel {} + //#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-theme\x-theme.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XTabsModule } from '../x-tabs/x-tabs.module'; +import { XListModule } from '../x-list/x-list.module'; +import { XFormModule } from '../x-form/x-form.module'; +import { XColorModule } from '../x-color/x-color.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XColorPickerModule } from '../x-color-picker/x-color-picker.module'; +import { XThemeManagerComponent } from './x-theme-manager/x-theme-manager.component'; +import { XModelPresenterModule } from '../x-model-presenter/x-model-presenter.module'; + +@NgModule({ + declarations: [XThemeManagerComponent], + imports: [ + XFormModule, + XIconModule, + XCardModule, + XTabsModule, + XListModule, + XColorModule, + CommonModule, + XColorPickerModule, + XBaseComponentModule, + XModelPresenterModule, + ], + exports: [ + XFormModule, + XIconModule, + XCardModule, + XTabsModule, + XListModule, + XColorModule, + CommonModule, + XColorPickerModule, + XBaseComponentModule, + XModelPresenterModule, + XThemeManagerComponent, + ], + providers: [], +}) +export class XThemeModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-theme\x-theme.tools.ts + +import { XThemePack, XThemeType } from 'x-framework-core'; +import { XThemeBaseFormModel, XThemeDarkFormModel, XThemeFormModel, XThemeLightFormModel } from './x-theme-typings'; + +/** + * convert to XThemeFormModel + * + * @param theme an instance of XThemePack ... + */ +export function themeToForm(theme: XThemePack) { + // + if (!theme) { + return getEmptyThemeFormModel(); + } + + // + return { + name: theme.name, + + // + //#region Dark ... + darkXColorPrimary: theme.dark.colors.XColorPrimary || '', + darkXColorPrimaryContrast: theme.dark.colors.XColorPrimaryContrast || '', + darkXColorPrimaryShade: theme.dark.colors.XColorPrimaryShade || '', + darkXColorPrimaryShadeContrast: + theme.dark.colors.XColorPrimaryShadeContrast || '', + darkXColorPrimaryTint: theme.dark.colors.XColorPrimaryTint || '', + darkXColorPrimaryTintContrast: + theme.dark.colors.XColorPrimaryTintContrast || '', + darkXColorSecondary: theme.dark.colors.XColorSecondary || '', + darkXColorSecondaryContrast: + theme.dark.colors.XColorSecondaryContrast || '', + darkXColorSecondaryShade: theme.dark.colors.XColorSecondaryShade || '', + darkXColorSecondaryShadeContrast: + theme.dark.colors.XColorSecondaryShadeContrast || '', + darkXColorSecondaryTint: theme.dark.colors.XColorSecondaryTint || '', + darkXColorSecondaryTintContrast: + theme.dark.colors.XColorSecondaryTintContrast || '', + darkXColorTertiary: theme.dark.colors.XColorTertiary || '', + darkXColorTertiaryContrast: theme.dark.colors.XColorTertiaryContrast || '', + darkXColorTertiaryShade: theme.dark.colors.XColorTertiaryShade || '', + darkXColorTertiaryShadeContrast: + theme.dark.colors.XColorTertiaryShadeContrast || '', + darkXColorTertiaryTint: theme.dark.colors.XColorTertiaryTint || '', + darkXColorTertiaryTintContrast: + theme.dark.colors.XColorTertiaryTintContrast || '', + darkXColorSuccess: theme.dark.colors.XColorSuccess || '', + darkXColorSuccessContrast: theme.dark.colors.XColorSuccessContrast || '', + darkXColorSuccessShade: theme.dark.colors.XColorSuccessShade || '', + darkXColorSuccessShadeContrast: + theme.dark.colors.XColorSuccessShadeContrast || '', + darkXColorSuccessTint: theme.dark.colors.XColorSuccessTint || '', + darkXColorSuccessTintContrast: + theme.dark.colors.XColorSuccessTintContrast || '', + darkXColorWarning: theme.dark.colors.XColorWarning || '', + darkXColorWarningContrast: theme.dark.colors.XColorWarningContrast || '', + darkXColorWarningShade: theme.dark.colors.XColorWarningShade || '', + darkXColorWarningShadeContrast: + theme.dark.colors.XColorWarningShadeContrast || '', + darkXColorWarningTint: theme.dark.colors.XColorWarningTint || '', + darkXColorWarningTintContrast: + theme.dark.colors.XColorWarningTintContrast || '', + darkXColorDanger: theme.dark.colors.XColorDanger || '', + darkXColorDangerContrast: theme.dark.colors.XColorDangerContrast || '', + darkXColorDangerShade: theme.dark.colors.XColorDangerShade || '', + darkXColorDangerShadeContrast: + theme.dark.colors.XColorDangerShadeContrast || '', + darkXColorDangerTint: theme.dark.colors.XColorDangerTint || '', + darkXColorDangerTintContrast: + theme.dark.colors.XColorDangerTintContrast || '', + darkXColorDark: theme.dark.colors.XColorDark || '', + darkXColorDarkContrast: theme.dark.colors.XColorDarkContrast || '', + darkXColorDarkShade: theme.dark.colors.XColorDarkShade || '', + darkXColorDarkShadeContrast: + theme.dark.colors.XColorDarkShadeContrast || '', + darkXColorDarkTint: theme.dark.colors.XColorDarkTint || '', + darkXColorDarkTintContrast: theme.dark.colors.XColorDarkTintContrast || '', + darkXColorMedium: theme.dark.colors.XColorMedium || '', + darkXColorMediumContrast: theme.dark.colors.XColorMediumContrast || '', + darkXColorMediumShade: theme.dark.colors.XColorMediumShade || '', + darkXColorMediumShadeContrast: + theme.dark.colors.XColorMediumShadeContrast || '', + darkXColorMediumTint: theme.dark.colors.XColorMediumTint || '', + darkXColorMediumTintContrast: + theme.dark.colors.XColorMediumTintContrast || '', + darkXColorLight: theme.dark.colors.XColorLight || '', + darkXColorLightContrast: theme.dark.colors.XColorLightContrast || '', + darkXColorLightShade: theme.dark.colors.XColorLightShade || '', + darkXColorLightShadeContrast: + theme.dark.colors.XColorLightShadeContrast || '', + darkXColorLightTint: theme.dark.colors.XColorLightTint || '', + darkXColorLightTintContrast: + theme.dark.colors.XColorLightTintContrast || '', + //#endregion + + // + //#region Light ... + lightXColorPrimary: theme.light.colors.XColorPrimary || '', + lightXColorPrimaryContrast: theme.light.colors.XColorPrimaryContrast || '', + lightXColorPrimaryShade: theme.light.colors.XColorPrimaryShade || '', + lightXColorPrimaryShadeContrast: + theme.light.colors.XColorPrimaryShadeContrast || '', + lightXColorPrimaryTint: theme.light.colors.XColorPrimaryTint || '', + lightXColorPrimaryTintContrast: + theme.light.colors.XColorPrimaryTintContrast || '', + lightXColorSecondary: theme.light.colors.XColorSecondary || '', + lightXColorSecondaryContrast: + theme.light.colors.XColorSecondaryContrast || '', + lightXColorSecondaryShade: theme.light.colors.XColorSecondaryShade || '', + lightXColorSecondaryShadeContrast: + theme.light.colors.XColorSecondaryShadeContrast || '', + lightXColorSecondaryTint: theme.light.colors.XColorSecondaryTint || '', + lightXColorSecondaryTintContrast: + theme.light.colors.XColorSecondaryTintContrast || '', + lightXColorTertiary: theme.light.colors.XColorTertiary || '', + lightXColorTertiaryContrast: + theme.light.colors.XColorTertiaryContrast || '', + lightXColorTertiaryShade: theme.light.colors.XColorTertiaryShade || '', + lightXColorTertiaryShadeContrast: + theme.light.colors.XColorTertiaryShadeContrast || '', + lightXColorTertiaryTint: theme.light.colors.XColorTertiaryTint || '', + lightXColorTertiaryTintContrast: + theme.light.colors.XColorTertiaryTintContrast || '', + lightXColorSuccess: theme.light.colors.XColorSuccess || '', + lightXColorSuccessContrast: theme.light.colors.XColorSuccessContrast || '', + lightXColorSuccessShade: theme.light.colors.XColorSuccessShade || '', + lightXColorSuccessShadeContrast: + theme.light.colors.XColorSuccessShadeContrast || '', + lightXColorSuccessTint: theme.light.colors.XColorSuccessTint || '', + lightXColorSuccessTintContrast: + theme.light.colors.XColorSuccessTintContrast || '', + lightXColorWarning: theme.light.colors.XColorWarning || '', + lightXColorWarningContrast: theme.light.colors.XColorWarningContrast || '', + lightXColorWarningShade: theme.light.colors.XColorWarningShade || '', + lightXColorWarningShadeContrast: + theme.light.colors.XColorWarningShadeContrast || '', + lightXColorWarningTint: theme.light.colors.XColorWarningTint || '', + lightXColorWarningTintContrast: + theme.light.colors.XColorWarningTintContrast || '', + lightXColorDanger: theme.light.colors.XColorDanger || '', + lightXColorDangerContrast: theme.light.colors.XColorDangerContrast || '', + lightXColorDangerShade: theme.light.colors.XColorDangerShade || '', + lightXColorDangerShadeContrast: + theme.light.colors.XColorDangerShadeContrast || '', + lightXColorDangerTint: theme.light.colors.XColorDangerTint || '', + lightXColorDangerTintContrast: + theme.light.colors.XColorDangerTintContrast || '', + lightXColorDark: theme.light.colors.XColorDark || '', + lightXColorDarkContrast: theme.light.colors.XColorDarkContrast || '', + lightXColorDarkShade: theme.light.colors.XColorDarkShade || '', + lightXColorDarkShadeContrast: + theme.light.colors.XColorDarkShadeContrast || '', + lightXColorDarkTint: theme.light.colors.XColorDarkTint || '', + lightXColorDarkTintContrast: + theme.light.colors.XColorDarkTintContrast || '', + lightXColorMedium: theme.light.colors.XColorMedium || '', + lightXColorMediumContrast: theme.light.colors.XColorMediumContrast || '', + lightXColorMediumShade: theme.light.colors.XColorMediumShade || '', + lightXColorMediumShadeContrast: + theme.light.colors.XColorMediumShadeContrast || '', + lightXColorMediumTint: theme.light.colors.XColorMediumTint || '', + lightXColorMediumTintContrast: + theme.light.colors.XColorMediumTintContrast || '', + lightXColorLight: theme.light.colors.XColorLight || '', + lightXColorLightContrast: theme.light.colors.XColorLightContrast || '', + lightXColorLightShade: theme.light.colors.XColorLightShade || '', + lightXColorLightShadeContrast: + theme.light.colors.XColorLightShadeContrast || '', + lightXColorLightTint: theme.light.colors.XColorLightTint || '', + lightXColorLightTintContrast: + theme.light.colors.XColorLightTintContrast || '', + //#endregion + } as XThemeFormModel; +} + +/** + * convert to XThemePack + * + * @param form an instance of XThemeFormModel ... + */ +export function formToTheme(form: XThemeFormModel) { + // + if (!form) { + return getEmptyThemePack(); + } + + // + return { + // + name: form.name || '', + + // + // Dark ... + dark: { + type: XThemeType.Dark, + colors: { + XColorPrimary: form.darkXColorPrimary || '', + XColorPrimaryContrast: form.darkXColorPrimaryContrast || '', + XColorPrimaryShade: form.darkXColorPrimaryShade || '', + XColorPrimaryShadeContrast: form.darkXColorPrimaryShadeContrast || '', + XColorPrimaryTint: form.darkXColorPrimaryTint || '', + XColorPrimaryTintContrast: form.darkXColorPrimaryTintContrast || '', + XColorSecondary: form.darkXColorSecondary || '', + XColorSecondaryContrast: form.darkXColorSecondaryContrast || '', + XColorSecondaryShade: form.darkXColorSecondaryShade || '', + XColorSecondaryShadeContrast: + form.darkXColorSecondaryShadeContrast || '', + XColorSecondaryTint: form.darkXColorSecondaryTint || '', + XColorSecondaryTintContrast: form.darkXColorSecondaryTintContrast || '', + XColorTertiary: form.darkXColorTertiary || '', + XColorTertiaryContrast: form.darkXColorTertiaryContrast || '', + XColorTertiaryShade: form.darkXColorTertiaryShade || '', + XColorTertiaryShadeContrast: form.darkXColorTertiaryShadeContrast || '', + XColorTertiaryTint: form.darkXColorTertiaryTint || '', + XColorTertiaryTintContrast: form.darkXColorTertiaryTintContrast || '', + XColorSuccess: form.darkXColorSuccess || '', + XColorSuccessContrast: form.darkXColorSuccessContrast || '', + XColorSuccessShade: form.darkXColorSuccessShade || '', + XColorSuccessShadeContrast: form.darkXColorSuccessShadeContrast || '', + XColorSuccessTint: form.darkXColorSuccessTint || '', + XColorSuccessTintContrast: form.darkXColorSuccessTintContrast || '', + XColorWarning: form.darkXColorWarning || '', + XColorWarningContrast: form.darkXColorWarningContrast || '', + XColorWarningShade: form.darkXColorWarningShade || '', + XColorWarningShadeContrast: form.darkXColorWarningShadeContrast || '', + XColorWarningTint: form.darkXColorWarningTint || '', + XColorWarningTintContrast: form.darkXColorWarningTintContrast || '', + XColorDanger: form.darkXColorDanger || '', + XColorDangerContrast: form.darkXColorDangerContrast || '', + XColorDangerShade: form.darkXColorDangerShade || '', + XColorDangerShadeContrast: form.darkXColorDangerShadeContrast || '', + XColorDangerTint: form.darkXColorDangerTint || '', + XColorDangerTintContrast: form.darkXColorDangerTintContrast || '', + XColorDark: form.darkXColorDark || '', + XColorDarkContrast: form.darkXColorDarkContrast || '', + XColorDarkShade: form.darkXColorDarkShade || '', + XColorDarkShadeContrast: form.darkXColorDarkShadeContrast || '', + XColorDarkTint: form.darkXColorDarkTint || '', + XColorDarkTintContrast: form.darkXColorDarkTintContrast || '', + XColorMedium: form.darkXColorMedium || '', + XColorMediumContrast: form.darkXColorMediumContrast || '', + XColorMediumShade: form.darkXColorMediumShade || '', + XColorMediumShadeContrast: form.darkXColorMediumShadeContrast || '', + XColorMediumTint: form.darkXColorMediumTint || '', + XColorMediumTintContrast: form.darkXColorMediumTintContrast || '', + XColorLight: form.darkXColorLight || '', + XColorLightContrast: form.darkXColorLightContrast || '', + XColorLightShade: form.darkXColorLightShade || '', + XColorLightShadeContrast: form.darkXColorLightShadeContrast || '', + XColorLightTint: form.darkXColorLightTint || '', + XColorLightTintContrast: form.darkXColorLightTintContrast || '', + }, + }, + + // + // Light ... + light: { + type: XThemeType.Light, + colors: { + XColorPrimary: form.lightXColorPrimary || '', + XColorPrimaryContrast: form.lightXColorPrimaryContrast || '', + XColorPrimaryShade: form.lightXColorPrimaryShade || '', + XColorPrimaryShadeContrast: form.lightXColorPrimaryShadeContrast || '', + XColorPrimaryTint: form.lightXColorPrimaryTint || '', + XColorPrimaryTintContrast: form.lightXColorPrimaryTintContrast || '', + XColorSecondary: form.lightXColorSecondary || '', + XColorSecondaryContrast: form.lightXColorSecondaryContrast || '', + XColorSecondaryShade: form.lightXColorSecondaryShade || '', + XColorSecondaryShadeContrast: + form.lightXColorSecondaryShadeContrast || '', + XColorSecondaryTint: form.lightXColorSecondaryTint || '', + XColorSecondaryTintContrast: + form.lightXColorSecondaryTintContrast || '', + XColorTertiary: form.lightXColorTertiary || '', + XColorTertiaryContrast: form.lightXColorTertiaryContrast || '', + XColorTertiaryShade: form.lightXColorTertiaryShade || '', + XColorTertiaryShadeContrast: + form.lightXColorTertiaryShadeContrast || '', + XColorTertiaryTint: form.lightXColorTertiaryTint || '', + XColorTertiaryTintContrast: form.lightXColorTertiaryTintContrast || '', + XColorSuccess: form.lightXColorSuccess || '', + XColorSuccessContrast: form.lightXColorSuccessContrast || '', + XColorSuccessShade: form.lightXColorSuccessShade || '', + XColorSuccessShadeContrast: form.lightXColorSuccessShadeContrast || '', + XColorSuccessTint: form.lightXColorSuccessTint || '', + XColorSuccessTintContrast: form.lightXColorSuccessTintContrast || '', + XColorWarning: form.lightXColorWarning || '', + XColorWarningContrast: form.lightXColorWarningContrast || '', + XColorWarningShade: form.lightXColorWarningShade || '', + XColorWarningShadeContrast: form.lightXColorWarningShadeContrast || '', + XColorWarningTint: form.lightXColorWarningTint || '', + XColorWarningTintContrast: form.lightXColorWarningTintContrast || '', + XColorDanger: form.lightXColorDanger || '', + XColorDangerContrast: form.lightXColorDangerContrast || '', + XColorDangerShade: form.lightXColorDangerShade || '', + XColorDangerShadeContrast: form.lightXColorDangerShadeContrast || '', + XColorDangerTint: form.lightXColorDangerTint || '', + XColorDangerTintContrast: form.lightXColorDangerTintContrast || '', + XColorDark: form.lightXColorDark || '', + XColorDarkContrast: form.lightXColorDarkContrast || '', + XColorDarkShade: form.lightXColorDarkShade || '', + XColorDarkShadeContrast: form.lightXColorDarkShadeContrast || '', + XColorDarkTint: form.lightXColorDarkTint || '', + XColorDarkTintContrast: form.lightXColorDarkTintContrast || '', + XColorMedium: form.lightXColorMedium || '', + XColorMediumContrast: form.lightXColorMediumContrast || '', + XColorMediumShade: form.lightXColorMediumShade || '', + XColorMediumShadeContrast: form.lightXColorMediumShadeContrast || '', + XColorMediumTint: form.lightXColorMediumTint || '', + XColorMediumTintContrast: form.lightXColorMediumTintContrast || '', + XColorLight: form.lightXColorLight || '', + XColorLightContrast: form.lightXColorLightContrast || '', + XColorLightShade: form.lightXColorLightShade || '', + XColorLightShadeContrast: form.lightXColorLightShadeContrast || '', + XColorLightTint: form.lightXColorLightTint || '', + XColorLightTintContrast: form.lightXColorLightTintContrast || '', + }, + }, + } as XThemePack; +} + +/** + * prepare an empty XThemePack instance ... + */ +export function getEmptyThemePack() { + return { + name: '', + dark: { + type: XThemeType.Dark, + colors: { + XColorPrimary: '', + XColorPrimaryContrast: '', + XColorPrimaryShade: '', + XColorPrimaryShadeContrast: '', + XColorPrimaryTint: '', + XColorPrimaryTintContrast: '', + XColorSecondary: '', + XColorSecondaryContrast: '', + XColorSecondaryShade: '', + XColorSecondaryShadeContrast: '', + XColorSecondaryTint: '', + XColorSecondaryTintContrast: '', + XColorTertiary: '', + XColorTertiaryContrast: '', + XColorTertiaryShade: '', + XColorTertiaryShadeContrast: '', + XColorTertiaryTint: '', + XColorTertiaryTintContrast: '', + XColorSuccess: '', + XColorSuccessContrast: '', + XColorSuccessShade: '', + XColorSuccessShadeContrast: '', + XColorSuccessTint: '', + XColorSuccessTintContrast: '', + XColorWarning: '', + XColorWarningContrast: '', + XColorWarningShade: '', + XColorWarningShadeContrast: '', + XColorWarningTint: '', + XColorWarningTintContrast: '', + XColorDanger: '', + XColorDangerContrast: '', + XColorDangerShade: '', + XColorDangerShadeContrast: '', + XColorDangerTint: '', + XColorDangerTintContrast: '', + XColorDark: '', + XColorDarkContrast: '', + XColorDarkShade: '', + XColorDarkShadeContrast: '', + XColorDarkTint: '', + XColorDarkTintContrast: '', + XColorMedium: '', + XColorMediumContrast: '', + XColorMediumShade: '', + XColorMediumShadeContrast: '', + XColorMediumTint: '', + XColorMediumTintContrast: '', + XColorLight: '', + XColorLightContrast: '', + XColorLightShade: '', + XColorLightShadeContrast: '', + XColorLightTint: '', + XColorLightTintContrast: '', + }, + }, + light: { + type: XThemeType.Light, + colors: { + XColorPrimary: '', + XColorPrimaryContrast: '', + XColorPrimaryShade: '', + XColorPrimaryShadeContrast: '', + XColorPrimaryTint: '', + XColorPrimaryTintContrast: '', + XColorSecondary: '', + XColorSecondaryContrast: '', + XColorSecondaryShade: '', + XColorSecondaryShadeContrast: '', + XColorSecondaryTint: '', + XColorSecondaryTintContrast: '', + XColorTertiary: '', + XColorTertiaryContrast: '', + XColorTertiaryShade: '', + XColorTertiaryShadeContrast: '', + XColorTertiaryTint: '', + XColorTertiaryTintContrast: '', + XColorSuccess: '', + XColorSuccessContrast: '', + XColorSuccessShade: '', + XColorSuccessShadeContrast: '', + XColorSuccessTint: '', + XColorSuccessTintContrast: '', + XColorWarning: '', + XColorWarningContrast: '', + XColorWarningShade: '', + XColorWarningShadeContrast: '', + XColorWarningTint: '', + XColorWarningTintContrast: '', + XColorDanger: '', + XColorDangerContrast: '', + XColorDangerShade: '', + XColorDangerShadeContrast: '', + XColorDangerTint: '', + XColorDangerTintContrast: '', + XColorDark: '', + XColorDarkContrast: '', + XColorDarkShade: '', + XColorDarkShadeContrast: '', + XColorDarkTint: '', + XColorDarkTintContrast: '', + XColorMedium: '', + XColorMediumContrast: '', + XColorMediumShade: '', + XColorMediumShadeContrast: '', + XColorMediumTint: '', + XColorMediumTintContrast: '', + XColorLight: '', + XColorLightContrast: '', + XColorLightShade: '', + XColorLightShadeContrast: '', + XColorLightTint: '', + XColorLightTintContrast: '', + }, + }, + } as XThemePack; +} + +/** + * prepare an empty XThemeBaseFormModel instance ... + */ +export function getEmptyThemeBaseFormModel() { + return { + name: '', + } as XThemeBaseFormModel; +} + +/** + * prepare an empty XThemeLightFormModel instance ... + */ +export function getEmptyThemeLightFormModel() { + return { + lightXColorPrimary: '', + lightXColorPrimaryContrast: '', + lightXColorPrimaryShade: '', + lightXColorPrimaryShadeContrast: '', + lightXColorPrimaryTint: '', + lightXColorPrimaryTintContrast: '', + lightXColorSecondary: '', + lightXColorSecondaryContrast: '', + lightXColorSecondaryShade: '', + lightXColorSecondaryShadeContrast: '', + lightXColorSecondaryTint: '', + lightXColorSecondaryTintContrast: '', + lightXColorTertiary: '', + lightXColorTertiaryContrast: '', + lightXColorTertiaryShade: '', + lightXColorTertiaryShadeContrast: '', + lightXColorTertiaryTint: '', + lightXColorTertiaryTintContrast: '', + lightXColorSuccess: '', + lightXColorSuccessContrast: '', + lightXColorSuccessShade: '', + lightXColorSuccessShadeContrast: '', + lightXColorSuccessTint: '', + lightXColorSuccessTintContrast: '', + lightXColorWarning: '', + lightXColorWarningContrast: '', + lightXColorWarningShade: '', + lightXColorWarningShadeContrast: '', + lightXColorWarningTint: '', + lightXColorWarningTintContrast: '', + lightXColorDanger: '', + lightXColorDangerContrast: '', + lightXColorDangerShade: '', + lightXColorDangerShadeContrast: '', + lightXColorDangerTint: '', + lightXColorDangerTintContrast: '', + lightXColorDark: '', + lightXColorDarkContrast: '', + lightXColorDarkShade: '', + lightXColorDarkShadeContrast: '', + lightXColorDarkTint: '', + lightXColorDarkTintContrast: '', + lightXColorMedium: '', + lightXColorMediumContrast: '', + lightXColorMediumShade: '', + lightXColorMediumShadeContrast: '', + lightXColorMediumTint: '', + lightXColorMediumTintContrast: '', + lightXColorLight: '', + lightXColorLightContrast: '', + lightXColorLightShade: '', + lightXColorLightShadeContrast: '', + lightXColorLightTint: '', + lightXColorLightTintContrast: '', + } as XThemeLightFormModel; +} + +/** + * prepare an empty XThemeDarkFormModel instance ... + */ +export function getEmptyThemeDarkFormModel() { + return { + darkXColorPrimary: '', + darkXColorPrimaryContrast: '', + darkXColorPrimaryShade: '', + darkXColorPrimaryShadeContrast: '', + darkXColorPrimaryTint: '', + darkXColorPrimaryTintContrast: '', + darkXColorSecondary: '', + darkXColorSecondaryContrast: '', + darkXColorSecondaryShade: '', + darkXColorSecondaryShadeContrast: '', + darkXColorSecondaryTint: '', + darkXColorSecondaryTintContrast: '', + darkXColorTertiary: '', + darkXColorTertiaryContrast: '', + darkXColorTertiaryShade: '', + darkXColorTertiaryShadeContrast: '', + darkXColorTertiaryTint: '', + darkXColorTertiaryTintContrast: '', + darkXColorSuccess: '', + darkXColorSuccessContrast: '', + darkXColorSuccessShade: '', + darkXColorSuccessShadeContrast: '', + darkXColorSuccessTint: '', + darkXColorSuccessTintContrast: '', + darkXColorWarning: '', + darkXColorWarningContrast: '', + darkXColorWarningShade: '', + darkXColorWarningShadeContrast: '', + darkXColorWarningTint: '', + darkXColorWarningTintContrast: '', + darkXColorDanger: '', + darkXColorDangerContrast: '', + darkXColorDangerShade: '', + darkXColorDangerShadeContrast: '', + darkXColorDangerTint: '', + darkXColorDangerTintContrast: '', + darkXColorDark: '', + darkXColorDarkContrast: '', + darkXColorDarkShade: '', + darkXColorDarkShadeContrast: '', + darkXColorDarkTint: '', + darkXColorDarkTintContrast: '', + darkXColorMedium: '', + darkXColorMediumContrast: '', + darkXColorMediumShade: '', + darkXColorMediumShadeContrast: '', + darkXColorMediumTint: '', + darkXColorMediumTintContrast: '', + darkXColorLight: '', + darkXColorLightContrast: '', + darkXColorLightShade: '', + darkXColorLightShadeContrast: '', + darkXColorLightTint: '', + darkXColorLightTintContrast: '', + } as XThemeDarkFormModel; +} + +/** + * prepare an empty XThemeFormModel instance ... + */ +export function getEmptyThemeFormModel() { + return { + ...getEmptyThemeBaseFormModel(), + ...getEmptyThemeDarkFormModel(), + ...getEmptyThemeLightFormModel(), + } as XThemeFormModel; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-theme\x-theme-manager\x-theme-manager.component.html + + + + + + + + +
+ +
+
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+
+ + + + + +
+
+
+ + + + {{ item.data.name }} + + + + + + + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-theme\x-theme-manager\x-theme-manager.component.scss + +:host ::ng-deep x-color { + --size: 24px !important; + display: inline-block; +} + +.actions { + margin: { + top: 8px; + bottom: 8px; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-theme\x-theme-manager\x-theme-manager.component.ts + +import { + nameof, + assign, + XParam, + toArray, + hasChild, + XUiEvent, + cloneJson, + parseJson, + isFunction, + isSameType, + XThemeType, + XThemePack, + isSameObject, + XResourceIDs, + XDialogResult, + XExceptionIDs, + XBaseKeyValue, + XStandardType, + XPickerOptions, + toNormalString, + XOneOrManyType, + XModelProperty, + XThemeDescriptor, + XModalButtonRole, + XModelKeyProvider, + isNullOrUndefined, + isNullOrEmptyString, + XThemeTypeIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XThemeManagerTabs, + XThemeManagerAction, + XThemeManagerActionModel, + XThemeManagerContainerComponent, +} from './x-theme-manager.typings'; +import { + XTabsAction, + XTabChangeEvent, + XTabsActionModel, +} from '../../x-tabs/x-tabs.typings'; +import { + XListItem, + XListAction, + XListActionModel, + XListItemSlideOption, + XListFilteringHandler, + defaultListFilteringHandler, +} from '../../x-list/x-list.typings'; +import { Subject, Subscription } from 'rxjs'; +import { + XActionBarItem, + XActionBarAction, + XActionBarActionModel, + DefaultActionBarItemIds, + defaultActionBarSetItem, + defaultActionBarUploadItem, + XActionBarActionFiredModel, +} from '../../x-action-bar/x-action-bar.typings'; +import { + XSearchBarType, + XSearchBarAction, + XSearchBarActionModel, + XSearchBarEventIdentifier, +} from '../../x-search-bar/x-search-bar.typings'; +import { + XFileUploadAction, + XFileUploadActionModel, +} from '../../x-file-upload/x-file-upload.typings'; +import { getEmptyThemePack } from '../x-theme.tools'; +import { XIconNames } from '../../x-icon/x-icon.typings'; +import { + XModelPresenterAction, + XModelPresenterActionModel, +} from '../../x-model-presenter/x-model-presenter.typings'; +import { XButtonType } from '../../x-button/x-button.typings'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { XSlotLayout, XSlotName } from '../../x-slotter/x-slotter.typings'; +import { XPopoverComponent } from '../../dialogs/x-popover/x-popover.component'; +import { XColorPickerOutputFormat } from '../../x-color-picker/x-color-picker.typings'; +import { XFileDropAreaActionModel } from '../../x-file-drop-area/x-file-drop-area.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-theme-manager', + templateUrl: './x-theme-manager.component.html', + styleUrls: ['./x-theme-manager.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XThemeManagerComponent extends XThemeManagerContainerComponent { + // + //#region Props ... + // + private loadingObject: HTMLIonLoadingElement; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly Tabs = Object.assign({}, XThemeManagerTabs); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + readonly ButtonTypes = Object.assign({}, XButtonType); + readonly ColorNames = Object.assign({}, XColorWithBrightness); + readonly themeUploadListActionProvider = new Subject(); + readonly allowedThemeExtensions: XStandardType> = [ + '.json', + ]; + readonly themeUploadActionProvider = new Subject(); + readonly themeUploadListActionBarHideActions: XStandardType< + XOneOrManyType + >; + readonly themeModelPresenterActionProvider = + new Subject(); + readonly themeUploadListActionbarActions: XStandardType< + XOneOrManyType + >; + readonly themeUploadFileDropAreaActionProvider = + new Subject(); + + // + canSaveTheme = false; + listItems: XListItem[]; + selectedTheme: XThemePack = null; + uploadThemes: XOneOrManyType; + currentTab: number = this.Tabs.List; + baseTheme: Readonly = null; + showList: XStandardType = true; + hiddenThemModelKeys: XOneOrManyType = [ + `${nameof('dark')}.${nameof('type')}`, + `${nameof('light')}.${nameof('type')}`, + ]; + themeKeyProvider: XModelKeyProvider = (key) => { + // + let result = key as string; + + // + switch (key) { + // + case nameof('name'): + result = this.resourceProvider(this.ResourceIDs.theme); + break; + + // + case nameof('dark'): + result = this.resourceProvider(this.ResourceIDs.theme_dark); + break; + + // + case nameof('light'): + result = this.resourceProvider(this.ResourceIDs.theme_light); + break; + + // + case nameof('colors'): + result = this.resourceProvider(this.ResourceIDs.colors); + break; + + // + default: + result = key as string; + break; + } + + // + return result; + }; + listActionProvider = new Subject(); + tabsActionProvider = new Subject(); + searchBarType: XStandardType = XSearchBarType.Search; + searchBarActionProvider = new Subject(); + actionBarActionProvider = new Subject(); + actionBarActions: XStandardType>; + themeModelPresenterActionActions: XOneOrManyType = [ + { + // + ...defaultActionBarSetItem, + + // + title: this.ResourceIDs.save, + disabled: true, + + // + handler: async (id) => { + await this.handleUpdateTheme(); + }, + }, + ]; + listFilteringHandler: XListFilteringHandler = defaultListFilteringHandler; + //#endregion + + // + //#region LifeCycles ... + async afterViewInit() { + super.afterViewInit(); + + // + await this.startUp(); + } + + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Provider ... + getColorValue(key: string, value: string) { + return { + key, + value, + } as XBaseKeyValue; + } + //#endregion + + // + //#region UI Handler ... + // + async handleSelectedTabChanged(event: XTabChangeEvent) { + this.currentTab = event.index; + } + + async handleListRefresh() { + // + await this.startUp(); + + // + if (!isNullOrUndefined(this.listEmptyRefresh)) { + this.listEmptyRefresh.emit(); + } + } + + async handleListActionBarActionFired(model: XActionBarActionFiredModel) { + // + if (isNullOrUndefined(model) || isNullOrEmptyString(model.action)) { + return; + } + + // + switch (model.action) { + // + case DefaultActionBarItemIds.Upload: + await this.handleUploadTheme(); + break; + } + + // + if (!isNullOrUndefined(this.listActionBarActionFired)) { + this.listActionBarActionFired.emit(model); + } + } + + async handleListSearchBarOnEvent(event: XSearchBarEventIdentifier) { + // + if (!isNullOrUndefined(this.listSearchBarOnEvent)) { + this.listSearchBarOnEvent.emit(event); + } + } + + async handleListSearchBarQueryChange(event: string) { + // + if (!isNullOrUndefined(this.listSearchBarQueryChange)) { + this.listSearchBarQueryChange.emit(event); + } + } + + async handleThemeUploadActionFired(model: XActionBarActionFiredModel) { + // + // Validate Args ... + if (isNullOrUndefined(model) || isNullOrEmptyString(model.action)) { + return; + } + + // + switch (model.action) { + // + case DefaultActionBarItemIds.Upload: + await this.handleThemesImport(); + break; + } + } + + async handleThemeUploadFileChanged(files: XOneOrManyType) { + this.uploadThemes = files; + } + + // + //#region ModelPresenter Handlers ... + async handleThemePropertyActionMenuClicked( + key: string, + prefix: string, + value: any + ) { + // + if (isNullOrEmptyString(key)) { + return; + } + + // + const onlyIcon = false; + const slideOptions: XListItemSlideOption[] = []; + + // + // Edit ... + slideOptions.push({ + id: 'property_edit', + icon: await this.getValueAsync(this.propertyEditIcon), + title: this.ResourceIDs.edit, + color: isNullOrUndefined(value) + ? await this.getValueAsync(this.emptyPropertyEditIconColor) + : await this.getValueAsync(this.propertyEditIconColor), + slot: 'start', + onlyIcon, + handler: async () => { + await this.handleThemePropertyEdit(key, prefix, value); + }, + }); + + // + // Menu Items ... + const item: XListItem = { + data: '', + slideOptions, + }; + + // + const popOver = await this.managerService.dialogService.presentPopover({ + component: XPopoverComponent, + componentProps: assign({}, { key: XParam.Item, value: item }), + translucent: true, + showBackdrop: true, + event, + }); + + // + const { data } = await popOver.onWillDismiss(); + if (!data || !data.id || !data.data) { + return; + } + + // + const optionId = data.id.toString() as string; + const option = + item && item.slideOptions + ? item.slideOptions.find((so) => so.id === optionId) + : null; + if (!option) { + return; + } + + // + const handler = option.handler; + if (!handler || !isFunction(handler)) { + return; + } + + // + // Run Handler ... + await handler(); + } + + async handleThemePropertyEdit(key: string, prefix: string, value: any) { + // + if (isNullOrEmptyString(key)) { + return; + } + + // + await this.colorPickerDialogService.openDialog({ + // + title: key, + + // + selectedColor: value, + + // + showColorInput: true, + showPresetColors: false, + showCopyToClipboard: true, + + // + cardColor: await this.getValueAsync(this.cardColor), + cardForegroundColor: await this.getValueAsync(this.cardForegroundColor), + + // + representColorFormats: [XColorPickerOutputFormat.XHEX], + }); + + // + const subscription = this.colorPickerDialogService.onResult + .asObservable() + .subscribe((result) => { + // + if ( + result.result !== XDialogResult.Ok || + !result.payload || + isNullOrEmptyString(result.payload) + ) { + return; + } + + // + const value = result.payload as string; + this.themeModelPresenterActionProvider.next({ + action: XModelPresenterAction.SetProperty, + payload: { + key, + value, + prefix, + } as XModelProperty, + }); + + // + subscription.unsubscribe(); + }); + } + + async handleThemeContentChanged(event: any) { + // + const isTheme = isSameType(event, this.selectedTheme); + if (!isTheme) { + return; + } + + // + const isThemeChanged = !isSameObject(this.baseTheme, this.selectedTheme); + this.canSaveTheme = isThemeChanged; + + // + toArray(this.themeModelPresenterActionActions)[0].disabled = + !this.canSaveTheme; + + // + this.themeChange.emit(isThemeChanged); + this.handleChangeDetection(); + } + + async handleUpdateTheme() { + // + if (!this.selectedTheme || !this.baseTheme || !this.canSaveTheme) { + return; + } + + // + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + async () => { + await this.updateTheme(this.selectedTheme); + } + ); + } + //#endregion + //#endregion + + // + //#region Actions ... + async back() { + // + switch (this.currentTab) { + // + case this.Tabs.List: + this.baseTheme = null; + this.selectedTheme = null; + break; + + // + case this.Tabs.Details: + await this.changeTab(this.Tabs.List); + break; + + // + case this.Tabs.Import: + // + this.uploadThemes = []; + this.themeUploadActionProvider.next({ + action: XFileUploadAction.CLEAR_LIST, + }); + await this.changeTab(this.Tabs.List); + break; + + // + case this.Tabs.Edit: + this.baseTheme = null; + this.selectedTheme = null; + this.canSaveTheme = false; + this.themeChange.emit(false); + await this.changeTab(this.Tabs.List); + break; + } + } + + async clearList() { + // + this.listItems = []; + this.handleChangeDetection(); + } + + async setTheme(theme: XThemePack, whichType: XThemeTypeIdentifier) { + // + if (!theme && isNullOrEmptyString(theme.name)) { + return; + } + + // + const restart = await this.getOptionalValueAsync(this.restartAfterSetTheme); + + // + const isSetted = await this.managerService.themeManagerService.setTheme( + theme, + whichType, + true, + restart + ); + if (isSetted) { + // + await this.prepareListItems(); + + // + this.managerService.settingsService.restart(); + } + } + + async editTheme(theme: XThemePack) { + // + if (!theme || !isNullOrUndefined(this.loadingObject)) { + return; + } + + // + await this.presentLoading(); + + // + this.baseTheme = cloneJson(theme); + this.selectedTheme = { + ...theme, + }; + await this.changeTab(this.Tabs.Edit); + this.handleChangeDetection(); + } + + async cloneTheme(theme: XThemePack) { + // + if (!theme) { + return; + } + + // + await this.managerService.dialogService.presentAlert({ + subHeader: XResourceIDs.theme, + message: XResourceIDs.theme_clone_title, + inputs: [ + { + type: 'text', + name: 'name', + placeholder: XResourceIDs.theme_name_description, + }, + ], + buttons: [ + { + text: XResourceIDs.clone, + role: XModalButtonRole.Selected, + cssClass: ['btn-selected'], + handler: async (value) => { + // + if (value && value.name && !isNullOrEmptyString(value.name)) { + // + const themeName = toNormalString(theme.name); + const clonedThemeName = toNormalString(value.name as string); + const isCloned = + await this.managerService.themeManagerService.cloneTheme( + themeName, + clonedThemeName, + false + ); + + // + if (isCloned) { + // + this.showSuccess(); + await this.prepareListItems(); + } else { + this.showError(XExceptionIDs.ActionFailed); + } + } else { + this.showError(XExceptionIDs.InvalidData); + } + }, + }, + { + text: XResourceIDs.cancel, + role: XModalButtonRole.Cancel, + cssClass: ['btn-cancel'], + handler: () => {}, + }, + ], + }); + } + + async downloadTheme(theme: XThemePack) { + // + if (!theme) { + return; + } + + // + await this.managerService.themeManagerService.exportTheme(theme.name); + } + + async removeTheme(theme: XThemePack) { + // + if (!theme) { + return; + } + + // + await this.managerService.dialogService.presentAreYouSureYesNoDialog(() => { + this.managerService.themeManagerService + .removeTheme(toNormalString(theme.name), false) + .then((isRemoved) => { + if (isRemoved) { + // + this.showSuccess(); + this.prepareListItems(); + } else { + this.showError(); + } + }); + }); + } + + async previewTheme(theme: XThemePack) { + // + if (!theme || !isNullOrUndefined(this.loadingObject)) { + return; + } + + // + await this.presentLoading(); + + // + this.selectedTheme = { + ...theme, + }; + await this.changeTab(this.Tabs.Details); + this.handleChangeDetection(); + } + + async saveTheme(theme: XThemePack) { + // + if (!theme) { + return false; + } + + // + if ( + isNullOrEmptyString(theme.name) || + (await this.managerService.themeManagerService.isThemeExists(theme.name)) + ) { + this.showError(XExceptionIDs.Duplicate); + return false; + } + + // + const isSaved = await this.managerService.themeManagerService.saveTheme( + theme, + false + ); + + // + if (isSaved) { + // + this.themeChange.emit(false); + this.managerService.settingsService.restart(); + } + + return isSaved; + } + + async updateTheme(theme: XThemePack) { + // + if (!theme || isNullOrEmptyString(theme.name)) { + return false; + } + + // + const isExists = + await this.managerService.themeManagerService.isThemeExists(theme.name); + if (!isExists) { + this.showError(XExceptionIDs.NotFound); + return false; + } + + // + const isUpdated = await this.managerService.themeManagerService.updateTheme( + theme.name, + theme, + false, + true + ); + + // + if (isUpdated) { + // + this.themeChange.emit(false); + this.managerService.settingsService.restart(); + } + + // + return isUpdated; + } + //#endregion + + // + //#region Private ... + // + //#region Handler Registerrers ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XThemeManagerAction.Refresh: + this.detectChanges(); + break; + + // + case XThemeManagerAction.Back: + await this.back(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + //#region Private Actions ... + private async startUp() { + // + this.tabChange.emit(this.currentTab); + await this.prepareActionBarActions(); + await this.prepareListItems(); + } + + private async prepareListItems() { + // + this.listItems = null; + + // + const loadingDialog = + await this.managerService.dialogService.presentLoading({ + showBackdrop: true, + backdropDismiss: false, + message: this.resourceProvider(XResourceIDs.loading), + }); + + // + const currentTheme = + await this.managerService.themeManagerService.getCurrentTheme(); + const themes = await this.managerService.themeManagerService.getThemes(); + if (!currentTheme || !hasChild(themes)) { + // + await this.clearList(); + await loadingDialog.dismiss(); + return; + } + + // + // Prepare List Items ... + this.listItems = themes.map((theme) => { + // + const isCurrent = + toNormalString(theme.name) === toNormalString(currentTheme.name); + + // + return { + data: theme, + slideOptions: [ + // + // Remove ... + { + id: 'delete_theme', + icon: XIconNames.remove, + title: this.ResourceIDs.delete, + color: XColorWithBrightness.Danger, + onlyIcon: true, + disabled: this.uiDisabled || this.loading || isCurrent, + slot: 'end', + handler: () => { + this.removeTheme(theme); + }, + }, + // + // Set As Current ... + { + id: 'set_theme', + icon: XIconNames.checked, + title: this.ResourceIDs.set, + color: XColorWithBrightness.Success, + onlyIcon: true, + disabled: + this.uiDisabled || + this.loading || + toNormalString(theme.name) === toNormalString(currentTheme.name), + slot: 'start', + handler: () => { + // + // Prepare Picker Options for Theme Setting ... + const options: XPickerOptions = { + buttons: [ + { + text: XResourceIDs.set, + role: XModalButtonRole.Selected, + cssClass: ['btn-selected'], + handler: async (value) => { + // + if (!value || !value.type || !value.type.value) { + return; + } + + // + const type = value.type.value as XThemeTypeIdentifier; + await this.setTheme(theme, type); + }, + }, + { + text: XResourceIDs.cancel, + role: XModalButtonRole.Cancel, + cssClass: ['btn-cancel'], + }, + ], + columns: [ + { + name: 'type', + options: [ + { + text: XResourceIDs.theme_light, + value: XThemeType.Light, + }, + { + text: XResourceIDs.theme_dark, + value: XThemeType.Dark, + }, + ], + }, + ], + }; + + // + // Present Theme Picker ... + this.managerService.dialogService.presentPicker(options); + }, + }, + // + // Check For Change Mode ... + ...(isCurrent + ? [ + // + // Change Mode ... + { + id: 'change_theme_mode', + icon: XIconNames.contrast, + title: this.ResourceIDs.theme_mode, + color: XColorWithBrightness.Success, + onlyIcon: true, + disabled: this.uiDisabled || this.loading, + slot: 'start', + handler: async () => { + // + const currentType = + await this.managerService.themeManagerService.getCurrentThemeType(); + const type = + currentType === XThemeType.Light + ? XThemeType.Dark + : XThemeType.Light; + await this.setTheme(theme, type); + }, + }, + ] + : []), + // + // Preview ... + { + id: 'preview_theme', + icon: XIconNames.preview, + title: this.ResourceIDs.details, + color: XColorWithBrightness.Secondary, + onlyIcon: true, + disabled: this.uiDisabled || this.loading, + slot: 'start', + handler: async () => { + await this.previewTheme(theme); + }, + }, + // + // Edit ... + { + id: 'edit_theme', + icon: XIconNames.edit, + title: this.ResourceIDs.edit, + color: XColorWithBrightness.Tertiary, + onlyIcon: true, + disabled: this.uiDisabled || this.loading, // || isCurrent, + slot: 'start', + handler: () => { + this.editTheme(theme); + }, + }, + // + // Clone ... + { + id: 'clone_theme', + icon: XIconNames.copy, + title: this.ResourceIDs.clone, + color: XColorWithBrightness.Success, + onlyIcon: true, + disabled: this.uiDisabled || this.loading, + slot: 'start', + handler: async () => { + await this.cloneTheme(theme); + }, + }, + // + // Download ... + { + id: 'download_theme', + icon: XIconNames.download, + title: this.ResourceIDs.download, + color: XColorWithBrightness.SecondaryShade, + onlyIcon: true, + disabled: this.uiDisabled || this.loading, + slot: 'start', + handler: async () => { + await this.downloadTheme(theme); + }, + }, + ], + } as XListItem; + }); + + // + await loadingDialog.dismiss(); + + // + this.handleChangeDetection(); + } + + private handleChangeDetection() { + // + this.tabsActionProvider.next({ + action: XTabsAction.Refresh, + }); + + // + this.listActionProvider.next({ + action: XListAction.Refresh, + }); + + // + this.actionBarActionProvider.next({ + action: XActionBarAction.Refresh, + }); + + // + this.searchBarActionProvider.next({ + action: XSearchBarAction.Refresh, + }); + + // + this.themeUploadActionProvider.next({ + action: XFileUploadAction.Refresh, + }); + + // + this.themeUploadListActionProvider.next({ + action: XListAction.Refresh, + }); + + // + this.themeModelPresenterActionProvider.next({ + action: XModelPresenterAction.Refresh, + }); + + // + this.themeUploadFileDropAreaActionProvider.next({ + action: XFileUploadAction.Refresh, + }); + + // + this.managerService.dispatchResizeEvent(); + + // + this.detectChanges(); + } + + private async presentLoading() { + // + this.loadingObject = await this.managerService.dialogService.presentLoading( + { + message: this.resourceProvider(this.ResourceIDs.loading), + } + ); + } + + private async changeTab(tab?: XThemeManagerTabs) { + // + if (!tab) { + tab = this.Tabs.List; + } + + // + // + // Prevent go forward if args is not ready ... + if (this.loading || this.uiDisabled || this.currentTab === tab) { + return; + } + + // + //#region Pre Change Actions ... + // + // Based On Current Tab ... + switch (this.currentTab) { + // + case this.Tabs.List: + break; + + // + case this.Tabs.Details: + break; + + // + case this.Tabs.Import: + break; + + // + case this.Tabs.Edit: + break; + } + + // + // Based On Dest Tab ... + switch (tab) { + // + case this.Tabs.List: + this.baseTheme = null; + this.selectedTheme = null; + break; + + // + case this.Tabs.Details: + // + if (isNullOrUndefined(this.selectedTheme)) { + return; + } + break; + + // + case this.Tabs.Import: + break; + + // + case this.Tabs.Edit: + // + if (isNullOrUndefined(this.selectedTheme)) { + return; + } + break; + } + //#endregion + + // + // Change Current Tab ... + this.currentTab = tab; + this.detectChanges(); + + // + //#region Post Change Actions ... + switch (tab) { + // + case this.Tabs.List: + break; + + // + case this.Tabs.Details: + break; + + // + case this.Tabs.Import: + break; + + // + case this.Tabs.Edit: + break; + } + //#endregion + + // + this.tabChange.emit(tab); + this.handleChangeDetection(); + } + + private async handleUploadTheme() { + await this.changeTab(this.Tabs.Import); + } + + private async handleThemesImport() { + // + if (!this.hasChild(this.uploadThemes)) { + return; + } + + // + const emptyTheme = getEmptyThemePack(); + const importedThemes = await this.getArrayValueAsync(this.uploadThemes); + + // + importedThemes.forEach(async (themeFile) => { + // + const fileReader = new FileReader(); + fileReader.readAsText(themeFile); + fileReader.onloadend = async (e) => { + // + if (!e || !e.target || !e.target.result) { + return; + } + + // + try { + // + // Check Theme Valid ... + const themePack = parseJson(e.target.result.toString()); + if (!themePack) { + // + await this.issueThemeError( + themeFile.name, + XExceptionIDs.InavlidFile + ); + + // + return; + } + + // + // Check Theme Type ... + const isCorrectThemeType = isSameType(themePack, emptyTheme); + if (!isCorrectThemeType) { + // + await this.issueThemeError( + themeFile.name, + XExceptionIDs.UnsupportedFileType + ); + + // + return; + } + + // + // Check Theme Name and Existance ... + if ( + isNullOrEmptyString(themePack.name) || + (await this.managerService.themeManagerService.isThemeExists( + themePack.name + )) + ) { + // + this.issueThemeError(themeFile.name, XExceptionIDs.Duplicate); + + // + return; + } + + // + const isSaved = await this.saveTheme(themePack); + if (isSaved) { + await this.prepareListItems(); + } else { + // + await this.issueThemeError( + themeFile.name, + XExceptionIDs.ActionFailed + ); + + // + return; + } + } catch { + await this.issueThemeError(themeFile.name, XExceptionIDs.InavlidFile); + } + }; + }); + + // + await this.back(); + } + + private async issueThemeError(fileName: string, exception: XExceptionIDs) { + // + if (isNullOrEmptyString(fileName)) { + return; + } + + // + const message = `${this.managerService.localizationService.getErrorMessage( + exception + )}_${fileName}`; + + // + await this.managerService.notificationService.presentDangerNotification({ + dissmissable: true, + message, + }); + } + //#endregion + + // + //#region ActionBar Actions ... + private async prepareActionBarActions() { + // + //#region Prepare Default Actions ... + const hideActions: string | string[] = []; + + // + const defaultActions: XOneOrManyType = []; + + // + // Import ... + const importId = DefaultActionBarItemIds.Upload.toString(); + if (!hideActions.includes(importId)) { + defaultActions.push({ + // + ...defaultActionBarUploadItem, + + // + title: XResourceIDs.import, + color: XColorWithBrightness.Success, + + // + index: 1, + disabled: this.uiDisabled, + }); + } + //#endregion + + // + if (!this.actionBarActions) { + // + this.actionBarActions = defaultActions; + this.handleChangeDetection(); + return; + } + + // + const actions = await this.getArrayValueAsync(this.actionBarActions); + if (!hasChild(actions)) { + // + this.actionBarActions = defaultActions; + this.handleChangeDetection(); + return; + } + + // + this.actionBarActions = actions; + this.handleChangeDetection(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-theme\x-theme-manager\x-theme-manager.typings.ts + +import { + Input, + Inject, + NgZone, + Output, + Renderer2, + Component, + Injectable, + ElementRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XResourceIDs, + XBorderStyle, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, + XBorderStyleIdentifier, +} from 'x-framework-core'; +import { + XCardContainer, + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../../x-card/x-card.typings'; +import { + XComponentListContainer, + IXComponentListContainer, +} from '../../x-list/x-list.typings'; +import { + XFileContainer, + IXFileContainer, +} from '../../classes/i-x-file.container'; +import { concatMap, map } from 'rxjs/operators'; +import { + XActionBarActionFiredModel, + XComponentActionBarContainer, + IXComponentActionBarContainer, +} from '../../x-action-bar/x-action-bar.typings'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { XIconIdentifier, XIconNames } from '../../x-icon/x-icon.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { IXFileUploadContainer } from '../../x-file-upload/x-file-upload.typings'; +import { XFileUploadContainer } from './../../x-file-upload/x-file-upload.typings'; +import { XSearchBarEventIdentifier } from '../../x-search-bar/x-search-bar.typings'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; +import { XColorPickerDialogService } from '../../x-color-picker/x-color-picker-dialog/x-color-picker-dialog.service'; + +export enum XThemeManagerAction { + // + Back = 'back', + Refresh = 'refresh', +} + +export type XThemeManagerActionIdentifier = XThemeManagerAction | string; + +export interface XThemeManagerActionModel + extends XBaseActionModel {} + +export enum XThemeManagerTabs { + List, + Details, + Edit, + Import, +} + +// +//#region Inside Card Container ... +// +type TXSelectedComponentListContainer = Pick< + IXComponentListContainer, + | 'listCssClass' + | 'listShowDivider' + // + // List Empty ... + | 'listShowEmpty' + | 'listEmptyActionProvider' + | 'listEmptyColor' + | 'listEmptyCssClass' + | 'listEmptyForegroundColor' + | 'listEmptyForegroundMessageColor' + | 'listEmptyForegroundRefreshButtonColor' + | 'listEmptyHasRefresh' + | 'listEmptyMessage' + | 'listEmptyMessageColor' + | 'listEmptyRefresh' + | 'listEmptyRefreshButtonColor' + | 'listEmptyRefreshIcon' + // + // List Search ... + | 'listSearchBarValue' + | 'listSearchBarColor' + | 'listSearchBarDebounce' + | 'listSearchBarSearchInputColor' + | 'listSearchBarForegroundSearchInputColor' + | 'listSearchBarCancelButtonIcon' + | 'listSearchBarCancelButtonTitle' + | 'listSearchBarClearButtonIcon' + | 'listSearchBarClearButtonIconColor' + | 'listSearchBarCssClass' + | 'listSearchBarEnterKeyHint' + | 'listSearchBarForegroundColor' + | 'listSearchBarKeyboardType' + | 'listActionBarActionFired' + | 'listSearchBarOnEvent' + | 'listSearchBarQueryChange' + | 'listSearchBarSearchButtonIcon' + | 'listSearchBarSearchButtonIconColor' + | 'listSearchBarShowCancelButton' + | 'listSearchBarSpellcheck' + | 'listSearchBarTitle' + | 'listShowSearchBar' +>; + +// +type TXSelectedComponentActionBarContainer = Pick< + IXComponentActionBarContainer, + | 'showActionBar' + | 'actionBarColor' + | 'actionBarCssClass' + | 'actionBarForegroundColor' +>; + +// +type TXSelectedComponentFileUploadContainer = Pick< + { + [TKey in keyof IXFileUploadContainer as `themeUpload${Capitalize< + string & TKey + >}`]: IXFileUploadContainer[TKey]; + }, + // + | 'themeUploadColor' + | 'themeUploadCssClass' + | 'themeUploadEmptyMessage' + | 'themeUploadForegroundColor' + | 'themeUploadFileDropAreaIcon' + | 'themeUploadShowFileDropArea' + | 'themeUploadFileDropAreaColor' + | 'themeUploadFileDropAreaMessage' + | 'themeUploadFileDropAreaCssClass' + | 'themeUploadFileDropAreaErrorColor' + | 'themeUploadFileDropAreaAccentColor' + | 'themeUploadFileDropAreaSuccessColor' + | 'themeUploadFileDropAreaForegroundColor' + | 'themeUploadEmptyForegroundMessageColor' + | 'themeUploadFileDropAreaSupportsMultipleFiles' +>; + +// +type TXSelectedFileContainer = Pick< + IXFileContainer, + | 'maxAllowedSize' + | 'minAllowedFileSize' + | 'maxAllowedFileSize' + | 'maxAllowedFiles' +>; + +// +type TXThemeManagerContainer = IXComponentCardContainer & + TXSelectedComponentActionBarContainer & + TXSelectedComponentListContainer & + TXSelectedComponentFileUploadContainer & + TXSelectedFileContainer & {}; + +// +export interface IXThemeManagerContainer extends TXThemeManagerContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + + // + themeChange: EventEmitter; + tabChange: EventEmitter>; + + // + restartAfterSetTheme: XStandardType; + colorViewBorderWidth: XStandardType; + colorViewBorderStyle: XStandardType; + + // + propertyLabelColor: XStandardType; + propertyValueColor: XStandardType; + colorViewBorderColor: XStandardType; + + // + propertyActionMenuIconColor: XStandardType; + propertyActionMenuIcon: XStandardType; + propertyEditIconColor: XStandardType; + propertyEditIcon: XStandardType; + propertyClearIconColor: XStandardType; + propertyClearIcon: XStandardType; + emptyPropertyEditIconColor: XStandardType; + + // + presenterHeaderColor: XStandardType; + presenterContentColor: XStandardType; + presenterHeaderHighlightColor: XStandardType; + + // + saveColor: XStandardType; + saveLabel: XStandardType; + resetColor: XStandardType; + resetLabel: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XThemeManagerContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + // + //#region Self ... + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + + // + readonly themeChange: EventEmitter = new EventEmitter(); + readonly tabChange: EventEmitter> = new EventEmitter< + XStandardType + >(); + + // + readonly restartAfterSetTheme: XStandardType = true; + readonly colorViewBorderWidth: XStandardType = 1; + readonly colorViewBorderStyle: XStandardType = + XBorderStyle.Solid; + + // + readonly propertyLabelColor: XStandardType = + XColorWithBrightness.Tertiary; + readonly propertyValueColor: XStandardType = + XColorWithBrightness.Dark; + readonly colorViewBorderColor: XStandardType = + XColorWithBrightness.LightShade; + + // + readonly propertyActionMenuIconColor: XStandardType = + XColorWithBrightness.Dark; + readonly propertyActionMenuIcon: XStandardType = + XIconNames.menu_vertical; + readonly propertyEditIconColor: XStandardType = + XColorWithBrightness.Tertiary; + readonly propertyEditIcon: XStandardType = XIconNames.edit; + readonly propertyClearIconColor: XStandardType = + XColorWithBrightness.Danger; + readonly propertyClearIcon: XStandardType = XIconNames.clear; + readonly emptyPropertyEditIconColor: XStandardType = + XColorWithBrightness.Danger; + + // + readonly presenterHeaderColor: XStandardType = + XColorWithBrightness.Light; + readonly presenterContentColor: XStandardType = + XColorWithBrightness.Dark; + readonly presenterHeaderHighlightColor: XStandardType = + XColorWithBrightness.LightShade; + + // + readonly saveColor: XStandardType = + XColorWithBrightness.Tertiary; + readonly saveLabel: XStandardType = XResourceIDs.save; + + // + readonly resetColor: XStandardType = + XColorWithBrightness.WarningShade; + readonly resetLabel: XStandardType = XResourceIDs.reset; + //#endregion + + // + //#region ActionBar ... + readonly showActionBar = this.actionBarPropertyProvider.showActionBar; + readonly actionBarColor = this.actionBarPropertyProvider.actionBarColor; + readonly actionBarCssClass = this.actionBarPropertyProvider.actionBarCssClass; + readonly actionBarForegroundColor = + this.actionBarPropertyProvider.actionBarForegroundColor; + //#endregion + + // + //#region List ... + readonly listCssClass = this.listPropertyProvider.listCssClass; + readonly listShowDivider = this.listPropertyProvider.listShowDivider; + + // + //#region List Empty ... + readonly listShowEmpty = this.listPropertyProvider.listShowEmpty; + readonly listEmptyActionProvider = + this.listPropertyProvider.listEmptyActionProvider; + readonly listEmptyColor = this.listPropertyProvider.listEmptyColor; + readonly listEmptyCssClass = this.listPropertyProvider.listEmptyCssClass; + readonly listEmptyForegroundColor = + this.listPropertyProvider.listEmptyForegroundColor; + readonly listEmptyForegroundMessageColor = + this.listPropertyProvider.listEmptyForegroundMessageColor; + readonly listEmptyForegroundRefreshButtonColor = + this.listPropertyProvider.listEmptyForegroundRefreshButtonColor; + readonly listEmptyHasRefresh = this.listPropertyProvider.listEmptyHasRefresh; + readonly listEmptyMessage = this.listPropertyProvider.listEmptyMessage; + readonly listEmptyMessageColor = + this.listPropertyProvider.listEmptyMessageColor; + readonly listEmptyRefresh = this.listPropertyProvider.listEmptyRefresh; + readonly listEmptyRefreshButtonColor = + this.listPropertyProvider.listEmptyRefreshButtonColor; + readonly listEmptyRefreshIcon = + this.listPropertyProvider.listEmptyRefreshIcon; + //#endregion + + // + //#region List Search ... + readonly listSearchBarValue = this.listPropertyProvider.listSearchBarValue; + readonly listSearchBarColor = this.listPropertyProvider.listSearchBarColor; + readonly listSearchBarDebounce = + this.listPropertyProvider.listSearchBarDebounce; + readonly listSearchBarSearchInputColor = + this.listPropertyProvider.listSearchBarSearchInputColor; + readonly listSearchBarForegroundSearchInputColor = + this.listPropertyProvider.listSearchBarForegroundSearchInputColor; + readonly listSearchBarCancelButtonIcon = + this.listPropertyProvider.listSearchBarCancelButtonIcon; + readonly listSearchBarCancelButtonTitle = + this.listPropertyProvider.listSearchBarCancelButtonTitle; + readonly listSearchBarClearButtonIcon = + this.listPropertyProvider.listSearchBarClearButtonIcon; + readonly listSearchBarClearButtonIconColor = + this.listPropertyProvider.listSearchBarClearButtonIconColor; + readonly listSearchBarCssClass = + this.listPropertyProvider.listSearchBarCssClass; + readonly listSearchBarEnterKeyHint = + this.listPropertyProvider.listSearchBarEnterKeyHint; + readonly listSearchBarForegroundColor = + this.listPropertyProvider.listSearchBarForegroundColor; + readonly listSearchBarKeyboardType = + this.listPropertyProvider.listSearchBarKeyboardType; + readonly listActionBarActionFired = + this.listPropertyProvider.listActionBarActionFired; + readonly listSearchBarOnEvent = + this.listPropertyProvider.listSearchBarOnEvent; + readonly listSearchBarQueryChange = + this.listPropertyProvider.listSearchBarQueryChange; + readonly listSearchBarSearchButtonIcon = + this.listPropertyProvider.listSearchBarSearchButtonIcon; + readonly listSearchBarSearchButtonIconColor = + this.listPropertyProvider.listSearchBarSearchButtonIconColor; + readonly listSearchBarShowCancelButton = + this.listPropertyProvider.listSearchBarShowCancelButton; + readonly listSearchBarSpellcheck = + this.listPropertyProvider.listSearchBarSpellcheck; + readonly listSearchBarTitle = this.listPropertyProvider.listSearchBarTitle; + readonly listShowSearchBar = this.listPropertyProvider.listShowSearchBar; + //#endregion + //#endregion + + // + //#region File Upload ... + readonly themeUploadColor = this.fileUploadPropertyProvider.color; + readonly themeUploadCssClass = this.fileUploadPropertyProvider.cssClass; + readonly themeUploadEmptyMessage = + this.fileUploadPropertyProvider.emptyMessage; + readonly themeUploadForegroundColor = + this.fileUploadPropertyProvider.foregroundColor; + readonly themeUploadFileDropAreaIcon = + this.fileUploadPropertyProvider.fileDropAreaIcon; + readonly themeUploadShowFileDropArea = + this.fileUploadPropertyProvider.showFileDropArea; + readonly themeUploadFileDropAreaColor = + this.fileUploadPropertyProvider.fileDropAreaColor; + readonly themeUploadFileDropAreaMessage = + this.fileUploadPropertyProvider.fileDropAreaMessage; + readonly themeUploadFileDropAreaCssClass = + this.fileUploadPropertyProvider.fileDropAreaCssClass; + readonly themeUploadFileDropAreaErrorColor = + this.fileUploadPropertyProvider.fileDropAreaErrorColor; + readonly themeUploadFileDropAreaAccentColor = + this.fileUploadPropertyProvider.fileDropAreaAccentColor; + readonly themeUploadFileDropAreaSuccessColor = + this.fileUploadPropertyProvider.fileDropAreaSuccessColor; + readonly themeUploadFileDropAreaForegroundColor = + this.fileUploadPropertyProvider.fileDropAreaForegroundColor; + readonly themeUploadEmptyForegroundMessageColor = + this.fileUploadPropertyProvider.emptyForegroundMessageColor; + readonly themeUploadFileDropAreaSupportsMultipleFiles = + this.fileUploadPropertyProvider.fileDropAreaSupportsMultipleFiles; + //#endregion + + // + //#region File ... + readonly maxAllowedSize = this.filePropertyProvider.maxAllowedSize; + readonly minAllowedFileSize = this.filePropertyProvider.minAllowedFileSize; + readonly maxAllowedFileSize = this.filePropertyProvider.maxAllowedFileSize; + readonly maxAllowedFiles = this.filePropertyProvider.maxAllowedFiles; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected filePropertyProvider: XFileContainer, + protected cardPropertyProvider: XCardContainer, + protected listPropertyProvider: XComponentListContainer, + protected fileUploadPropertyProvider: XFileUploadContainer, + protected actionBarPropertyProvider: XComponentActionBarContainer + ) { + super(cardPropertyProvider); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XThemeManagerContainerComponent + extends XComponentCardContainerComponent + implements IXThemeManagerContainer +{ + // + //#region Props ... + // + //#region Self ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + color: XStandardType = this.propertyProvider.color; + + // + @Output() + themeChange: EventEmitter = new EventEmitter(); + + @Output() + tabChange: EventEmitter> = new EventEmitter(); + + // + @Input() + restartAfterSetTheme: XStandardType = + this.propertyProvider.restartAfterSetTheme; + + @Input() + colorViewBorderWidth: XStandardType = + this.propertyProvider.colorViewBorderWidth; + + @Input() + colorViewBorderStyle: XStandardType = + this.propertyProvider.colorViewBorderStyle; + + // + @Input() + propertyLabelColor: XStandardType = + this.propertyProvider.propertyLabelColor; + + @Input() + propertyValueColor: XStandardType = + this.propertyProvider.propertyValueColor; + + @Input() + colorViewBorderColor: XStandardType = + this.propertyProvider.colorViewBorderColor; + + // + @Input() + propertyActionMenuIconColor: XStandardType = + this.propertyProvider.propertyActionMenuIconColor; + + @Input() + propertyActionMenuIcon: XStandardType = + this.propertyProvider.propertyActionMenuIcon; + + @Input() + propertyEditIconColor: XStandardType = + this.propertyProvider.propertyEditIconColor; + + @Input() + propertyEditIcon: XStandardType = + this.propertyProvider.propertyEditIcon; + + @Input() + propertyClearIconColor: XStandardType = + this.propertyProvider.propertyClearIconColor; + + @Input() + propertyClearIcon: XStandardType = + this.propertyProvider.propertyClearIcon; + + @Input() + emptyPropertyEditIconColor: XStandardType = + this.propertyProvider.emptyPropertyEditIconColor; + + // + @Input() + presenterHeaderColor: XStandardType = + this.propertyProvider.presenterHeaderColor; + + @Input() + presenterContentColor: XStandardType = + this.propertyProvider.presenterContentColor; + + @Input() + presenterHeaderHighlightColor: XStandardType = + this.propertyProvider.presenterHeaderHighlightColor; + + // + @Input() + saveColor: XStandardType = this.propertyProvider.saveColor; + + @Input() + saveLabel: XStandardType = + this.propertyProvider.saveLabel; + + // + @Input() + resetColor: XStandardType = + this.propertyProvider.resetColor; + + @Input() + resetLabel: XStandardType = + this.propertyProvider.resetLabel; + //#endregion + + // + //#region ActionBar ... + @Input() + showActionBar: XStandardType = this.propertyProvider.showActionBar; + + @Input() + actionBarColor: XStandardType = + this.propertyProvider.actionBarColor; + + @Input() + actionBarCssClass: XStandardType> = + this.propertyProvider.actionBarCssClass; + + @Input() + actionBarForegroundColor: XStandardType = + this.propertyProvider.actionBarForegroundColor; + //#endregion + + // + //#region List ... + @Input() + listCssClass = this.propertyProvider.listCssClass; + + @Input() + listShowDivider = this.propertyProvider.listShowDivider; + + // + //#region List Empty ... + @Input() + listShowEmpty = this.propertyProvider.listShowEmpty; + + @Input() + listEmptyActionProvider = this.propertyProvider.listEmptyActionProvider; + + @Input() + listEmptyColor = this.propertyProvider.listEmptyColor; + + @Input() + listEmptyCssClass = this.propertyProvider.listEmptyCssClass; + + @Input() + listEmptyForegroundColor = this.propertyProvider.listEmptyForegroundColor; + + @Input() + listEmptyForegroundMessageColor = + this.propertyProvider.listEmptyForegroundMessageColor; + + @Input() + listEmptyForegroundRefreshButtonColor = + this.propertyProvider.listEmptyForegroundRefreshButtonColor; + + @Input() + listEmptyHasRefresh = this.propertyProvider.listEmptyHasRefresh; + + @Input() + listEmptyMessage = this.propertyProvider.listEmptyMessage; + + @Input() + listEmptyMessageColor = this.propertyProvider.listEmptyMessageColor; + + @Output() + listEmptyRefresh: EventEmitter = new EventEmitter(); + + @Input() + listEmptyRefreshButtonColor = + this.propertyProvider.listEmptyRefreshButtonColor; + + @Input() + listEmptyRefreshIcon = this.propertyProvider.listEmptyRefreshIcon; + //#endregion + + // + //#region List Search ... + @Input() + listSearchBarValue = this.propertyProvider.listSearchBarValue; + + @Input() + listSearchBarColor = this.propertyProvider.listSearchBarColor; + + @Input() + listSearchBarDebounce = this.propertyProvider.listSearchBarDebounce; + + @Input() + listSearchBarSearchInputColor = + this.propertyProvider.listSearchBarSearchInputColor; + + @Input() + listSearchBarForegroundSearchInputColor = + this.propertyProvider.listSearchBarForegroundSearchInputColor; + + @Input() + listSearchBarCancelButtonIcon = + this.propertyProvider.listSearchBarCancelButtonIcon; + + @Input() + listSearchBarCancelButtonTitle = + this.propertyProvider.listSearchBarCancelButtonTitle; + + @Input() + listSearchBarClearButtonIcon = + this.propertyProvider.listSearchBarClearButtonIcon; + + @Input() + listSearchBarClearButtonIconColor = + this.propertyProvider.listSearchBarClearButtonIconColor; + + @Input() + listSearchBarCssClass = this.propertyProvider.listSearchBarCssClass; + + @Input() + listSearchBarEnterKeyHint = this.propertyProvider.listSearchBarEnterKeyHint; + + @Input() + listSearchBarForegroundColor = + this.propertyProvider.listSearchBarForegroundColor; + + @Input() + listSearchBarKeyboardType = this.propertyProvider.listSearchBarKeyboardType; + + @Output() + listActionBarActionFired: EventEmitter = new EventEmitter(); + + @Output() + listSearchBarOnEvent: EventEmitter = new EventEmitter(); + + @Output() + listSearchBarQueryChange: EventEmitter = new EventEmitter(); + + @Input() + listSearchBarSearchButtonIcon = + this.propertyProvider.listSearchBarSearchButtonIcon; + + @Input() + listSearchBarSearchButtonIconColor = + this.propertyProvider.listSearchBarSearchButtonIconColor; + + @Input() + listSearchBarShowCancelButton = + this.propertyProvider.listSearchBarShowCancelButton; + + @Input() + listSearchBarSpellcheck = this.propertyProvider.listSearchBarSpellcheck; + + @Input() + listSearchBarTitle = this.propertyProvider.listSearchBarTitle; + + @Input() + listShowSearchBar = this.propertyProvider.listShowSearchBar; + //#endregion + //#endregion + + // + //#region File Upload ... + @Input() + themeUploadColor = this.propertyProvider.themeUploadColor; + + @Input() + themeUploadCssClass = this.propertyProvider.themeUploadCssClass; + + @Input() + themeUploadEmptyMessage = this.propertyProvider.themeUploadEmptyMessage; + + @Input() + themeUploadForegroundColor = this.propertyProvider.themeUploadForegroundColor; + + @Input() + themeUploadFileDropAreaIcon = + this.propertyProvider.themeUploadFileDropAreaIcon; + + @Input() + themeUploadShowFileDropArea = + this.propertyProvider.themeUploadShowFileDropArea; + + @Input() + themeUploadFileDropAreaColor = + this.propertyProvider.themeUploadFileDropAreaColor; + + @Input() + themeUploadFileDropAreaMessage = + this.propertyProvider.themeUploadFileDropAreaMessage; + + @Input() + themeUploadFileDropAreaCssClass = + this.propertyProvider.themeUploadFileDropAreaCssClass; + + @Input() + themeUploadFileDropAreaErrorColor = + this.propertyProvider.themeUploadFileDropAreaErrorColor; + + @Input() + themeUploadFileDropAreaAccentColor = + this.propertyProvider.themeUploadFileDropAreaAccentColor; + + @Input() + themeUploadFileDropAreaSuccessColor = + this.propertyProvider.themeUploadFileDropAreaSuccessColor; + + @Input() + themeUploadFileDropAreaForegroundColor = + this.propertyProvider.themeUploadFileDropAreaForegroundColor; + + @Input() + themeUploadEmptyForegroundMessageColor = + this.propertyProvider.themeUploadEmptyForegroundMessageColor; + + @Input() + themeUploadFileDropAreaSupportsMultipleFiles = + this.propertyProvider.themeUploadFileDropAreaSupportsMultipleFiles; + //#endregion + + // + //#region File ... + @Input() + maxAllowedSize = this.propertyProvider.maxAllowedSize; + + @Input() + minAllowedFileSize = this.propertyProvider.minAllowedFileSize; + + @Input() + maxAllowedFileSize = this.propertyProvider.maxAllowedFileSize; + + @Input() + maxAllowedFiles = this.propertyProvider.maxAllowedFiles; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XThemeManagerContainer, + public colorPickerDialogService: XColorPickerDialogService + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXThemeManagerContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + + // + restartAfterSetTheme: this.getValue( + container + ? container.restartAfterSetTheme + : this.restartAfterSetTheme + ), + colorViewBorderWidth: this.getValue( + container + ? container.colorViewBorderWidth + : this.colorViewBorderWidth + ), + colorViewBorderStyle: this.getValue( + container + ? container.colorViewBorderStyle + : this.colorViewBorderStyle + ), + + // + propertyLabelColor: this.getValue( + container ? container.propertyLabelColor : this.propertyLabelColor + ), + propertyValueColor: this.getValue( + container ? container.propertyValueColor : this.propertyValueColor + ), + colorViewBorderColor: this.getValue( + container + ? container.colorViewBorderColor + : this.colorViewBorderColor + ), + color: this.getValue(container ? container.color : this.color), + + // + propertyActionMenuIconColor: this.getValue( + container + ? container.propertyActionMenuIconColor + : this.propertyActionMenuIconColor + ), + propertyActionMenuIcon: this.getValue( + container + ? container.propertyActionMenuIcon + : this.propertyActionMenuIcon + ), + propertyEditIconColor: this.getValue( + container + ? container.propertyEditIconColor + : this.propertyEditIconColor + ), + propertyEditIcon: this.getValue( + container ? container.propertyEditIcon : this.propertyEditIcon + ), + propertyClearIconColor: this.getValue( + container + ? container.propertyClearIconColor + : this.propertyClearIconColor + ), + propertyClearIcon: this.getValue( + container ? container.propertyClearIcon : this.propertyClearIcon + ), + emptyPropertyEditIconColor: this.getValue( + container + ? container.emptyPropertyEditIconColor + : this.emptyPropertyEditIconColor + ), + + // + presenterHeaderColor: this.getValue( + container + ? container.presenterHeaderColor + : this.presenterHeaderColor + ), + presenterContentColor: this.getValue( + container + ? container.presenterContentColor + : this.presenterContentColor + ), + presenterHeaderHighlightColor: this.getValue( + container + ? container.presenterHeaderHighlightColor + : this.presenterHeaderHighlightColor + ), + + // + saveColor: this.getValue( + container ? container.saveColor : this.saveColor + ), + saveLabel: this.getValue( + container ? container.saveLabel : this.saveLabel + ), + + // + resetColor: this.getValue( + container ? container.resetColor : this.resetColor + ), + resetLabel: this.getValue( + container ? container.resetLabel : this.resetLabel + ), + + // + showActionBar: this.getValue( + container ? container.showActionBar : this.showActionBar + ), + actionBarColor: this.getValue( + container ? container.actionBarColor : this.actionBarColor + ), + actionBarCssClass: this.getValue( + container ? container.actionBarCssClass : this.actionBarCssClass + ), + actionBarForegroundColor: this.getValue( + container + ? container.actionBarForegroundColor + : this.actionBarForegroundColor + ), + + // + listCssClass: this.getValue( + container ? container.listCssClass : this.listCssClass + ), + listShowDivider: this.getValue( + container ? container.listShowDivider : this.listShowDivider + ), + + // + listShowEmpty: this.getValue( + container ? container.listShowEmpty : this.listShowEmpty + ), + listEmptyColor: this.getValue( + container ? container.listEmptyColor : this.listEmptyColor + ), + listEmptyCssClass: this.getValue( + container ? container.listEmptyCssClass : this.listEmptyCssClass + ), + listEmptyForegroundColor: this.getValue( + container + ? container.listEmptyForegroundColor + : this.listEmptyForegroundColor + ), + listEmptyForegroundMessageColor: this.getValue( + container + ? container.listEmptyForegroundMessageColor + : this.listEmptyForegroundMessageColor + ), + listEmptyForegroundRefreshButtonColor: this.getValue( + container + ? container.listEmptyForegroundRefreshButtonColor + : this.listEmptyForegroundRefreshButtonColor + ), + listEmptyHasRefresh: this.getValue( + container + ? container.listEmptyHasRefresh + : this.listEmptyHasRefresh + ), + listEmptyMessage: this.getValue( + container ? container.listEmptyMessage : this.listEmptyMessage + ), + listEmptyMessageColor: this.getValue( + container + ? container.listEmptyMessageColor + : this.listEmptyMessageColor + ), + listEmptyRefreshButtonColor: this.getValue( + container + ? container.listEmptyRefreshButtonColor + : this.listEmptyRefreshButtonColor + ), + listEmptyRefreshIcon: this.getValue( + container + ? container.listEmptyRefreshIcon + : this.listEmptyRefreshIcon + ), + + // + listSearchBarValue: this.getValue( + container ? container.listSearchBarValue : this.listSearchBarValue + ), + listSearchBarColor: this.getValue( + container ? container.listSearchBarColor : this.listSearchBarColor + ), + listSearchBarDebounce: this.getValue( + container + ? container.listSearchBarDebounce + : this.listSearchBarDebounce + ), + listSearchBarSearchInputColor: this.getValue( + container + ? container.listSearchBarSearchInputColor + : this.listSearchBarSearchInputColor + ), + listSearchBarForegroundSearchInputColor: this.getValue( + container + ? container.listSearchBarForegroundSearchInputColor + : this.listSearchBarForegroundSearchInputColor + ), + listSearchBarCancelButtonIcon: this.getValue( + container + ? container.listSearchBarCancelButtonIcon + : this.listSearchBarCancelButtonIcon + ), + listSearchBarCancelButtonTitle: this.getValue( + container + ? container.listSearchBarCancelButtonTitle + : this.listSearchBarCancelButtonTitle + ), + listSearchBarClearButtonIcon: this.getValue( + container + ? container.listSearchBarClearButtonIcon + : this.listSearchBarClearButtonIcon + ), + listSearchBarClearButtonIconColor: this.getValue( + container + ? container.listSearchBarClearButtonIconColor + : this.listSearchBarClearButtonIconColor + ), + listSearchBarCssClass: this.getValue( + container + ? container.listSearchBarCssClass + : this.listSearchBarCssClass + ), + listSearchBarEnterKeyHint: this.getValue( + container + ? container.listSearchBarEnterKeyHint + : this.listSearchBarEnterKeyHint + ), + listSearchBarForegroundColor: this.getValue( + container + ? container.listSearchBarForegroundColor + : this.listSearchBarForegroundColor + ), + listSearchBarKeyboardType: this.getValue( + container + ? container.listSearchBarKeyboardType + : this.listSearchBarKeyboardType + ), + listSearchBarSearchButtonIcon: this.getValue( + container + ? container.listSearchBarSearchButtonIcon + : this.listSearchBarSearchButtonIcon + ), + listSearchBarSearchButtonIconColor: this.getValue( + container + ? container.listSearchBarSearchButtonIconColor + : this.listSearchBarSearchButtonIconColor + ), + listSearchBarShowCancelButton: this.getValue( + container + ? container.listSearchBarShowCancelButton + : this.listSearchBarShowCancelButton + ), + listSearchBarSpellcheck: this.getValue( + container + ? container.listSearchBarSpellcheck + : this.listSearchBarSpellcheck + ), + listSearchBarTitle: this.getValue( + container ? container.listSearchBarTitle : this.listSearchBarTitle + ), + listShowSearchBar: this.getValue( + container ? container.listShowSearchBar : this.listShowSearchBar + ), + + // + themeUploadColor: this.getValue( + container ? container.themeUploadColor : this.themeUploadColor + ), + themeUploadCssClass: this.getValue( + container + ? container.themeUploadCssClass + : this.themeUploadCssClass + ), + themeUploadEmptyMessage: this.getValue( + container + ? container.themeUploadEmptyMessage + : this.themeUploadEmptyMessage + ), + themeUploadForegroundColor: this.getValue( + container + ? container.themeUploadForegroundColor + : this.themeUploadForegroundColor + ), + themeUploadFileDropAreaIcon: this.getValue( + container + ? container.themeUploadFileDropAreaIcon + : this.themeUploadFileDropAreaIcon + ), + themeUploadShowFileDropArea: this.getValue( + container + ? container.themeUploadShowFileDropArea + : this.themeUploadShowFileDropArea + ), + themeUploadFileDropAreaColor: this.getValue( + container + ? container.themeUploadFileDropAreaColor + : this.themeUploadFileDropAreaColor + ), + themeUploadFileDropAreaMessage: this.getValue( + container + ? container.themeUploadFileDropAreaMessage + : this.themeUploadFileDropAreaMessage + ), + themeUploadFileDropAreaCssClass: this.getValue( + container + ? container.themeUploadFileDropAreaCssClass + : this.themeUploadFileDropAreaCssClass + ), + themeUploadFileDropAreaErrorColor: this.getValue( + container + ? container.themeUploadFileDropAreaErrorColor + : this.themeUploadFileDropAreaErrorColor + ), + themeUploadFileDropAreaAccentColor: this.getValue( + container + ? container.themeUploadFileDropAreaAccentColor + : this.themeUploadFileDropAreaAccentColor + ), + themeUploadFileDropAreaSuccessColor: this.getValue( + container + ? container.themeUploadFileDropAreaSuccessColor + : this.themeUploadFileDropAreaSuccessColor + ), + themeUploadFileDropAreaForegroundColor: this.getValue( + container + ? container.themeUploadFileDropAreaForegroundColor + : this.themeUploadFileDropAreaForegroundColor + ), + themeUploadEmptyForegroundMessageColor: this.getValue( + container + ? container.themeUploadEmptyForegroundMessageColor + : this.themeUploadEmptyForegroundMessageColor + ), + themeUploadFileDropAreaSupportsMultipleFiles: this.getValue( + container + ? container.themeUploadFileDropAreaSupportsMultipleFiles + : this.themeUploadFileDropAreaSupportsMultipleFiles + ), + + // + maxAllowedSize: this.getValue( + container ? container.maxAllowedSize : this.maxAllowedSize + ), + minAllowedFileSize: this.getValue( + container ? container.minAllowedFileSize : this.minAllowedFileSize + ), + maxAllowedFileSize: this.getValue( + container ? container.maxAllowedFileSize : this.maxAllowedFileSize + ), + maxAllowedFiles: this.getValue( + container ? container.maxAllowedFiles : this.maxAllowedFiles + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + themeChange: this.themeChange, + tabChange: this.tabChange, + + // + listEmptyActionProvider: container + ? container.listEmptyActionProvider + : this.listEmptyActionProvider, + + // + listEmptyRefresh: this.listEmptyRefresh, + + // + listActionBarActionFired: this.listActionBarActionFired, + listSearchBarOnEvent: this.listSearchBarOnEvent, + listSearchBarQueryChange: this.listSearchBarQueryChange, + }; + }) + ); + } + //#endregion +} + +function XStandardType() { + throw new Error('Function not implemented.'); +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-thumbnail\x-thumbnail.component.html + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-thumbnail\x-thumbnail.component.scss + +:host { + --thumbnail-size: 32px; +} + +x-icon { + --size: var(--thumbnail-size, 32px); + color: inherit; +} + +.x-item, +.x-wrapper, +ion-thumbnail { + // + width: var(--thumbnail-size, 32px); + min-width: var(--thumbnail-size, 32px); + + // + height: var(--thumbnail-size, 32px); + min-height: var(--thumbnail-size, 32px); + + // + color: inherit; +} + +ion-thumbnail.circular { + --border-radius: 50%; +} + +.x-thumbnail { + // + display: inline-block; + position: relative; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-thumbnail\x-thumbnail.component.ts + +import { + nameof, + isString, + XFileType, + getFileType, + XGeneralThumbnails, + getGeneralThumbnail, +} from 'x-framework-core'; +import { + XThumbnailType, + XThumbnailAction, + XThumbnailInputType, + XThumbnailActionModel, + XThumbnailContainerComponent, +} from './x-thumbnail.typings'; +import { Subject, Subscription } from 'rxjs'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import { map, filter, concatMap } from 'rxjs/operators'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-thumbnail', + templateUrl: './x-thumbnail.component.html', + styleUrls: ['./x-thumbnail.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XThumbnailComponent extends XThumbnailContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private PREPARED_IDENTIFIER: XThumbnailInputType; + + get preparedIdentifier() { + return this.PREPARED_IDENTIFIER; + } + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Identifier Changed ... + const isIdentifierChanged = changeKeys.includes( + nameof('identifier') + ); + if (isIdentifierChanged) { + await this.prepareIdentifier(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Register Handlers ... + //#endregion + + // + //#region UI Providers ... + // + //#region Type Providers ... + isTypeGeneral() { + return this.getValue(this.type).pipe( + map( + (type) => + type === XThumbnailType.GENERAL || type !== XThumbnailType.CUSTOM + ) + ); + } + + isTypeCustom() { + return this.getValue(this.type).pipe( + map((type) => type === XThumbnailType.CUSTOM) + ); + } + //#endregion + + isUrlIdentifier() { + // + // && isValidURL(identifier as string) + return this.getIdentifier().pipe(map((identifier) => isString(identifier))); + } + + isImageTypeIdentifier() { + return this.isUrlIdentifier().pipe( + filter((isUrl) => !isUrl), + concatMap(() => this.getIdentifier()), + map((identifier) => { + // + const type = getFileType(identifier); + return [ + XFileType.CoverImage, + XFileType.Image, + XFileType.ProfileImage, + ].includes(type); + }) + ); + } + + getGThumbnail(identifier: XThumbnailInputType) { + return this.getValue(getGeneralThumbnail(identifier)).pipe( + map((type) => { + switch (type) { + // + case XGeneralThumbnails.IMAGE: + return XIconNames.image; + + // + case XGeneralThumbnails.AUDIO: + return XIconNames.audio; + + // + case XGeneralThumbnails.VIDEO: + return XIconNames.video; + + // + case XGeneralThumbnails.DOCUMENT: + default: + return XIconNames.document; + } + }) + ); + } + //#endregion + + // + //#region UI Handlers ... + async handleClicked() { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + const isClickable = await this.getValueAsync(this.clickable); + if (isClickable) { + this.clicked.emit(); + } + } + //#endregion + + // + //#region Actions ... + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XThumbnailAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private getIdentifier() { + return this.getValue(this.identifier); + } + + private async prepareIdentifier() { + // + const identifier = await this.getValueAsync(this.identifier); + this.PREPARED_IDENTIFIER = identifier; + + // + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-thumbnail\x-thumbnail.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XThumbnailComponent } from './x-thumbnail.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XThumbnailComponent], + imports: [CommonModule, XIconModule, XBaseComponentModule], + exports: [XIconModule, XBaseComponentModule, XThumbnailComponent], +}) +export class XThumbnailModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-thumbnail\x-thumbnail.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XFileBlob, XOneOrManyType, XStandardType } from 'x-framework-core'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XThumbnailType { + GENERAL = 'general', + CUSTOM = 'custom', +} + +export type XThumbnailTypeIdentifier = XThumbnailType | string; + +export type XThumbnailInputType = File | Blob | XFileBlob | string; + +export enum XThumbnailAction { + Refresh = 'refresh', +} + +export type XThumbnailActionIdentifier = XThumbnailAction | string; + +export interface XThumbnailActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXThumbnailContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + circular: XStandardType; + clickable: XStandardType; + selectable: XStandardType; + type: XStandardType; + identifier: XStandardType; + + // + clicked: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XThumbnailContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly circular: XStandardType = false; + + readonly clickable: XStandardType = false; + + readonly selectable: XStandardType = false; + + readonly type: XStandardType = XThumbnailType.GENERAL; + + readonly identifier: XStandardType; + + // + readonly clicked: EventEmitter = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XThumbnailContainerComponent + extends XBaseContainerComponent + implements IXThumbnailContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + circular: XStandardType = this.propertyProvider.circular; + + @Input() + clickable: XStandardType = this.propertyProvider.clickable; + + @Input() + selectable: XStandardType = this.propertyProvider.selectable; + + @Input() + type: XStandardType = this.propertyProvider.type; + + @Input() + identifier: XStandardType = + this.propertyProvider.identifier; + + @Output() + clicked: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XThumbnailContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXThumbnailContainer + ): Observable { + return forkJoin({ + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + circular: this.getValue(container ? container.circular : this.circular), + clickable: this.getValue( + container ? container.clickable : this.clickable + ), + selectable: this.getValue( + container ? container.selectable : this.selectable + ), + type: this.getValue(container ? container.type : this.type), + identifier: this.getValue( + container ? container.identifier : this.identifier + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + clicked: this.clicked, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tree\x-tree.component.html + + + + + + + + +
+ +
+
+
+ + + + + + + + + + + + +
+ +
+
+ + + + + + + +
+ +
+
+
+
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tree\x-tree.component.scss + +:host(::ng-deep.mat-tree) { + background-color: unset !important; +} + +:host(::ng-deep.mat-tree-node) { + align-items: baseline !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tree\x-tree.component.ts + +import { + XTreeNode, + XTreeAction, + XTreeFlatNode, + XTreeActionModel, + XTreeContainerComponent, +} from './x-tree.typings'; +import { + nameof, + toArray, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + MatTreeFlattener, + MatTreeFlatDataSource, +} from '@angular/material/tree'; +import { Subject, Subscription } from 'rxjs'; +import { FlatTreeControl } from '@angular/cdk/tree'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-tree', + templateUrl: './x-tree.component.html', + styleUrls: ['./x-tree.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XTreeComponent extends XTreeContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + treeControl: FlatTreeControl = null; + + // + treeFlattener: MatTreeFlattener< + XTreeNode, + XTreeFlatNode, + XTreeFlatNode + > = null; + + // + dataSource: MatTreeFlatDataSource< + XTreeNode, + XTreeFlatNode, + XTreeFlatNode + > = null; + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Nodes Changes ... + const isNodesChanged = changeKeys.includes(nameof('nodes')); + if (isNodesChanged) { + await this.prepareDataSource(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Register Handlers ... + //#endregion + + // + //#region UI Providers ... + getNodeClassNames(node: XTreeFlatNode) { + // + const result = ['x-tree-node', `x-tree-node-level-${node.level}`]; + + // + if (node.expandable) { + result.push('x-tree-expandable-node'); + } + + // + if (!isNullOrEmptyString(node.id)) { + result.push(`x-tree-node-id-${node.id}`); + result.push(`x-tree-node-id-${node.id}-level-${node.level}`); + } + + // + return this.getArrayValue(result); + } + + nodeHasChild = (_: number, node: XTreeFlatNode) => node.expandable; + //#endregion + + // + //#region UI Handlers ... + handleTreeNodeToggle(id: string, isExpanded: boolean) { + // + if (isNullOrEmptyString(id)) { + return; + } + + // + this.onNodeToggle.emit({ + id, + isExpanded, + }); + } + //#endregion + + // + //#region Actions ... + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XTreeAction.Refresh: + this.detectChanges(); + break; + + // + case XTreeAction.Expand: + // + if (this.treeControl) { + // + // Get Tree Node ... + if (model.payload) { + // + const id = model.payload.toString(); + if (!isNullOrEmptyString(id)) { + // + const node = this.treeControl.dataNodes.find( + (d) => d.id === id + ); + if (!isNullOrUndefined(node)) { + this.treeControl.expand(node); + } + } + } + } + break; + + // + case XTreeAction.Collapse: + // + if (this.treeControl) { + // + // Get Tree Node ... + if (model.payload) { + // + const id = model.payload.toString(); + if (!isNullOrEmptyString(id)) { + // + const node = this.treeControl.dataNodes.find( + (d) => d.id === id + ); + if (!isNullOrUndefined(node)) { + this.treeControl.collapse(node); + } + } + } + } + break; + + // + case XTreeAction.ExpandAll: + // + if (this.treeControl) { + this.treeControl.expandAll(); + } + break; + + // + case XTreeAction.CollapseAll: + // + if (this.treeControl) { + this.treeControl.collapseAll(); + } + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + //#region Tree ... + async prepareDataSource() { + // + this.treeControl = null; + this.treeFlattener = null; + this.dataSource = null; + + // + const nodes = await this.getArrayValueAsync(this.nodes); + if (!this.hasChild(nodes)) { + this.detectChanges(); + return; + } + + // + this.treeControl = new FlatTreeControl( + (node) => node.level, + (node) => node.expandable + ); + + // + this.treeFlattener = new MatTreeFlattener( + (node: XTreeNode, level: number) => { + return { + id: node.id, + level: level, + data: node.data, + tooltip: node.tooltip, + expandable: this.hasChild(node.childs), + } as XTreeFlatNode; + }, + (node) => node.level, + (node) => node.expandable, + (node) => toArray(node.childs) + ); + + // + this.dataSource = new MatTreeFlatDataSource( + this.treeControl, + this.treeFlattener + ); + this.dataSource.data = nodes; + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tree\x-tree.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XTreeComponent } from './x-tree.component'; +import { XCardModule } from '../x-card/x-card.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XTreeComponent], + imports: [CommonModule, XCardModule, XIconModule, XBaseComponentModule], + exports: [XIconModule, XCardModule, XBaseComponentModule, XTreeComponent], +}) +export class XTreeModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tree\x-tree.typings.ts + +import { + Input, + Inject, + Output, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { Observable, Subject, forkJoin } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export interface XTreeNodeToggle { + id: string; + isExpanded: boolean; +} + +export interface XTreeNode { + id: string; + data: T; + childs?: XOneOrManyType>; + tooltip?: XStandardType; +} + +export interface XTreeFlatNode { + id: string; + level: number; + data: XTreeNode; + expandable: boolean; + tooltip?: XStandardType; +} + +export enum XTreeAction { + // + Refresh = 'refresh', + + // + Expand = 'expand', + Collapse = 'collapse', + + // + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', +} + +export type XTreeActionIdentifier = XTreeAction | string; + +export interface XTreeActionModel + extends XBaseActionModel {} + +// +//#region Component Container Typings ... +export interface IXTreeContainer extends IXComponentCardContainer { + // + //#region Props ... + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + isForegroundToggleColor: XStandardType; + toggleIconColor: XStandardType; + + // + nodes: XStandardType>>; + + // + nodeTemplate: TemplateRef; + + // + onNodeToggle: EventEmitter; + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +export class XTreeContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject; + readonly cssClass: XStandardType>; + + // + readonly isForegroundToggleColor: XStandardType = true; + readonly toggleIconColor: XStandardType = + XColorWithBrightness.Dark; + + // + readonly nodes: XStandardType>>; + + // + readonly nodeTemplate: TemplateRef; + + // + readonly onNodeToggle = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XTreeContainerComponent + extends XComponentCardContainerComponent + implements IXTreeContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + // + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + toggleIconColor: XStandardType = + this.propertyProvider.toggleIconColor; + + // + @Input() + isForegroundToggleColor: XStandardType = + this.propertyProvider.isForegroundToggleColor; + + // + @Input() + nodes: XStandardType>> = + this.propertyProvider.nodes; + + // + @Input() + nodeTemplate: TemplateRef = this.propertyProvider.nodeTemplate; + + // + @Output() + onNodeToggle = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XTreeContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXTreeContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + toggleIconColor: this.getValue( + container ? container.toggleIconColor : this.toggleIconColor + ), + + // + isForegroundToggleColor: this.getValue( + container + ? container.isForegroundToggleColor + : this.isForegroundToggleColor + ), + + // + nodes: this.getArrayValue(container ? container.nodes : this.nodes), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + nodeTemplate: container + ? container.nodeTemplate + : this.nodeTemplate, + + // + onNodeToggle: container.onNodeToggle + ? container.onNodeToggle + : this.onNodeToggle, + }; + }) + ); + } + //#endregion +} +//#endregion diff --git a/Modules/xFrameworkComponentsHolder b/Modules/xFrameworkComponentsHolder index 203c09a..0f37e94 160000 --- a/Modules/xFrameworkComponentsHolder +++ b/Modules/xFrameworkComponentsHolder @@ -1 +1 @@ -Subproject commit 203c09a0e80ac0a132158b9b08dee576d9937c21 +Subproject commit 0f37e94ac9c5b3dbab4f77b726a5e84b1c5da7da diff --git a/Modules/xFrameworkCoreHolder b/Modules/xFrameworkCoreHolder index fb123af..fb70130 160000 --- a/Modules/xFrameworkCoreHolder +++ b/Modules/xFrameworkCoreHolder @@ -1 +1 @@ -Subproject commit fb123afb951e78816f2820398229746a7f95f3a9 +Subproject commit fb701301d8fc072df7a7712a2af868cdacddd120 diff --git a/xFrameworkTest b/xFrameworkTest index fac270d..594acc6 160000 --- a/xFrameworkTest +++ b/xFrameworkTest @@ -1 +1 @@ -Subproject commit fac270d30140c00d61f98206c9d68def3bbe094c +Subproject commit 594acc61496f9ed94a93a5cd5ab3570b71a25c4d