diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..59d9a3a3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index 82ee25a1..53a8849e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,47 @@ -node_modules -dist -.idea +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc +# Only exists if Bazel was run +/bazel-out + +# dependencies +/node_modules + +# profiling files +chrome-profiler-events*.json +speed-measure-plugin*.json + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# misc +/.angular/cache +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..29e567ec --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1037 @@ +## 9.0.0 (21.09.2025) + +This version requires Angular v20. Follow the [Angular Update Guide](https://angular.dev/update-guide) to migrate your project to Angular 20. + +### Breaking changes: + +- Updated Angular to v20, this version is required in MDB Angular v9 + +### Fixes and improvements: + +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) + - Improved blocking of months and years cells in some edge cases + - Resolved problems with errors when parsing invalid date formats +- [File upload](https://mdbootstrap.com/docs/angular/plugins/file-upload/) + - Added error handling for `maxFileQuantity` + - Improved extensions validation logic to handle problems with uploading files with extensions defined in `mimeTypes` input +- [Input fields](https://mdbootstrap.com/docs/angular/forms/input-fields/) - resolved problem with border gap updates for dynamically rendered label +- [Select](https://mdbootstrap.com/docs/angular/forms/select/) - resolved issue where clicking the arrow icon in one Select input would not close the dropdown of another Select component + +### New features: + +- Added new SCSS and CSS variables for plugins styles +- [Autocomplete](https://mdbootstrap.com/docs/angular/forms/autocomplete/) - added new `dropdownWidth` input that allows to set custom width for the dropdown menu +- [Datatables](https://mdbootstrap.com/docs/angular/data/datatables/) - added new `defaultSortDirection` input that allows to set default sort direction for the table header + +--- + +## 8.0.0 (07.04.2025) + +This version requires Angular v19. Follow the [Angular Update Guide](https://angular.dev/update-guide) to migrate your project to Angular 19. + +### Breaking changes: + +- Updated Angular to v19, this version is required in MDB Angular v8 +- Older theming styles are no longer supported, use new [color modes](https://mdbootstrap.com/docs/angular/content-styles/theme/) instead +- Slightly increased cell width in [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) +- The `.navbar-light` class is no longer used in [Navbar](https://mdbootstrap.com/docs/angular/navigation/navbar), use [color modes](https://mdbootstrap.com/docs/angular/content-styles/theme/) instead + +### Design updates: + +Introduced a new theming system that allows setting the theme for the entire page, its parts, or selected elements using data attributes. + +Read [Colors modes](https://mdbootstrap.com/docs/angular/content-styles/theme/) page to learn more about new theming. + +### Fixes and improvements: + +- [Modal](https://mdbootstrap.com/docs/angular/components/modal/) - resolved problem with opening animation +- [Select](https://mdbootstrap.com/docs/angular/forms/select/) - resolved problem with not hiding option groups labels when using filter +- [Popconfirm](https://mdbootstrap.com/docs/angular/components/popconfirm/) - added default offset to the component +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) - fixed date parsing bug for `yy` year format +- [Stepper](https://mdbootstrap.com/docs/angular/components/stepper/) - added 'Optional' text to the steps that use `optional` input +- [Onboarding](https://mdbootstrap.com/docs/angular/plugins/onboarding/) - added gap between the buttons and fixed border styles +- Fixed [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) and [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) toggle button padding in Firefox browser +- Removed unnecessary `BrowserAnimationsModule` imports from [Onboarding](https://mdbootstrap.com/docs/angular/plugins/onboarding/), [Ecommerce gallery](https://mdbootstrap.com/docs/angular/plugins/ecommerce-gallery/) and [Organization chart](https://mdbootstrap.com/docs/angular/plugins/organization-chart/) plugins + +### New features: + +- Added new SCSS and CSS variables for plugins styles +- [File upload](https://mdbootstrap.com/docs/angular/plugins/file-upload/) - added new `mimeTypes` input that allow to define a list of mime types for supported file types +- [Multi item carousel](https://mdbootstrap.com/docs/angular/plugins/multi-item-carousel/) - added new `(slideClick)` event +- [Color picker](https://mdbootstrap.com/docs/angular/plugins/color-picker/) - added new `color-picker-next-format-button`, `color-picker-previous-format-button` and `color-picker-copy-button` classes for the buttons + +--- + +## 7.1.0 (18.11.2024) + +### Fixes and improvements: + +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) + - Resolved problem with `close` method being called twice on component close + - Fixed dark theme styles in inline mode +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) + - Added `aria-disabled` attributes to elements that display disabled dates + - Fixed `aria-label` attribute value in the element used to display day value + - Resolved problem with adding `aria-selected` attribute to the element that display day value +- [Select](https://mdbootstrap.com/docs/angular/forms/select/) + - Fixed disabled options styles in custom theme + - Resolved problem with opening dropdown on `space` key press + - Added `aria-label` and `aria-labelledby` attributes to the component + - Fixed value returned by `(deselect)` event +- [Autocomplete](https://mdbootstrap.com/docs/angular/forms/autocomplete/) + - Fixed `aria-expended` attribute values for opened and closed menu + - Fixed problem where component menu was opening even when input was disbled +- [Transfer](https://mdbootstrap.com/docs/angular/plugins/transfer/) + - Fixed events output for target container + - Fixed checkboxes styles +- [Range](https://mdbootstrap.com/docs/angular/forms/range/) - fixed thumb position on component init +- [Onboarding](https://mdbootstrap.com/docs/angular/plugins/onboarding/) - added fix to prevent memory leak after component destroy +- [Input mask](https://mdbootstrap.com/docs/angular/plugins/input-mask/) - fixed a problem with value formatting when pasting all content into input at once +- [Vector maps](https://mdbootstrap.com/docs/angular/plugins/vector-maps/) - fixed shadow styles in zoom buttons +- [Transfer](https://mdbootstrap.com/docs/angular/plugins/wysiwyg-editor/) - fixed dropdown menu alignment +- [Dropdown](https://mdbootstrap.com/docs/angular/component/dropdowns/) - fixed `aria-expended` attribute values for opened and closed menu +- [Sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) - fixed problem with focus trap when the last focused element is inside the component content + +### New features: + +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) + - Added new `showClearBtn` input + - Added new `(clear)` event that will be fired after using Clear button +- [File upload](https://mdbootstrap.com/docs/angular/plugins/file-upload/) + - Added `svg` and `webp` extensions to the list of allowed file types for default preview + - Added new `datepickerOptions` that allow to define options for the date pickers used by the plugin +- [Select](https://mdbootstrap.com/docs/angular/forms/select/) - added new `(search)` event that will be fired after using search input +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) - added new `(viewChanged)` event that will be fired on component view change +- [Progress](https://mdbootstrap.com/docs/angular/components/progress/) - added new circular version of the component + +--- + +## 7.0.0 (16.09.2024) + +This version requires Angular v18. Follow the [Angular Update Guide](https://angular.dev/update-guide) to migrate your project to Angular 18. + +### Breaking changes: + +- Updated Angular to v18, this version is required in MDB Angular v7. +- [Checkbox](https://mdbootstrap.com/docs/angular/forms/checkbox/) - changed `margin-right` style from `4px` to `6px` in `.form-check-input` element. +- [Forms](https://mdbootstrap.com/docs/angular/forms/overview/) - added `padding-left: 0.15rem` style to `.form-check-label` element. +- [Switch](https://mdbootstrap.com/docs/angular/forms/switch/) - changed `margin-right` style from `4px` to `8px` in `.form-check-input` element. +- [Progress](https://mdbootstrap.com/docs/angular/components/progress/) - added `box-shadow: none` style to `.progress` element. +- [Input group](https://mdbootstrap.com/docs/angular/forms/input-group/) - added `flex-wrap: nowrap` style to `.input-group` element. +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) - changed SCSS variable `$datepicker-small-cell-content-width` value from `36px` to `40px`. +- [Range](https://mdbootstrap.com/docs/angular/forms/range/): + - Added `box-shadow: none` style to `.form-range ::-webkit-slider-runnable-track` element. + - Added `box-shadow: none` style to `.form-range ::-moz-range-track` element. +- [Captcha](https://mdbootstrap.com/docs/angular/plugins/captcha/): + - Changed `error` event name to `captchaError`. + - Changed `expire` event name to `captchaExpire`. + - Changed `success` event name to `captchaSuccess`. +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/): + - Redesigned clock's page HTML structure and styles. + - Arrow icons are now displayed when hour/minute buttons are hovered in inline mode. +- [Treeview](https://mdbootstrap.com/docs/angular/plugins/tree-view/): + - Redesigned entire HTML structure. + - Replaced `li` element with `mdb-treeview-item`. + - Removed the `
` wrapper element from the entire component. + - Removed the `checkboxesField` input. + - Added a public `MdbTreeviewColor` type for the color input. + - Added a new mechanism for setting the arrow icon with the `collapseIcon` property. + - Added keyboard navigation handling. + +### Fixes and improvements: + +- [Multi range](https://mdbootstrap.com/docs/angular/forms/multi-range-slider/) - resolved the issue with `TouchEvent` in Firefox. +- [Select](https://mdbootstrap.com/docs/angular/forms/select/) - resolved the issue with unhandled `tabindex` input. +- [Onboarding](https://mdbootstrap.com/docs/angular/plugins/onboarding/) - resolved the issue with initializing onboarding with a delay after navigating to another page. +- [Input fields](https://mdbootstrap.com/docs/angular/forms/input-fields/) - resolved the issue with displaying the value after setting it programmatically in all inputs with built-in placeholders (e.g., `datetime-local` or `time`). +- [Datatable](https://mdbootstrap.com/docs/angular/data/datatables/) - resolved the issue with the `showAllEntries` input not working properly with the `entries` input. +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) - resolved the issue with `ArrowUp` and `ArrowDown` key presses not working upon opening the timepicker. +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/): + - Resolved the issue with returned form control values for empty or invalid input values. + - Resolved the issue with closing the datepicker using the input toggle. +- [Calendar](https://mdbootstrap.com/docs/angular/plugins/calendar/): + - Resolved the issue with unpreserved event IDs on edit. + - Resolved the issue with dragging in `readonly` mode. + - Resolved the issue with view selection when non-default captions are used. + - Resolved the issue with rendering the period in the correct format in Month view. + +### New features: + +- [Dropdown](https://mdbootstrap.com/docs/angular/components/dropdown/) - added `MdbDropdownPositionClass` type to the public API. +- [Modal](https://mdbootstrap.com/docs/angular/components/modal/) - added `focusElementSelector` property to the `open` method's options for specifying the element to focus on when the modal opens. +- [Calendar](https://mdbootstrap.com/docs/angular/plugins/calendar/): + - Added `addEventButtonCaption` property to the `options` input for setting a custom caption for the add event button. + - Added `MdbCalendarViews` Enum to the public API. + +--- + +## 6.1.0 (27.05.2024) + +### Fixes and improvements: + +- [Multi range](https://mdbootstrap.com/docs/angular/forms/multi-range-slider/) + - Fixed problem with thumb limiting logic when using custom step + - Fixed problem with updating thumb positions via form controls +- [Popconfirm](https://mdbootstrap.com/docs/angular/components/popconfirm/) - added focus trap +- [Autocomplete](https://mdbootstrap.com/docs/angular/forms/autocomplete/) - restored native `shift + home` and `shift + end` keys behavior (open/close dropdown) +- [Select](https://mdbootstrap.com/docs/angular/forms/select/) - added support for opening and closing dropdown with `alt + arrow-up` and `alt + arrow-down` keys + +### New: + +- [Table pagination](https://mdbootstrap.com/docs/angular/data/datatables/) - added new `page` input that allows to set page number +- [Multi range](https://mdbootstrap.com/docs/angular/forms/multi-range-slider/) - added new `highlightRange` input that allows to highlight range +- [Parallax](https://mdbootstrap.com/docs/angular/plugins/parallax/) - added new `container` input that allows to set wrapper element for parallax effect + +--- + +## 6.0.0 (15.01.2024) + +This version requires Angular v17. Follow the [Angular Update Guide](https://update.angular.io/?l=3&v=16.0-17.0) to migrate your project to Angular 17. + +### Breaking changes: + +- Updated Angular to v17, this version is required in MDB Angular v6 +- [Calendar](https://mdbootstrap.com/docs/angular/plugins/calendar/) - changed type of `defaultView` input from `string` to `MdbCalendarView` +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) - changed type of `options` input from `any` to `MdbDatepickerOptions` +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) + - Changed type of `options` input from `Options` to `MdbTimepickerOptions` and made all parameters optional + - Changed `SelectedTime` type name to `MdbTimepickerSelectedTime` and added this type to public exports +- [Popover](https://mdbootstrap.com/docs/angular/components/popover/) - removed unused `template` input +- [Sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) + - Changed return type of all events from `MdbSidenavComponent` to `void` + - Removed redundant `li` element from `MdbSidenavItemComponent` template +- [Transfer](https://mdbootstrap.com/docs/angular/plugins/transfer/) + - Changed `onSearchOutput` event name to `searchOutput` + - Changed `selectOutput` event name to `selectOutput` + - Changed `onChange` event name to `listChange` + - Changed `onSearch` event name to `itemSearch` + - Changed `onSelect` event name to `itemSelect` + +### Fixes and improvements: + +- [Sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) - removed height animation transition +- [Select](https://mdbootstrap.com/docs/angular/forms/select/) - blocked input clearing in disabled component +- [Input fields](https://mdbootstrap.com/docs/angular/forms/input-fields/) - resolved problem with default label position in all inputs with built-in placeholder (like `datetime-local` or `time`) +- [Lightbox](https://mdbootstrap.com/docs/angular/components/lightbox/) - resolved problem with component removal from DOM after using browser's back button +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) - resolved problem with font size in landscape view + +### New fetures: + +- [Select](https://mdbootstrap.com/docs/angular/forms/select/) - added new `inputId` and `inputFilterId` inputs that allow to declare ids for input elements + +--- + +## 5.2.0 (04.12.2023) + +### Fixes and improvements: + +- Resolved problem with components rendering when using Server Side Rendering +- Resolved problem with overlay when using `menuPositionClass` in [Datatable](https://mdbootstrap.com/docs/angular/components/dropdowns/) +- Replaced hardcoded `padding-left` value in [Sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) link with a value from CSS variable +- Replaced hardcoded `box-shadow`, `border-color` and `background-color` values in [Buttons](https://mdbootstrap.com/docs/angular/components/buttons/) with a values from CSS variables +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) + - Fixed the button press behavior to consider the duration of the press + - Removed the default scroll effect from the arrow keydown events in inline mode +- Fixed events types for `opened` and `closed` events in [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) +- Resolved problem with initial value in [Rating](https://mdbootstrap.com/docs/angular/components/rating) +- [Multi Range Slider](https://mdbootstrap.com/docs/angular/forms/multi-range-slider/) + - Resolved problem with thumbs position updates on `ngModel` or `formControl` value changes + - Added thumbs position constraints so that the position of a given thumb is limited by its counterpart +- Resolved problem with the `Host already has a portal attached` error in [Wysiwyg](https://mdbootstrap.com/docs/angular/plugins/wysiwyg-editor/) + +### New: + +- A new `MdbSidenavMenuDirective` directive has been added to [Sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) allowing to create multiple menus within one component +- A new `size` input has been added to [Select](https://mdbootstrap.com/docs/angular/orms/select/) allowing to change input size to `sm` or `lg` + +--- + +## 5.1.0 (09.10.2023) + +### Fixes and improvements: + +- [Datatable](https://mdbootstrap.com/docs/angular/data/datatables/) + - Added missing `cursor: pointer` styles to clickable rows + - Resolved problems with pagination width styles + - Resolved problems with page number calculation in pagination +- [Sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) + - Resolved problems with accessibility + - Removed the need to define template variables in HTML template + - Adjusted padding in slim version to correctly display link icon and arrow +- [Tabs](https://mdbootstrap.com/docs/angular/navigation/tabs/) + - Improved animation smoothness + - Added `MdbTabChange` event type to public exports +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) + - Resolved problem with `disabled` input + - Resolved problem with disabling and enabling component via Reactive Forms methods + - Removed border styles from focused buttons +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) + - Resolved problem with border radius styles + - Resolved problem with disabling and enabling component via Reactive Forms methods +- [Autocomplete](https://mdbootstrap.com/docs/angular/forms/autocomplete/) + - Removed auto highlight from first option + - Resolved problems with input and dropdown keyboard navigation when using `HOME` and `END` keys +- [Multi range](https://mdbootstrap.com/docs/angular/forms/multi-range-slider/) + - Resolved problem with component render in apps using Angular 16 + - Resolved problem with unhandled `endDrag` event +- [Onboarding](https://mdbootstrap.com/docs/angular/plugins/onboarding/) + - Resolved problem with component render in apps using Angular 16 + - Resolved problems with popover styles + - Fixed event types + - Fixed event emitted when jumping to next step +- [Treeview](/docs/angular/plugins/tree-view/) + - Improved animation smoothness + - Added correct types to public events + - Resolved problem with `accordion` option + - Resolved problem with `openOnClick` option + - Improved accessibility +- Resolved problem with styles of anchor elements used as [floating buttons](https://mdbootstrap.com/docs/angular/components/buttons/#section-floating) +- Resolved problem with adding new [Chips](https://mdbootstrap.com/docs/angular/components/chips/) on blur event +- Resolved problem with [Dropdown](https://mdbootstrap.com/docs/angular/components/dropdowns/) menu position +- Fixed focus styles in [Select](https://mdbootstrap.com/docs/angular/forms/select/) with `form-white` class +- Resolved problem with position of bottom frame [non-invasive Modal](https://mdbootstrap.com/docs/angular/components/modal/#section-non-invasive-modal) +- Fixed type of `infiniteScrollCompleted` event in [Infinite scroll](https://mdbootstrap.com/docs/angular/methods/infinite-scroll/) +- Added mechanism to handle dynamic updates in [Input mask](https://mdbootstrap.com/docs/angular/plugins/input-mask/) plugin +- Resolved problems with [Color picker](https://mdbootstrap.com/docs/angular/plugins/color-picker/) plugin styles and slider in Firefox browser +- Resolved problem with [Parallax](https://mdbootstrap.com/docs/angular/plugins/parallax/) plugin render in apps using Angular 16 +- Fixed event types and unhandled events in [Drag and drop](https://mdbootstrap.com/docs/angular/plugins/drag-and-drop/) plugin +- Resolved problem with reverting lists transformation in [WYSIWYG editor](https://mdbootstrap.com/docs/angular/plugins/wysiwyg-editor/) plugin +- Resolved problem with `changeView` method in [Calendar](https://mdbootstrap.com/docs/angular/plugins/calendar/) plugin +- Added types to public exports in [Data parser](https://mdbootstrap.com/docs/angular/plugins/data-parser/) plugin + +### New: + +- Added new [Treetable](https://mdbootstrap.com/docs/angular/plugins/treetable/) plugin +- Added mechanism that allow to add context for `ng-template` template in [Popover](https://mdbootstrap.com/docs/angular/components/popovers/) +- Added new `showAllEntries` option to [Datatable pagination](https://mdbootstrap.com/docs/angular/data/datatables/) +- Added new `filterFn` option to [Select](https://mdbootstrap.com/docs/angular/forms/select/) +- Added new directive that allow to create a custom header in [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) +- Added new `positionClass` and `menuPositionClass` options to [Dropdown](https://mdbootstrap.com/docs/angular/components/dropdowns/) +- Added new `disabled` input that allow to disable [Accordion](https://mdbootstrap.com/docs/angular/components/accordion/)Accordion items +- Added mechanism that allow to define custom icon template with `ng-template` in [Datepicker](/docs/angular/forms/datepicker/) and [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) +- Added mechanism that allow to define custom header template with `ng-template` in [Stepper](https://mdbootstrap.com/docs/angular/components/stepper/) +- Added new `$link-decoration` and `--mdb-link-decoration` variables to make it easier to customize `text-decoration` styles for anchor elements +- Added new inputs for disabling specific features in [Calendar](https://mdbootstrap.com/docs/angular/plugins/calendar/) plugin + +--- + +## 5.0.0 (26.06.2023) + +This version requires Angular v16. Follow the [Angular update guide](https://update.angular.io/?l=3&v=15.0-16.0) to migrate your project to Angular 16. + +### Dependencies: + +- Updated Angular to v16, this version is required in MDB Angular v5 +- Updated Bootstrap to [5.2.3](https://github.com/twbs/bootstrap/releases/tag/v5.2.3) version. + +### Design changes: + +- Changed arrow styles in [Select](https://mdbootstrap.com/docs/angular/forms/select/) input +- Slightly changed hover styles in [outline buttons](https://mdbootstrap.com/docs/angular/components/buttons/#section-outline) to make them more elegant and subtle + +### Fixes and improvements: + +- Fixed problems with schematics installation in MDB Angular Free version +- Fixed problem with display of [Sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) item when its content is translated with the `translate` pipe from the `@ngx-translate` library +- Fixed position of smaller icons in relation to the text in [Rating](https://mdbootstrap.com/docs/angular/components/rating/) + +### New: + +- Converted MDB components to CSS variables +- Added SCSS and CSS variables for `mdb-option` and `mdb-option-group` components +- Added access to the underlying component instance from ref element in [Modal](https://mdbootstrap.com/docs/angular/components/modal/), [Popconfirm](https://mdbootstrap.com/docs/angular/components/popconfirm/), [Alerts](https://mdbootstrap.com/docs/angular/components/alerts/) and [Toasts](https://mdbootstrap.com/docs/angular/components/toasts/) + +--- + +## 4.1.0 (24.01.2023) + +### Fixes and improvements: + +- Fixed default value display in [Autocomplete](https://mdbootstrap.com/docs/angular/forms/autocomplete/) when the value is an object +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) + - Fixed focus trap + - Fixed keyboard navigation in inline mode + - Fixed the problem with minTime and maxTime range +- Fixed [Ripple effect](https://mdbootstrap.com/docs/angular/methods/ripple/) on inputs styled as buttons +- Fixed background colors of [Toasts](https://mdbootstrap.com/docs/angular/components/toasts/) and [Alerts](https://mdbootstrap.com/docs/angular/components/alerts/) in MDB theme +- [Modal](https://mdbootstrap.com/docs/angular/components/modal/) + - Fixed the problem with scrollbar on bottom frame modal init + - Removed rounded corners from frame modals + - Removed unnecessary body scroll when using `scrollable` modal +- [Datatable](https://mdbootstrap.com/docs/angular/data/datatables/) + - Removed ability to focus disabled buttons in pagination + - Fixed the problem with case-sensitive sorting +- Fixed the problem with hiding buttons in the [Wysiwyg](https://mdbootstrap.com/docs/angular/plugins/wysiwyg-editor/) toolbar +- Fixed problem with event types in [Select](https://mdbootstrap.com/docs/angular/forms/select/) +- Fixed problem with `Rxjs operators` import paths in all the components and plugins + +### New: + +- Added new [Data Parser](https://mdbootstrap.com/docs/angular/plugins/data-parser/) plugin +- Added new [Organization Chart](https://mdbootstrap.com/docs/angular/plugins/organization-chart/) plugin +- Added new [Captcha](https://mdbootstrap.com/docs/angular/plugins/captcha/) plugin +- Added new [Chips](https://mdbootstrap.com/docs/angular/components/chips/) component +- Added new `[collapsible]` input to [Scrollspy](https://mdbootstrap.com/docs/angular/navigation/scrollspy/) +- Added new `[disableWindowScroll]` input to the [Sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) +- Added new [non-invasive Modal](https://mdbootstrap.com/docs/angular/components/modal/#section-non-invasive-modal) +- [Datatable](https://mdbootstrap.com/docs/angular/data/datatables/) + - Added new `[forceSort]` input that allow to disable sort reset on third click + - Added new `[disableSort]` input that allow to disable a specific sort header + - Added new `[disabled]` input to pagination component +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) +- Added new `[removeOkBtn]`, `[removeCancelBtn]` and `[removeClearBtn]` inputs that allow to remove specific buttons from the component footer +- Addew new `[confirmDateOnSelect]` input that allow to select date without a confirmation by click on `Ok` button + +--- + +## 4.0.0 (09.01.2023) + +### Design updates: + +Our basic color palette has been updated. We toned down our colors to be less flashy and more elegant and subtle. This affects virtually all of our components, so be aware of this before upgrading your project to v4.0.0. + +Read [colors docs](https://mdbootstrap.com/docs/angular/content-styles/colors/) to learn more about new palette. + +### Breaking changes: + +- Added support for Angular 15, this Angular version is now required, +- Improved [buttons](https://mdbootstrap.com/docs/angular/components/buttons/) +- Improved existing [accordion](https://mdbootstrap.com/docs/angular/components/accordion/) and added new examples +- Improved [stepper](https://mdbootstrap.com/docs/angular/components/stepper/) design +- Improved [badges](https://mdbootstrap.com/docs/angular/components/badges/) design and added new examples +- Improved [popovers](https://mdbootstrap.com/docs/angular/components/popovers/) and [popconfirm](https://mdbootstrap.com/docs/angular/components/popconfirm/) design +- Removed default configuration of `chartjs-plugin-datalabels` from [charts](https://mdbootstrap.com/docs/angular/data/charts/), all plugins must be now registered before use + +### Fixes and improvements: + +- Resolved problem with [scrollbar](https://mdbootstrap.com/docs/angular/methods/scrollbar/) initialization on element with a `mdbScrollbar` directive +- Removed unnecessary border animation on initialization of `mdb-form-control` component +- Resolved problem with global registration of controllers and plugins in [charts](https://mdbootstrap.com/docs/angular/data/charts/) +- Improved types in `mdbChart` directive inputs +- Added some fixes to the [transfer plugin](https://mdbootstrap.com/docs/angular/plugins/transfer/) + - Improved 'select all' option implementation + - Resolved problems with value updates in search bar input + - Resolved problems with component view updates when using pagination +- Improved theme styles in the following components: + - List group + - Pagination + - Datepicker + +### New: + +- Addew new [color picker plugin](https://mdbootstrap.com/docs/angular/plugins/color-picker/) plugin +- Addew new [multi item carousel plugin](https://mdbootstrap.com/docs/angular/plugins/multi-item-carousel/) +- Addew new [ecommerce gallery plugin](https://mdbootstrap.com/docs/angular/plugins/ecommerce-gallery/) +- Addew new `[borderless]` input to [accordion](https://mdbootstrap.com/docs/angular/components/accordion/) +- Added new `[withPush]` input to [dropdown](https://mdbootstrap.com/docs/angular/components/dropdown/) +- Added new `[plugins]` input to [charts](https://mdbootstrap.com/docs/angular/data/charts/) +- Added public access to the chart instance in `mdbChart` directive +- Added new `[ofText]` input to [datatables](https://mdbootstrap.com/docs/angular/data/datatables/) +- Added new `[titleSource]` and `[titleTarget]` inputs to [transfer plugin](https://mdbootstrap.com/docs/angular/plugins/transfer/) + +--- + +## 3.0.1 (05.12.2022) + +### Fixes and improvements: + +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) + - Removed border styles displayed on focused elements + - Resolved problems with keyboard navigation +- It will be now possible to jump to any step in [linear stepper](https://mdbootstrap.com/docs/angular/components/stepper/#section-linear-stepper-example/), as long as all previous steps are completed +- Resolved problems with `acceptedExtensions` in [file upload plugin](https://mdbootstrap.com/docs/angular/plugins/file-upload/) +- Select all option will now select/deselect only filtered options when used inside a [select component with filter](https://mdbootstrap.com/docs/angular/forms/select/#section-search/) +- Events `itemShown` and `itemHidden` in [accordion](https://mdbootstrap.com/docs/angular/components/accordion/) will be now correctly emitted after animation end +- Resolved problem with close animation in [popconfirm](https://mdbootstrap.com/docs/angular/components/popconfirm/) +- Resolved problem with value returned to [autocomplete](https://mdbootstrap.com/docs/angular/forms/autocomplete/) form control on option selection +- Resolved problem with wrong page value returned by `(paginationChange)` event in [datatable](https://mdbootstrap.com/docs/angular/data/datatables/) +- Increased backdrop z-index in [onboarding plugin](https://mdbootstrap.com/docs/angular/plugins/onboarding/) +- Resolved problem with `autohide` option in [toast](https://mdbootstrap.com/docs/angular/components/toasts/), notification will be removed only if it is not hovered +- Added default padding to the content container in [WYSIWYG editor plugin](https://mdbootstrap.com/docs/angular/plugins/wysiwyg-editor/) +- Resolved problem with Angular dependencies versions in schematics installation + +### New: + +- Addew new [color picker plugin](https://mdbootstrap.com/docs/angular/plugins/color-picker/) +- Addew new [scroll status plugin](https://mdbootstrap.com/docs/angular/plugins/scroll-status/) + +--- + +## 3.0.0 (10.10.2022) + +This version requires Angular v14 and Node 14.15.0 (or later). Follow the [Angular update guide](https://update.angular.io/?l=3&v=13.0-14.0) to migrate your project to Angular 14: + +### Breaking changes: + +- Added support for Angular 14, this Angular version is now required, +- Removed `~` from styles imports, this syntax is now deprecated +- Updated [calendar](https://mdbootstrap.com/docs/angular/plugins/calendar/) plugin: + - redesigned toolbar, events, views and modals + - replaced view toggle buttons with select + - created an `Add event` button + - added [blur](https://mdbootstrap.com/docs/angular/plugins/calendar/#section-blur/) option to style past events + - improved long events styling + - improved responsiveness +- Design changes: + - Changed shadows for components such as [card](https://mdbootstrap.com/docs/angular/components/cards/), [popover](https://mdbootstrap.com/docs/angular/components/popovers/), [toast](https://mdbootstrap.com/docs/angular/components/toasts/), [modal](https://mdbootstrap.com/docs/angular/components/modal/), [image hoverable](https://mdbootstrap.com/docs/angular/content-styles/images/), [dropdown menu](https://mdbootstrap.com/docs/angular/components/dropdowns/), [popconfirm](https://mdbootstrap.com/docs/angular/components/popconfirm/) + - Changed styling of border for [card](https://mdbootstrap.com/docs/angular/components/cards/), [modal](https://mdbootstrap.com/docs/angular/components/modal/), header and footer + - Changed [table](https://mdbootstrap.com/docs/angular/data/tables/) font weight and text color + - Changed [checkbox](https://mdbootstrap.com/docs/angular/forms/checkbox/) and [radio](https://mdbootstrap.com/docs/angular/forms/radio/) border color + - Changed [switch](https://mdbootstrap.com/docs/angular/forms/switch/) background color + - Changed [checkbox](https://mdbootstrap.com/docs/angular/forms/checkbox/) border radius size + - Changed [list group](https://mdbootstrap.com/docs/angular/components/list-group/), [pagination](https://mdbootstrap.com/docs/angular/navigation/pagination/) and [dropdown](https://mdbootstrap.com/docs/angular/components/dropdowns/) text color as it is in the body + - Changed [toast](https://mdbootstrap.com/docs/angular/components/toasts/) color palette + - Changed [datatables](https://mdbootstrap.com/docs/angular/data/datatables/) striped and hover background color as it is in the usual table + - Changed [select](https://mdbootstrap.com/docs/angular/forms/select/) states background colors + - Changed [sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) icons colors and width of the slim version + - Added new [toast](https://mdbootstrap.com/docs/angular/components/toasts/) color classes that replaced background color classes. Old: `toast bg-primary`. New: `toast toast-primary` + +### Fixes and improvements: + +- [Lightbox](https://mdbootstrap.com/docs/angular/components/lightbox/) + - Resolved problems with zoom + - Resolved problems with swipe on mobile devices + - Resolved problem with display of smaller images + - Fixed image position in fullscreen mode + - Disabled elements will no longer be displayed inside the component modal +- Fixed problems with `rebuild` method in [charts](https://mdbootstrap.com/docs/angular/data/charts/) +- Replaced hardcoded color values with SCSS variables in [autocomplete](https://mdbootstrap.com/docs/angular/forms/autocomplete/) and [select](https://mdbootstrap.com/docs/angular/forms/select/) +- Resolved problem with [carousel](https://mdbootstrap.com/docs/angular/components/carousel/) animations inside a component with OnPush change detection strategy +- Position of dropdown menus in all components will be now correctly updated on scroll event +- Resolved problem with fade animation in [tabs](https://mdbootstrap.com/docs/angular/components/tabs/) +- Label values in [select](https://mdbootstrap.com/docs/angular/forms/select/) will be now dynamically updated on option label change +- All event listeners in the [WYSIWYG](https://mdbootstrap.com/docs/angular/plugins/wysiwyg-editor/) plugin will be now correctly removed when component is destroyed +- Resolved problem with [input](https://mdbootstrap.com/docs/angular/forms/input-fields/) label position when browser autofill is used + +### New: + +- Addew new [countdown plugin](https://mdbootstrap.com/docs/angular/plugins/countdown/) +- Addew new [input mask plugin](https://mdbootstrap.com/docs/angular/plugins/input-mask/) +- Addew new [parallax plugin](https://mdbootstrap.com/docs/angular/plugins/parallax/) +- Addew new [multi range component](https://mdbootstrap.com/docs/angular/components/multi-range-slider/) +- Added new `[fade]` input that allow to toggle fade animations in [tabs](https://mdbootstrap.com/docs/angular/components/tabs/) + +### Design updates: + +- Updated icon colors of basic light navbar and footer with secondary color +- Added new horizontal dividers classes `.hr` and `.hr-blurry` +- Updated styles of vertical divider class `.vr` and add new class `.vr-blurry` +- Added new sidenav with menu categories and class `.sidenav-sm` +- Added new `object-fit` and `object-position` utilities + +### Removed: + +- Deprecated button close classes. Old: `.close`. New: `.btn-close` and `.btn-close-white` +- Deprecated embed classes. Old: `.embed`. New: `.ratio` +- Deprecated flag classes. Check [flags](https://mdbootstrap.com/docs/angular/content-styles/flags/) docs +- Deprecated utils + +### Deprecated: + +- `.divider-horizontal` and `.divider-horizontal-blurry` +- `.divider-vertical` and `.divider-vertical-blurry` + +--- + +## 2.3.0 (27.06.2022) + +### Fixes and improvements + +- [Sidenav](https://mdbootstrap.com/docs/b5/angular/navigation/sidenav/) + - Resolved problems with arrow position updates in slim mode and accordion mode + - Resolved problem with initialization of component with `[right]="true"` and `[hidden]="false"` options + - Fixed problem with long content display in component with `[right]="true"` option +- Fixed problems with long label positioning in [checkbox](https://mdbootstrap.com/docs/b5/angular/forms/checkbox/), [switch](https://mdbootstrap.com/docs/b5/angular/forms/switch/) and [radio](https://mdbootstrap.com/docs/b5/angular/forms/radio/) +- Resolved problem with multiple `paginationChange` events emitted on [datatable](https://mdbootstrap.com/docs/b5/angular/data/datatables/) initialization +- Resolved problems with [pagination](https://mdbootstrap.com/docs/b5/angular/navigation/pagination/) and [accordion](https://mdbootstrap.com/docs/b5/angular/components/accordion/) styles when using [theme](https://mdbootstrap.com/docs/b5/angular/content-styles/theme/) +- Fixed problem with max file quantity in [file upload](https://mdbootstrap.com/docs/b5/angular/plugins/file-upload/) plugin with `multiple` mode +- Resolved problem with first option highlight in [select](https://mdbootstrap.com/docs/b5/angular/forms/select/) with a `[highlightFirst]="false"` option +- Added `type="button"` to the 'insert horizontal line' button in [WYSIWYG](https://mdbootstrap.com/docs/b5/angular/plugins/wysiwyg-editor/) to resolve problem with form submit +- Zero-length [tooltip](https://mdbootstrap.com/docs/b5/angular/components/tooltips/) and [popover](https://mdbootstrap.com/docs/b5/angular/components/popovers/) will no longer be displayed +- Fixed problem with multiple `(selected)` events emitted after click on [autocomplete](https://mdbootstrap.com/docs/b5/angular/forms/autocomplete/) option + +### New + +- Addew new [onboarding plugin](https://mdbootstrap.com/docs/b5/angular/plugins/onboarding/) +- [Stepper](https://mdbootstrap.com/docs/b5/angular/components/stepper/) + - Added possibility to block step navigation on step header click + - Added possibility to edit buttons and header text in mobile mode +- Added new `--mdb-bg-opacity` CSS variable +- Added optional auto select on tab-out in [select](https://mdbootstrap.com/docs/b5/angular/forms/select/) and [autocomplete](https://mdbootstrap.com/docs/b5/angular/forms/autocomplete/) +- Added list group new variant with `.list-group-light` class +- Added `.table-group-divider` and `.table-divider-color` classes to emphasize the separation of thead from tbody +- Added new `.divider-horizontal`, `.divider-vertical`, `.divider-horizontal-blurry` and `.divider-vertical-blurry` classes + +--- + +## 2.2.0 (16.05.2022) + +### Fixes and improvements: + +- Datepicker - resolved problem with returned month value when `m` format is used, +- Treeview - resolved problem with `(selected)` event emit when selecting checkbox, +- Select - resolved problem with keyboard navigation and option highlight after filter input is used. +- Charts - resolved problem with chart options being overriden by options defined for other charts, +- Range - resolved problem with thumb position update after change in `ngModel` or `formControl` + +### New: + +- [Added filter plugin](https://mdbootstrap.com/docs/b5/angular/plugins/filters/) +- Dropdown - added keyboard navigation + +--- + +## 2.1.0 (11.04.2022) + +### Fixes and improvements: + +- Datepicker - resolved problem with validation of date typed into input, +- Sidenav - removed unnecessary transition animation on initialization in slim mode, +- File upload plugin - fixed typo in main error message, +- Carousel/Lightbox - updated icons styles for Font Awesome v6. + +### New: + +- [Cookies management](https://mdbootstrap.com/docs/b5/angular/plugins/cookies-management/) +- [Storage management](https://mdbootstrap.com/docs/b5/angular/plugins/storage-management/) +- [Mention](https://mdbootstrap.com/docs/b5/angular/plugins/mention/) +- [Transfer](https://mdbootstrap.com/docs/b5/angular/plugins/transfer/) + +--- + +## 2.0.0 (28.02.2022) + +### Breaking changes: + +- Added support for Angular 13, this Angular version is now required, +- Sidenav - removed support for automatic item expansion based on an active link ([in our documentation](https://mdbootstrap.com/docs/b5/angular/navigation/sidenav/) you can find information on how to achieve this effect using methods provided by Angular Router). + +### Dependencies: + +- Updated Font Awesome to v6.0.0 + +### Fixes and improvements: + +- Toasts/Alerts - resolved problem with positioning when stacking and position bottom options are used, +- Select/Datepicker - resolved problems with input, label and icons styles when `form-white` class is used on `mdb-form-control` component, +- Select - resolved problem with selection when multiple options have the same label (in some cases component incorrectly displayed option value instead of option label in input), +- Datatable pagination - component will now display correct information when data source is empty. + +### New features: + +- Tabs - added new `[navColumnClass]` and `[contentColumnClass]` inputs that allow to customize width of the navigation and content sections in vertical mode. + +--- + +## 1.6.1 (24.01.2022) + +### Optimization: + +- Documentation migration from Wordpress to Hugo, +- Updated code in snippets in documentation to work properly with tsconfig strict settings. + +### Fixes and improvements: + +- Input - resolved problem with label position in input with type="date", +- Datepicker/Timepicker - improved backdrop animation (removed unnecessary delay), +- Datepicker - resolved problem with navigation using previous/next arrows when min and max date is specified, +- Sidenav - animation of the collapsed item in slim mode will be now in sync with animation of the menu (previously there was unnecessary delay) +- Select - list of filtered options will be now correctly reset after the dropdown menu is closed, +- Treeview plugin - click on checkbox will no longer change collapsed state of the node, +- Treeview plugin - checked state of the checkox in parent node will be now in sync with the checkboxes in child nodes. + +--- + +## 1.6.0 (27.12.2021) + +### Dependencies: + +- Updated Bootstrap to 5.1.3 version. + +### Fixes and improvements: + +- Charts - resolved problem with `chartjs-plugin-datalabels` configuration, +- Carousel - component should now work correctly inside components with `OnPush` change detection strategy, +- Table - updated `dataSource` type to resolve problem with asynchronous data and async pipe, +- File upload plugin - resolved problem with extensions handled by the `acceptedExtensions` input, +- Popconfirm - target element will be now optional in modal display mode, +- Sidenav - resolved problem with `child.querySelector is not a function` error when using `ngFor` directive to render sidenav items, +- Popover - `mdbPopover` input will now correctly accept value with `TemplateRef` type. + +### New: + +- Dropdown - added new `closeOnOutsideClick`, `closeOnItemClick`, `closeOnEsc` inputs that allow to configure menu closing actions, +- File upload plugin - added a new `reset` method that allow to reset component state to default settings. + +--- + +## 1.5.1 (22.11.2021) + +### Fixes and improvements + +- Toast/Alert - resolved problem with stacking and close animation, +- Modal - resolved problem with closing when mouseup event is detected outside the component, +- Sidenav - setting `hidden` input to `false` will no longer trigger component animation, +- Sidenav - resolved problem with arrow rotation update when `[collapsed]="false"` is used, +- Sidenav - removed focus trap in side and push modes, +- Sidenav - default position will be now correctly set to `fixed`, +- Input - resolved problem with border top gap recalculation when used inside a dynamically loaded component (such as tabs), +- Overlay - resolved problem with z-index in components using overlay (e.g. modal, popconfirm, tooltip, components with dropdown menus). The components will be correctly displayed above the elements with sticky/fixed styles, +- Charts - fixed default options and resolved problem with custom options merge. + +### Vector maps 1.1.0: + +- resolved problem with automatic updates of colors defined in `colorMap`, +- resolved problem with tooltip display when `[hover]="false"` is used, +- added possibility to display custom tooltips. + +--- + +## 1.5.0 (02.11.2021) + +### New + +- [File upload](https://mdbootstrap.com/docs/b5/angular/plugins/file-upload) +- [Treeview](https://mdbootstrap.com/docs/b5/angular/plugins/tree-view) + +--- + +## 1.4.0 (18.10.2021) + +### New + +- [Drag and drop](https://mdbootstrap.com/docs/b5/angular/plugins/drag-and-drop) +- [Vector maps](https://mdbootstrap.com/docs/b5/angular/plugins/vector-maps) + +--- + +## 1.3.0 (04.10.2021) + +### New + +- [Wysiwyg](https://mdbootstrap.com/docs/b5/angular/plugins/wysiwyg-editor) + +### Fixes and improvements: + +- Popover/Tooltip - resolved problem with closing component when quickly moving mouse over trigger element + +--- + +## 1.2.0 (20.09.2021) + +### New + +- [Calendar](https://mdbootstrap.com/docs/b5/angular/plugins/calendar) +- [Table Editor](https://mdbootstrap.com/docs/b5/angular/plugins/table-editor) + +--- + +## 1.1.0 (06.09.2021) + +### Fixes and improvements: + +- Table pagination - resolved problem with disabled state of next button, +- Input - resolved problem with disabled state updates using Angular form control methods, +- Table - resolved problem with default filter function, +- Datepicker - resolved problem with disabled state of toggle button, +- Timepicker - resolved problem with setting default value in component with 24h format, +- Sidenav - resolved problem with `Cannot read property destroy of undefined` error, +- Select - resolved problem with disabled state of checkboxes in options, +- Select - resolved problem with closing modal on clear button click, +- Dropdown - menu will be now closed correctly on item click. + +### New components: + +- [Theming](https://mdbootstrap.com/docs/b5/angular/content-styles/theme) + +### New features: + +- Table pagination - added new `rowsPerPageText` input that allow to change default 'Rows per page' text + +--- + +## 1.0.0 (09.08.2021) + +In this version we introduced some breaking changes, please check `Breaking changes` section and update your application accordingly. + +### Breaking changes: + +- Inputs - removed `margin-bottom` styles from inputs with validation classes. + +### Fixes and improvements: + +- Select - dropdown will be correctly removed on component destroy, +- Select - resolved problem with select-all option state on component initialization, +- Select - resolved problem with selection of options with false values, +- Dropdown - resolved problem with opening component on icon click, +- Toasts/Alerts - resolved problem with z-index, +- Popconfirm - resolved problem with `onClose` and `onConfirm` events, +- Loading management - backdrop will be correctly removed on component destroy when fullscreen option is used, +- Timepicker - resolved problem with setting default value using Angular form controls, +- Datepicker - previous/next button disabled state will be now correctly updated on component initialization, +- Datepicker/Timepicker - click on toggle button will no longer submit form, +- Datepicker/Timepicker - resolved problems with `valueChanges` event and validation status updates, +- Datatables - resolved problem with scroll position when component is rendered inside a tab. + +### New components: + +- [Accordion](https://mdbootstrap.com/docs/b5/angular/components/accordion/) +- [Charts advanced](https://mdbootstrap.com/docs/b5/angular/data/charts-advanced/) +- [Lightbox](https://mdbootstrap.com/docs/b5/angular/components/lightbox/) +- [Smooth scroll](https://mdbootstrap.com/docs/b5/angular/methods/smooth-scroll/) + +--- + +## 1.0.0-beta8 (12.07.2021) + +In this version we introduced some breaking changes, please check `Breaking changes` section and update your application accordingly. + +### Breaking changes: + +- Popover - `[template]` input will now accept value of type `TemplateRef` and can be used to display `ng-template` content. + +### Fixes and improvements: + +- Toast - component will no longer throw error after reopening, +- Toast - stacked components will now slide up automatically, +- Sidenav - resolved problem with auto expand when route has route parameters, +- Dropdown - opened menu will be now correctly destroyed on route change, +- Table pagination - resolved problem with data automatic updates after change in `[entryOptions]` input. + +### New components: + +- [Popconfirm](https://mdbootstrap.com/docs/b5/angular/components/popconfirm/) +- [Lazy loading](https://mdbootstrap.com/docs/b5/angular/methods/lazy-loading/) +- [Loading management](https://mdbootstrap.com/docs/b5/angular/methods/loading-management/) + +### New features: + +- Popover - `[template]` input will now accept value of type `TemplateRef` and can be used to display `ng-template` content. + +--- + +## 1.0.0-beta7 (28.06.2021) + +In this version we introduced some breaking changes, please check `Breaking changes` section and update your application accordingly. + +### Breaking changes: + +- Changed `mdb-select-option` selector to `mdb-option`, +- Removed `select-` prefix from option and option group class names, +- Moved option and option group styles to individual file. + +### Fixes and improvements: + +- Sidenav - resolved problem with arrow icons in collapsed items, +- Sidenav - resolved problem with z-index, +- Select - resolved problem with dropdown toggle on arrow icon click, +- Input - resolved problem with label position when setting value dynamically using Angular form controls. + +### New components: + +- [Autcomplete](https://mdbootstrap.com/docs/b5/angular/forms/autocomplete/) +- [Infinite scroll](https://mdbootstrap.com/docs/b5/angular/methods/infinite-scroll/) +- [Touch](https://mdbootstrap.com/docs/b5/angular/methods/touch/) + +### New features: + +- Select - added new `[filterPlaceholder]` input that allow to change filter input placeholder. + +--- + +## 1.0.0-beta6 (14.06.2021) + +In this version we introduced some breaking changes, please check `Breaking changes` section and update your application accordingly. The list of all individual modules and entry points can be found here: + +[MDB Angular UI Kit Free Modules And Imports](https://mdbootstrap.com/docs/b5/angular/getting-started/modules-and-imports/) + +[MDB Angular UI Kit Pro Essential Modules And Imports](https://mdbootstrap.com/docs/b5/angular/pro/modules-and-imports/) + +### Breaking changes: + +- Updated Angular to v12 (this version is now required), +- Components, modules and types can no longer be imported from `mdb-angular-ui-kit` entry point. Use the newly added secondary entry points, such as `mdb-angular-ui-kit/checkbox` to import individual elements, +- Removed main `MdbModule`, import individual modules from its entry points, for example: `import { MdbCheckboxModule } from 'mdb-angular-ui-kit/checkbox'`, +- Renamed `MdbTimePickerComponent` to `MdbTimepickerComponent`, +- Renamed `MdbTimePickerDirective` to `MdbTimepickerDirective`, +- Renamed `MdbTimePickerModule` to `MdbTimepickerModule`, +- Updated Bootstrap styles to the latest stable version. + +### Components redesign: + +- Redesigned shadows for components: Cards, Dropdowns, Modal, Popover, Toasts, Buttons, Button Group, Navbar, Pagination, Pills, Sidenav, +- Redesigned padding for components: Alerts, Cards, List Group, +- Redesigned border radius to 0.5rem for components: Alerts, Cards, Dropdowns, Modal, List group, Popover, Toasts, Dateipcker, Timepicker. + +### Fixes and improvements: + +- Sidenav - resolved problem with height of the element with `.sidenav-menu` class, +- Range - resolved problem with a hardcoded `Example label` text, +- Datepicker - `dateChanged` event will be now correctly emited on date change, +- Datepicker - resolved problem with components updates on Angular form control changes, +- File input - updated styles to Material Design styles, +- Pills - fixed width of pills when they're filled or justified, +- Checkbox/Switch/Radio - fix margin styles and positioning. + +### New components: + +- [Stepper](https://mdbootstrap.com/docs/b5/angular/components/stepper/) +- [Sticky](https://mdbootstrap.com/docs/b5/angular/components/sticky/) + +### New features: + +- Navbar - added a new `.navbar-nav-scroll` class to enable vertical scrolling when a collapsed navbar is opened, +- Navbar - re-added `flex-grow` to the `.navbar-collapse` to restore the flexbox behaviors from v4 and prevent some content from being inadvertently squished, +- List group - added a new `.list-group-numbered` variation to list groups that uses pseudo-elements for numbering list group items, +- Shadows - added a new styles design: shadows soft, shadows standard, shadows strong, +- Added color-scheme mixin. + +--- + +## 1.0.0-beta5 (31.05.2021) + +### New components: + +- [Datatables](https://mdbootstrap.com/docs/b5/angular/data/datatables/) +- [Rating](https://mdbootstrap.com/docs/b5/angular/components/rating/) + +--- + +## 1.0.0-beta4 (04.05.2021) + +### New components: + +- [Charts](https://mdbootstrap.com/docs/b5/angular/data/charts/) + +### Bug fixes: + +- Animations - resolved problem with parameters in HTML template, +- Sidenav - resolved problems with `mode` and `hidden` inputs, +- Sidenav - resolved problem with `show` method. + +--- + +## 1.0.0-beta3 (19.04.2021) + +### New components: + +- [Alerts](https://mdbootstrap.com/docs/b5/angular/components/alerts/) +- [Carousel](https://mdbootstrap.com/docs/b5/angular/components/carousel) +- [Toasts](https://mdbootstrap.com/docs/b5/angular/components/toasts) + +### Bug fixes: + +- Datepicker - resolved problem with keyboard navigation when using `DownArrow` key, +- Datepicker - resolved problem with selecting dates using `Enter/Space` keys in component with date filter, +- Datepicker - added correct aria-labels to the previous/next buttons in the days view. + +--- + +## 1.0.0-beta2 (06.04.2021) + +### New components: + +- [Datepicker](https://mdbootstrap.com/docs/b5/angular/forms/datepicker/) +- [Timepicker](https://mdbootstrap.com/docs/b5/angular/forms/timepicker) + +--- + +## 1.0.0-beta1 (22.03.2021) + +### New components: + +- [Range](https://mdbootstrap.com/docs/b5/angular/forms/range/) +- [File](https://mdbootstrap.com/docs/b5/angular/forms/file) +- [Switch](https://mdbootstrap.com/docs/b5/angular/forms/switch/) +- [Input group](https://mdbootstrap.com/docs/b5/angular/forms-input-group/) +- [Pills](https://mdbootstrap.com/docs/b5/angular/navigation/pills/) +- [Tabs](https://mdbootstrap.com/docs/b5/angular/navigation/tabs/) + +### Bug fixes: + +- Scrollspy - added `cursor: pointer` styles to scrollspy links, +- Sidenav - resolved problem with errors when `RouterModule` is not imported, +- Sidenav - component will be correctly updated on inputs changes, +- Sidenav - resolved problem with scroll position, +- Sidenav - added components and module exports to main library index. + +### New features: + +- Animations - added new animations: `slideLeft`, `slideRight`, `slideUp`, `slideDown`, +- Sidenav - added focus trap, +- Sidenav - escape button will now close the component. + +--- + +## 1.0.0-alpha4 (08.03.2021) + +### New components: + +- [Animations](https://mdbootstrap.com/docs/b5/angular/content-styles/animations/) +- [Ripple](https://mdbootstrap.com/docs/b5/angular/methods/ripple/) +- [Sidenav](https://mdbootstrap.com/docs/b5/angular/navigation/sidenav/) +- [Scrollspy](https://mdbootstrap.com/docs/b5/angular/navigation/scrollbar/) +- [Validation](https://mdbootstrap.com/docs/b5/angular/forms/validation/) + +### Bug fixes: + +- Select - `x options selected` text will be displayed correctly when more than 5 options have been selected, +- Select - fixed clear button focusing issue. + +### New features: + +- Select - added new `displayedLabels` input that allows to change maximum number of comma-separated options labels displayed in the multiselect input, +- Select - added new `optionsSelectedLabel` input that allows to customize x options selected text, +- Select - added new `filterDebounce` input that allows to add delay to the options list updates when using filter input + +--- + +## 1.0.0-alpha3 (22.02.2021) + +### New components: + +- [Dropdown](https://mdbootstrap.com/docs/b5/angular/components/dropdowns/) +- [Modal](https://mdbootstrap.com/docs/b5/angular/components/modal/) +- [Select](https://mdbootstrap.com/docs/b5/angular/forms/select/) +- [Scrollbar](https://mdbootstrap.com/docs/b5/angular/methods/scrollbar/) + +--- + +## 1.0.0-alpha2 (25.01.2021) + +### New components: + +- [Popover](https://mdbootstrap.com/docs/b5/angular/components/popovers/) +- [Tooltip](https://mdbootstrap.com/docs/b5/angular/components/tooltips/) +- [Checkbox](https://mdbootstrap.com/docs/b5/angular/forms/checkbox/) +- [Input](https://mdbootstrap.com/docs/b5/angular/forms/input-fields/) +- [Radio](https://mdbootstrap.com/docs/b5/angular/forms/radio/) + +--- + +## 1.0.0-alpha1 (11.01.2021) + +The initial release of MDB 5 Angular Alpha 1. + +### New components: + +- [Badges](https://mdbootstrap.com/docs/b5/angular/components/badges/) +- [Buttons](https://mdbootstrap.com/docs/b5/angular/components/buttons/) +- [Button Group](https://mdbootstrap.com/docs/b5/angular/components/button-group/) +- [Cards](https://mdbootstrap.com/docs/b5/angular/components/cards/) +- [Collapse](https://mdbootstrap.com/docs/b5/angular/components/collapse/) +- [List Group](https://mdbootstrap.com/docs/b5/angular/components/list-group/) +- [Progress](https://mdbootstrap.com/docs/b5/angular/components/progress/) +- [Spinners](https://mdbootstrap.com/docs/b5/angular/components/spinners/) +- [Tables](https://mdbootstrap.com/docs/b5/angular/data/tables/) +- [Breadcrumb](https://mdbootstrap.com/docs/b5/angular/navigation/breadcrumb/) +- [Footer](https://mdbootstrap.com/docs/b5/angular/navigation/footer/) +- [Headers](https://mdbootstrap.com/docs/b5/angular/navigation/headers/) +- [Navbar](https://mdbootstrap.com/docs/b5/angular/navigation/navbar/) +- [Pagination](https://mdbootstrap.com/docs/b5/angular/navigation/pagination/) + +### New sections: + +- Layout +- Utilities +- Content & styles diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index fdde873d..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,39 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@mdbootstrap.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 294ba523..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,53 +0,0 @@ -# Contributing Guide - -We value your and our time, we created this Guidline to avoid unnecessary diffuculties with pull requests and contribution process. - -Please read the text below **before starting your improvements** in order to help us make cooperation and reviewing experience as pleasent and effective as possible. - -Not following these rules and procedures may entail closing issues or rejection of your work even if it's theoreticly adequate. - -## Issues and Support - -1. **Use Support Forum** - don't use GitHub Issues for personal questions and support requests. We're maintaining a [dedicated Support Forum](https://mdbootstrap.com/support/) for this purpose. This is also a go-to place for every suggestion, opinion, bugs, and issue reports - if you'd like to receive help as soon as possible. Reports posted on Support Forum are **prior to GitHub issues**. - -2. **Check available resources** - always check [current issues](https://github.com/mdbootstrap/Angular-Bootstrap-with-Material-Design/issues) before opening your own. Also check [Support Forum](https://mdbootstrap.com/support/) and [Documentation](https://mdbootstrap.com/angular/) - most likely you'll find your answer there. Unchecked issues and duplicates will be closed immediatelly. - -3. **Examine issue source** - MDB code is depending on other projects, mainly [Bootstrap](https://github.com/twbs/bootstrap). Don't open issues concerning sources we cannot influence. For example - lack of flexbox support for older IE versions is determined by Bootstrap not MDB. - -4. **Check updates** - We resolve most of reported bugs and issues with every new release. Check if your MDB version is the latest. You can take a look at our [changelog](https://mdbootstrap.com/angular/changelog/) to find if your problem wasn't already taken care of. If you want to keep track on updates and bug fixes, the best way is joining our newsletter [here](https://mdbootstrap.com/angular/newsletter/). - -5. **Be specific** - describe your case **in English**, clearly and with details. Attach code examples, screenshots, links and any other relevant resources. - -## Contributing Standards -All improvements, fixes and new elements are welcome and we're deeply grateful for every help attempt. Although we recommend thinking twice before starting. We don't want to waste your time and some changes may be rejected not becouse of their quality but simply as a result of different style, organization or prefifined procedures we maintain. -That's why we came up with standards below, following them will maximize the chance of your contribution beeing included in project. - -1. Make sure that your idea doesn't contradict the concepts of [Bootstrap](https://getbootstrap.com/), [Material Design](https://material.io/guidelines/) and [Angular](https://angular.io/) -2. Verify if it fits the rest of MDB envoirement and style -3. Write and form your code accordingly to [those rules](http://codeguide.co/) -4. Use Gulp to minify your code after finishing -5. Create clear titles and detailed descriptions for your Pull Requests - -## Submitting a Pull Request - -Before you submit your Pull Request (PR) consider the following guidelines: - -1. Search [GitHub](https://github.com/mdbootstrap/Angular-Bootstrap-with-Material-Design/pulls) for an open or closed PR that relates to your submission. You don't want to duplicate effort. -2. Fork the mdbootstrap/Angular-Bootstrap-with-Material-Design repo. -3. Make your changes in a new git branch: - - ```shell - git checkout -b your-branch-name master - ``` -4. Commit your changes using a descriptive commit message. -5. Push your branch to GitHub - - ```shell - git push origin your-branch-name - ``` -6. In GitHub, send a pull request to `Angular-Bootstrap-with-Material-Design:dev` - -Thank you for your contribution! - -## Questions -If you're not sure about your ideas or you'd like to talk about them with our team before starting, write to: contact@mdbootstrap.com, and for every query regarding technical consultance open your case on [Support Forum](https://mdbootstrap.com/support/) diff --git a/Free Backend Setup.url b/Free Backend Setup.url deleted file mode 100644 index 6f1bfce9..00000000 --- a/Free Backend Setup.url +++ /dev/null @@ -1,5 +0,0 @@ -[{000214A0-0000-0000-C000-000000000046}] -Prop3=19,11 -[InternetShortcut] -IDList= -URL=https://mdbootstrap.com/freebies/angular/boilerplate/?utm_campaign=FreeBackendSetup&utm_source=AngularFree diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index e7871c55..00000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,9 +0,0 @@ -### Expected behavior - -### Actual behavior - -### Your working environment and MDB version information - -### Resources (screenshots, code snippets etc.) - -For every **question of technical nature**, in order to get the most detailed answer as soon as possible, ask on our dedicated [Support Forum](https://mdbootstrap.com/support/) diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..fbaac732 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +MIT license for MDB Free + +Free packages are available under the MIT License. + +-- Highlights + +● Free for personal use + +● Free for commercial use + +● No attribution required + +-- Copyright notice + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions. + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +The software is provided "As is", without warranty of any kind, express or implied, including but not limited To the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall The authors or copyright holders be liable for any claim, damages or other liability, whether in an action of Contract, tort or otherwise, arising from, out of or in connection with the software or the use or other Dealings in the software. diff --git a/License.pdf b/License.pdf deleted file mode 100644 index 41b5512c..00000000 Binary files a/License.pdf and /dev/null differ diff --git a/README.md b/README.md index 45483dee..8c742a74 100644 --- a/README.md +++ b/README.md @@ -1,297 +1,240 @@ -

- - - -

- -

Angular Bootstrap with Material Design

- -

- Built with Angular 8, Bootstrap 4 and TypeScript. CLI version available. Absolutely no jQuery. -

-

- 400+ material UI elements, 600+ material icons, 74 CSS animations, TypeScript modules, SASS files and many more. -

-

- All fully responsive. All compatible with different browsers. -

- -

- Downloads - License - npm - -

- -

- - logo - -

- -________ - - -# Table of Contents - -* [Other Technologies](#other-technologies) -* [Demo](#demo) -* [Version](#version) -* [Quick start](#quick-start) -* [Available commands](#available-commands) -* [Modules list](#modules-list) -* [How to install MDB via npm](#how-to-install-mdb-via-npm) -* [Supported Browsers](#supported-browsers) -* [Contributing](#contributing) -* [Getting started](#getting-started) -* [Additional tutorials](#additional-tutorials) -* [PRO version](#pro-version) -* [Documentation](#documentation) -* [Highlights](#highlights) -* [Useful Links](#useful-links) -* [Social Media](#social-media) - -# Other Technologies - -[](https://mdbootstrap.com/docs/jquery/)[](https://mdbootstrap.com/docs/react/)[](https://mdbootstrap.com/docs/vue/) +Bootstrap 5 & Angular 20 UI KIT - 700+ components, MIT license, simple installation. + +MDB is a collection of free Bootstrap templates, themes, design tools & resources. + +--- + +# Get started + +### [>> Get Started in 1 minute](https://mdbootstrap.com/docs/angular/getting-started/installation/) +Simple installation via .zip, npm or cdnjs. + +### [>> Install with MDBGO](https://mdbgo.com/) + Free Hosting, WordPress support, custom domains, SSL support, free database, frontend & backend templates, webpack starter included, git repostiory, FTP & jenkins support. + +### [>> Install with MDBGO + e-commerce shop integration](https://mdbgo.com/wordpress-shop/) +One click setup! MDB GO allows you to create a WordPress page with a single click. +Regardless whether you want to create a Travel Blog or an e-commerce shop to sell your product you can easily do that. You can even combine both into single page. + + +## About Material Design for Bootstrap 5 & Angular 20 + +

Created by + Downloads +License + + + MDBootstrap tutorial +

+ +Trusted by 3 000 000+ developers & designers. Used by companies & institutions like + + + + + + + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + + +
    +
  • 700+ UI components
  • +
  • Super simple, 1 minute installation
  • +
  • Detailed docs & practical examples
  • +
  • Lots of tutorials
  • +
  • Huge and active community
  • +
  • MIT license - free for personal & commercial use
  • +
+
+ +___ + +# Bootstrap 5 tutorial + +**[>> Learn more about Bootstrap 5](https://mdbootstrap.com/docs/standard/)** + + +**[>> Bootstrap 5 Tutorial](https://mdbootstrap.com/learn/mdb-foundations/basics/introduction/)** + +**[>> Subscribe to our YouTube channel with dozens of Bootstrap tutorials](https://www.youtube.com/c/Mdbootstrap)** + + + + + + + + + + + + +
+ + + + + + + +
+

Start learning from Basics

+ + + +
+

Learn Bootstrap 5 | Crash Course for Beginners in 1.5H

+ + + +
+ +--- # Demo -[Main demo](https://mdbootstrap.com/docs/angular/components/demo/) +#### Simplicity and ease of use are key features of MDB 5 Angular UI Kit. You need only one minute to install and run it. -# Version +### Buttons -- Angular 8 -- Angular CLI 8 +

Use MDB custom button styles for actions in forms, dialogs, and more with support for multiple sizes, states, and more.

-# Quick start + +

+ +

+
-- Clone following repo: -```javascript -git clone https://github.com/mdbootstrap/Angular-Bootstrap-with-Material-Design.git . -``` -note "." at the end. It will clone files directly into current folder. -- Run `npm i` -- Run `npm start` -- Voilà! Open browser and visit http://localhost:4200 + +

+ +

+
-Now you can navigate to [our documentation](https://mdbootstrap.com/docs/angular/), pick any component and place within your project. + +

+ +

+
-## Demo application + +

+ +

+
-Feel free to check our live example components: Just type `ng serve mdb-demo` in terminal! +### Spinners -Type one of the below commands to remove demo application from this project: -* npm `run remove-demo-unix` to remove demo application on UNIX based systems, -* npm `run remove-demo-windows` to remove demo application on Windows systems. +

Indicate the loading state of a component or page with MDB spinners, built entirely with HTML, CSS, and no JavaScript.

-# Available commands + +

+ +

+
-* npm run build:lib - building library, -* npm run pack - copying assets and packaging /dist directory into .tgz archive -* npm run version - adjusting src/package.json version from main package.json file, -* npm run compile - Executing above commands with correct sequence. + +

+ +

+
-# Modules list +### Cards -* ButtonsModule, -* CarouselModule, -* ChartsModule, -* CollapseModule, -* InputsModule, -* ModalModule, -* NavbarModule, -* PopoverModule, -* TooltipModule, -* WavesModule, -* MDBBootstrapModule - contains every MDB modules. +

A card is a flexible and extensible content container. It includes options for headers and footers, a wide variety of content, contextual background colors, and powerful display options.

-# How to install MDB via npm + +

+ +

+
-- create new project `ng new project_name --style=scss` -- `npm i angular-bootstrap-md --save` -- to app.module.ts add -```javascript -import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; -import { MDBBootstrapModule } from 'angular-bootstrap-md'; +### Footer -@NgModule({ - imports: [ - MDBBootstrapModule.forRoot() - ], - schemas: [ NO_ERRORS_SCHEMA ] -}); -``` -- in angular.json change: +

A footer is an additional navigation component. It can hold links, buttons, company info, copyrights, forms, and many other elements.

-`"styleExt": "css"` to `"styleExt": "scss"` + +

+ +

+
-rename /src/styles.css to styles.scss +### Hover -- if you want to change styles in exisiting project you can use `ng config schematics.@schematics/angular:component.styleext scss` +

MDB hover effect appears when the user positions the computer cursor over an element without activating it. Hover effects make a website more interactive.

-- add below lines to angular.json: -```javascript -"styles": [ - "node_modules/font-awesome/scss/font-awesome.scss", - "node_modules/angular-bootstrap-md/assets/scss/bootstrap/bootstrap.scss", - "node_modules/angular-bootstrap-md/assets/scss/mdb.scss", - "src/styles.scss" -], -"scripts": [ - "node_modules/chart.js/dist/Chart.js", - "node_modules/hammerjs/hammer.min.js" -], -``` -- install external libs -```bash -npm install -–save chart.js@2.5.0 font-awesome hammerjs -``` + +

+ +

+
-### Run server + +

+ +

+
-```bash -ng serve --open -``` +### Notes -# Supported browsers +

Notes are small components very helpful in inserting an additional piece of information.

-MDBootstrap supports the **latest, stable releases** of all major browsers and platforms. + +

+ +

+
-Alternative browsers which use the latest version of WebKit, Blink, or Gecko, whether directly or via the platform’s web view API, are not explicitly supported. However, MDBootstrap should (in most cases) display and function correctly in these browsers as well. + -### Mobile devices +___ -Generally speaking, MDBootstrap supports the latest versions of each major platform’s default browsers. Note that proxy browsers (such as Opera Mini, Opera Mobile’s Turbo mode, UC Browser Mini, Amazon Silk) are not supported. +# Extended documentation -| | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | Android Browser & WebView | [IE / Edge](http://godban.github.io/browsers-support-badges/)
Miscrosoft Edge | -|:--------------------:|:---------------------------:|:----------------------------:|:----------------------------:|:----------------------------:|:-------------------------------------------------------------------------:| -| Android | Supported | Supported | N/A | Android v5.0+ supported | Supported | -| iOS | Supported | Supported | Supported | N/A | Supported | -| Windows 10 Mobile | N/A | N/A | N/A | N/A | Supported | + diff --git a/README.txt b/README.txt new file mode 100644 index 00000000..d746aa60 --- /dev/null +++ b/README.txt @@ -0,0 +1,15 @@ +MDB 5 Angular + +Version: FREE 9.0.0 + +Documentation: +https://mdbootstrap.com/docs/angular/ + +Installation: +https://mdbootstrap.com/docs/angular/getting-started/installation/ + +CLI & hosting: +https://mdbgo.com/ + +Support: +https://mdbootstrap.com/support/cat/angular/ diff --git a/Useful_Resources.pdf b/Useful_Resources.pdf deleted file mode 100644 index fc05094f..00000000 Binary files a/Useful_Resources.pdf and /dev/null differ diff --git a/angular-bootstrap-md-8.10.1.tgz b/angular-bootstrap-md-8.10.1.tgz deleted file mode 100644 index 7968fc57..00000000 Binary files a/angular-bootstrap-md-8.10.1.tgz and /dev/null differ diff --git a/angular.json b/angular.json index a1d24343..4cfda010 100644 --- a/angular.json +++ b/angular.json @@ -3,42 +3,41 @@ "version": 1, "newProjectRoot": "projects", "projects": { - "angular-bootstrap-md-app": { - "root": "", - "sourceRoot": "src", + "mdb-angular-ui-kit-free": { "projectType": "application", - "prefix": "app", "schematics": { "@schematics/angular:component": { - "styleext": "scss" + "style": "scss" } }, + "root": "", + "sourceRoot": "src", + "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:browser", + "builder": "@angular-devkit/build-angular:application", "options": { - "outputPath": "dist/angular-bootstrap-md-app", + "outputPath": { + "base": "dist/mdb-angular-ui-kit-free" + }, "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "src/tsconfig.app.json", + "polyfills": [ + "src/polyfills.ts" + ], + "tsConfig": "tsconfig.app.json", "assets": [ "src/favicon.ico", "src/assets" ], "styles": [ - "node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss", - "node_modules/@fortawesome/fontawesome-free/scss/solid.scss", - "node_modules/@fortawesome/fontawesome-free/scss/regular.scss", - "node_modules/@fortawesome/fontawesome-free/scss/brands.scss", - "node_modules/angular-bootstrap-md/assets/scss/bootstrap/bootstrap.scss", - "node_modules/angular-bootstrap-md/assets/scss/mdb.scss", "src/styles.scss" ], - "scripts": [ - "node_modules/chart.js/dist/Chart.js", - "node_modules/hammerjs/hammer.min.js" - ] + "scripts": [], + "extractLicenses": false, + "sourceMap": true, + "optimization": false, + "namedChunks": true, + "browser": "src/main.ts" }, "configurations": { "production": { @@ -51,133 +50,121 @@ "optimization": true, "outputHashing": "all", "sourceMap": false, - "extractCss": true, "namedChunks": false, - "aot": true, "extractLicenses": true, - "vendorChunk": false, - "buildOptimizer": true, "budgets": [ { "type": "initial", "maximumWarning": "2mb", "maximumError": "5mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "6kb", + "maximumError": "10kb" } ] } - } + }, + "defaultConfiguration": "" }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { - "browserTarget": "angular-bootstrap-md-app:build" + "buildTarget": "mdb-angular-ui-kit-free:build" }, "configurations": { "production": { - "browserTarget": "angular-bootstrap-md-app:build:production" + "buildTarget": "mdb-angular-ui-kit-free:build:production" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { - "browserTarget": "angular-bootstrap-md-app:build" + "buildTarget": "mdb-angular-ui-kit-free:build" } }, "test": { - "builder": "@angular-devkit/build-angular:karma", + "builder": "@angular-builders/jest:run", "options": { - "main": "src/test.ts", "polyfills": "src/polyfills.ts", - "tsConfig": "src/tsconfig.spec.json", - "karmaConfig": "src/karma.conf.js", - "styles": [ - "src/styles.scss" - ], - "scripts": [], + "tsConfig": "tsconfig.spec.json", "assets": [ "src/favicon.ico", "src/assets" - ] - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "src/tsconfig.app.json", - "src/tsconfig.spec.json" ], - "exclude": [ - "**/node_modules/**" - ] + "styles": [ + "src/styles.scss" + ], + "scripts": [] } - } - } - }, - "angular-bootstrap-md-app-e2e": { - "root": "e2e/", - "projectType": "application", - "prefix": "", - "architect": { + }, "e2e": { "builder": "@angular-devkit/build-angular:protractor", "options": { "protractorConfig": "e2e/protractor.conf.js", - "devServerTarget": "angular-bootstrap-md-app:serve" + "devServerTarget": "mdb-angular-ui-kit-free:serve" }, "configurations": { "production": { - "devServerTarget": "angular-bootstrap-md-app:serve:production" + "devServerTarget": "mdb-angular-ui-kit-free:serve:production" } } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": "e2e/tsconfig.e2e.json", - "exclude": [ - "**/node_modules/**" - ] - } } } }, - "angular-bootstrap-md": { - "root": "projects/angular-bootstrap-md", - "sourceRoot": "projects/angular-bootstrap-md/src", + "mdb-angular-ui-kit": { "projectType": "library", - "prefix": "mdb", + "root": "projects/mdb-angular-ui-kit", + "sourceRoot": "projects/mdb-angular-ui-kit/src", + "prefix": "lib", "architect": { "build": { - "builder": "@angular-devkit/build-ng-packagr:build", + "builder": "@angular-devkit/build-angular:ng-packagr", "options": { - "tsConfig": "projects/angular-bootstrap-md/tsconfig.lib.json", - "project": "projects/angular-bootstrap-md/ng-package.json" + "tsConfig": "projects/mdb-angular-ui-kit/tsconfig.lib.json", + "project": "projects/mdb-angular-ui-kit/ng-package.json" + }, + "configurations": { + "production": { + "tsConfig": "projects/mdb-angular-ui-kit/tsconfig.lib.prod.json" + } } }, "test": { - "builder": "@angular-devkit/build-angular:karma", + "builder": "@angular-builders/jest:run", "options": { - "main": "projects/angular-bootstrap-md/src/test.ts", - "tsConfig": "projects/angular-bootstrap-md/tsconfig.spec.json", - "karmaConfig": "projects/angular-bootstrap-md/karma.conf.js" - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "projects/angular-bootstrap-md/tsconfig.lib.json", - "projects/angular-bootstrap-md/tsconfig.spec.json" - ], - "exclude": [ - "**/node_modules/**" - ] + "tsConfig": "projects/mdb-angular-ui-kit/tsconfig.spec.json" } } } } }, - "defaultProject": "angular-bootstrap-md-app" -} + "schematics": { + "@schematics/angular:component": { + "type": "component" + }, + "@schematics/angular:directive": { + "type": "directive" + }, + "@schematics/angular:service": { + "type": "service" + }, + "@schematics/angular:guard": { + "typeSeparator": "." + }, + "@schematics/angular:interceptor": { + "typeSeparator": "." + }, + "@schematics/angular:module": { + "typeSeparator": "." + }, + "@schematics/angular:pipe": { + "typeSeparator": "." + }, + "@schematics/angular:resolver": { + "typeSeparator": "." + } + } +} \ No newline at end of file diff --git a/changelog b/changelog deleted file mode 100644 index b40d26de..00000000 --- a/changelog +++ /dev/null @@ -1,1409 +0,0 @@ -8.10.1 -In version 8.10.1 we added bug fixes for WYSIWYG plugin and new guide for styles customization. Check what changed below: - -**Fixes:** - -* WYSIWYG plugin - resolved problem with "Cannot read property parentElement of null" error, -* WYSIWYG plugin - resolved problem with resetting value in reactive forms. - -**Docs:** - -* Added new guide for styles customization. - -8.10.0 -In version 8.10.0 we added some bug fixes and new features for the existing components. Check what changed below: - -**Fixes:** - -* Carousel - selectSlide method will work correctly even if animation type is not specified, -* Datepicker - resolved problem with setting disabled state in reactive forms (modal version), -* Textarea - resolved problem with auto resize for default value, -* Select/Multiselect - resolved problem with background color of highlighted option in colorful select, -* Select/Multiselect - resolved problem with position of value text in outline mode. - -**New features:** - -* Carousel - added new allowSwipe input that allow to enable/disable swipe gestures, -* Icon - added support for Font Awesome Pro Duotone Icons. - -8.9.0 -In version 8.9.0 we added some fixes and features for the existing components. Check what changed below: - -**Fixes:** - -* Validation - resolved problem with styles of outline inputs, -* Select - adjusted outline input height to the height of the inputs in other form controls, -* Stepper - (stepChange) event will not be emitted for disabled steps, -* Datepicker - resolved problems with global options, -* Autocomplete - fixed dropdown bottom position in outline mode, -* Timepicker - component will now return new value and change event when clear method is used, -* Checkbox - resolved problem with label styles (removed unnecessary overflow and white-space properties), -* Sidenav - resolved problem with styles of the links with waves effect, -* Sortable plugin - input/textarea funcitonality will no longer be blocked by the sortable directive, -* Sidenav/Accordion - added checks for window object to resolve problems with server side rendering. - -**New features:** - -* Dropdown - added new dynamicPosition input, when this option is active component will dynamically update dropdown position (if the menu does not fit the viewport), -* Timepicker - added possibility to add custom styles for footer and AM/PM buttons and custom translation to buttons, -* Timepicker - added possibility to add new button that will clear the current selected value. - -8.8.2 -In version 8.8.2 we added some fixes for the existing components and removed duplicated css styles. Check what changed below: - -**Fixes:** - -* Styles - removed duplicated code, -* Toast - resolved problem with box-shadow styles on hover, -* Table - removed unecessary styles from a elements, -* Table sort - slightly improved performance, -* Validation - prefix icons will get correct validation color styles (success/error), -* Validation - fixed position of validation messages, -* Sidenav - will not be closed when viewport height change, -* Select - will not be overlapped by table header with sticky-top class, -* Pills - removed unecessary shadow styles on hover, -* Static modal - backdrop will be added/removed correctly when config value change, -* Calendar plugin - fixed parameter name in object emitted by (monthChanged) output. - -8.8.1 -In version 8.8.1 we added some fixes for the existing components and small update for the calendar plugin. Check what changed below: - -**Fixes:** - -* Inputs - resolved problem with styles of long label, -* Datepicker inline - clicking on the icon won't open the disabled component, -* Textarea - resolved problem with position of validation message, -* Textarea - active styles for prefix icon will be applied correctly, -* Search - resolved problem with the icon position when the input is inside container with .md-form class, -* Autocomplete - clear button will be added correctly if default value is set, -* Admin Template Pro - resolved problem with styles of images that use waves effect, -* Admin Template Pro - resolved problem with styles of icons in the sidenav. - -**Plugins:** - -* Calendar - added new [defaultView] input that allow to set default view for the component (week/month/list). - -8.8.0 -In version 8.8.0 we added some fixes and new tree view component. Check what changed below: - -**Fixes:** - -* Sticky header - resolved problem with navbar z-index, -* Table search/Table editor - resolved problem with filtering when a property value is null, -* Datepicker - resolved problem with setting minYear and maxYear options, -* Select - clear button will no longer be displayed if no option is selected, -* Timepicker - component view will be now updated correctly on form control value change, -* Color picker plugin - resolved problem with displaying wrong rgba color on component initialization, -* Resolved problem with memory leaks caused by unsubscribed subscriptions. - -**New Features:** - -* Added new tree view component. - -8.7.0 -In version 8.7.0 we added some fixes and new features. Check what changed below: - -**Fixes:** - -* Sidenav - resolved problem with closing sidenav when mobile keyboard appear on the screen, -* Datepicker - resolved problem with next and previous arrows styles on IE11, -* Select - resolved problem with search input and value container styles in outline mode, -* Select - added validation styles for bootstrap version, -* Autocomplete - resolved problem with dropdown dynamic position when appendToBody option is used, -* Autocomplete - resolved problem with dynamic dropdown position updates on option filtering, -* Lightbox - resolved problems with browser navigation, -* Buttons - resolved problem with dynamic style updates for outline and flat button, -* Color picker plugin - resolved problem with rendering the plugin inside modal, -* Zip packages/Admin template - resolved problem with ng test and ng lint. - -**New Features:** - -* Table search - added possibility to search through multiple table columns, -* Reveal cards - added new (animationStart) and (animationEnd) outputs, -* Table pagination - added new (lastPageClick) and (firstPageClick) outputs. - -8.6.0 -In version 8.6.0 we added some fixes and new features. Check what changed below: - -**Fixes:** - -* Select - resolved problem with maintaing focus on component when navigating using keyboard and added proper colors for focused component, -* Select - resolved problem with navigation to other element using tab or shift+tab keys, -* Select - added different color for highlighted options to help to distinguish selected options from highlighted options, -* Select - resolved problem with highlighting option when typing its name (it works when filter input is disabled), -* Select - resolved problem with dropdown position when using filter input or custom content, -* Select - resolved problems with styles and dropdown position in outline mode, -* Select - resolved problems with resetting selected value, -* Buttons - styles will be now properly updated if inputs values change, -* Prefix icon - removed unecessary color transition delay, -* Datepicker - resolved problem with disabling component with reactive forms -* Datepicker - resolved problem with z-index in inline mode (this problem occured when multiple components were used in the same view), -* Table sort - resolved problem with returning undefined sortOrder in (sorted) output. - -**New Features:** - -* Select - added possibility to use prefix icon, -* Select - added proper aria attributes, -* Timepicker - added possibility to mark input as readonly, -* WYSIWYG plugin - added new (valueContent) output that will return only text content without HTML tags. - -8.5.0 -In version 8.5.0 we added some fixes and new features. Check what changed below: - -**Fixes:** - -* Datepicker - resolved problem with opening multiple datepickers on label click when inline mode is used, -* Datepicker - resolved problem with styles of right and left arrows when date picker is rendered inside mdb-stepper, -* Datepicker - resolved problem with z-index in inline mode (dropdown should no longer be overlapped by a footer), -* Multiselect - resolved problem with styles of checkboxes when select is used inside element with md-form class, -* Select - resolved problem with setting default value when change is detected in option array, -* Select - component styles have been corrected to adapt its appearance to other MDB form elements, -* Autocomplete - number of visible options will no longer be set by default. It resolves problem with options styles when mdb-options have a non-standard height. - -**New Features:** - -* Table sort - added new (sorted) output that emits data, sortDirection and sortBy parameters, -* Autocomplete - added new [dropdownHeight] input that allows to customize dropdown height (it will only work if visibleOptions input is not used). - -**Docs:** - -* Modal - added list of options available for dynamic modals -* MDB Angular Boilerplate - added guide on how to configure MDB Angular Pro version. - -8.4.0 -In version 8.4.0 we added some fixes and new features. Check what changed below: - -**Fixes:** - -* Datepicker - resolved problem with opening multiple datepickers on label click. Every component will now have unique id. You can specify this id in [id] input, otherwise custom id will be created, -* Chips - resolved problem with displaying default tags on component initialization, -* Chips - resolved problem with deleting items with backspace/delete key, -* Chips - resolved problem with input focus after adding first tag or removing last tag, -* Modal - resolved problem with modal position when backdrop option is set to false, -* Autocompleter - resolved problem with closing dropdown on scroll click. We needed to drop support for dropdown state updates on input blur event, because in many cases it was impossible to check whether dropdow should b* closed. If you need to programatically change focus to other element with focus() method, you should also programatically hide autocompleter dropdow* by using its hide() method, -* Autocompleter - removed unecessary border styles from mdb-option, -* Autocomplter - resolved problem with opening dropdown when [clearButton] is set to false, -* Dropdown - resolved problem with change detection, dropdown should be correctly removed from DOM even when OnPush strategy is used in parent component, -* Select - dropdown won't open on right click on input, -* Select - resolved problem with displaying long placeholder and label text, -* Select - resolved problem with opening dropdown on label click (this problem occured only when label was added as html element and not with [label] input , -* Sortable plugin - resolved problem with blocked click events, -* Sortable plugin - resolved problem with width of cards in card deck example. - -**New Features:** - -* Select - added possibility to add object to the value parameter in select options array, -* Select - added new [compareWith] input that accepts a function that will be used to find corresponding option for specified value (useful when using object as option value), -* Calendar plugin - added new (monthChanged), (weekChanged) and (listChanged) outputs, -* Popover - added new [bodyClass], [headerClass] and [containerClass] inputs that allow to specify custom classes. - -8.3.1 -In version 8.3.1 we added some fixes and updated code in the documentation pages. Check what changed below: - -**Fixes:** - -* Checkbox - resolved problem with checked state updates on ngModel value change, -* Autocompleter - resolved problem with Cannot read property 'value' of undefined on up arrow navigation, -* Dropdown - resolved problem with material styles in default version, -* Textarea - added possibility to resize material version, -* Timepicker - resolved problem with clear button, it should be now displayed correctly, -* Table pagination - resolved problem with pagination when switching to last item, -* Sidenav - resolved problem with accordion auto expand/collapse when queryParams are used. - -8.3.0 -In version 8.3.0 we added some fixes, new features and updated code in documentation pages. Check what changed below: - -**Fixes:** - -* Admin Pro Template - resolved problem with responsivity, -* Flipping cards - resolved problem with animation, -* Time picker - resolved problem with change detection when setting default value, -* Time picker - component will now open on input focus, to disable this behavior set openOnFocus input to false, -* Checkbox - resolved problem with position of validation message for longer text, -* Radio buttons group - resolved problem with styles for rounded buttons, -* Breadcrumbs - added cursor: pointer style for inactive elements, -* File upload - resolved problem with uploading the same file multiple times in a row, -* Sidenav - resolved problems with accordion items auto expand on route change, -* Sidenav - reduced size of indicator arrow in accordion items and fixed its animation, -* Modal - resolved problem with alignment of items in dynamic full height modal, -* Select - resolved problem with styles on disabled state, -* Toast - resolved problem with maxOpened option, -* Carousel - resolved problem with displaying first slide when OnPush strategy is used in parent component, -* Accordion - resolved problem with aria-expanded value, -* Select - resolve problem with highlightColor and highlightTextColor options, -* Autocompleter - dropdown will no longer be closed on input click. - -**New Features:** - -* Date picker - added possibility to add default date in JavaScript Date Object format. Component will also return date object if useDateObject option is set to true (default value is false) -* Dropdown - added new dropupDefault input that allow to use dropup component with default Bootstrap styles, -* Autocompleter - added new clear method that allow to reset displayed value, -* Table - added new searchLocalDataByFields method that allow to filter result only in specific table columns, -* Autocompleter - added new optionHeight input that allow to set height for the dropdown options, -* Autocompleter - added new visibleOptions input that allow to change number of options visible in the dropdown, -* Autocompleter - added new displayValue input that allow to specify option value that will be displayed in the input text field. This is useful in case where we want the value processed by the form control to be different from the displayed value (for example when value of the option is an object). - -**Docs:** - -* Datatables - added example of master detail table -* Charts - added examples of data formatting -* Time picker - added validation example - -8.2.0 -In version 8.2.0 we resolved some problems with change detection and reduced number of unecessary change detection cycles caused by our components. These changes should positively affect the performance of applications that use MDB Angular. - -**Fixes:** - -* Credit card directive - changed limit of Diners card from 19 to 14, -* Autocomplete - resolved problem with position of dropdown displayed above the completer input (it should now update correctly when number of displayed options change), -* Date picker - resolved problem with label animation when click is detected directly on label text, -* Date picker - resolved problem with display of default date when using locales other than 'en', -* Carousel - resolved problem with video playing in the background even when slide is not active, -* Select - resolved problem with long placeholder overlapping select input, -* Toast - resolved problem with null check in show method, -* Navbar - resolved problem with scroll in navbar dropdown when .fixed-top class is added, -* Dropdown - resolved problem with fade out animation, -* File upload plugin - resolved problem with resetting default file, -* File upload plugin - resolved problem with 'No file choosen' message when default file is available. - -**New features:** - -* Autocomplete - added new (selected) output that is fired on selection change, -* Accordion - added new [autoExpand] input that allow to disable auto expanding of accordion items in sidenav when active route is detected, -* Table editor plugin - added sorting feature. - -8.1.1 -In version 8.1.1 we added some minor fixes and updated documentation pages. Check what changed below: - -**Fixes:** - -* Lightbox - resolved problem with styles (hover, cursor, margin), -* Lightbox - resolved problems with back button in mobile browsers, -* Ligthbox/Carousel - resolved problem with paths to images, -* Autocompleter - resolved problem with dropdown position when its rendered on top, it should no longer cover the input -* Autocompleter - resolved problem with closing the dropdown with hide() method, -* Fixed button - removed unnecessary right and left styles, -* Select - removed display: none style from standard HTML select, -* Inline date picker - resolved problem with closing dropdown when using multiple date pickers in the same view, the dropdown should now close automatically when another date picker is opened -* Inline date picker - resolved problem with previous/next buttons styles, -* Credit card directive - resolved problem with formatting of the Diners card number, -* Rounded buttons - resolved problem with styles when using rounded buttons and mdb-calendar plugin in the same view, -* Range slider - resolved problem with value updates in reactive forms. - -**Docs:** - -* Cards - updated code responsible for rendering image overlay, -* Progressbar - updated API section of the component. - -8.1.0 -In version 8.1.0 we added some new features and bug fixes. Check what changed below: - -**Fixes:** - -* Dynamic modal - resolved problem with backdrop in lazy loaded modules (it should no longer remain on screen after navigating to a different module), -* Carousel - resolved problem with auto slide, -* Sticky header - resolved problem with stickyHeader="true" in Chrome browser, -* Table search - resolved problem with filtering for nested data, -* Vertical list group (mdb-tabset) - resolved problem with display on hover in Safari browser, -* Time picker and Date picker - resolved problem with different input position in these form controls, -* Popover - resolved problem with outsideClick in Safari browser (ipad), -* Accordion - resolved problem with alignment of indicator icon, -* Accordion - resolved problem with OnPush change detection, -* Register/login modal - resolved problem with alignment of mdb-success and mdb-error messages, -* Video modal - resolved problem with not pausing video when closing modal, -* Select - resolve problem with position of clear button, -* Double navigation - resolved problem with missing margin-left and margin-right in dobule navigation layouts, -* Textarea - resolved problem with active state of the label (it should no longer have blue color when it's not focused), -* Fixed caption button - resolved problem with element[NATIVE_ADD_LISTENER] is not a function error. - -**New features:** - -* Table pagination - added new buttons for navigation to first and last page, -* Charts - added new getPointDataAtEvent(event) for getting cursor position after click. - -**Docs:** - -* Migration guide - added information about cards and skins, -* Sidenav - added live preview for slim sidenav, -* Charts- added example on how to display values for each point of the graph, -* Forms - updated example code with new validation method, -* Validation - updated example code for onSubmit validation method, -* Dynamic modal - added example on how to use output events, -* Parallax - added additional information about the usage in Angular project. - -8.0.0 -New version brings refactor in styles and few components. Check every change in 8.0.0 below. - -**MIGRATION INSTRUCTIONS:** -You should definitely read the entire migration guide carefully and understandably before updating your application to the latest. - -**Most important changes in version 8.0.0:** - -* support for Angular 8 - new Angular version is now required in MDB Angular 8.0.0, -* modular styles - components styles will be now loaded with thier modules, that means you can load only the styles you need in your application (some of the styles still remain global as they are not part of any component), -* some bug fixes and new features. - -**Fixes:** - -* Fluid modal - the modal will no longer overwrite padding-right styles on the body element, -* Fixed button - resolved problem with ExpressionChangedAfterItHasBeenCheckedError error, -* Dynamic modal - resolved problem with focus trap (other elements on the page should no longer be focusable if the modal is displayed), -* Autocompleter - resolved problem with click event on the clear button, -* Autocompleter - resolved problem with blocked events in autocompleter dropdown (it should be possible to add events to mdb-option components and other custom elements that are displayed in completer dropdown), -* Stepper - removed automatic mode change from horizontal to vertical on smaller screens. The stepper mode will be changed only if [vertical]value change, -* Dropdown - resolved problem with visibility of dropdown menu when it was opened from element with mdbTooltipdirective. - -**New Features:** - -* Steppers - added new [stepChange] output which will be fired on every step change, -* Autocompleter - added Control Value Accessor to completer input (that should resolve problems wit valueChange in reactive forms). -* Material Select - added ChangeDetectionStrategy.OnPush from default to the Material Select dropdown. - -**Docs:** - -All code examples have been updated to work correctly in Angular 8 projects. Due to the changes in styles modularity, there may be slight differences when it comes to overwritting component styles. We also reviewed the documentation and removed minor errors like typos and old, unused code. - -* Autocompleter - changed the code of the examples to make it shorter and more readable. - -7.5.4 -New version brings some bug fixes and new features. Check every change in 7.5.4 below. - -**Fixes:** - -* Select label with prefilled value 0 won't break lifting up label element, -* Select will now be properly aligned with other material inputs, -* Material Select will now close it's dropdown while clicking outside of it on iOS, -* visibleOptions in Material Select will now work again when select was opened for the first time, -* Carousel won't throw classList is undefined anymore, -* Datepicker won't throw document is undefined while SSR no more, -* Datepicker will now allow to use Font Awesome 5 Pro with css, -* Datepicker will now format date properly while using patchValue and JS date format, -* Toast with changed opacity won't blink anymore, -* Resolved problems with access specificators on fullTemplateTypeCheck mode, -* Color Picker won't throw erorrs when destroying it's instance, -* Sidenav with routerLink will now open active links after reload while using HashLocationStrategy. - -**New Features:** - -* Material Select - Added new input - [outline] which will allow to use outline input type, -* Multi Select - Added new input - [outline] which will allow to use outline input type, -* Material Select - added ChangeDetectionStrategy.OnPush from default to the Material Select dropdown, -* Autocomplete - Added new output which will emit value after clicking in clear button, -* Table Editor plugin - added behavior to highlight table editor row when editing row, -* Table Pagination - added two new ofKeyword, dashKeyword inputs which allow to overwrite default text in Table Pagination, -* Accordion - added new (animationStateChange) event which will be fired after accordion animation end, -* Cards - added ChangeDetectionStrategy.OnPush from default to the Cards components, -* Sortable plugin - added new [disabledDragElements]="[]" input which allow to determine, on which elements sortable effect should be disabled. -* Chat plugin - added new chat example - Small Chat, -* E-commerce components - added new example - Shopping Cart. - -**Docs:** - -* Navbars - resolved problem with dropdown alignment on mobile screen, -* Material Select - added missing inputs and outputs to the Material Select API docs. - -**Other:** - -* Resolved vulnerability problems on four repositories: MDB Free, MDB Admin Free, MDB Pro, MDB Admin Pro. - -7.5.3 -New version brings some bug fixes and new features. Check every change in 7.5.3 below. - -**Fixes:** - -* Tooltip with routerLink and nested i tag won't reload page after click, -* Improved positioning in Tooltip - use dynamicPosition input to turn off adjusting position, -* Improved positioning in Popover - use dynamicPosition input to turn off adjusting position, -* Accordion will now allow to use keyboard navigation (Tab, Enter, Space). - -**New Features:** - -* Double Range Slider - Added new component - Two dots range slider, -* Accordion - changed icon behavior - now it is rendered with css and content property, -* Flipping Cards - added (animationStart) and (animationEnd) outputs, -* File Upload plugin - added new customText input which allow to overwrite default text in File Upload, -* File Upload Plugin - added new [defaultFile]" input which allow to set default file for File Input component, -* Date Picker - added possibility to use LocaleService as global service - one instance for every instance of Date Picker, -* Material Select - added feature which allow to highlight specific item after keyboard button push (eg. push "F", and Select will highlight item which starts on "F" char), -* Material Select - added new [filterAutocomplete]="true" input which allow to determine, Select should allow browser autocomplete mechanism or not, -* Material Select - Select will now allow you to define custom template in it, -* Toast / Notification - added new actionButtonClass config property which allow to add CSS class to action button in toast, -* Date Picker - added new editableDateField config property which allow to disable Date Picker input field to put there some text, -* Popover - added new [outsideClick]="true" input which allow to determine, if Popover should be closed after clicking in it's content, -* Date Picker - added new [outlineInput]="true" input which allow to use the .md-outline class inside Date Picker, -* Time Picker - added new [outlineInput]="true" input which allow to use the .md-outline class inside Time Picker. - -**Docs:** - -* Multi Item Carousel - added new Multi item responsive carousel, -* Flipping Cards - added information about this, that Flipping Cards requires BrowserAnimationsModule. - -7.5.2 -New version brings some bug fixes and new features. Check every change in 7.5.2 below. - -**Fixes:** - -* Table pagination - resolved problem with displayed value of maxVisibleItems, -* Tooltip - resolved problems with alignment near the edge of viewport, -* Dropdown - resolved problem with menu position when its open near the edge of viewport, -* Autocompleter - resolved problem with dropdown position in Chrome, -* Select/multiselect - resolved problem with animation of label added with html tag, -* Date picker - resolved problem with dynamic value and format updates when locale input change, -* Date picker - resolved problem with disabling component in reactive forms, -* Validation - resolved problems with validation of default value in mdb-select component, -* Sidenav - resolved problem with component blinking on initialization, -* WYSIWYG plugin - added type="button" to the options buttons to resolve problem with form submitting. - -**New Features:** - -* Select / Multiselect - added possibility to customize toggle icon, -* Sidenav - added new slim sidenav version, -* Calendar plugin - added new [editable] input that allow to disable event editing, -* Calendar plugin - added new [options] input that allow to add custom configuration options, options. - -**Docs:** - -* Modals - updated example code for long content modals, -* Date picker - added new examples for setting default value, -* Datatables - added new example for passing table data to dynamic modal, -* Calendar plugin - added new instructions on how to use custom configuration options. - -7.5.1 -New version brings some bug fixes and new features. Check every change in 7.5.1 below. - -*** Fixes: *** - - * Sticky Header - resolved problem with 'BrowserModule has already been loaded', - * Toast - resolved problem with animation in Edge, - * Carousel - resolved problem with 'Cannot read property classList of undefined', - * Card reveal - resolved problems with animation, - * WYSIWYG plugin - resolve problems with value dynamic updates and valueChange event. - -*** New Features: *** - - * Fixed buttons - added new mdbFixedCaption directive that allow to add caption to button, - * Navbar - added new [scrollSensitivity] input that allow to specify scroll amount needed for the top-nav-collapse class to be added, - * Stepper - added new [editable] input that allow to disable edition of finished step, - * Sidenav - added new [side] input that allow to change position of the sidenav. Use [side]="'right'" or [side]="'left'" options, - * Inputs - added new Material 2.0 input with background and animated border. - -*** Docs: *** - - * Card reveal - added live example, - * Composition - added example with hidden sidenav under fixed navbar. - -7.5.0 -New version brings breaking changes in tables, some bug fixes and new features. Check every change in 7.5.0 below! - -*** Breaking changes in tables: *** - -MdbTableService will be no longer used and it will be removed from package in MDB Angular 8 version. All methods available in this service were moved to the MdbTableDirective. - -New [tableEl] input was added to MdbTablePaginationComponent. This input accepts instance of MdbTableDirective, and it's required for the pagination to function properly. - -toLowerCase() method was removed from MdbTableSort directive due to problems with sorting in Chrome browser. Sort headers values must be now exactly the same as values of data source objects properties. - -All changes have been included in the new examples in the tables documentation. - -*** Fixes: *** - - * Frame Modal - resolved problem with displaying in Safari browser, - * Fluid Modal - resolved problem with position on smaller screens, - * Autocompleter - resolved problem with dropdown position in modal, - * Autocompleter - resolved problem with form submitting when using enter key to select an option, - * Collapse - resolved problem with displaying shadow of card component, - * Horizontal Stepper - resolved problem with dynamic height update, - * Accordion - resolved problem with asynchronous data, - * Accordion - resolved problem with multiple accordion instances inside one component, - * Table sort header - resolved problem with accessibility, - * Navbar - resolved problem with accessibility of navbar toggler button. - -*** New Features: *** - - * Date picker - added new inline version, - * Date picker - added javascript date to object emitted by (dateChanged) output, - * Date picker - added new (closeButtonClicked), (todayButtonClicked) and (clearButtonClicked) outputs, - * Table sort - added new (sortEnd) output that will emit sorted data, - * Sticky header - added new mdbStickyHeader directive that can be added to mdb-navbar component to hide navbar when scrolling down and bring it back when scrolling up. - * WYSIWYG plugin - added ControlValueAccessor implementation. - - -*** Docs: *** - - * Inputs - added new fix for yellow or blue background that is added with Chrome auto-fill, - * File upload plugin - changed component type from FileUploadComponent to MdbFileUploadComponent, - * Table sort - added TitleCase pipe to sort headers, - * Table pagination - onPreviousPageClick and onNextPageClick methods were moved to the MdbTablePaginationComponent internal code. - -7.4.4 -New version brings some bug fixes and new features. Check every change in 7.4.4 below! - -**Fixes:** - -* Multiselect - disabled options will be now skipped correctly in 'Select all' method, -* Table pagination - resolved problem with accessibility, -* Table filter - resolved problem with searching in list with null values, -* Table filter - search will now work correctly for uppercase letters, -* Table sort - resolved problem with sorting null values, -* Autocompleter - resolved problem with scrollbar, -* Autocompleter - resolved problem with z-index in modal, -* Textarea - resolved problem with rows attribute. - -**New Features:** - -* Time picker - added new (timeChanged) output that will be emitted on time change, -* WYSIWYG plugin - added new (valueChanged) output which emits current value as string. - -**Docs:** - -* Skins - added new instructions on how to add new data to skin. - -7.4.3 -New version brings a lot of bug fixes, and improved performance in Material Select Component. Check the every change in 7.4.3 below! - -**Fixes:** - -* Select - resolved problem with performance, -* Select - resolved problem with option height on dynamic option update, -* Multiselect with filter - resolved problem with options position, -* Dynamic modal - resolved problem with animation when mdb-select component is added to modal template, -* Datepicker - resolved problem with event emitting on input focus and added new [openOnFocus] input. Add [openOnFocus]="false" to prevent date picker from opening on input focus, -* Checkbox - (change) event won't be emitted before valueChanges on Edge anymore, -* Lightbox - magnifier icon will zoom picture properly, -* Dynamic Modal - animation on Edge will now won't blink, -* Dynamic Modal with Select - the animation won't blink on Edge anymore, -* Modals - resolved problem with focusing element from background while modal was opened, -* Auto Completer - resolved problem with scrolling down completer's dropdown with keyboard on Chrome, -* Auto Completer - fixed problem with wrong highlighted item after first click on arrow down key, -* Inputs with icon prefix - icon will now be highlighted after input focus, -* Carousel with Crossfade - rewritten the crossfade animation so it now looks like this one from MDB jQuery. - -**New features:** - -* Auto Completer - added functionality to pick highlighted (with keyboard) item as input's value, -* Auto Completer - added (select) output event which is fired after item selection, -* Icon - added classInside input which allow to add class to inside element of mdb-icon element, -* Badge - added classInside input which allow to add class to inside element of mdb-badge element, -* Navbars - added possibility to add custom element to mdb-navbar template (it will allow to add custom back button to iOS devices), -* Table Sort - added functionality to sort over nested object properties. - -**Docs:** - -* File Input - added example of upload options usage, -* File Input - added few new examples how to use options, -* Date Picker - added new [openOnFocus] input to input list, -* Carousel - updated input list in API section, -* Auto Completer - added example how to send API calls after (input) event, -* Charts - added Stacked Bar Chart example, -* Checkbox - Added Template-Driven forms example, -* Checkbox - Added Reactive Forms example, -* Table with Pagination - Modified Basic Example code, -* Table Editable - Modified Basic Example code (now it has bigger padding on table items), -* Plugins - Added Live Preview in MDB Angular Demo App, -* Navbar - Added 4 new navbar examples to the Basic Example section, -* Dropdowns - Added new example how to open dropdown from various elements, -* Sticky Content - renamed sticky-after input to stickyAfter. Previous code is still compatible, -* Steppers - Added new example how to add icons to the each steps with SCSS, -* Toast with opacity -Customized the opacity scss code, -* Sidenav - Added new example how to build sidenav with nested accordion links, -* DataTables - Customized the searchItems() function in every DataTables example. - -**Changes:** - -* Auto Completer - changed behavior of textNoResults input. From now, you have to set it to be visible. From default, this text is not visible. - -7.4.2 -In this version we resolved few bugs and resolved the problem with mdb-angular-free zip install on Windows. Check the 7.4.2 changes below. - -**Fixes:** - -* Collapse animation will now work properly on MacOS and Safari Browser, -* Lightbox won't throw the this.galleryDescription is undefined no more,, -* Resolved problem with search.toLowerCase() is undefined in Table Search, -* mdbWavesEffect won't hide button text no more while button is nested in Accordion on Chrome 71 & 72. - -**New features:** - -* Table Sort - added custom trim function which allows to sort table data while table data contains white signs. - -7.4.1 -In this version we resolved problems with a lot of bugs. Check the 7.4.1 changes below. - -**Fixes:** - -* mdb-auto-completer component won't throw errors when navigating with keyboard while dropdown is closed, -* mdbAutoCompleter - resolved problem with rendering completer's dropdown below the parent with overflow: hidden style - necessary to use the [appendToBody]="true" input, -* mdbAutoCompleter will now render along the top edge of the input when it won't fit below the input, -* mdbAutoCompleter - resolved problem with showing clear button in the first mdb-auto-completer while there were two or more of them, -* mdb-image-card - image card will now properly scale when his container is .col-6 or more, -* mdb-card - from now it's possible to overwrite the default shadow with one of .z-depth-* class, -* mdbInputDirective - renamed the mdbValidate input to the mdbValidation due to duplicated name conflict, -* mdbInput - from now, it's possible to bind dynamic value to the input placeholder via placeholder="{{text}}" string interpolation, -* mdbCheckboxChange - will now be exported, so problem with unable to import this class from the ng-uikit-pro-standard will gone, -* Sidenav & Nested Accordion - from now only the active mdb-accordion-item element will be highlighted, -* Navbar - added possibility to use the hide() method to collapse navbar after click on mobile view, -* Select - added mechanism to prevent line wrap in mdb-select item when it is wider than select dropdown, -* Lightbox - image size will now be little bit larger, -* Toast - the positioning classes .md-toast-top-center .md-toast-bottom-center will now work as they should, -* Checkbox - resolved problem with validation while using onSubmit or onBlur properties, -* Textarea validation - validation message won't be overlapped with bottom border of the element on Firefox, -* Accordion - resolved problem with not working [multiple]="false" on mdb-accordion generated with *ngFor loop, -* Thumbnail Carousel - thumbnails will now be properly highlighted while active. - -**New Features:** - -* Inputs - new Material 2.0 inputs (outlined), -* Added new helper classes for opacity: Incremented by 10, .opacity-0, .opacity-10 until 100. -* mdbAutoCompleter - added new [appendToBody]="true" input which will determine if completer's dropdown should be appended to the body element or it's parent. Useful while completer's parent got overflow: hidden style. -* Date Picker - added new closeBtnClicked() method which allow to hide Date Picker programmatically. - -**Documentation:** - -* Masonry - Added documentation for Masonry Layout, -* Autocompleter - added example how to use [appendToBody]="true" input, -* Date Picker - added example how to show / hide date picker with typescript method, -* Vertical Tabs - added new necessary class .list-group for the Vertical Tabs in list group, -* Notifications - changed the iconClasses in API section, -* Notifications - resolved problem with not working alerts, -* Pattern Validation - updated the allow only letters regex pattern, -* Navbar - corrected the method names, -* Multi-Item carousel gallery - added new most recent code. - - -7.4.0 -In this version we resolved problems with our modules and tree shaking. -It is now possible to import only those modules you need in your application, which will significantly reduce bundle size. We also updated Bootstrap to the newest version (4.2.1). - -*** Bootstrap version update: *** - - * Added new spinner loading component, - * Added new .font-weight-lighter and .font-weight-bolder utitlities, - * Added new .text-decoration-none class, - * Added new negative margin utility classes (e.g, .mb-n3), - * Changed auto columns (e.g, .col-auto) from max-width: none to max-width: 100% to prevent content from causing a column to overflow the parent. - * Added md prefix to scss code of our alert components to avoid overwritting bootstrap classes - -*** Fixes: *** - - * Popover / Tooltip - resolved problems with displaying in Firefox/Safari browsers on Mac OS, - * Multiselect - changed some methods to provide better support for older browsers, - * Carousel - resolved problem with OnPush change detection, - * Validation - resolved problem with positioning messages under inputs with icons, - * Select with filter - resolved problem with ExpressionChangedAfterItHasBeenCheckedError, - * Select with icons - resolved problem with padding, - * Steppers - updated styles to resolve problem with Font Awesome 5 Pro icons, - * Tooltip - resolved problem with placement. - -*** Documentation: *** - - * Multi item carousel - updated example code. From now only one item should be displayed on smaller screens, - * Switch - changed syntax of default component, - * Borders - added new border-white and rounded-pill examples, - * Overflow - new documentation page in utilities section - * Icons list - updated example code of the icons - * Table editable - changed example code to resolve problems with editing on Edge 15. - -*** New features: *** - - * Select - added new [optionHeight] input which allow to customize options height, - * Select - you can now use [appendToBody]="true" input to avoid problems with displaying select dropdown in container with overflow: hidden, - * Multiselect - added new selectAllLabel input which allow to customize text of 'Select all' option, - * Tabs - added possibility to add action buttons to tabset header. - -7.3.0 -In this release we have migrated our entire library to Font Awesome 5. Changing from 4 to 5 is not backward compatible, so we recommend updating the application to Font Awesome 5. - -*** Fixes: *** - - * Datepicker - Solved problems with date formatting, when the component is in *ngIf, - * Tabs - It is no longer necessary to double-click to activate the tab during OnPush, - * Tabs - Disabled tab will not be marked as active, - * Sidenav & Custom Skin - Solved problem with text color overwriting in Sidenav in combination with custom skin, - * Select & Custom Skin - Solved problem with overwriting text color in Select with Custom Skin, - * Table Sort - Sorting tables will not display an error .toLowerCase() is undefined, - * Tooltips & Admin Template Pro - Fixed tooltips in Admin Template Pro, - * API - Removed deprecated API - HttpModule, eflectiveInjector, NavigationExtras.preserveQueryParams, - * Lightbox & Firebase - Solved problem with Lightbox and Firebase, - * Navbar - Navbar in mobile version will not flash on the screen, - * Textarea - Autoresizing Textarea will expand when text is added before rendering a view, - * Auto Completer - Resolved problem with .trim() is undefined in mdb-option component. - -*** Documentation: *** - - * Checkbox - Added documentation for Template Driven & Reactive Forms Checkbox, - * PWA Tutorial - Published new version of Angular PWA tutorial with Firebase and IndexedDB! - -*** New Features: *** - - * Font Awesome 5 Icons - compatibility with Font Awesome 5, - * Admin Template Pro - Added Sections page to Admin Template Pro, - * Steppers - added new Steppers component, - * Dynamic Modals - Added a new way to pass data to dynamic modals, - * Scrollspy - Added event activeLinkChange to Scrollspy. - -7.2.0 -In this release we added new mdb-auto-completer component, which is improved version of mdb-autocomplete. - -We encourage you to use mdb-auto-completer, but mdb-autocomplete will still be available until version 8 of MDB Angular. - -*** Fixes: *** - - * Select - resolved problems with updating values dynamically, - * Dropdown - resolved problem with content position when opened in navbar, - * Modal - remove unnecessary icon styles, - * Autocomplete - resolved problem with angular/http. - -*** Documentation: *** - - * Table with panel - resolved problem with icon names, - * Navbars - removed unnecessary margin from search element, - * Validation - added example for file upload validation. - -7.1.1 -*** Fixes: *** - - * Select - resolved problems with OnPush change detection, - * Select - changed styles and animation to add more material look, - * Multiselect - resolved problem with disabled option styles, - * Date Picker - resolved problem with dynamic value updating, - * Date Picker - resolved problem with opening using built-in method, - * Table Pagination - resolved two minor problems - -*** New features: *** - - * Dynamic Modal - added new scroll property to modal options object. It can be used to add overflow-y: auto to modal with long content, - * Multiselect - added select all option. This option is enabled by default and can be turned off with [enableSelectAll]="false" input, - * Multiselect - added button for clearing selected options, - * Select and Multiselect - added new [highlightFirst] input which can be used to turn off highlight added by default to the first option. - -*** Documentation: *** - - * Scrollspy - added new example for MDB scrollspy on full page, - * Accordion - added new example, - * File Input - added new example, - * Select - updated code in examples - * Multiselect - updated code in examples - * Added description how to change default Roboto font, - * Intro Sections - resolved few minor problems, - * Accordion - added toggle() method description. - -7.1.0 -We have rewritten the entire validation of forms. New validation is faster, lighter, better! Read about it below. - -The mdbInputDirective directive has been divided into mdbInput and mdbValidate directives. Validation messages can ben now added with mdb-error and mdb-success components. More information on the usage can be found in validation documentation. - -New validation offers more flexibility and solves problems with updateOn: 'blur' and updateOn: 'submit' options. - -Those changes are backwards compatible. Directive mdbInputDirective will be still available for next few versions, but we recommend to use the new directives. - -*** Fixes: *** - - * Data Tables - pagination and search will now work without problems against data coming from Remote API, - * Carousel with thumbnails - The problem with a larger thumbnails strip width than the slide photo in case of more slides has been solved, - * Material Select - Filtering results with filterEnabled will now returns each line that contains the search character, - * Date Picker - Significantly improved component performance. Solved an issue where Date Picker was rendered before it was needed, - * Tooltip - A tooltip that does not fit in a free space along the top edge of the screen will be placed along the bottom edge, and vice versa, - * Tooltip - Added input customHeight which supports the above effect, but in case the height of the tooltip has been changed, - * Tabs - The problem in which static tabs were rendered before those with *ngIf was solved. It is required to use a new input tabOrder. Read more about this here, - * Scroll Spy - Fixed problem with handling nested links. - * Modals - Solved problem with built-in modal service. Now it is possible to call modals from this service. - -*** Documentation: *** - - * Data Tables - added new example how to use Data Tables with data from remote API, - * Tabs - added new example how to use both static & dynamic tabs, - * Tabs - added new tabOrder input, - * Tooltips - added new example how to modify the Tooltip height, - * Tooltips - added new customHeight input. - * Modals - added examples how to use built-in modal's service. - -7.0.0 -Angular 7 version is now supported in MDB Angular. - -*** Fixes: *** - - * Date picker - resolved problem with disabled state added in reactive forms - * Date picker - resolved problem with typing the date in the input field - * Date picker - changed z-index styles to fix the problem that caused other elements to overlap the component - * Time picker - changed z-index styles to fix the problem that caused other elements to overlap the component - * Accordion - resolved problem with 'Cannot read property expandAnimationState of undefined' - * MdbInputDirective - resolved problem with displaying error and success messages when errorMessage and successMessage inputs values are empty strings - * File Upload - resolved problem with 'Cannot read property unsubscribe of undefined' - -*** New features: *** - - * Date picker - added new [ tabIndex ] input that allow to set tabindex of the date picker input field - * Time picker - added new [ tabIndex ] input that allow to set tabindex of the time picker input field - * Tabs - added new [ disableWaves ] input that allow to disable waves effect on the tabs buttons - -6.3.0 -The key "defaultProject" has been added to angular.json files of all packages containing the demo application. Thanks to that you can restart the application using the ng serve command. - -In this update, support for the ng add function has been added to the MDB Angular Free product in the npmjs repository - angular-bootstrap-md. Thanks to that it is possible to install MDB Angular Free with one command ng add angular-bootstrap-md. - -*** Fixes: *** - - * Select - resolved problem with placeholder when value is null, - * Select - resolved problems with label styling. For the label to work correctly, we encourage you to add it via [ label ] input instead of HTML
diff --git a/projects/angular-bootstrap-md/src/lib/free/checkbox/checkbox.component.ts b/projects/angular-bootstrap-md/src/lib/free/checkbox/checkbox.component.ts deleted file mode 100644 index 2c1954a0..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/checkbox/checkbox.component.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { - Component, - EventEmitter, - forwardRef, - HostListener, - Input, - OnChanges, - OnInit, - Output, - SimpleChanges, - ViewChild, - ViewEncapsulation, - ChangeDetectionStrategy, - ChangeDetectorRef, -} from '@angular/core'; -import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { Subject, timer } from 'rxjs'; -import { take } from 'rxjs/operators'; - -export const CHECKBOX_VALUE_ACCESSOR: any = { - provide: NG_VALUE_ACCESSOR, - // tslint:disable-next-line: no-use-before-declare - useExisting: forwardRef(() => CheckboxComponent), - multi: true, -}; - -let defaultIdNumber = 0; - -export class MdbCheckboxChange { - element: CheckboxComponent; - checked: boolean; -} - -@Component({ - selector: 'mdb-checkbox', - templateUrl: './checkbox.component.html', - styleUrls: ['checkbox-module.scss'], - encapsulation: ViewEncapsulation.None, - providers: [CHECKBOX_VALUE_ACCESSOR], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class CheckboxComponent implements OnInit, OnChanges { - @ViewChild('input', { static: true }) inputEl: any; - - private defaultId = `mdb-checkbox-${++defaultIdNumber}`; - - @Input() class: string; - @Input() id: string = this.defaultId; - @Input() required: boolean; - @Input() name: string; - @Input() value: string; - @Input() checked = false; - @Input() filledIn = false; - @Input() indeterminate = false; - @Input() disabled: boolean; - @Input() rounded = false; - @Input() checkboxPosition = 'left'; - @Input() default = false; - @Input() inline = false; - @Input() tabIndex: number; - - @Output() change: EventEmitter = new EventEmitter(); - - private checkboxClicked = new Subject(); - - constructor(private _cdRef: ChangeDetectorRef) {} - - @HostListener('click', ['$event']) - onLabelClick(event: any) { - event.stopPropagation(); - this.checkboxClicked.next(true); - } - - @HostListener('document:click') - onDocumentClick() { - this.checkboxClicked.next(false); - } - - ngOnInit() { - if (this.indeterminate && !this.filledIn && !this.rounded) { - this.inputEl.indeterminate = true; - } - } - - ngOnChanges(changes: SimpleChanges) { - if (changes.hasOwnProperty('checked')) { - this.checked = changes.checked.currentValue; - } - } - - get changeEvent() { - const newChangeEvent = new MdbCheckboxChange(); - newChangeEvent.element = this; - newChangeEvent.checked = this.checked; - return newChangeEvent; - } - - toggle() { - if (this.disabled) { - return; - } - this.checked = !this.checked; - this.indeterminate = false; - this.onChange(this.checked); - - this._cdRef.markForCheck(); - } - - onCheckboxClick(event: any) { - event.stopPropagation(); - this.toggle(); - } - - onCheckboxChange(event: any) { - event.stopPropagation(); - timer(0).subscribe(() => this.change.emit(this.changeEvent)); - } - - onBlur() { - this.checkboxClicked.pipe(take(1)).subscribe(val => { - if (!val) { - this.onTouched(); - } - }); - } - - // Control Value Accessor Methods - onChange = (_: any) => {}; - onTouched = () => {}; - - writeValue(value: any) { - this.value = value; - this.checked = !!value; - this._cdRef.markForCheck(); - } - - registerOnChange(fn: (_: any) => void) { - this.onChange = fn; - } - - registerOnTouched(fn: () => void) { - this.onTouched = fn; - } - - setDisabledState(isDisabled: boolean) { - this.disabled = isDisabled; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/checkbox/checkbox.module.ts b/projects/angular-bootstrap-md/src/lib/free/checkbox/checkbox.module.ts deleted file mode 100644 index 17f3930f..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/checkbox/checkbox.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; - -import { CheckboxComponent } from './checkbox.component'; - -export { CHECKBOX_VALUE_ACCESSOR, CheckboxComponent } from './checkbox.component'; - -@NgModule({ - declarations: [CheckboxComponent], - exports: [CheckboxComponent], - imports: [CommonModule, FormsModule], -}) -export class CheckboxModule {} diff --git a/projects/angular-bootstrap-md/src/lib/free/checkbox/index.ts b/projects/angular-bootstrap-md/src/lib/free/checkbox/index.ts deleted file mode 100644 index 7af96b16..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/checkbox/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { CHECKBOX_VALUE_ACCESSOR, CheckboxComponent, MdbCheckboxChange } from './checkbox.component'; -export { CheckboxModule } from './checkbox.module'; diff --git a/projects/angular-bootstrap-md/src/lib/free/collapse/collapse.component.ts b/projects/angular-bootstrap-md/src/lib/free/collapse/collapse.component.ts deleted file mode 100755 index 9f481bc0..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/collapse/collapse.component.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { - Component, - OnInit, - HostBinding, - Input, - Output, - EventEmitter, - HostListener, - ContentChildren, - QueryList, - ChangeDetectionStrategy, - ChangeDetectorRef, -} from '@angular/core'; -import { state, style, trigger, transition, animate } from '@angular/animations'; -import { FixedButtonCaptionDirective } from '../buttons/fixed-caption.directive'; - -@Component({ - // tslint:disable-next-line:component-selector - selector: '[mdbCollapse]', - exportAs: 'bs-collapse', - template: '', - animations: [ - trigger('expandBody', [ - state('collapsed', style({ height: '0px' })), - state('expanded', style({ height: '*' })), - transition('expanded <=> collapsed', animate('500ms ease')), - ]), - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class CollapseComponent implements OnInit { - @ContentChildren(FixedButtonCaptionDirective) captions: QueryList; - @Input() isCollapsed = true; - - @Output() showBsCollapse: EventEmitter = new EventEmitter(); - @Output() shownBsCollapse: EventEmitter = new EventEmitter(); - @Output() hideBsCollapse: EventEmitter = new EventEmitter(); - @Output() hiddenBsCollapse: EventEmitter = new EventEmitter(); - @Output() collapsed: EventEmitter = new EventEmitter(); - @Output() expanded: EventEmitter = new EventEmitter(); - - constructor(private _cdRef: ChangeDetectorRef) {} - - @HostBinding('@expandBody') expandAnimationState: string; - @HostBinding('style.overflow') overflow = 'hidden'; - - @HostListener('@expandBody.done', ['$event']) - onExpandBodyDone(event: any) { - setTimeout(() => { - if (event.toState === 'expanded') { - this.shownBsCollapse.emit(this); - this.expanded.emit(this); - this.overflow = 'visible'; - this.showCaptions(); - } else { - this.hiddenBsCollapse.emit(this); - this.collapsed.emit(this); - } - }, 0); - } - - showCaptions() { - this.captions.forEach((caption: FixedButtonCaptionDirective) => caption.showCaption()); - } - - toggle() { - this.isCollapsed ? this.show() : this.hide(); - } - - show() { - this.expandAnimationState = 'expanded'; - this.isCollapsed = false; - - this.showBsCollapse.emit(this); - this._cdRef.markForCheck(); - } - - hide() { - this.overflow = 'hidden'; - this.expandAnimationState = 'collapsed'; - this.isCollapsed = true; - - this.hideBsCollapse.emit(this); - this._cdRef.markForCheck(); - } - - initializeCollapseState() { - this.isCollapsed ? this.hide() : this.show(); - } - - ngOnInit() { - this.initializeCollapseState(); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/collapse/collapse.module.ts b/projects/angular-bootstrap-md/src/lib/free/collapse/collapse.module.ts deleted file mode 100755 index 9d9e5fdf..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/collapse/collapse.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NgModule, ModuleWithProviders } from '@angular/core'; -import { CollapseComponent } from './collapse.component'; - -@NgModule({ - declarations: [CollapseComponent], - exports: [CollapseComponent] -}) -export class CollapseModule { - public static forRoot(): ModuleWithProviders { - return {ngModule: CollapseModule, providers: []}; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/collapse/index.ts b/projects/angular-bootstrap-md/src/lib/free/collapse/index.ts deleted file mode 100755 index 13547a4a..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/collapse/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { CollapseComponent } from './collapse.component'; -export { CollapseModule } from './collapse.module'; diff --git a/projects/angular-bootstrap-md/src/lib/free/dropdown/_dropdown.scss b/projects/angular-bootstrap-md/src/lib/free/dropdown/_dropdown.scss deleted file mode 100644 index 27696e44..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/dropdown/_dropdown.scss +++ /dev/null @@ -1,103 +0,0 @@ -// Dropdowns - -.dropdown-menu { - .dropdown-item { - &:active { - background-color: $grey-darken-1; - } - } -} - -// Angular styles -.show { - // Show the menu - > .dropdown-menu { - display: block; - } - - // Remove the outline when :focus is triggered - > a { - outline: 0; - } -} - -.dropdown-menu { - display: none; - position: absolute; - - margin-top: 5px; - left: 0px; - will-change: transform; -} -.various-dropdown { - transform: translate3d(0px, 21px, 0px) !important; -} -.a-various-dropdown { - transform: translate3d(0px, 29px, 0px) !important; -} -.medium-dropdown { - transform: translate3d(0px, 36px, 0px) !important; -} -.small-dropdown { - transform: translate3d(5px, 34px, 0px) !important; -} -.large-dropdown { - transform: translate3d(5px, 57px, 0px) !important; -} -.btn-group { - > .dropdown-menu { - transform: translate3d(0px, 43px, 0px); - } -} -//dropup -.dropup { - // Different positioning for bottom up menu - > .dropdown-menu { - display: none; - // // position: absolute; - transform: translate3d(117px, 0px, 0px) !important; - // // top: 0px; - // // left: 0px; - will-change: transform; - } -} -//dropup animation -.dropup.show { - .dropdown-menu { - display: block; - opacity: 0; - } - - .fadeInDropdown { - opacity: 1; - } -} -.dropup-material.show { - .dropdown-menu { - transition: 0.55s; - } -} - -//material dropdown - -.dropdown-menu { - display: none; - position: absolute; - transform: translate3d(6px, 49px, 0px); - top: 0px; - left: 0px; - will-change: transform; -} - -//material dropdown animation -.dropdown.show { - .dropdown-menu { - display: block; - opacity: 0; - transition: 0.55s; - } - - .fadeInDropdown { - opacity: 1; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown-container.component.html b/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown-container.component.html deleted file mode 100755 index 8841ad9f..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown-container.component.html +++ /dev/null @@ -1,6 +0,0 @@ -
- -
\ No newline at end of file diff --git a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown-container.component.ts b/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown-container.component.ts deleted file mode 100755 index 2c19ab60..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown-container.component.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { ChangeDetectionStrategy, Component, OnDestroy, HostBinding } from '@angular/core'; -import { BsDropdownState } from './dropdown.state'; - -@Component({ - selector: 'mdb-dropdown-container', - changeDetection: ChangeDetectionStrategy.OnPush, - template: ` -
- -
- `, -}) -export class BsDropdownContainerComponent implements OnDestroy { - isOpen = false; - - @HostBinding('style.display') display = 'block'; - @HostBinding('style.position') position = 'absolute'; - - get direction(): 'down' | 'up' { - return this._state.direction; - } - - private _subscription: any; - - constructor(private _state: BsDropdownState) { - this._subscription = _state.isOpenChange.subscribe((value: boolean) => { - this.isOpen = value; - }); - } - - ngOnDestroy(): void { - this._subscription.unsubscribe(); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown-menu.directive.ts b/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown-menu.directive.ts deleted file mode 100755 index b8af437c..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown-menu.directive.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Directive, TemplateRef, ViewContainerRef } from '@angular/core'; -import { BsDropdownState } from './dropdown.state'; - -@Directive({ - selector: '[mdbDropdownMenu],[dropdownMenu]', - exportAs: 'bs-dropdown-menu' -}) -export class BsDropdownMenuDirective { - constructor(_state: BsDropdownState, - _viewContainer: ViewContainerRef, - _templateRef: TemplateRef) { - _state.resolveDropdownMenu({ - templateRef: _templateRef, - viewContainer: _viewContainer - }); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown-module.scss b/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown-module.scss deleted file mode 100644 index 2c8427e5..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown-module.scss +++ /dev/null @@ -1,7 +0,0 @@ -@import '../../assets/scss/core/mixins'; -@import '../../assets/scss/core/colors'; -@import '../../assets/scss/core/variables'; - - -@import 'dropdown'; - diff --git a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown-toggle.directive.ts b/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown-toggle.directive.ts deleted file mode 100755 index 9471a6d4..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown-toggle.directive.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Directive, ElementRef, HostBinding, HostListener, OnDestroy } from '@angular/core'; -import { Subscription } from 'rxjs'; - -import { BsDropdownState } from './dropdown.state'; - -@Directive({ - selector: '[mdbDropdownToggle],[dropdownToggle]', - exportAs: 'bs-dropdown-toggle', -}) -export class BsDropdownToggleDirective implements OnDestroy { - private _subscriptions: Subscription[] = []; - - @HostBinding('attr.aria-haspopup') ariaHaspopup = true; - @HostBinding('attr.disabled') isDisabled: boolean | any = null; - @HostBinding('attr.aria-expanded') isOpen: boolean; - - @HostListener('click') - onClick(): void { - if (this.isDisabled) { - return; - } - this._state.toggleClick.emit(); - } - - @HostListener('document:click', ['$event']) - onDocumentClick(event: any): void { - if ( - this._state.autoClose && - event.button !== 2 && - !this._element.nativeElement.contains(event.target) - ) { - this._state.toggleClick.emit(false); - } - } - - @HostListener('keyup.esc') - onEsc(): void { - if (this._state.autoClose) { - this._state.toggleClick.emit(false); - } - } - - constructor(private _state: BsDropdownState, private _element: ElementRef) { - // sync is open value with state - this._subscriptions.push( - this._state.isOpenChange.subscribe((value: boolean) => (this.isOpen = value)) - ); - // populate disabled state - this._subscriptions.push( - this._state.isDisabledChange.subscribe( - (value: boolean | any) => (this.isDisabled = value || null) - ) - ); - } - - ngOnDestroy(): void { - for (const sub of this._subscriptions) { - sub.unsubscribe(); - } - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown.config.ts b/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown.config.ts deleted file mode 100755 index 51d4988b..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable } from '@angular/core'; - -/** Default dropdown configuration */ -@Injectable() -export class BsDropdownConfig { - /** default dropdown auto closing behavior */ - autoClose = true; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown.directive.ts b/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown.directive.ts deleted file mode 100755 index b883a11b..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown.directive.ts +++ /dev/null @@ -1,408 +0,0 @@ -import { - Component, - ElementRef, - EmbeddedViewRef, - EventEmitter, - HostBinding, - Input, - OnDestroy, - OnInit, - Output, - Renderer2, - ViewContainerRef, - ViewEncapsulation, - ChangeDetectorRef, -} from '@angular/core'; -import { Subscription, Subject } from 'rxjs'; - -import { ComponentLoader } from '../utils/component-loader/component-loader.class'; -import { ComponentLoaderFactory } from '../utils/component-loader/component-loader.factory'; -import { BsDropdownConfig } from './dropdown.config'; -import { BsDropdownContainerComponent } from './dropdown-container.component'; -import { BsDropdownState } from './dropdown.state'; -import { BsComponentRef } from '../utils/component-loader/bs-component-ref.class'; -import { BsDropdownMenuDirective } from './dropdown-menu.directive'; -import { isBs3 } from '../utils/ng2-bootstrap-config'; -import { takeUntil } from 'rxjs/operators'; - -@Component({ - // tslint:disable-next-line:component-selector - selector: '[mdbDropdown],[dropdown]', - exportAs: 'bs-dropdown', - template: '', - styleUrls: ['dropdown-module.scss'], - encapsulation: ViewEncapsulation.None, - providers: [BsDropdownState], -}) -// tslint:disable-next-line:component-class-suffix -export class BsDropdownDirective implements OnInit, OnDestroy { - /** - * Placement of a popover. Accepts: "top", "bottom", "left", "right" - */ - @Input() placement: string; - /** - * Specifies events that should trigger. Supports a space separated list of - * event names. - */ - @Input() triggers: string; - /** - * A selector specifying the element the popover should be appended to. - * Currently only supports "body". - */ - @Input() container: string; - @Input() dropup: boolean; - @Input() dropupDefault = false; - @Input() dynamicPosition = false; - /** - * This attribute indicates that the dropdown should be opened upwards - */ - @HostBinding('class.dropup') public get isDropup() { - if (this.dropup) { - this._isDropupDefault = false; - return this.dropup; - } else if (this.dropupDefault) { - this._isDropupDefault = true; - return this.dropupDefault; - } else if (this.dropupDefault && this.dropup) { - this._isDropupDefault = false; - return this.dropup; - } - } - - /** - * Indicates that dropdown will be closed on item or document click, - * and after pressing ESC - */ - @Input() set autoClose(value: boolean) { - if (typeof value === 'boolean') { - this._state.autoClose = value; - } - } - - get autoClose(): boolean { - return this._state.autoClose; - } - - /** - * Disables dropdown toggle and hides dropdown menu if opened - */ - @Input() set isDisabled(value: boolean) { - this._isDisabled = value; - this._state.isDisabledChange.emit(value); - if (value) { - this.hide(); - } - } - - get isDisabled(): boolean { - return this._isDisabled; - } - - /** - * Returns whether or not the popover is currently being shown - */ - @HostBinding('class.open') - @HostBinding('class.show') - @Input() - get isOpen(): boolean { - if (this._showInline) { - return this._isInlineOpen; - } - return this._dropdown.isShown; - } - - set isOpen(value: boolean) { - if (value) { - this.show(); - } else { - this.hide(); - } - } - - /** - * Emits an event when isOpen change - */ - @Output() isOpenChange: EventEmitter; - - /** - * Emits an event when the popover is shown - */ - // tslint:disable-next-line:no-output-on-prefix - @Output() onShown: EventEmitter; - @Output() shown: EventEmitter; - - /** - * Emits an event when the popover is hidden - */ - // tslint:disable-next-line:no-output-on-prefix - @Output() onHidden: EventEmitter; - @Output() hidden: EventEmitter; - - private _destroy$: Subject = new Subject(); - - get isBs4(): boolean { - return !isBs3(); - } - - _isInlineOpen = false; - _showInline: boolean; - _inlinedMenu: EmbeddedViewRef; - - _isDisabled: boolean; - _dropdown: ComponentLoader; - _dropup: boolean; - _subscriptions: Subscription[] = []; - _isInited = false; - _isDropupDefault: boolean; - - constructor( - private _elementRef: ElementRef, - private _renderer: Renderer2, - private _viewContainerRef: ViewContainerRef, - private _cis: ComponentLoaderFactory, - private _config: BsDropdownConfig, - private _state: BsDropdownState, - private cdRef: ChangeDetectorRef - ) { - // create dropdown component loader - this._dropdown = this._cis - .createLoader( - this._elementRef, - this._viewContainerRef, - this._renderer - ) - .provide({ provide: BsDropdownState, useValue: this._state }); - - this.onShown = this._dropdown.onShown; - this.shown = this._dropdown.shown; - this.onHidden = this._dropdown.onHidden; - this.hidden = this._dropdown.hidden; - this.isOpenChange = this._state.isOpenChange; - - // set initial dropdown state from config - this._state.autoClose = this._config.autoClose; - } - - ngOnInit(): void { - // fix: seems there are an issue with `routerLinkActive` - // which result in duplicated call ngOnInit without call to ngOnDestroy - // read more: https://github.com/valor-software/ngx-bootstrap/issues/1885 - if (this._isInited) { - return; - } - this._isInited = true; - - this._showInline = !this.container; - - this._dropup = this.dropup; - - // attach DOM listeners - this._dropdown.listen({ - triggers: this.triggers, - show: () => this.show(), - }); - - // toggle visibility on toggle element click - this._state.toggleClick - .pipe(takeUntil(this._destroy$)) - .subscribe((value: boolean) => this.toggle(value)); - - // hide dropdown if set disabled while opened - this._state.isDisabledChange.pipe(takeUntil(this._destroy$)).subscribe((element: any) => { - if (element === true) { - this.hide(); - } - }); - - // attach dropdown menu inside of dropdown - if (this._showInline) { - this._state.dropdownMenu.then((dropdownMenu: BsComponentRef) => { - this._inlinedMenu = dropdownMenu.viewContainer.createEmbeddedView(dropdownMenu.templateRef); - }); - } - - this._state.isOpenChange.pipe(takeUntil(this._destroy$)).subscribe(() => { - setTimeout(() => { - const dropdownContainer = this._elementRef.nativeElement.querySelector('.dropdown-menu'); - const left = dropdownContainer.getBoundingClientRect().left; - - if ( - dropdownContainer.classList.contains('dropdown-menu-right') && - left <= dropdownContainer.clientWidth - ) { - if (left < 0) { - this._renderer.setStyle(dropdownContainer, 'right', left + 'px'); - } else { - this._renderer.setStyle(dropdownContainer, 'right', '0'); - } - } - }, 0); - }); - } - - /** - * Opens an element’s popover. This is considered a “manual” triggering of - * the popover. - */ - show(): void { - if (this.isOpen || this.isDisabled) { - return; - } - // material and dropup dropdown animation - - const button = this._elementRef.nativeElement.children[0]; - const container = this._elementRef.nativeElement.querySelector('.dropdown-menu'); - - if ( - !container.parentNode.classList.contains('btn-group') && - !container.parentNode.classList.contains('dropdown') && - !this._isDropupDefault - ) { - container.parentNode.classList.add('dropdown'); - } - if (this.dropup && !this._isDropupDefault) { - container.parentNode.classList.add('dropup-material'); - } - if (button.tagName !== 'BUTTON') { - if (button.tagName === 'A') { - container.classList.add('a-various-dropdown'); - } else { - container.classList.add('various-dropdown'); - } - } else { - if (button.classList.contains('btn-sm')) { - container.classList.add('small-dropdown'); - } - if (button.classList.contains('btn-md')) { - container.classList.add('medium-dropdown'); - } - if (button.classList.contains('btn-lg')) { - container.classList.add('large-dropdown'); - } - } - setTimeout(() => { - container.classList.add('fadeInDropdown'); - - if (this.dynamicPosition) { - const bounding = container.getBoundingClientRect(); - const out: { top: boolean; bottom: boolean } = { - top: bounding.top < 0, - bottom: bounding.bottom > (window.innerHeight || document.documentElement.clientHeight), - }; - - if (this.dropup && out.top) { - this.dropup = false; - } else if (!this.dropup && out.bottom) { - this.dropup = true; - } - } - }, 0); - - if (this._showInline) { - this._isInlineOpen = true; - if ( - container.parentNode.classList.contains('dropdown') || - container.parentNode.classList.contains('dropup-material') - ) { - setTimeout(() => { - this.onShown.emit(true); - this.shown.emit(true); - }, 560); - } else { - setTimeout(() => { - this.onShown.emit(true); - this.shown.emit(true); - }, 0); - } - this._state.isOpenChange.emit(true); - - return; - } - this._state.dropdownMenu.then(dropdownMenu => { - // check direction in which dropdown should be opened - const _dropup = this.dropup === true || this.dropupDefault === true; - - this._state.direction = _dropup ? 'up' : 'down'; - const _placement = this.placement || (_dropup ? 'top left' : 'bottom left'); - - // show dropdown - this._dropdown - .attach(BsDropdownContainerComponent) - .to(this.container) - .position({ attachment: _placement }) - .show({ - content: dropdownMenu.templateRef, - placement: _placement, - }); - - this._state.isOpenChange.emit(true); - }); - } - - /** - * Closes an element’s popover. This is considered a “manual” triggering of - * the popover. - */ - hide(): void { - if (!this.isOpen) { - return; - } - - if (this.dropup !== this._dropup) { - this.dropup = this._dropup; - } - - const container = this._elementRef.nativeElement.querySelector('.dropdown-menu'); - - container.classList.remove('fadeInDropdown'); - if ( - container.parentNode.classList.contains('dropdown') || - container.parentNode.classList.contains('dropup-material') - ) { - setTimeout(() => { - if (this._showInline) { - this._isInlineOpen = false; - this.onHidden.emit(true); - this.hidden.emit(true); - this.cdRef.markForCheck(); - } else { - this._dropdown.hide(); - } - - this._state.isOpenChange.emit(false); - }, 560); - } else { - setTimeout(() => { - if (this._showInline) { - this._isInlineOpen = false; - this.onHidden.emit(true); - this.hidden.emit(true); - this.cdRef.markForCheck(); - } else { - this._dropdown.hide(); - } - - this._state.isOpenChange.emit(false); - }, 0); - } - } - - /** - * Toggles an element’s popover. This is considered a “manual” triggering of - * the popover. - */ - toggle(value?: boolean): void { - if (this.isOpen || value === false) { - return this.hide(); - } - - return this.show(); - } - - ngOnDestroy(): void { - // clean up subscriptions and destroy dropdown - this._destroy$.next(); - this._destroy$.complete(); - this._dropdown.dispose(); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown.module.ts b/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown.module.ts deleted file mode 100755 index 65fb8999..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown.module.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ModuleWithProviders, NgModule } from '@angular/core'; -import { ComponentLoaderFactory } from '../utils/component-loader/component-loader.factory'; - -import { PositioningService } from '../utils/positioning/positioning.service'; -import { BsDropdownContainerComponent } from './dropdown-container.component'; -import { BsDropdownMenuDirective } from './dropdown-menu.directive'; -import { BsDropdownToggleDirective } from './dropdown-toggle.directive'; -import { BsDropdownConfig } from './dropdown.config'; - -import { BsDropdownDirective } from './dropdown.directive'; -import { BsDropdownState } from './dropdown.state'; - -@NgModule({ - declarations: [ - BsDropdownMenuDirective, - BsDropdownToggleDirective, - BsDropdownContainerComponent, - BsDropdownDirective - ], - exports: [ - BsDropdownMenuDirective, - BsDropdownToggleDirective, - BsDropdownDirective - ], - entryComponents: [BsDropdownContainerComponent] -}) -export class DropdownModule { - public static forRoot(config?: any): ModuleWithProviders { - return { - ngModule: DropdownModule, providers: [ - ComponentLoaderFactory, - PositioningService, - BsDropdownState, - {provide: BsDropdownConfig, useValue: config ? config : {autoClose: true}} - ] - }; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown.state.ts b/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown.state.ts deleted file mode 100755 index 403dc4e1..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/dropdown/dropdown.state.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { EventEmitter, Injectable } from '@angular/core'; -import { BsComponentRef } from '../utils/component-loader/bs-component-ref.class'; - -@Injectable() -export class BsDropdownState { - direction: 'down' | 'up' = 'down'; - autoClose: boolean; - isOpenChange = new EventEmitter(); - isDisabledChange = new EventEmitter(); - toggleClick = new EventEmitter(); - - /** - * Content to be displayed as popover. - */ - dropdownMenu: Promise>; - resolveDropdownMenu: (componentRef: BsComponentRef) => void; - - constructor() { - this.dropdownMenu = new Promise((resolve) => { - this.resolveDropdownMenu = resolve; - }); - } - } diff --git a/projects/angular-bootstrap-md/src/lib/free/dropdown/index.ts b/projects/angular-bootstrap-md/src/lib/free/dropdown/index.ts deleted file mode 100755 index 64b15674..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/dropdown/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { BsDropdownDirective } from './dropdown.directive'; -export { BsDropdownMenuDirective } from './dropdown-menu.directive'; -export { BsDropdownToggleDirective } from './dropdown-toggle.directive'; -export { BsDropdownContainerComponent } from './dropdown-container.component'; -export { BsDropdownState } from './dropdown.state'; -export { BsDropdownConfig } from './dropdown.config'; -export { DropdownModule } from './dropdown.module'; diff --git a/projects/angular-bootstrap-md/src/lib/free/icons/directives/fab.directive.ts b/projects/angular-bootstrap-md/src/lib/free/icons/directives/fab.directive.ts deleted file mode 100644 index 9f39fc14..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/icons/directives/fab.directive.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Directive, ElementRef, Renderer2 } from '@angular/core'; - -// tslint:disable-next-line:directive-selector -@Directive({ selector: '[fab], [brands]' }) -export class FabDirective { - constructor(private _el: ElementRef, private _r: Renderer2) { - this._r.addClass(this._el.nativeElement, 'fab'); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/icons/directives/fad.directive.ts b/projects/angular-bootstrap-md/src/lib/free/icons/directives/fad.directive.ts deleted file mode 100644 index 63c7f066..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/icons/directives/fad.directive.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Directive, ElementRef, Renderer2 } from '@angular/core'; - -// tslint:disable-next-line:directive-selector -@Directive({ selector: '[fad], [duotone]' }) -export class FadDirective { - constructor(private _el: ElementRef, private _r: Renderer2) { - this._r.addClass(this._el.nativeElement, 'fad'); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/icons/directives/fal.directive.ts b/projects/angular-bootstrap-md/src/lib/free/icons/directives/fal.directive.ts deleted file mode 100644 index 02a0e91c..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/icons/directives/fal.directive.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Directive, ElementRef, Renderer2 } from '@angular/core'; - -// tslint:disable-next-line:directive-selector -@Directive({ selector: '[fal], [light]' }) -export class FalDirective { - constructor(private _el: ElementRef, private _r: Renderer2) { - this._r.addClass(this._el.nativeElement, 'fal'); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/icons/directives/far.directive.ts b/projects/angular-bootstrap-md/src/lib/free/icons/directives/far.directive.ts deleted file mode 100644 index 6de0e802..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/icons/directives/far.directive.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Directive, ElementRef, Renderer2 } from '@angular/core'; - -// tslint:disable-next-line:directive-selector -@Directive({ selector: '[far], [regular]' }) -export class FarDirective { - constructor(private _el: ElementRef, private _r: Renderer2) { - this._r.addClass(this._el.nativeElement, 'far'); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/icons/directives/fas.directive.ts b/projects/angular-bootstrap-md/src/lib/free/icons/directives/fas.directive.ts deleted file mode 100644 index 728366f0..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/icons/directives/fas.directive.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Directive, ElementRef, Renderer2 } from '@angular/core'; - -// tslint:disable-next-line:directive-selector -@Directive({ selector: '[fas], [solid]' }) -export class FasDirective { - constructor(private _el: ElementRef, private _r: Renderer2) { - this._r.addClass(this._el.nativeElement, 'fas'); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/icons/icon.component.html b/projects/angular-bootstrap-md/src/lib/free/icons/icon.component.html deleted file mode 100644 index 38061652..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/icons/icon.component.html +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/projects/angular-bootstrap-md/src/lib/free/icons/icon.component.ts b/projects/angular-bootstrap-md/src/lib/free/icons/icon.component.ts deleted file mode 100644 index 664da2e2..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/icons/icon.component.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { - Component, - Input, - ElementRef, - OnInit, - Renderer2, - ChangeDetectionStrategy, -} from '@angular/core'; -import { Utils } from '../utils'; - -@Component({ - selector: 'mdb-icon', - templateUrl: './icon.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class MdbIconComponent implements OnInit { - @Input() icon: string; - @Input() size: string; - @Input() class: string; - @Input() classInside: string; - - fab = false; - far = false; - fal = false; - fad = false; - fas = true; - - sizeClass = ''; - - utils: Utils = new Utils(); - - constructor(private _el: ElementRef, private _renderer: Renderer2) {} - - ngOnInit() { - if (this.size) { - this.sizeClass = `fa-${this.size}`; - } - - const classList = this._el.nativeElement.classList; - this.fab = classList.contains('fab'); - this.far = classList.contains('far'); - this.fas = classList.contains('fas'); - this.fal = classList.contains('fal'); - this.fad = classList.contains('fad'); - - const formWrapper = - this.utils.getClosestEl(this._el.nativeElement, '.md-form') || - this.utils.getClosestEl(this._el.nativeElement, '.md-outline'); - - if (formWrapper) { - formWrapper.childNodes.forEach((el: any) => { - if (el.tagName === 'INPUT' || 'TEXTAREA') { - this._renderer.listen(el, 'focus', () => { - this._renderer.addClass(this._el.nativeElement, 'active'); - }); - this._renderer.listen(el, 'blur', () => { - this._renderer.removeClass(this._el.nativeElement, 'active'); - }); - } - }); - } - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/icons/icon.module.ts b/projects/angular-bootstrap-md/src/lib/free/icons/icon.module.ts deleted file mode 100644 index 4241d36e..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/icons/icon.module.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { MdbIconComponent } from './icon.component'; -import { NgModule } from '@angular/core'; -import { FabDirective } from './directives/fab.directive'; -import { FarDirective } from './directives/far.directive'; -import { FasDirective } from './directives/fas.directive'; -import { FalDirective } from './directives/fal.directive'; -import { CommonModule } from '@angular/common'; -import { FadDirective } from './directives/fad.directive'; - -@NgModule({ - declarations: [ - MdbIconComponent, - FabDirective, - FarDirective, - FasDirective, - FalDirective, - FadDirective, - ], - imports: [CommonModule], - exports: [MdbIconComponent, FabDirective, FarDirective, FasDirective, FalDirective, FadDirective], -}) -export class IconsModule {} diff --git a/projects/angular-bootstrap-md/src/lib/free/icons/index.ts b/projects/angular-bootstrap-md/src/lib/free/icons/index.ts deleted file mode 100644 index 40cd00a1..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/icons/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { MdbIconComponent } from './icon.component'; -export { FalDirective } from './directives/fal.directive'; -export { FarDirective } from './directives/far.directive'; -export { FasDirective } from './directives/fas.directive'; -export { FabDirective } from './directives/fab.directive'; -export { FadDirective } from './directives/fad.directive'; - -export { IconsModule } from './icon.module'; diff --git a/projects/angular-bootstrap-md/src/lib/free/input-utilities/_input-utilities.scss b/projects/angular-bootstrap-md/src/lib/free/input-utilities/_input-utilities.scss deleted file mode 100644 index af070122..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/input-utilities/_input-utilities.scss +++ /dev/null @@ -1,19 +0,0 @@ -.error-message, -.success-message { - position: absolute; - top: 40px; - left: 0; - font-size: 0.8rem; -} -textarea ~ .error-message, -textarea ~ .success-message { - top: unset; - bottom: -20px; -} -.error-message { - color: $input-error-color; -} - -.success-message { - color: $input-success-color; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/input-utilities/error.directive.ts b/projects/angular-bootstrap-md/src/lib/free/input-utilities/error.directive.ts deleted file mode 100644 index c0d57714..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/input-utilities/error.directive.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { - Input, - HostBinding, - OnInit, - ElementRef, - Renderer2, - OnDestroy, - ViewEncapsulation, - Component, -} from '@angular/core'; -import { Utils } from '../utils'; - -let defaultIdNumber = 0; - -@Component({ - selector: 'mdb-error', - template: '', - styleUrls: ['./input-utilities-module.scss'], - encapsulation: ViewEncapsulation.None, -}) -// tslint:disable-next-line:component-class-suffix -export class MdbErrorDirective implements OnInit, OnDestroy { - prefix: HTMLElement; - @Input() id = `mdb-error-${defaultIdNumber++}`; - - @HostBinding('class.error-message') errorMsg = true; - @HostBinding('attr.id') messageId = this.id; - - textareaListenFunction: Function; - - private utils: Utils = new Utils(); - - constructor(private el: ElementRef, private renderer: Renderer2) {} - - private _calculateMarginTop() { - const parent = this.el.nativeElement.parentNode.querySelector('.form-check'); - const heightParent = parent ? parent.offsetHeight : null; - if (heightParent) { - const margin = heightParent / 12.5; - this.el.nativeElement.style.top = `${heightParent + heightParent / margin}px`; - } - } - - ngOnInit() { - this.prefix = this.el.nativeElement.parentNode.querySelector('.prefix'); - if (this.prefix) { - this.prefix.classList.add('error-message'); - } - - const textarea = this.utils.getClosestEl(this.el.nativeElement, '.md-textarea'); - this._calculateMarginTop(); - if (textarea) { - let height = textarea.offsetHeight + 4 + 'px'; - this.renderer.setStyle(this.el.nativeElement, 'top', height); - - this.textareaListenFunction = this.renderer.listen(textarea, 'keyup', () => { - height = textarea.offsetHeight + 4 + 'px'; - this.renderer.setStyle(this.el.nativeElement, 'top', height); - }); - } - } - - ngOnDestroy() { - if (this.textareaListenFunction) { - this.textareaListenFunction(); - } - if (this.prefix) { - this.prefix.classList.remove('error-message'); - } - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/input-utilities/input-utilities-module.scss b/projects/angular-bootstrap-md/src/lib/free/input-utilities/input-utilities-module.scss deleted file mode 100644 index 04f774bd..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/input-utilities/input-utilities-module.scss +++ /dev/null @@ -1,4 +0,0 @@ -@import '../../assets/scss/core/colors'; -@import '../../assets/scss/core/variables'; - -@import 'input-utilities'; diff --git a/projects/angular-bootstrap-md/src/lib/free/input-utilities/success.directive.ts b/projects/angular-bootstrap-md/src/lib/free/input-utilities/success.directive.ts deleted file mode 100644 index ff3577e9..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/input-utilities/success.directive.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - Input, - HostBinding, - ElementRef, - Renderer2, - OnInit, - OnDestroy, - Component, - ViewEncapsulation, -} from '@angular/core'; -import { Utils } from '../utils'; - -let defaultIdNumber = 0; - -@Component({ - selector: 'mdb-success', - template: '', - styleUrls: ['./input-utilities-module.scss'], - encapsulation: ViewEncapsulation.None, -}) -// tslint:disable-next-line:component-class-suffix -export class MdbSuccessDirective implements OnInit, OnDestroy { - prefix: HTMLElement; - @Input() id = `mdb-success-${defaultIdNumber++}`; - - @HostBinding('class.success-message') successMsg = true; - @HostBinding('attr.id') messageId = this.id; - - textareaListenFunction: Function; - - private utils: Utils = new Utils(); - - constructor(private el: ElementRef, private renderer: Renderer2) {} - - private _calculateMarginTop() { - const parent = this.el.nativeElement.parentNode.querySelector('.form-check'); - const heightParent = parent ? parent.offsetHeight : null; - if (heightParent) { - const margin = heightParent / 12.5; - this.el.nativeElement.style.top = `${heightParent + heightParent / margin}px`; - } - } - - ngOnInit() { - this.prefix = this.el.nativeElement.parentNode.querySelector('.prefix'); - if (this.prefix) { - this.prefix.classList.add('success-message'); - } - - const textarea = this.utils.getClosestEl(this.el.nativeElement, '.md-textarea'); - - this._calculateMarginTop(); - if (textarea) { - let height = textarea.offsetHeight + 4 + 'px'; - this.renderer.setStyle(this.el.nativeElement, 'top', height); - - this.textareaListenFunction = this.renderer.listen(textarea, 'keyup', () => { - height = textarea.offsetHeight + 4 + 'px'; - this.renderer.setStyle(this.el.nativeElement, 'top', height); - }); - } - } - - ngOnDestroy() { - if (this.textareaListenFunction) { - this.textareaListenFunction(); - } - if (this.prefix) { - this.prefix.classList.remove('success-message'); - } - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/input-utilities/validate.directive.ts b/projects/angular-bootstrap-md/src/lib/free/input-utilities/validate.directive.ts deleted file mode 100644 index a5ea31dd..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/input-utilities/validate.directive.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Directive, ElementRef, Input, OnInit, Renderer2 } from '@angular/core'; - -@Directive({ - selector: '[mdbValidate]', -}) -export class MdbValidateDirective implements OnInit { - private _validate = true; - private _validateSuccess = true; - private _validateError = true; - - @Input() mdbValidate: boolean; - @Input() - get validate() { - return this._validate; - } - set validate(value: boolean) { - this._validate = value; - this.updateErrorClass(); - this.updateSuccessClass(); - } - - @Input() - get validateSuccess() { - return this._validateSuccess; - } - set validateSuccess(value: boolean) { - this._validateSuccess = value; - this.updateSuccessClass(); - } - - @Input() - get validateError() { - return this._validateError; - } - set validateError(value: boolean) { - this._validateError = value; - this.updateErrorClass(); - this.updateSuccessClass(); - } - - constructor(private renderer: Renderer2, private el: ElementRef) {} - - updateSuccessClass() { - if (this.validate && this.validateSuccess) { - this.renderer.addClass(this.el.nativeElement, 'validate-success'); - } else { - this.renderer.removeClass(this.el.nativeElement, 'validate-success'); - } - } - - updateErrorClass() { - if (this.validate && this.validateError) { - this.renderer.addClass(this.el.nativeElement, 'validate-error'); - } else { - this.renderer.removeClass(this.el.nativeElement, 'validate-error'); - } - } - - ngOnInit() { - this.updateSuccessClass(); - this.updateErrorClass(); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/inputs/equal-validator.directive.ts b/projects/angular-bootstrap-md/src/lib/free/inputs/equal-validator.directive.ts deleted file mode 100755 index 6727fc92..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/inputs/equal-validator.directive.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Directive, forwardRef, Attribute } from '@angular/core'; -import { Validator, AbstractControl, NG_VALIDATORS } from '@angular/forms'; - -@Directive({ - selector: - '[mdb-validateEqual][formControlName],[validateEqual][formControl],[validateEqual][ngModel]', - providers: [ - { provide: NG_VALIDATORS, useExisting: forwardRef(() => EqualValidatorDirective), multi: true }, - ], -}) -export class EqualValidatorDirective implements Validator { - constructor( - @Attribute('validateEqual') public validateEqual: string, - @Attribute('reverse') public reverse: string - ) {} - - private get isReverse() { - if (!this.reverse) { - return false; - } - return this.reverse === 'true' ? true : false; - } - - validate(c: AbstractControl): { [key: string]: any } | null { - // self value (e.g. retype password) - const v = c.value; - - // control value (e.g. password) - const e: any = c.root.get(this.validateEqual); - - // value not equal - if (e && v !== e.value) { - return { validateEqual: false }; - } - - // value equal and reverse - if (e && v === e.value && this.isReverse) { - delete e.errors['validateEqual']; - if (!Object.keys(e.errors).length) { - e.setErrors(null); - } - } - - // value not equal and reverse - if (e && v !== e.value && this.isReverse) { - e.setErrors({ - validateEqual: false, - }); - } - - // return null; - return null; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/inputs/index.ts b/projects/angular-bootstrap-md/src/lib/free/inputs/index.ts deleted file mode 100755 index aa37298f..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/inputs/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { InputsModule } from './inputs.module'; -export { EqualValidatorDirective} from './equal-validator.directive'; -export { MdbInputDirective } from './mdb-input.directive'; -export { MdbInput } from './input.directive'; diff --git a/projects/angular-bootstrap-md/src/lib/free/inputs/input.directive.ts b/projects/angular-bootstrap-md/src/lib/free/inputs/input.directive.ts deleted file mode 100644 index faf62826..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/inputs/input.directive.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { isPlatformBrowser } from '@angular/common'; -import { - Directive, - ElementRef, - Renderer2, - Input, - AfterViewInit, - HostListener, - PLATFORM_ID, - Inject, - AfterViewChecked, -} from '@angular/core'; -import { DOWN_ARROW, UP_ARROW } from '../utils/keyboard-navigation'; - -@Directive({ - selector: '[mdbInput]', -}) -// tslint:disable-next-line:directive-class-suffix -export class MdbInput implements AfterViewChecked, AfterViewInit { - public elLabel: ElementRef | any = null; - public elIcon: Element | any = null; - - @Input() focusCheckbox = true; - @Input() focusRadio = true; - - isBrowser: any = false; - isClicked = false; - element: any = null; - - constructor( - private el: ElementRef, - private _renderer: Renderer2, - @Inject(PLATFORM_ID) platformId: string - ) { - this.isBrowser = isPlatformBrowser(platformId); - } - - @HostListener('focus') onfocus() { - try { - this._renderer.addClass(this.elLabel, 'active'); - this.isClicked = true; - } catch (error) {} - } - - @HostListener('blur') onblur() { - try { - if (this.el.nativeElement.value === '') { - this._renderer.removeClass(this.elLabel, 'active'); - } - this.isClicked = false; - } catch (error) {} - } - - @HostListener('change') onchange() { - try { - this.checkValue(); - } catch (error) {} - } - - @HostListener('keydown', ['$event']) onkeydown(event: any) { - try { - if (event.target.type === 'number') { - if (event.shiftKey) { - switch (event.keyCode) { - case UP_ARROW: - event.target.value = +event.target.value + 10; - break; - case DOWN_ARROW: - event.target.value = +event.target.value - 10; - break; - } - } - if (event.altKey) { - switch (event.keyCode) { - case UP_ARROW: - event.target.value = +event.target.value + 0.1; - break; - case DOWN_ARROW: - event.target.value = +event.target.value - 0.1; - break; - } - } - } - } catch (error) {} - this.delayedResize(); - } - @HostListener('cut') oncut() { - try { - setTimeout(() => { - this.delayedResize(); - }, 0); - } catch (error) {} - } - @HostListener('paste') onpaste() { - try { - setTimeout(() => { - this.delayedResize(); - }, 0); - } catch (error) {} - } - @HostListener('drop') ondrop() { - try { - setTimeout(() => { - this.delayedResize(); - }, 0); - } catch (error) {} - } - - ngAfterViewInit() { - if (this.isBrowser) { - try { - this.element = document.querySelector('.md-textarea-auto'); - if (this.element) { - this.delayedResize(); - } - } catch (error) {} - } - const type = this.el.nativeElement.type; - if (this.focusCheckbox && type === 'checkbox') { - this._renderer.addClass(this.el.nativeElement, 'onFocusSelect'); - } - if (this.focusRadio && type === 'radio') { - this._renderer.addClass(this.el.nativeElement, 'onFocusSelect'); - } - } - - ngAfterViewChecked() { - this.initComponent(); - this.checkValue(); - } - - resize() { - if (this.el.nativeElement.classList.contains('md-textarea-auto')) { - this._renderer.setStyle(this.el.nativeElement, 'height', 'auto'); - this._renderer.setStyle( - this.el.nativeElement, - 'height', - this.el.nativeElement.scrollHeight + 'px' - ); - } - } - - delayedResize() { - setTimeout(() => { - this.resize(); - }, 0); - } - - public initComponent(): void { - let inputId; - let inputP; - if (this.isBrowser) { - try { - inputId = this.el.nativeElement.id; - } catch (err) {} - - try { - inputP = this.el.nativeElement.parentNode; - } catch (err) {} - - this.elLabel = - inputP.querySelector('label[for="' + inputId + '"]') || inputP.querySelector('label'); - if (this.elLabel && this.el.nativeElement.value !== '') { - this._renderer.addClass(this.elLabel, 'active'); - } - this.elIcon = inputP.querySelector('i') || false; - } - } - - private checkValue(): void { - let value = ''; - if (this.elLabel != null) { - value = this.el.nativeElement.value || ''; - if (value === '') { - this._renderer.removeClass(this.elLabel, 'active'); - if (this.elIcon) { - this._renderer.removeClass(this.elIcon, 'active'); - } - } - if ( - (value === '' && this.isClicked) || - (value === '' && this.el.nativeElement.placeholder) || - (value === '' && this.el.nativeElement.attributes.placeholder) - ) { - this._renderer.addClass(this.elLabel, 'active'); - } - } - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/inputs/inputs.module.ts b/projects/angular-bootstrap-md/src/lib/free/inputs/inputs.module.ts deleted file mode 100644 index 8cb8ed0a..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/inputs/inputs.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NgModule, ModuleWithProviders, NO_ERRORS_SCHEMA} from '@angular/core'; -import { EqualValidatorDirective } from './equal-validator.directive'; -import { MdbInputDirective } from './mdb-input.directive'; -import { MdbInput } from './input.directive'; - -@NgModule({ - declarations: [MdbInput, MdbInputDirective, EqualValidatorDirective], - exports: [MdbInput, MdbInputDirective, EqualValidatorDirective], - schemas: [NO_ERRORS_SCHEMA], -}) - -export class InputsModule { - public static forRoot(): ModuleWithProviders { - return { ngModule: InputsModule, providers: [] }; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/inputs/licens.md b/projects/angular-bootstrap-md/src/lib/free/inputs/licens.md deleted file mode 100755 index 8e1c25fb..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/inputs/licens.md +++ /dev/null @@ -1,3 +0,0 @@ -https://github.com/SebastianM/angular-google-maps/blob/master/LICENSE -https://www.npmjs.com/package/ng2-completer -https://github.com/jkuri/ngx-uploader diff --git a/projects/angular-bootstrap-md/src/lib/free/inputs/mdb-input.directive.ts b/projects/angular-bootstrap-md/src/lib/free/inputs/mdb-input.directive.ts deleted file mode 100644 index be5543cc..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/inputs/mdb-input.directive.ts +++ /dev/null @@ -1,393 +0,0 @@ -import { isPlatformBrowser } from '@angular/common'; -import { - Directive, - ElementRef, - Renderer2, - Input, - AfterViewInit, - HostListener, - PLATFORM_ID, - Inject, - AfterViewChecked, - OnInit, - DoCheck, - OnChanges, - SimpleChanges, -} from '@angular/core'; -import { DOWN_ARROW, UP_ARROW } from '../utils/keyboard-navigation'; - -@Directive({ - selector: '[mdbInputDirective]', -}) -export class MdbInputDirective - implements AfterViewChecked, OnInit, AfterViewInit, DoCheck, OnChanges { - public wrongTextContainer: any; - public rightTextContainer: any; - public el: ElementRef | any = null; - public elLabel: ElementRef | any = null; - public elIcon: Element | any = null; - - element: any = null; - - @Input() mdbInputDirective: MdbInputDirective; - @Input() customRegex: any; - @Input() mdbValidate = true; - @Input() validateSuccess = true; - @Input() validateError = true; - @Input() focusCheckbox = true; - @Input() focusRadio = true; - @Input() errorMessage: string; - @Input() successMessage: string; - - isBrowser: any = false; - isClicked = false; - - constructor( - private _elRef: ElementRef, - private _renderer: Renderer2, - @Inject(PLATFORM_ID) platformId: string - ) { - this.isBrowser = isPlatformBrowser(platformId); - } - - @HostListener('focus') onfocus() { - try { - this._renderer.addClass(this.elLabel, 'active'); - this.isClicked = true; - } catch (error) {} - } - - @HostListener('blur') onblur() { - this.validationFunction(); - try { - if (this.el.nativeElement.value === '') { - this._renderer.removeClass(this.elLabel, 'active'); - } - this.isClicked = false; - } catch (error) {} - } - - @HostListener('change') onchange() { - try { - this.checkValue(); - } catch (error) {} - } - - @HostListener('input') oniput() { - this.validationFunction(); - } - - @HostListener('keydown', ['$event']) onkeydown(event: any) { - try { - if (event.target.type === 'number') { - if (event.shiftKey) { - switch (event.keyCode) { - case UP_ARROW: - event.target.value = +event.target.value + 10; - break; - case DOWN_ARROW: - event.target.value = +event.target.value - 10; - break; - } - } - if (event.altKey) { - switch (event.keyCode) { - case UP_ARROW: - event.target.value = +event.target.value + 0.1; - break; - case DOWN_ARROW: - event.target.value = +event.target.value - 0.1; - break; - } - } - } - } catch (error) {} - this.delayedResize(); - } - - @HostListener('cut') oncut() { - try { - setTimeout(() => { - this.delayedResize(); - }, 0); - } catch (error) {} - } - - @HostListener('paste') onpaste() { - try { - setTimeout(() => { - this.delayedResize(); - }, 0); - } catch (error) {} - } - - @HostListener('drop') ondrop() { - try { - setTimeout(() => { - this.delayedResize(); - }, 0); - } catch (error) {} - } - - updateErrorMsg(value: string) { - if (this.wrongTextContainer) { - this.wrongTextContainer.innerHTML = value; - } - } - - updateSuccessMsg(value: string) { - if (this.rightTextContainer) { - this.rightTextContainer.innerHTML = value; - } - } - - ngOnInit() { - try { - setTimeout(() => { - this.delayedResize(); - }, 0); - } catch (error) { - console.log(error); - } - - // Inititalise a new wrong/right elements and render it below the host component. - if (this.mdbValidate) { - this.wrongTextContainer = this._renderer.createElement('span'); - this._renderer.addClass(this.wrongTextContainer, 'inputVal'); - this._renderer.addClass(this.wrongTextContainer, 'text-danger'); - this._renderer.appendChild(this._elRef.nativeElement.parentElement, this.wrongTextContainer); - const textWrong = this._elRef.nativeElement.getAttribute('data-error'); - this.wrongTextContainer.innerHTML = textWrong ? textWrong : 'wrong'; - if (!textWrong && this.errorMessage !== undefined) { - this.wrongTextContainer.innerHTML = this.errorMessage; - } - this._renderer.setStyle(this.wrongTextContainer, 'visibility', 'hidden'); - - this.rightTextContainer = this._renderer.createElement('span'); - this._renderer.addClass(this.rightTextContainer, 'inputVal'); - this._renderer.addClass(this.rightTextContainer, 'text-success'); - this._renderer.appendChild(this._elRef.nativeElement.parentElement, this.rightTextContainer); - const textSuccess = this._elRef.nativeElement.getAttribute('data-success'); - this.rightTextContainer.innerHTML = textSuccess ? textSuccess : 'success'; - if (!textSuccess && this.successMessage !== undefined) { - this.rightTextContainer.innerHTML = this.successMessage; - } - this._renderer.setStyle(this.rightTextContainer, 'visibility', 'hidden'); - } - } - - ngOnChanges(changes: SimpleChanges) { - if (changes.hasOwnProperty('errorMessage')) { - const newErrorMsg = changes.errorMessage.currentValue; - this.updateErrorMsg(newErrorMsg); - } - - if (changes.hasOwnProperty('successMessage')) { - const newSuccessMsg = changes.successMessage.currentValue; - this.updateSuccessMsg(newSuccessMsg); - } - } - - ngDoCheck() { - if ( - this.mdbValidate && - this._elRef.nativeElement.classList.contains('ng-valid') && - this._elRef.nativeElement.classList.contains('ng-dirty') && - !this._elRef.nativeElement.classList.contains('counter-success') - ) { - this._renderer.addClass(this._elRef.nativeElement, 'counter-success'); - this._renderer.setStyle(this.wrongTextContainer, 'visibility', 'hidden'); - this._renderer.setStyle(this.rightTextContainer, 'visibility', 'visible'); - this._renderer.setStyle( - this.rightTextContainer, - 'top', - this._elRef.nativeElement.offsetHeight + 'px' - ); - this._renderer.setStyle( - this.wrongTextContainer, - 'top', - this._elRef.nativeElement.offsetHeight + 'px' - ); - } - if ( - this.mdbValidate && - this._elRef.nativeElement.classList.contains('ng-invalid') && - this._elRef.nativeElement.classList.contains('ng-dirty') && - !this._elRef.nativeElement.classList.contains('counter-danger') - ) { - this._renderer.addClass(this._elRef.nativeElement, 'counter-danger'); - this._renderer.setStyle(this.rightTextContainer, 'visibility', 'hidden'); - this._renderer.setStyle(this.wrongTextContainer, 'visibility', 'visible'); - this._renderer.setStyle( - this.rightTextContainer, - 'top', - this._elRef.nativeElement.offsetHeight + 'px' - ); - this._renderer.setStyle( - this.wrongTextContainer, - 'top', - this._elRef.nativeElement.offsetHeight + 'px' - ); - } - if ( - (this._elRef.nativeElement.classList.contains('ng-invalid') && - this._elRef.nativeElement.classList.contains('ng-pristine') && - this._elRef.nativeElement.classList.contains('ng-untouched')) || - this._elRef.nativeElement.disabled - ) { - if (this._elRef.nativeElement.classList.contains('counter-success')) { - this._renderer.removeClass(this._elRef.nativeElement, 'counter-success'); - this._renderer.setStyle(this.rightTextContainer, 'visibility', 'hidden'); - } else if (this._elRef.nativeElement.classList.contains('counter-danger')) { - this._renderer.removeClass(this._elRef.nativeElement, 'counter-danger'); - this._renderer.setStyle(this.wrongTextContainer, 'visibility', 'hidden'); - } - } - if (!this.validateSuccess) { - this._renderer.removeClass(this._elRef.nativeElement, 'counter-success'); - this._renderer.setStyle(this.rightTextContainer, 'display', 'none'); - if (this._elRef.nativeElement.classList.contains('ng-valid')) { - this._renderer.removeClass(this._elRef.nativeElement, 'counter-danger'); - } - } - - if (!this.validateError) { - this._renderer.removeClass(this._elRef.nativeElement, 'counter-danger'); - this._renderer.setStyle(this.wrongTextContainer, 'display', 'none'); - if (this._elRef.nativeElement.classList.contains('ng-invalid')) { - this._renderer.removeClass(this._elRef.nativeElement, 'counter-success'); - } - } - } - - validationFunction() { - setTimeout(() => { - if (this._elRef.nativeElement.classList.contains('ng-invalid')) { - this._renderer.removeClass(this._elRef.nativeElement, 'counter-success'); - this._renderer.removeClass(this._elRef.nativeElement, 'counter-danger'); - } - if ( - this._elRef.nativeElement.classList.contains('ng-touched') && - this._elRef.nativeElement.classList.contains('ng-invalid') - ) { - if (this.mdbValidate) { - this._renderer.addClass(this._elRef.nativeElement, 'counter-danger'); - this._renderer.setStyle(this.rightTextContainer, 'visibility', 'hidden'); - this._renderer.setStyle(this.wrongTextContainer, 'visibility', 'visible'); - this._renderer.setStyle( - this.rightTextContainer, - 'top', - this._elRef.nativeElement.offsetHeight + 'px' - ); - this._renderer.setStyle( - this.wrongTextContainer, - 'top', - this._elRef.nativeElement.offsetHeight + 'px' - ); - } - } else if ( - this._elRef.nativeElement.classList.contains('ng-touched') && - this._elRef.nativeElement.classList.contains('ng-valid') - ) { - if (this.mdbValidate) { - this._renderer.addClass(this._elRef.nativeElement, 'counter-success'); - this._renderer.setStyle(this.rightTextContainer, 'visibility', 'visible'); - this._renderer.setStyle(this.wrongTextContainer, 'visibility', 'hidden'); - this._renderer.setStyle( - this.rightTextContainer, - 'top', - this._elRef.nativeElement.offsetHeight + 'px' - ); - this._renderer.setStyle( - this.wrongTextContainer, - 'top', - this._elRef.nativeElement.offsetHeight + 'px' - ); - } - } - }, 0); - } - - ngAfterViewInit() { - if (this.isBrowser) { - try { - this.element = document.querySelector('.md-textarea-auto'); - } catch (error) {} - } - const type = this.el.nativeElement.type; - if (this.focusCheckbox && type === 'checkbox') { - this._renderer.addClass(this.el.nativeElement, 'onFocusSelect'); - } - if (this.focusRadio && type === 'radio') { - this._renderer.addClass(this.el.nativeElement, 'onFocusSelect'); - } - } - - ngAfterViewChecked() { - this.initComponent(); - this.checkValue(); - } - - resize() { - if (this.el.nativeElement.classList.contains('md-textarea-auto')) { - this._renderer.setStyle(this.el.nativeElement, 'height', 'auto'); - this._renderer.setStyle( - this.el.nativeElement, - 'height', - this.el.nativeElement.scrollHeight + 'px' - ); - } - } - - delayedResize() { - setTimeout(() => { - this.resize(); - }, 0); - } - - public initComponent(): void { - let inputId; - let inputP; - if (this.isBrowser) { - try { - inputId = this.el.nativeElement.id; - } catch (err) {} - - try { - inputP = this.el.nativeElement.parentNode; - } catch (err) {} - - this.elLabel = - inputP.querySelector('label[for="' + inputId + '"]') || inputP.querySelector('label'); - if (this.elLabel && this.el.nativeElement.value !== '') { - this._renderer.addClass(this.elLabel, 'active'); - } - this.elIcon = inputP.querySelector('i') || false; - - if (this.elIcon) { - this._renderer.addClass(this.elIcon, 'active'); - } - } - } - - private checkValue(): void { - let value = ''; - if (this.elLabel != null) { - value = this.el.nativeElement.value || ''; - if (value === '') { - this._renderer.removeClass(this.elLabel, 'active'); - if (this.elIcon) { - this._renderer.removeClass(this.elIcon, 'active'); - } - // tslint:disable-next-line:max-line-length - } - if ( - (value === '' && this.isClicked) || - (value === '' && this.el.nativeElement.placeholder) || - (value === '' && this.el.nativeElement.attributes.placeholder) - ) { - this._renderer.addClass(this.elLabel, 'active'); - } - } - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/mdb-free.module.ts b/projects/angular-bootstrap-md/src/lib/free/mdb-free.module.ts deleted file mode 100755 index 584b0009..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/mdb-free.module.ts +++ /dev/null @@ -1,183 +0,0 @@ -// free -import { ModuleWithProviders, NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; - -import { CardsModule } from './cards/cards.module'; -import { ButtonsModule } from './buttons/buttons.module'; -import { NavbarModule } from './navbars/navbar.module'; -import { DropdownModule } from './dropdown/dropdown.module'; -import { CarouselModule } from './carousel/carousel.module'; -import { ChartsModule } from './charts/chart.module'; -import { CollapseModule } from './collapse/collapse.module'; -import { ModalModule } from './modals/modal.module'; -import { TooltipModule } from './tooltip/tooltip.module'; -import { PopoverModule } from './popover/popover.module'; -import { InputsModule } from './inputs/inputs.module'; -import { WavesModule } from './waves/waves.module'; -import { IconsModule } from './icons/icon.module'; -import { CheckboxModule } from './checkbox/checkbox.module'; -import { TableModule } from './tables/tables.module'; -import { BadgeModule } from './badge/badge.module'; -import { BreadcrumbModule } from './breadcrumbs/breadcrumb.module'; -import { InputUtilitiesModule } from './input-utilities/input-utilities.module'; -import { StickyHeaderModule } from './sticky-header/sticky-header.module'; - -export { StickyHeaderDirective, StickyHeaderModule } from './sticky-header/index'; - -export { - MdbErrorDirective, - MdbSuccessDirective, - MdbValidateDirective, - InputUtilitiesModule, -} from './input-utilities/index'; - -export { - MdbBreadcrumbComponent, - MdbBreadcrumbItemComponent, - BreadcrumbModule, -} from './breadcrumbs/index'; - -export { MDBBadgeComponent, BadgeModule } from './badge/index'; - -export { - MdbTablePaginationComponent, - MdbTableRowDirective, - MdbTableScrollDirective, - MdbTableSortDirective, - MdbTableDirective, - MdbTableService, - TableModule, -} from './tables/index'; - -export { CHECKBOX_VALUE_ACCESSOR, CheckboxComponent, CheckboxModule } from './checkbox/index'; - -export { - ButtonsModule, - ButtonRadioDirective, - ButtonCheckboxDirective, - MdbBtnDirective, - FixedButtonCaptionDirective, -} from './buttons/index'; - -export { - CardsModule, - MdbCardComponent, - MdbCardBodyComponent, - MdbCardImageComponent, - MdbCardTextComponent, - MdbCardTitleComponent, - MdbCardFooterComponent, - MdbCardHeaderComponent, -} from './cards/index'; - -export { WavesModule, WavesDirective } from './waves/index'; - -export { InputsModule, MdbInputDirective, MdbInput } from './inputs/index'; - -export { NavbarModule } from './navbars/index'; - -export { - BsDropdownConfig, - BsDropdownContainerComponent, - BsDropdownDirective, - BsDropdownMenuDirective, - DropdownModule, - BsDropdownState, - BsDropdownToggleDirective, -} from './dropdown/index'; - -export { CarouselComponent, CarouselConfig, CarouselModule } from './carousel/index'; - -export { ChartsModule, BaseChartDirective } from './charts/index'; - -export { CollapseComponent, CollapseModule } from './collapse/index'; - -export { - ModalBackdropComponent, - ModalBackdropOptions, - ModalDirective, - ModalModule, - ModalOptions, - MDBModalService, - ModalContainerComponent, - MDBModalRef, -} from './modals/index'; - -export { - TooltipConfig, - TooltipContainerComponent, - TooltipDirective, - TooltipModule, -} from './tooltip/index'; - -export { - PopoverConfig, - PopoverContainerComponent, - PopoverModule, - PopoverDirective, -} from './popover/index'; - -export { - IconsModule, - MdbIconComponent, - FalDirective, - FarDirective, - FasDirective, - FabDirective, - FadDirective -} from './icons/index'; - -const MODULES = [ - ButtonsModule, - CardsModule, - WavesModule, - InputsModule, - NavbarModule, - DropdownModule, - CarouselModule, - ChartsModule, - CollapseModule, - ModalModule, - TooltipModule, - PopoverModule, - IconsModule, - CheckboxModule, - TableModule, - BadgeModule, - BreadcrumbModule, - InputUtilitiesModule, - StickyHeaderModule, -]; - -@NgModule({ - imports: [ - ButtonsModule, - WavesModule.forRoot(), - InputsModule.forRoot(), - NavbarModule, - DropdownModule.forRoot(), - CarouselModule.forRoot(), - ChartsModule, - CollapseModule.forRoot(), - ModalModule.forRoot(), - TooltipModule.forRoot(), - PopoverModule.forRoot(), - IconsModule, - CardsModule.forRoot(), - CheckboxModule, - TableModule, - BadgeModule, - BreadcrumbModule, - InputUtilitiesModule, - StickyHeaderModule, - ], - exports: MODULES, - schemas: [NO_ERRORS_SCHEMA], -}) -export class MDBRootModule {} - -@NgModule({ exports: MODULES }) -export class MDBBootstrapModule { - public static forRoot(): ModuleWithProviders { - return { ngModule: MDBRootModule }; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/modals/_modals.scss b/projects/angular-bootstrap-md/src/lib/free/modals/_modals.scss deleted file mode 100644 index 7c0968e7..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/modals/_modals.scss +++ /dev/null @@ -1,995 +0,0 @@ -// Modals -// Styles for body -body { - &.modal-open { - overflow: auto; - } - &.scrollable { - overflow-y: auto; - } -} - -// *** ENHANCED BOOTSTRAP MODALS ***/// -// General styles -.modal-dialog { - .modal-content { - box-shadow: $z-depth-1-half; - border: 0; - border-radius: $border-radius-base; - .modal-header { - border-top-left-radius: $border-radius-base; - border-top-right-radius: $border-radius-base; - } - } - // Cascading modals - &.cascading-modal { - margin-top: 10%; - .close { - opacity: 1; - text-shadow: none; - color: $white-base; - outline: 0; - } - // Cascading header - .modal-header { - box-shadow: $z-depth-1-half; - margin: $cascading-modal-margin-top $cascading-modal-margin-right - $cascading-modal-margin-bottom $cascading-modal-margin-left; - border: none; - border-radius: $border-radius-base; - padding: $cascading-modal-padding; - text-align: center; - .close { - margin-right: $cascading-modal-close-margin-right; - } - .title { - margin-bottom: 0; - width: 100%; - font-size: $cascading-modal-font-size; - .fas, - .fab, - .far { - margin-right: $cascading-modal-fa-margin-right; - } - } - .social-buttons { - margin-top: $cascading-modal-social-margin-top; - a { - font-size: $cascading-modal-a-font-size; - } - } - } - // Cascading tabs nav - .modal-c-tabs { - .md-tabs { - box-shadow: $z-depth-1; - margin: $cascading-modal-tabs-margin-top $cascading-modal-tabs-margin-x 0 - $cascading-modal-tabs-margin-x; - display: flex; - li { - flex: 1; - a { - text-align: center; - } - } - } - .tab-content { - padding: $cascading-modal-tabs-padding-top 0 0 0; - box-shadow: unset; - } - //.md-tabs { - // border-radius: $md-card-border-radius; - // .nav-item { - // .nav-link { - // border-radius: $md-card-border-radius; - // background-color: inherit; - // color: $white-base; - // } - // } - //} - } - // Footer customization - .modal-body, - .modal-footer { - padding-left: $modal-body-padding-left; - padding-right: $modal-body-padding-right; - color: $grey-darken-2; - .additional-option { - margin-top: $modal-body-margin-top; - text-align: center; - } - } - // Cascading avatar - &.modal-avatar { - margin-top: $modal-avatar-margin-top; - .modal-header { - box-shadow: none; - @extend .img-fluid; - margin: $modal-avatar-header-margin-top 0 $modal-avatar-header-margin-bottom; - img { - width: $modal-avatar-img-width; - box-shadow: $z-depth-2; - margin-left: auto; - margin-right: auto; - } - } - } - } - // Modal notify - &.modal-notify { - .heading { - margin: 0; - padding: $modal-notify-heading-padding; - font-size: $modal-notify-font-size; - color: $white-base; - } - .modal-header { - box-shadow: $z-depth-1; - border: 0; - } - .close { - opacity: 1; - } - .modal-body { - padding: $modal-notify-body-padding; - color: $grey-darken-2; - } - @each $name, $color in $basic { - &.modal-#{$name} { - .modal-header { - background-color: $color; - } - .fas, - .fab, - .far { - color: $color; - } - .badge { - background-color: $color; - } - .btn { - .fas, - .fab, - .far { - color: #fff; - } - &.btn-outline-#{$name} { - .fas, - .fab, - .far { - color: $color; - } - } - } - } - } - } -} - -// Position & Size -.modal { - padding-right: 0 !important; - .modal-dialog { - @media (min-width: 768px) { - &.modal-top { - top: 0; - } - &.modal-left { - left: 0; - } - &.modal-right { - right: 0; - } - &.modal-bottom { - bottom: 0; - } - &.modal-top-left { - top: $modal-distance; - left: $modal-distance; - } - &.modal-top-right { - top: $modal-distance; - right: $modal-distance; - } - &.modal-bottom-left { - bottom: $modal-distance; - left: $modal-distance; - } - &.modal-bottom-right { - bottom: $modal-distance; - right: $modal-distance; - } - } - } - &.fade { - &.top:not(.show) .modal-dialog { - transform: $modal-fade-top-transform; - } - &.left:not(.show) .modal-dialog { - transform: $modal-fade-left-transform; - } - &.right:not(.show) .modal-dialog { - transform: $modal-fade-right-transform; - } - &.bottom:not(.show) .modal-dialog { - transform: $modal-fade-bottom-transform; - } - } - @media (min-width: $medium-screen) { - &.modal-scrolling { - position: relative; - .modal-dialog { - position: fixed; - z-index: 1050; - } - } - &.modal-content-clickable { - top: auto; - bottom: auto; - .modal-dialog { - position: fixed; - } - } - .modal-fluid { - width: 100%; - max-width: 100%; - .modal-content { - width: 100%; - } - } - .modal-frame { - position: absolute; - margin: 0 !important; - width: 100%; - max-width: 100% !important; - &.modal-bottom { - bottom: 0; - } - &.modal-dialog { - height: inherit; - } - } - .modal-full-height { - position: absolute; - display: flex; - margin: 0; - width: $modal-width; - min-height: 100%; - height: auto; - min-height: 100%; - top: 0; - right: 0; - &.modal-top, - &.modal-bottom { - display: block; - width: 100%; - max-width: 100%; - height: auto; - } - &.modal-top { - bottom: auto; - } - &.modal-bottom { - min-height: 0; - top: auto; - } - .modal-content { - width: 100%; - } - &.modal-lg { - width: 90%; - max-width: 90%; - @media (min-width: $medium-screen) { - width: $modal-full-height-medium-screen; - max-width: $modal-full-height-medium-screen; - } - @media (min-width: $large-screen) { - width: $modal-full-height-large-screen; - max-width: $modal-full-height-large-screen; - } - } - } - .modal-side { - position: absolute; - bottom: $modal-distance; - right: $modal-distance; - margin: 0; - width: $modal-width; - } - } -} - -// Angular styles -/* You can add global styles to this file, and also import other style files */ - -// Distance -$modal-distance: 10px; -$modal-info-color: #5394ff; -$modal-success-color: #01d36b; -$modal-warning-color: #ff8e38; -$modal-danger-color: #ff4b4b; - -// Styles for body -body { - &.scrollable { - overflow-y: auto; - } -} - -// *** ENHANCED BOOTSTRAP MODALS ***/// -// General styles -.modal-dialog { - .modal-content { - // @include border-radius(2px); - // @extend .z-depth-1-half; - border: 0; - } -} - -// Position & Size -.modal { - padding-right: 0 !important; - - .modal-dialog { - @media (min-width: 768px) { - &.modal-top { - top: 0; - left: 0; - right: 0; - } - &.modal-left { - left: 0; - } - &.modal-right { - right: 0; - } - &.modal-bottom > .modal-content { - position: absolute; - bottom: 0; - } - &.modal-top-left { - top: $modal-distance; - left: $modal-distance; - } - &.modal-top-right { - top: $modal-distance; - right: $modal-distance; - } - &.modal-bottom-left { - left: $modal-distance; - bottom: $modal-distance; - } - &.modal-bottom-right { - right: $modal-distance; - bottom: $modal-distance; - } - } - } - - .modal-side { - &.modal-top { - top: 0; - } - - &.modal-left { - left: 0; - } - - &.modal-right { - right: 0; - } - - &.modal-bottom { - bottom: 0; - } - - &.modal-top-left { - top: $modal-distance; - left: $modal-distance; - } - - &.modal-top-right { - top: $modal-distance; - right: $modal-distance; - } - - &.modal-bottom-left { - left: $modal-distance; - bottom: $modal-distance; - } - - &.modal-bottom-right { - right: $modal-distance; - bottom: $modal-distance; - } - } - - &.fade { - &.top:not(.show) .modal-dialog { - transform: translate3d(0, -25%, 0); - } - - &.left:not(.show) .modal-dialog { - transform: translate3d(-25%, 0, 0); - } - - &.right:not(.show) .modal-dialog { - transform: translate3d(25%, 0, 0); - } - - &.bottom:not(.show) .modal-dialog { - transform: translate3d(0, 25%, 0); - } - - &.in { - opacity: 1; - - .modal-dialog { - // -webkit-transform: translate(0, 0); - transform: translate(0, 0); - - .relative { - display: inline-block; - // transform: translate3d(0, 0, 0); - } - } - } - } - - &.modal-scrolling { - position: relative; - - .modal-dialog { - position: fixed; - z-index: 1050; - } - } - - &.modal-content-clickable { - top: auto; - bottom: auto; - - .modal-dialog { - position: fixed; - } - } - - .modal-fluid { - width: 100%; - max-width: 100%; - - .modal-content { - width: 100%; - } - } - - .modal-frame { - position: absolute; - width: 100%; - max-width: 100%; - margin: 0; - @media (max-width: 767px) { - padding: 0.5rem; - } - &.modal-bottom { - bottom: 0; - } - } - - .modal-full-height { - display: flex; - position: absolute; - width: 400px; - min-height: 100%; - margin: 0; - top: 0; - // bottom: 0; - right: 0; - @media (max-width: 576px) { - width: 100%; - padding: 0.5rem; - } - - @media (max-width: 992px) { - width: 100%; - height: unset; - position: unset; - } - - &.modal-top, - &.modal-left, - &.modal-right { - @media (max-width: 992px) { - margin: 1.75rem auto; - min-height: unset; - } - } - - &.modal-bottom { - @media (max-width: 768px) { - margin-top: 1.75rem; - } - @media (min-width: 768px) and (max-width: 992px) { - margin-bottom: 1.75rem; - .modal-content { - bottom: 1rem; - } - } - } - - &.modal-top, - &.modal-bottom, - &.modal-left, - &.modal-right { - @media (max-width: 992px) { - margin-left: auto; - margin-right: auto; - } - } - - &.modal-top, - &.modal-bottom { - display: block; - width: 100%; - height: auto; - } - &.modal-top { - bottom: auto; - } - - &.modal-bottom { - bottom: 0; - } - - .modal-content { - width: 100%; - } - - &.modal-lg { - max-width: 90%; - width: 90%; - @media (min-width: 992px) { - max-width: 800px; - width: 800px; - } - @media (min-width: 1200px) { - max-width: 1000px; - width: 1000px; - } - } - } - - .modal-side { - position: absolute; - right: $modal-distance; - bottom: $modal-distance; - margin: 0; - min-width: 100px; - @media (max-width: 768px) { - padding-left: 0.5rem; - } - } -} - -// Styles -.modal-dialog { - // Cascading modals - &.cascading-modal { - margin-top: 10%; - // Cascading header - .modal-header { - text-align: center; - margin: -2rem 1rem 1rem 1rem; - padding: 1.5rem; - border: none; - flex-direction: column; - // @extend .z-depth-1-half; - // @include border-radius(3px); - .close { - margin-right: 2.5rem; - } - - &.white-text { - .close { - color: #fff; - opacity: 1; - } - } - - .title { - width: 100%; - margin-bottom: 0; - font-size: 1.25rem; - - .fa { - margin-right: 9px; - } - } - - .social-buttons { - margin-top: 1.5rem; - - a { - font-size: 1rem; - } - } - } - - // Cascading tabs nav - .modal-c-tabs { - .md-tabs { - margin: -1.5rem 1rem 0 1rem; - // @extend .z-depth-1; - } - - .tab-content { - padding: 1.7rem 0 0 0; - } - } - - // Footer customization - .modal-body, - .modal-footer { - color: #616161; - padding-right: 2rem; - padding-left: 2rem; - - .additional-option { - text-align: center; - margin-top: 1rem; - } - } - - // Cascading avatar - &.modal-avatar { - margin-top: 6rem; - - .modal-header { - // @extend .z-depth-0; - // @extend .img-fluid; - margin: -6rem 2rem -1rem 2rem; - - img { - width: 130px; - // @extend .z-depth-2; - } - } - } - } - - // Modal notify - &.modal-notify { - .heading { - margin: 0; - padding: 0.3rem; - color: #fff; - font-size: 1.15rem; - } - - .modal-header { - // @extend .z-depth-1; - border: 0; - } - - .close { - opacity: 1; - } - - .modal-body { - padding: 1.5rem; - color: #616161; - } - - .btn-outline-secondary-modal { - background-color: transparent; - } - - // Modal info - &.modal-info { - .modal-header { - background-color: $modal-info-color; - } - - .fa { - color: $modal-info-color; - } - - .badge { - background-color: $modal-info-color; - } - - .btn-primary-modal { - background: $modal-info-color; - - &:hover, - &:focus, - &:active { - background-color: lighten($modal-info-color, 5%) !important; - } - - &.active { - background-color: darken($modal-info-color, 20%) !important; - // @extend .z-depth-1-half; - } - } - - .btn-outline-secondary-modal { - border: 2px solid $modal-info-color; - color: $modal-info-color !important; - } - } - - // Modal warning - &.modal-warning { - .modal-header { - background-color: $modal-warning-color; - } - - .fa { - color: $modal-warning-color; - } - - .badge { - background-color: $modal-warning-color; - } - - .btn-primary-modal { - background: $modal-warning-color; - - &:hover, - &:focus, - &:active { - background-color: lighten($modal-warning-color, 5%) !important; - } - - &.active { - background-color: darken($modal-warning-color, 20%) !important; - // @extend .z-depth-1-half; - } - } - - .btn-outline-secondary-modal { - border: 2px solid $modal-warning-color; - color: $modal-warning-color !important; - } - } - - // Modal success - &.modal-success { - .modal-header { - background-color: $modal-success-color; - } - - .fa { - color: $modal-success-color; - } - - .badge { - background-color: $modal-success-color; - } - - .btn-primary-modal { - background: $modal-success-color; - - &:hover, - &:focus, - &:active { - background-color: lighten($modal-success-color, 5%) !important; - } - - &.active { - background-color: darken($modal-success-color, 20%) !important; - // @extend .z-depth-1-half; - } - } - - .btn-outline-secondary-modal { - border: 2px solid $modal-success-color; - color: $modal-success-color !important; - } - } - - // Modal danger - &.modal-danger { - .modal-header { - background-color: $modal-danger-color; - } - - .fa { - color: $modal-danger-color; - } - - .badge { - background-color: $modal-danger-color; - } - - .btn-primary-modal { - background: $modal-danger-color; - - &:hover, - &:focus, - &:active { - background-color: lighten($modal-danger-color, 5%) !important; - } - - &.active { - background-color: darken($modal-danger-color, 20%) !important; - // @extend .z-depth-1-half; - } - } - - .btn-outline-secondary-modal { - border: 2px solid $modal-danger-color; - color: $modal-danger-color !important; - } - } - } -} - -.modal-sm .modal-content { - margin: 0 auto; - max-width: 300px; -} - -@media (min-width: 768px) { - .modal-sm { - // max-width: 100%; - max-width: 300px; - } -} - -.modal .modal-fluid, -.modal .modal-frame { - width: 100%; - max-width: 100%; -} - -/********************* - Modals -**********************/ - -// Modal Login & Modal Register -.modal-ext .modal-content { - .modal-header { - text-align: center; - } - - .options { - float: left; - } - - .modal-body .text-xs-center fieldset { - margin-top: 20px; - } - - .call { - margin-top: 1rem; - } - - .modal-body { - padding: 2rem 2rem 1rem 2rem; - } -} - -.modal-content:not(.card-image) { - .close { - position: absolute; - right: 15px; - } -} - -// Modal Cart -.modal-cart { - li p { - margin: 5px; - font-weight: 400; - - .badge { - margin-left: 10px; - margin-top: 3px; - font-weight: 400; - position: absolute; - } - - .quantity { - font-size: 16px; - margin-right: 7px; - font-weight: 300; - } - } - - .cartPageLink { - margin-left: 10px; - - a { - text-decoration: underline; - color: #666; - } - } - - .total { - float: right; - font-weight: 400; - } -} - -// Modals normalize -.cf-phone { - margin-left: 7px; -} - -// Container that the modal scrolls within -.side-modal { - position: fixed; - width: 400px; - height: 100%; - width: 100%; - z-index: 9999; - // Shell div to position the modal with bottom padding - .modal-dialog { - position: absolute; - bottom: 10px; - right: 10px; - width: 400px; - margin: 10px; - // @extend .z-depth-1-half; - @media (max-width: 760px) { - display: none; - } - } - - // Actual modal - .modal-header { - padding: 1rem; - - .heading { - margin: 0; - padding: 0; - } - } - - .modal-content { - border: none; - } - - // Modal background -} -.modal-dynamic > :first-child { - display: flex; - flex-direction: column; - height: 100%; -} - -.side-modal.fade:not(.show) .modal-dialog { - -webkit-transform: translate3d(25%, 0, 0); - transform: translate3d(25%, 0, 0); -} - -// Transparent backdrop -.transparent-bd { - opacity: 0 !important; -} - -.modal-backdrop.in { - opacity: 0.5; -} - -.modal-backdrop { - opacity: 0.5; -} - -#exampleModalScroll { - overflow-x: hidden; - overflow-y: auto; -} - -.modal-open .modal { - overflow-x: hidden; - overflow-y: hidden; -} - -.form-dark { - .card-image { - background-size: 100%; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/modals/index.ts b/projects/angular-bootstrap-md/src/lib/free/modals/index.ts deleted file mode 100755 index d9407229..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/modals/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { ModalBackdropComponent, ModalBackdropOptions } from './modalBackdrop.component'; -export { ModalOptions, MDBModalRef } from './modal.options'; -export { ModalDirective } from './modal.directive'; -export { ModalModule } from './modal.module'; -export { MDBModalService } from './modal.service'; -export { ModalContainerComponent } from './modalContainer.component'; diff --git a/projects/angular-bootstrap-md/src/lib/free/modals/licens.md b/projects/angular-bootstrap-md/src/lib/free/modals/licens.md deleted file mode 100755 index 68dc12fd..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/modals/licens.md +++ /dev/null @@ -1 +0,0 @@ -https://github.com/valor-software/ngx-bootstrap/blob/development/LICENSE \ No newline at end of file diff --git a/projects/angular-bootstrap-md/src/lib/free/modals/modal.directive.ts b/projects/angular-bootstrap-md/src/lib/free/modals/modal.directive.ts deleted file mode 100755 index ca9a7c72..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/modals/modal.directive.ts +++ /dev/null @@ -1,406 +0,0 @@ -import { - AfterViewInit, - Component, - ComponentRef, - ElementRef, - EventEmitter, - HostListener, - Input, - OnDestroy, - OnChanges, - Output, - Renderer2, - ViewContainerRef, - ViewEncapsulation, -} from '@angular/core'; - -import { document, navigator, window } from '../utils/facade/browser'; - -import { isBs3 } from '../utils/ng2-bootstrap-config'; -import { Utils } from '../utils/utils.class'; -import { ModalBackdropComponent } from './modalBackdrop.component'; -import { ClassName, DISMISS_REASONS, modalConfigDefaults, ModalOptions } from './modal.options'; -import { ComponentLoader } from '../utils/component-loader/component-loader.class'; -import { ComponentLoaderFactory } from '../utils/component-loader/component-loader.factory'; - -const TRANSITION_DURATION = 300; -const BACKDROP_TRANSITION_DURATION = 150; - -/** Mark any code with directive to show it's content in modal */ -@Component({ - // tslint:disable-next-line:component-selector - selector: '[mdbModal]', - template: '', - styleUrls: ['./modals-module.scss'], - encapsulation: ViewEncapsulation.None, - exportAs: 'mdb-modal, mdbModal', -}) -// tslint:disable-next-line:component-class-suffix -export class ModalDirective implements AfterViewInit, OnDestroy, OnChanges { - /** allows to set modal configuration via element property */ - @Input() - public set config(conf: ModalOptions | any) { - this._config = this.getConfig(conf); - } - - public get config(): ModalOptions | any { - return this._config; - } - - /** This event fires immediately when the `show` instance method is called. */ - // tslint:disable-next-line:no-output-on-prefix - @Output() public onShow: EventEmitter = new EventEmitter(); - @Output() public open: EventEmitter = new EventEmitter(); - /** This event is fired when the modal has been made visible to the user (will wait for CSS transitions to complete) */ - // tslint:disable-next-line:no-output-on-prefix - @Output() public onShown: EventEmitter = new EventEmitter(); - @Output() public opened: EventEmitter = new EventEmitter(); - /** This event is fired immediately when the hide instance method has been called. */ - // tslint:disable-next-line:no-output-on-prefix - @Output() public onHide: EventEmitter = new EventEmitter(); - @Output() public close: EventEmitter = new EventEmitter(); - /** This event is fired when the modal has finished being hidden from the user (will wait for CSS transitions to complete). */ - // tslint:disable-next-line:no-output-on-prefix - @Output() public onHidden: EventEmitter = new EventEmitter(); - @Output() public closed: EventEmitter = new EventEmitter(); - - // seems like an Options - public isAnimated = true; - /** This field contains last dismiss reason. - Possible values: `backdrop-click`, `esc` and `null` (if modal was closed by direct call of `.hide()`). */ - public dismissReason: string | any; - - public get isShown(): boolean { - return this._isShown; - } - - protected _config: ModalOptions | any; - protected _isShown = false; - - protected isBodyOverflowing = false; - protected originalBodyPadding = 0; - protected scrollbarWidth = 0; - - protected timerHideModal: any = 0; - protected timerRmBackDrop: any = 0; - - // reference to backdrop component - protected backdrop: ComponentRef | undefined; - private _backdrop: ComponentLoader; - // todo: implement _dialog - _dialog: any; - - isNested = false; - - utils: Utils = new Utils(); - - @HostListener('keydown', ['$event']) onKeyDown(event: any) { - this.utils.focusTrapModal(event, this._element); - } - - @HostListener('click', ['$event']) - public onClick(event: any): void { - if ( - this.config.ignoreBackdropClick || - this.config.backdrop === 'static' || - event.target !== this._element.nativeElement - ) { - return; - } - this.dismissReason = DISMISS_REASONS.BACKRDOP; - this.hide(event); - } - - // todo: consider preventing default and stopping propagation - @HostListener('keydown.esc') - public onEsc(): void { - if (this.config.keyboard) { - this.dismissReason = DISMISS_REASONS.ESC; - this.hide(); - } - } - - public constructor( - protected _element: ElementRef, - _viewContainerRef: ViewContainerRef, - protected _renderer: Renderer2, - clf: ComponentLoaderFactory - ) { - this._backdrop = clf.createLoader( - _element, - _viewContainerRef, - _renderer - ); - } - - public ngOnDestroy(): any { - this.config = void 0; - if (this._isShown) { - this._isShown = false; - this.hideModal(); - this._backdrop.dispose(); - } - } - - public ngAfterViewInit(): any { - this._config = this._config || this.getConfig(); - setTimeout(() => { - if (this._config.show) { - this.show(); - } - }, 0); - } - - public ngOnChanges(): any { - this.config.backdrop ? this.showBackdrop() : this.removeBackdrop(); - } - - /* Public methods */ - - /** Allows to manually toggle modal visibility */ - public toggle(): void { - return this._isShown ? this.hide() : this.show(); - } - - /** Allows to manually open modal */ - public show(): void { - this.dismissReason = null; - this.onShow.emit(this); - this.open.emit(this); - if (this._isShown) { - return; - } - clearTimeout(this.timerHideModal); - clearTimeout(this.timerRmBackDrop); - - this._isShown = true; - - this.checkScrollbar(); - this.setScrollbar(); - - if (document && document.body) { - if (document.body.classList.contains(ClassName.OPEN)) { - this.isNested = true; - } else { - this._renderer.addClass(document.body, ClassName.OPEN); - } - } - this.showBackdrop(() => { - this.showElement(); - }); - if (!this.config.backdrop && this.config.ignoreBackdropClick) { - this._renderer.setStyle(this._element.nativeElement, 'position', 'fixed'); - - if ( - navigator.userAgent.indexOf('Safari') !== -1 && - navigator.userAgent.indexOf('Chrome') === -1 - ) { - this._renderer.setStyle(this._element.nativeElement, 'overflow', 'unset'); - this._renderer.setStyle(this._element.nativeElement, 'overflow-y', 'unset'); - this._renderer.setStyle(this._element.nativeElement, 'overflow-x', 'unset'); - } - } - } - - /** Allows to manually close modal */ - public hide(event?: Event): void { - if (event) { - event.preventDefault(); - } - - // fix(modal): resolved problem with not pausing iframe/video when closing modal - const iframeElements = Array.from(this._element.nativeElement.querySelectorAll('iframe')); - const videoElements = Array.from(this._element.nativeElement.querySelectorAll('video')); - - iframeElements.forEach((iframe: HTMLIFrameElement) => { - const srcAttribute: any = iframe.getAttribute('src'); - this._renderer.setAttribute(iframe, 'src', srcAttribute); - }); - - videoElements.forEach((video: HTMLVideoElement) => { - video.pause(); - }); - - this.onHide.emit(this); - this.close.emit(this); - - if (!this._isShown) { - return; - } - - clearTimeout(this.timerHideModal); - clearTimeout(this.timerRmBackDrop); - - this._isShown = false; - this._renderer.removeClass(this._element.nativeElement, ClassName.IN); - if (!isBs3()) { - this._renderer.removeClass(this._element.nativeElement, ClassName.SHOW); - } - - if (this.isAnimated) { - this.timerHideModal = setTimeout(() => this.hideModal(), TRANSITION_DURATION); - } else { - this.hideModal(); - } - } - - /** Private methods @internal */ - protected getConfig(config?: ModalOptions): ModalOptions { - return Object.assign({}, modalConfigDefaults, config); - } - - /** - * Show dialog - * @internal - */ - protected showElement(): void { - if ( - !this._element.nativeElement.parentNode || - this._element.nativeElement.parentNode.nodeType !== Node.ELEMENT_NODE - ) { - // don't move modals dom position - if (document && document.body) { - document.body.appendChild(this._element.nativeElement); - } - } - - this._renderer.setAttribute(this._element.nativeElement, 'aria-hidden', 'false'); - this._renderer.setStyle(this._element.nativeElement, 'display', 'block'); - this._renderer.setProperty(this._element.nativeElement, 'scrollTop', 0); - - if (this.isAnimated) { - Utils.reflow(this._element.nativeElement); - } - - this._renderer.addClass(this._element.nativeElement, ClassName.IN); - if (!isBs3()) { - this._renderer.addClass(this._element.nativeElement, ClassName.SHOW); - } - - const transitionComplete = () => { - if (this._config.focus) { - this._element.nativeElement.focus(); - } - this.onShown.emit(this); - this.opened.emit(this); - }; - - if (this.isAnimated) { - setTimeout(transitionComplete, TRANSITION_DURATION); - } else { - transitionComplete(); - } - } - - /** @internal */ - protected hideModal(): void { - this._renderer.setAttribute(this._element.nativeElement, 'aria-hidden', 'true'); - this._renderer.setStyle(this._element.nativeElement, 'display', 'none'); - this.showBackdrop(() => { - if (!this.isNested) { - if (document && document.body) { - this._renderer.removeClass(document.body, ClassName.OPEN); - } - } - this.resetAdjustments(); - this.focusOtherModal(); - this.onHidden.emit(this); - this.closed.emit(this); - }); - } - - /** @internal */ - protected showBackdrop(callback?: Function): void { - if ( - this._isShown && - this.config.backdrop && - (!this.backdrop || !this.backdrop.instance.isShown) - ) { - this.removeBackdrop(); - this._backdrop - .attach(ModalBackdropComponent) - .to('body') - .show({ isAnimated: this.isAnimated }); - this.backdrop = this._backdrop._componentRef; - - if (!callback) { - return; - } - - if (!this.isAnimated) { - callback(); - return; - } - - setTimeout(callback, BACKDROP_TRANSITION_DURATION); - } else if (!this._isShown && this.backdrop) { - this.backdrop.instance.isShown = false; - - const callbackRemove = () => { - this.removeBackdrop(); - if (callback) { - callback(); - } - }; - - if (this.backdrop.instance.isAnimated) { - this.timerRmBackDrop = setTimeout(callbackRemove, BACKDROP_TRANSITION_DURATION); - } else { - callbackRemove(); - } - } else if (callback) { - callback(); - } - } - - /** @internal */ - protected removeBackdrop(): void { - this._backdrop.hide(); - this.backdrop = undefined; - } - - protected focusOtherModal() { - try { - const otherOpenedModals = this._element.nativeElement.parentElement.querySelectorAll( - '.in[mdbModal]' - ); - if (!otherOpenedModals.length) { - return; - } - otherOpenedModals[otherOpenedModals.length - 1].nativeElement.focus(); - } catch (error) {} - } - - /** @internal */ - protected resetAdjustments(): void { - this._renderer.setStyle(this._element.nativeElement, 'paddingLeft', ''); - this._renderer.setStyle(this._element.nativeElement, 'paddingRight', ''); - } - - /** Scroll bar tricks */ - /** @internal */ - protected checkScrollbar(): void { - this.isBodyOverflowing = document.body.clientWidth < window.innerWidth; - this.scrollbarWidth = this.getScrollbarWidth(); - } - - protected setScrollbar(): void { - if (!document) { - return; - } - this.originalBodyPadding = parseInt( - window.getComputedStyle(document.body).getPropertyValue('padding-right') || 0, - 10 - ); - } - - // thx d.walsh - protected getScrollbarWidth(): number { - const scrollDiv = this._renderer.createElement('div', void 0); - this._renderer.appendChild(document.body, scrollDiv); - scrollDiv.className = ClassName.SCROLLBAR_MEASURER; - const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; - document.body.removeChild(scrollDiv); - return scrollbarWidth; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/modals/modal.module.ts b/projects/angular-bootstrap-md/src/lib/free/modals/modal.module.ts deleted file mode 100755 index 187c0cbf..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/modals/modal.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { NgModule, ModuleWithProviders, NO_ERRORS_SCHEMA } from '@angular/core'; - -import { ModalBackdropComponent } from './modalBackdrop.component'; -import { ModalDirective } from './modal.directive'; -import { PositioningService } from '../utils/positioning/positioning.service'; -import { ComponentLoaderFactory } from '../utils/component-loader/component-loader.factory'; -import { ModalContainerComponent } from './modalContainer.component'; -import { MDBModalService } from './modal.service'; - -@NgModule({ - declarations: [ModalBackdropComponent, ModalDirective, ModalContainerComponent], - exports: [ModalBackdropComponent, ModalDirective], - entryComponents: [ModalBackdropComponent, ModalContainerComponent], - schemas: [NO_ERRORS_SCHEMA] -}) -export class ModalModule { - public static forRoot(): ModuleWithProviders { - return {ngModule: ModalModule, providers: [MDBModalService, ComponentLoaderFactory, PositioningService]}; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/modals/modal.options.ts b/projects/angular-bootstrap-md/src/lib/free/modals/modal.options.ts deleted file mode 100755 index 48fd8c3a..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/modals/modal.options.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable() -export class ModalOptions { - /** - * Includes a modal-backdrop element. Alternatively, specify static for a backdrop which doesn't close the modal on click. - */ - backdrop?: boolean | 'static' | any; - /** - * Closes the modal when escape key is pressed. - */ - keyboard?: boolean; - - focus?: boolean; - /** - * Shows the modal when initialized. - */ - show?: boolean; - /** - * Ignore the backdrop click - */ - ignoreBackdropClick?: boolean; - /** - * Css class for opened modal - */ - class?: string; - /** - * Toggle animation - */ - containerClass?: string; - animated?: boolean; - scroll?: boolean; - data?: Object; -} - -@Injectable() -export class MDBModalRef { - /** - * Reference to a component inside the modal. Null if modal's been created with TemplateRef - */ - content?: any | null; - /** - * Hides the modal - */ - hide(): void {} -} - -export const modalConfigDefaults: ModalOptions = { - backdrop: true, - keyboard: true, - focus: true, - show: false, - ignoreBackdropClick: false, - class: '', - containerClass: '', - animated: true, - scroll: false, - data: {}, -}; - -export const ClassName: any = { - SCROLLBAR_MEASURER: 'modal-scrollbar-measure', - BACKDROP: 'modal-backdrop', - OPEN: 'modal-open', - FADE: 'fade', - IN: 'in', // bs3 - SHOW: 'show', // bs4 -}; - -export const Selector: any = { - DIALOG: '.modal-dialog', - DATA_TOGGLE: '[data-toggle="modal"]', - DATA_DISMISS: '[data-dismiss="modal"]', - FIXED_CONTENT: '.navbar-fixed-top, .navbar-fixed-bottom, .is-fixed', -}; - -export const TransitionDurations: any = { - MODAL: 300, - BACKDROP: 150, -}; - -export const DISMISS_REASONS = { - BACKRDOP: 'backdrop-click', - ESC: 'esc', -}; diff --git a/projects/angular-bootstrap-md/src/lib/free/modals/modal.service.ts b/projects/angular-bootstrap-md/src/lib/free/modals/modal.service.ts deleted file mode 100755 index a4b2f3bd..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/modals/modal.service.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { - ComponentRef, - Injectable, - TemplateRef, - EventEmitter, - Renderer2, - RendererFactory2, - ViewContainerRef, - ElementRef, -} from '@angular/core'; - -import { ComponentLoader } from '../utils/component-loader/component-loader.class'; -import { ComponentLoaderFactory } from '../utils/component-loader/component-loader.factory'; -import { ModalBackdropComponent } from './modalBackdrop.component'; -import { ModalContainerComponent } from './modalContainer.component'; -import { - MDBModalRef, - ClassName, - modalConfigDefaults, - ModalOptions, - TransitionDurations, -} from './modal.options'; - -@Injectable() -export class MDBModalService { - public config: ModalOptions = modalConfigDefaults; - private renderer: Renderer2; - private vcr: ViewContainerRef; - private el: ElementRef; - - public open: EventEmitter = new EventEmitter(); - public opened: EventEmitter = new EventEmitter(); - public close: EventEmitter = new EventEmitter(); - public closed: EventEmitter = new EventEmitter(); - - protected isBodyOverflowing = false; - protected originalBodyPadding = 0; - - protected scrollbarWidth = 0; - - protected backdropRef: ComponentRef | any; - private _backdropLoader: ComponentLoader; - private modalsCount = 0; - private lastDismissReason: any = ''; - - private loaders: ComponentLoader[] = []; - public constructor(rendererFactory: RendererFactory2, private clf: ComponentLoaderFactory) { - this._backdropLoader = this.clf.createLoader( - this.el, - this.vcr, - this.renderer - ); - this.renderer = rendererFactory.createRenderer(null, null); - } - - /** Shows a modal */ - show(content: string | TemplateRef | any, config?: any): MDBModalRef { - this.modalsCount++; - this._createLoaders(); - this.config = Object.assign({}, modalConfigDefaults, config); - this._showBackdrop(); - this.lastDismissReason = null; - return this._showModal(content); - } - - hide(level: number) { - if (this.modalsCount === 1) { - this._hideBackdrop(); - this.resetScrollbar(); - } - this.modalsCount = this.modalsCount >= 1 ? this.modalsCount - 1 : 0; - setTimeout( - () => { - this._hideModal(level); - this.removeLoaders(level); - }, - this.config.animated ? TransitionDurations.BACKDROP : 0 - ); - } - - _showBackdrop(): void { - const isBackdropEnabled = this.config.backdrop || this.config.backdrop === 'static'; - const isBackdropInDOM = !this.backdropRef || !this.backdropRef.instance.isShown; - - if (this.modalsCount === 1) { - this.removeBackdrop(); - - if (isBackdropEnabled && isBackdropInDOM) { - this._backdropLoader - .attach(ModalBackdropComponent) - .to('body') - .show({ isAnimated: this.config.animated }); - this.backdropRef = this._backdropLoader._componentRef; - } - } - } - - _hideBackdrop(): void { - if (!this.backdropRef) { - return; - } - this.backdropRef.instance.isShown = false; - const duration = this.config.animated ? TransitionDurations.BACKDROP : 0; - setTimeout(() => this.removeBackdrop(), duration); - } - - _showModal(content: any): MDBModalRef { - const modalLoader = this.loaders[this.loaders.length - 1]; - const mdbModalRef = new MDBModalRef(); - const modalContainerRef = modalLoader - .provide({ provide: ModalOptions, useValue: this.config }) - .provide({ provide: MDBModalRef, useValue: mdbModalRef }) - .attach(ModalContainerComponent) - .to('body') - .show({ - content, - isAnimated: this.config.animated, - data: this.config.data, - mdbModalService: this, - }); - modalContainerRef.instance.focusModalElement(); - modalContainerRef.instance.level = this.getModalsCount(); - mdbModalRef.hide = () => { - modalContainerRef.instance.hide(); - }; - mdbModalRef.content = modalLoader.getInnerComponent() || null; - return mdbModalRef; - } - - _hideModal(level: number): void { - const modalLoader = this.loaders[level - 1]; - if (modalLoader) { - modalLoader.hide(); - } - } - - getModalsCount(): number { - return this.modalsCount; - } - - setDismissReason(reason: string) { - this.lastDismissReason = reason; - } - - protected removeBackdrop(): void { - this._backdropLoader.hide(); - this.backdropRef = null; - } - - /** AFTER PR MERGE MODAL.COMPONENT WILL BE USING THIS CODE*/ - /** Scroll bar tricks */ - /** @internal */ - public checkScrollbar(): void { - this.isBodyOverflowing = document.body.clientWidth < window.innerWidth; - this.scrollbarWidth = this.getScrollbarWidth(); - } - - public setScrollbar(): void { - if (!document) { - return; - } - - this.originalBodyPadding = parseInt( - window.getComputedStyle(document.body).getPropertyValue('padding-right') || '0', - 10 - ); - } - - private resetScrollbar(): void { - document.body.style.paddingRight = this.originalBodyPadding + 'px'; - } - - // thx d.walsh - private getScrollbarWidth(): number { - const scrollDiv = this.renderer.createElement('div'); - this.renderer.addClass(scrollDiv, ClassName.SCROLLBAR_MEASURER); - this.renderer.appendChild(document.body, scrollDiv); - const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; - this.renderer.removeChild(document.body, scrollDiv); - - return scrollbarWidth; - } - - private _createLoaders(): void { - const loader = this.clf.createLoader(this.el, this.vcr, this.renderer); - this.copyEvent(loader.onBeforeShow, this.open); - this.copyEvent(loader.onShown, this.opened); - this.copyEvent(loader.onBeforeHide, this.close); - this.copyEvent(loader.onHidden, this.closed); - this.loaders.push(loader); - } - - private removeLoaders(level: number): void { - this.loaders.splice(level - 1, 1); - this.loaders.forEach((loader: ComponentLoader, i: number) => { - loader.instance.level = i + 1; - }); - } - - private copyEvent(from: EventEmitter, to: EventEmitter) { - from.subscribe(() => { - to.emit(this.lastDismissReason); - }); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/modals/modalBackdrop.component.html b/projects/angular-bootstrap-md/src/lib/free/modals/modalBackdrop.component.html deleted file mode 100755 index e69de29b..00000000 diff --git a/projects/angular-bootstrap-md/src/lib/free/modals/modalBackdrop.component.ts b/projects/angular-bootstrap-md/src/lib/free/modals/modalBackdrop.component.ts deleted file mode 100755 index a211a8da..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/modals/modalBackdrop.component.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Component, ElementRef, OnInit, Renderer2, HostBinding } from '@angular/core'; - -import { ClassName } from './modal.options'; -import { isBs3 } from '../utils/ng2-bootstrap-config'; -import { Utils } from '../utils/utils.class'; - -export class ModalBackdropOptions { - public animate = true; - - public constructor(options: ModalBackdropOptions) { - Object.assign(this, options); - } -} - -/** This component will be added as background layout for modals if enabled */ -@Component({ - selector: 'mdb-modal-backdrop', - template: ``, -}) -export class ModalBackdropComponent implements OnInit { - @HostBinding('class.modal-backdrop') public classNameBackDrop = true; - - public get isAnimated(): boolean { - return this._isAnimated; - } - - public set isAnimated(value: boolean) { - this._isAnimated = value; - } - - public get isShown(): boolean { - return this._isShown; - } - - public set isShown(value: boolean) { - this._isShown = value; - if (value) { - this.renderer.addClass(this.element.nativeElement, `${ClassName.IN}`); - - if (!isBs3()) { - this.renderer.addClass(this.element.nativeElement, `${ClassName.SHOW}`); - } - } else { - this.renderer.removeClass(this.element.nativeElement, `${ClassName.IN}`); - - if (!isBs3()) { - this.renderer.removeClass(this.element.nativeElement, `${ClassName.SHOW}`); - } - } - } - - protected _isAnimated: boolean; - protected _isShown = false; - - public constructor(public element: ElementRef, public renderer: Renderer2) {} - - ngOnInit(): void { - if (this.isAnimated) { - this.renderer.addClass(this.element.nativeElement, `${ClassName.FADE}`); - Utils.reflow(this.element.nativeElement); - } else { - this.renderer.addClass(this.element.nativeElement, `${ClassName.FADE}`); - Utils.reflow(this.element.nativeElement); - } - this.isShown = true; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/modals/modalContainer.component.html b/projects/angular-bootstrap-md/src/lib/free/modals/modalContainer.component.html deleted file mode 100755 index f2120a3b..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/modals/modalContainer.component.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/projects/angular-bootstrap-md/src/lib/free/modals/modalContainer.component.ts b/projects/angular-bootstrap-md/src/lib/free/modals/modalContainer.component.ts deleted file mode 100755 index 68302131..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/modals/modalContainer.component.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { - Component, - ElementRef, - HostListener, - OnDestroy, - OnInit, - Renderer2, - HostBinding, - ViewEncapsulation, -} from '@angular/core'; -import { ClassName, DISMISS_REASONS, ModalOptions, TransitionDurations } from './modal.options'; -import { isBs3 } from '../utils/ng2-bootstrap-config'; -import { Utils } from '../utils'; -import { MDBModalService } from './modal.service'; - -@Component({ - selector: 'mdb-modal-container', - templateUrl: 'modalContainer.component.html', - styleUrls: ['./modals-module.scss'], - encapsulation: ViewEncapsulation.None, -}) -export class ModalContainerComponent implements OnInit, OnDestroy { - modalClass = 'modal'; - @HostBinding('tabindex') tabindex = -1; - @HostBinding('role') role = 'dialog'; - @HostBinding('class.modal') modal = true; - - private mdbModalService: MDBModalService; - - public config: ModalOptions; - public isShown = false; - public level: number; - public isAnimated: boolean; - protected _element: ElementRef; - private isModalHiding = false; - - private utils: Utils = new Utils(); - - @HostListener('click', ['$event']) - public onClick(event: any): void { - if ( - this.config.ignoreBackdropClick || - this.config.backdrop === 'static' || - event.target !== this._element.nativeElement - ) { - return; - } - this.mdbModalService.setDismissReason(DISMISS_REASONS.BACKRDOP); - this.hide(); - } - - @HostListener('window:keydown.esc') - public onEsc(): void { - if (this.config.keyboard && this.level === this.mdbModalService.getModalsCount()) { - this.mdbModalService.setDismissReason(DISMISS_REASONS.ESC); - this.hide(); - } - } - - @HostListener('keydown', ['$event']) onKeyDown(event: any) { - this.utils.focusTrapModal(event, this._element); - } - - public constructor(options: ModalOptions, _element: ElementRef, private _renderer: Renderer2) { - // this.mdbModalService = msConfig.serviceInstance; - - this._element = _element; - this.config = Object.assign({}, options); - } - - ngOnInit(): void { - if (this.config.animated) { - this._renderer.addClass(this._element.nativeElement, 'fade'); - } - this._renderer.setStyle(this._element.nativeElement, 'display', 'block'); - if ( - (window && - window.navigator.userAgent.indexOf('Edge') !== -1 && - this.config && - this.config.toString().indexOf('side-modal') === -1) || - (window && - window.navigator.userAgent.indexOf('Edge') !== -1 && - this.config && - this.config.toString().indexOf('modal-full-height') === -1) - ) { - this.isShown = true; - this._renderer.addClass(this._element.nativeElement, isBs3() ? ClassName.IN : ClassName.SHOW); - this._renderer.setStyle(this._element.nativeElement, 'transition', 'transform 0.3s ease-out'); - this._renderer.setStyle(this._element.nativeElement, 'transform', 'translate(0, 25px)'); - } else { - setTimeout( - () => { - this.isShown = true; - this._renderer.addClass( - this._element.nativeElement, - isBs3() ? ClassName.IN : ClassName.SHOW - ); - }, - this.isAnimated ? TransitionDurations.BACKDROP : 0 - ); - } - - if (document && document.body) { - if (this.mdbModalService.getModalsCount() === 1) { - this.mdbModalService.checkScrollbar(); - this.mdbModalService.setScrollbar(); - } - this._renderer.addClass(document.body, ClassName.OPEN); - } - - if (this.config.containerClass) { - this.updateContainerClass(); - } - - if (this.config.scroll) { - this._renderer.setStyle(this._element.nativeElement, 'overflow-y', 'auto'); - } - } - - focusModalElement() { - if (this.config.focus) { - this._element.nativeElement.focus(); - } - } - - updateContainerClass() { - if (this.config.containerClass) { - const containerClasses = this.config.containerClass; - const classArr = containerClasses.split(' '); - - for (let i = 0; i < classArr.length; i++) { - this._renderer.addClass(this._element.nativeElement, classArr[i]); - } - } - } - - ngOnDestroy(): void { - if (this.isShown) { - this.hide(); - } - } - - hide(): void { - if (this.isModalHiding || !this.isShown) { - return; - } - this.isModalHiding = true; - this._renderer.removeClass( - this._element.nativeElement, - isBs3() ? ClassName.IN : ClassName.SHOW - ); - - // fix(modal): resolved problem with not pausing iframe/video when closing modal - const iframeElements = Array.from(this._element.nativeElement.querySelectorAll('iframe')); - const videoElements = Array.from(this._element.nativeElement.querySelectorAll('video')); - - iframeElements.forEach((iframe: HTMLIFrameElement) => { - const srcAttribute: any = iframe.getAttribute('src'); - this._renderer.setAttribute(iframe, 'src', srcAttribute); - }); - - videoElements.forEach((video: HTMLVideoElement) => { - video.pause(); - }); - - setTimeout( - () => { - this.isShown = false; - if (document && document.body && this.mdbModalService.getModalsCount() === 1) { - this._renderer.removeClass(document.body, ClassName.OPEN); - } - this.mdbModalService.hide(this.level); - this.isModalHiding = false; - }, - this.isAnimated ? TransitionDurations.MODAL : 0 - ); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/modals/modalService.config.ts b/projects/angular-bootstrap-md/src/lib/free/modals/modalService.config.ts deleted file mode 100755 index e9493746..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/modals/modalService.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const msConfig = { - serviceInstance: new Object() -}; diff --git a/projects/angular-bootstrap-md/src/lib/free/modals/modals-module.scss b/projects/angular-bootstrap-md/src/lib/free/modals/modals-module.scss deleted file mode 100644 index e1fea9c7..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/modals/modals-module.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import '../../assets/scss/core/mixins'; -@import '../../assets/scss/core/colors'; -@import '../../assets/scss/core/variables'; -@import '../../assets/scss/core/helpers'; - -@import 'modals'; diff --git a/projects/angular-bootstrap-md/src/lib/free/navbars/_navbars.scss b/projects/angular-bootstrap-md/src/lib/free/navbars/_navbars.scss deleted file mode 100644 index 195b1ade..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/navbars/_navbars.scss +++ /dev/null @@ -1,331 +0,0 @@ -// Navbars -.navbar { - box-shadow: $z-depth-1; - font-weight: $navbar-font-weight; - form { - .md-form { - input { - margin: 0 $navbar-form-input-mr $navbar-form-input-mb $navbar-form-input-ml; - } - } - } - .breadcrumb { - margin: 0; - padding: $navbar-breadcrumb-padding-top 0 0 $navbar-breadcrumb-padding-left; - background-color: inherit; - font-size: $navbar-double-font-size; - font-weight: $navbar-font-weight; - .breadcrumb-item { - color: $white-base; - &.active { - color: $navbar-breadcrumb-color; - } - &:before { - color: $navbar-breadcrumb-color; - } - } - } - .navbar-toggler { - outline: 0; - border-width: 0; - } - .nav-flex-icons { - flex-direction: row; - } - .container { - @media (max-width: $medium-screen) { - width: 100%; - .navbar-toggler-right { - right: 0; - } - } - } - .nav-item { - .nav-link { - display: block; - &.disabled { - &:active { - pointer-events: none; - } - } - .fas, - .fab, - .far { - padding-right: $navbar-flex-icons-padding-lg; - padding-left: $navbar-flex-icons-padding-lg; - } - @media (max-width: $medium-screen) { - padding-right: $navbar-flex-icons-padding-md; - padding-left: $navbar-flex-icons-padding-md; - } - } - } - .dropdown-menu { - position: absolute !important; - margin-top: 0; - a { - padding: $navbar-dropdown-menu-padding; - font-size: $navbar-dropdown-font-size; - font-weight: $navbar-font-weight; - color: $black; - } - form { - @media (max-width: $small-screen) { - width: 17rem; - } - @media (min-width: $small-screen) { - width: 22rem; - } - } - } - &.navbar-light { - @include make-navbar( - $navbar-light-disabled-color, - $navbar-light-toggler-icon, - $black, - $navbar-light-hover-color, - $navbar-light-bg-active-color - ); - } - &.navbar-dark { - @include make-navbar( - $navbar-dark-disabled-color, - $navbar-dark-toggler-icon, - $white, - $navbar-dark-hover-color, - $navbar-dark-bg-active-color - ); - } - &.scrolling-navbar { - @media (min-width: $small-screen) { - transition: $navbar-scrolling-transition; - padding-top: $navbar-scrolling-padding; - padding-bottom: $navbar-scrolling-padding; - .navbar-nav > li { - transition-duration: $navbar-scrolling-transition-duration; - } - &.top-nav-collapse { - padding-top: $navbar-top-collapse-padding; - padding-bottom: $navbar-top-collapse-padding; - } - } - } -} - -// Angular styles -@media (min-width: 1200px) { - .navbar.navbar-expand-xl { - links, - navlinks { - display: flex; - flex-direction: row; - align-items: center !important; - align-self: center !important; - width: 100%; - } - } -} - -@media (min-width: 992px) { - .navbar > logo > div > a { - img { - margin-left: 20px; - } - } - .navbar.navbar-expand-lg { - links, - navlinks { - display: flex; - flex-direction: row; - align-items: center !important; - align-self: center !important; - width: 100%; - } - } -} - -@media (min-width: 768px) { - .navbar.navbar-expand-md { - links, - navlinks { - display: flex; - flex-direction: row; - width: 100%; - } - } -} - -@media (min-width: 576px) { - .navbar.navbar-expand-sm { - links, - navlinks { - display: flex; - flex-direction: row; - width: 100%; - } - } -} - -@media all and (max-width: 992px) { - .collapsed-navbar-scroll { - max-height: calc(100vh - 40px); - overflow-y: scroll; - } -} - -// .navbar { -// z-index: 91; -// } -.navbar-container { - order: -1; - width: 50px !important; - padding-left: 5px; - padding-right: 5px; -} - -.navbar-nav { - .dropdown-menu-right.dropdown-menu { - left: unset; - } - .dropdown-menu { - top: 100% !important; - transform: translate3d(0, 0, 0) !important; - } -} - -.breadcrumbs { - display: flex; - padding-left: 5px; - padding-right: 5px; - order: 0; - align-items: center; - @media (min-width: 1441px) { - margin-left: -0.6rem; - } -} //EDGE -@supports (-ms-ime-align: auto) { - .ie-nav { - @media all and (min-width: 992px) { - .navbar-nav.nav-flex-icons { - position: absolute; - top: 30%; - right: 0; - } - .navbar-nav { - position: absolute; - top: 30%; - margin-left: 88px; - } - .navbar-brand > img { - margin-top: -2px; - padding-right: 16px; - } - } - .navbar-toggler { - position: absolute; - margin-top: -40px; - right: 0; - } - } - .intro-non-fixed-nav > links { - @media all and (min-width: 992px) { - .navbar-collapse { - display: inline-flex !important; - align-items: center !important; - justify-content: space-between !important; - } - } - } - .intro-fixed-nav { - @media all and (min-width: 992px) { - .navbar-nav.nav-flex-icons { - position: absolute; - top: 30%; - right: 0; - } - .navbar-nav { - position: absolute; - top: 30%; - margin-left: 88px; - } - .navbar-brand { - img { - margin-top: -2px; - padding-right: 16px; - } - } - } - .navbar-toggler { - position: absolute; - margin-top: -40px; - right: 0; - } - } -} //IE10+ -@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { - .ie-nav { - @media all and (min-width: 992px) { - .navbar-nav.nav-flex-icons { - position: absolute; - top: 30%; - right: 0; - } - .navbar-nav { - position: absolute; - top: 30%; - margin-left: 88px; - } - .navbar-brand > img { - margin-top: -2px; - padding-right: 16px; - } - } - .navbar-toggler { - position: absolute; - margin-top: -40px; - right: 0; - } - } - .intro-non-fixed-nav > links { - @media all and (min-width: 992px) { - .navbar-collapse { - display: inline-flex !important; - align-items: center !important; - justify-content: space-between !important; - } - } - } - .intro-fixed-nav { - @media all and (min-width: 992px) { - .navbar-nav.nav-flex-icons { - position: absolute; - top: 30%; - right: 0; - } - .navbar-nav { - position: absolute; - top: 30%; - margin-left: 88px; - } - .navbar-brand { - img { - margin-top: -2px; - padding-right: 16px; - } - } - } - .navbar-toggler { - position: absolute; - margin-top: -40px; - right: 0; - } - } -} - -// Fix for situation when in Chrome, dropdown button got default browser styling -button, -html [type='button'], -[type='reset'], -[type='submit'] { - -webkit-appearance: none; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/navbars/index.ts b/projects/angular-bootstrap-md/src/lib/free/navbars/index.ts deleted file mode 100755 index 1e2addff..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/navbars/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { NavbarComponent } from './navbar.component'; -export { NavbarModule } from './navbar.module'; -export { LinksComponent } from './links.component'; -export { NavlinksComponent } from './navlinks.component'; -export { LogoComponent } from './logo.component'; -export { NavbarService } from './navbar.service'; diff --git a/projects/angular-bootstrap-md/src/lib/free/navbars/links.component.ts b/projects/angular-bootstrap-md/src/lib/free/navbars/links.component.ts deleted file mode 100755 index 4ee9f6de..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/navbars/links.component.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { NavbarService } from './navbar.service'; -import { - AfterContentInit, - Component, - ContentChildren, - ElementRef, - QueryList, - EventEmitter, - Output, - Renderer2, -} from '@angular/core'; -import { RouterLinkWithHref } from '@angular/router'; - -@Component({ - // tslint:disable-next-line:component-selector - selector: 'links', - template: ` - - `, -}) -export class LinksComponent implements AfterContentInit { - @ContentChildren(RouterLinkWithHref, { read: ElementRef, descendants: true }) - links: QueryList; - - @Output() linkClick = new EventEmitter(); - - constructor(private _navbarService: NavbarService, private renderer: Renderer2) {} - - ngAfterContentInit() { - setTimeout(() => { - this.links.forEach((link: ElementRef) => { - this.renderer.listen(link.nativeElement, 'click', () => { - this._navbarService.setNavbarLinkClicks(); - }); - }); - }, 0); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/navbars/logo.component.ts b/projects/angular-bootstrap-md/src/lib/free/navbars/logo.component.ts deleted file mode 100755 index e7feda2d..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/navbars/logo.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {Component} from '@angular/core'; - -@Component({ - selector: 'logo, mdb-navbar-brand', - template: `` -}) -export class LogoComponent { - -} diff --git a/projects/angular-bootstrap-md/src/lib/free/navbars/navbar.component.html b/projects/angular-bootstrap-md/src/lib/free/navbars/navbar.component.html deleted file mode 100755 index c916a274..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/navbars/navbar.component.html +++ /dev/null @@ -1,32 +0,0 @@ - diff --git a/projects/angular-bootstrap-md/src/lib/free/navbars/navbar.component.ts b/projects/angular-bootstrap-md/src/lib/free/navbars/navbar.component.ts deleted file mode 100755 index 14177402..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/navbars/navbar.component.ts +++ /dev/null @@ -1,251 +0,0 @@ -import { NavbarService } from './navbar.service'; -import { - AfterContentChecked, - AfterViewInit, - Component, - ContentChild, - ElementRef, - HostListener, - Input, - OnInit, - Renderer2, - ViewChild, - ViewEncapsulation, - ChangeDetectorRef, - ChangeDetectionStrategy, - Inject, - NgZone, - OnDestroy, -} from '@angular/core'; -import { fromEvent, Subject } from 'rxjs'; -import { LinksComponent } from './links.component'; -import { DOCUMENT } from '@angular/common'; -import { takeUntil } from 'rxjs/operators'; - -@Component({ - selector: 'mdb-navbar', - templateUrl: 'navbar.component.html', - styleUrls: ['./navbars-module.scss'], - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class NavbarComponent implements AfterViewInit, OnInit, AfterContentChecked, OnDestroy { - @Input() iconBackground: string | string[]; - @Input() SideClass: string; - @Input() containerInside = true; - @Input() collapseId = 'navbarCollapse'; - @Input() scrollSensitivity = 120; - @Input() scrollableNavbar = false; - - private _destroy$: Subject = new Subject(); - - navbarLinkClicks: any; - shown = false; - - public doubleNav: boolean; - public height: number; - public duration = 350; // ms - - public collapse = true; - public showClass = false; - public collapsing = false; - - private _itemsLength = 0; - - ariaExpanded = false; - - @ViewChild('navbar', { static: true }) el: ElementRef; - @ViewChild('mobile', { static: false }) mobile: ElementRef; - @ViewChild('nav', { static: true }) navbar: ElementRef; - @ViewChild('container', { static: true }) container: ElementRef; - @ViewChild('toggler', { static: false }) toggler: ElementRef; - @ContentChild(LinksComponent, { static: false }) links: LinksComponent; - - constructor( - public renderer: Renderer2, - private _navbarService: NavbarService, - private _cdRef: ChangeDetectorRef, - private _ngZone: NgZone, - @Inject(DOCUMENT) private _document: any - ) { - this._navbarService - .getNavbarLinkClicks() - .pipe(takeUntil(this._destroy$)) - .subscribe(navbarLinkClicks => { - this.closeNavbarOnClick(navbarLinkClicks); - }); - } - - closeNavbarOnClick(navbarLinkClicks: any) { - this.navbarLinkClicks = navbarLinkClicks; - if (this.showClass) { - this.hide(); - } - } - - addTogglerIconClasses() { - if (this.iconBackground) { - if (Array.isArray(this.iconBackground)) { - this.iconBackground.forEach(iconClass => { - this.renderer.addClass(this.toggler.nativeElement, iconClass); - }); - } else { - this.renderer.addClass(this.toggler.nativeElement, this.iconBackground); - } - } - } - - private _listenToScroll() { - this._ngZone.runOutsideAngular(() => { - fromEvent(this._document, 'scroll') - .pipe(takeUntil(this._destroy$)) - .subscribe(() => { - if (window.pageYOffset > this.scrollSensitivity) { - this.renderer.addClass(this.navbar.nativeElement, 'top-nav-collapse'); - } else { - this.renderer.removeClass(this.navbar.nativeElement, 'top-nav-collapse'); - } - }); - }); - } - - ngOnInit() { - const isDoubleNav = this.SideClass.split(' '); - this.doubleNav = isDoubleNav.indexOf('double-nav') !== -1; - } - - ngAfterViewInit() { - if (!this.containerInside) { - const childrens = Array.from(this.container.nativeElement.children); - childrens.forEach(child => { - this.renderer.appendChild(this.navbar.nativeElement, child); - this.container.nativeElement.remove(); - }); - } - if (this.el.nativeElement.children.length === 0) { - this.el.nativeElement.remove(); - } - this.addTogglerIconClasses(); - if (this.scrollableNavbar) { - this.renderer.addClass(this.el.nativeElement, 'collapsed-navbar-scroll'); - } - - if (this.navbar.nativeElement.classList.contains('scrolling-navbar')) { - this._listenToScroll(); - } - } - - toggle() { - if (!this.collapsing) { - if (this.shown) { - this.hide(); - } else { - this.show(); - } - } - } - - show() { - this.shown = true; - this.collapse = false; - this.collapsing = true; - this.ariaExpanded = true; - - setTimeout(() => { - this.height = this.el.nativeElement.scrollHeight; - this.renderer.setStyle(this.el.nativeElement, 'height', this.height + 'px'); - }, 0); - - setTimeout(() => { - this.collapsing = false; - this.collapse = true; - this.showClass = true; - }, this.duration); - - this._cdRef.markForCheck(); - } - - hide() { - if (this.shown) { - this.shown = false; - this.collapse = false; - this.showClass = false; - this.collapsing = true; - this.ariaExpanded = false; - setTimeout(() => { - this.renderer.setStyle(this.el.nativeElement, 'height', '0px'); - }, 0); - - setTimeout(() => { - this.collapsing = false; - this.collapse = true; - }, this.duration); - } - - this._cdRef.markForCheck(); - } - - get displayStyle() { - if (!this.containerInside) { - return 'flex'; - } else { - return ''; - } - } - - @HostListener('window:resize', ['$event']) onResize(event: any) { - let breakpoint = 0; - - if (this.SideClass.includes('navbar-expand-xl')) { - breakpoint = 1200; - } else if (this.SideClass.includes('navbar-expand-lg')) { - breakpoint = 992; - } else if (this.SideClass.includes('navbar-expand-md')) { - breakpoint = 768; - } else if (this.SideClass.includes('navbar-expand-sm')) { - breakpoint = 576; - } else { - breakpoint = event.target.innerWidth + 1; - } - - if (event.target.innerWidth < breakpoint) { - if (!this.shown) { - this.collapse = false; - this.renderer.setStyle(this.el.nativeElement, 'height', '0px'); - this.renderer.setStyle(this.el.nativeElement, 'opacity', '0'); - setTimeout(() => { - this.height = this.el.nativeElement.scrollHeight; - this.collapse = true; - this.renderer.setStyle(this.el.nativeElement, 'opacity', ''); - }, 4); - } - } else { - this.collapsing = false; - this.shown = false; - this.showClass = false; - this.collapse = true; - this.ariaExpanded = false; - this.renderer.setStyle(this.el.nativeElement, 'height', ''); - } - } - - ngAfterContentChecked() { - if (this.el.nativeElement.firstElementChild) { - if ( - this._itemsLength !== - this.el.nativeElement.firstElementChild.firstElementChild.children.length - ) { - this.height = this.el.nativeElement.firstElementChild.firstElementChild.clientHeight; - this.renderer.setStyle(this.el.nativeElement, 'height', this.height + 'px'); - } - - this._itemsLength = this.el.nativeElement.firstElementChild.firstElementChild.children.length; - } - this._cdRef.markForCheck(); - } - - ngOnDestroy() { - this._destroy$.next(); - this._destroy$.complete(); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/navbars/navbar.module.ts b/projects/angular-bootstrap-md/src/lib/free/navbars/navbar.module.ts deleted file mode 100755 index 83166c8d..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/navbars/navbar.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { LinksComponent } from './links.component'; -import { LogoComponent } from './logo.component'; -import { NavbarService } from './navbar.service'; -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {NavbarComponent} from './navbar.component'; -import { NavlinksComponent } from './navlinks.component'; -@NgModule({ - imports: [CommonModule], - declarations: [NavbarComponent, LinksComponent, LogoComponent, NavlinksComponent], - exports: [NavbarComponent, LinksComponent , LogoComponent, NavlinksComponent], - providers: [NavbarService] -}) -export class NavbarModule {} diff --git a/projects/angular-bootstrap-md/src/lib/free/navbars/navbar.service.ts b/projects/angular-bootstrap-md/src/lib/free/navbars/navbar.service.ts deleted file mode 100755 index b0ba3276..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/navbars/navbar.service.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Subject, Observable } from 'rxjs'; - -@Injectable() -export class NavbarService { - private navbarLinkClicks = new Subject(); - - getNavbarLinkClicks(): Observable { - return this.navbarLinkClicks.asObservable(); - } - - setNavbarLinkClicks() { - this.navbarLinkClicks.next(); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/navbars/navbars-module.scss b/projects/angular-bootstrap-md/src/lib/free/navbars/navbars-module.scss deleted file mode 100644 index 469c9ebb..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/navbars/navbars-module.scss +++ /dev/null @@ -1,7 +0,0 @@ -@import '../../assets/scss/core/mixins'; -@import '../../assets/scss/core/colors'; -@import '../../assets/scss/core/variables'; - - -@import 'navbars'; - diff --git a/projects/angular-bootstrap-md/src/lib/free/navbars/navlinks.component.ts b/projects/angular-bootstrap-md/src/lib/free/navbars/navlinks.component.ts deleted file mode 100755 index 5e2b27b4..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/navbars/navlinks.component.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { NavbarService } from './navbar.service'; -import { - AfterContentInit, - Component, - ContentChildren, - ElementRef, - QueryList, - EventEmitter, - Output, - Renderer2, -} from '@angular/core'; -import { RouterLinkWithHref } from '@angular/router'; - -@Component({ - // tslint:disable-next-line:component-selector - selector: 'navlinks', - template: ` - - `, -}) -export class NavlinksComponent implements AfterContentInit { - @ContentChildren(RouterLinkWithHref, { read: ElementRef, descendants: true }) - links: QueryList; - - @Output() linkClick = new EventEmitter(); - - constructor(private _navbarService: NavbarService, private renderer: Renderer2) {} - - ngAfterContentInit() { - setTimeout(() => { - this.links.forEach((link: ElementRef) => { - this.renderer.listen(link.nativeElement, 'click', () => { - this._navbarService.setNavbarLinkClicks(); - }); - }); - }, 0); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/popover/_popover.scss b/projects/angular-bootstrap-md/src/lib/free/popover/_popover.scss deleted file mode 100644 index 6d8b6602..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/popover/_popover.scss +++ /dev/null @@ -1,172 +0,0 @@ -//Bootstrap 4 alpha popover styles - works with ng-bootstrap by Valor popover plugin - -.popover.popover-top, -.popover.bs-tether-element-attached-bottom { - margin-top: -10px; -} - -.popover.popover-top::before, -.popover.popover-top::after, -.popover.bs-tether-element-attached-bottom::before, -.popover.bs-tether-element-attached-bottom::after { - left: 50%; - border-bottom-width: 0; -} - -.popover.popover-top::before, -.popover.bs-tether-element-attached-bottom::before { - bottom: -11px; - margin-left: -11px; - border-top-color: rgba(0, 0, 0, 0.25); -} - -.popover.popover-top::after, -.popover.bs-tether-element-attached-bottom::after { - bottom: -10px; - margin-left: -10px; - border-top-color: #fff; -} - -.popover.popover-right, -.popover.bs-tether-element-attached-left { - margin-left: 10px; -} - -.popover.popover-right::before, -.popover.popover-right::after, -.popover.bs-tether-element-attached-left::before, -.popover.bs-tether-element-attached-left::after { - top: 50%; - border-left-width: 0; -} - -.popover.popover-right::before, -.popover.bs-tether-element-attached-left::before { - left: -11px; - margin-top: -11px; - border-right-color: rgba(0, 0, 0, 0.25); -} - -.popover.popover-right::after, -.popover.bs-tether-element-attached-left::after { - left: -10px; - margin-top: -10px; - border-right-color: #fff; -} - -.popover.popover-bottom, -.popover.bs-tether-element-attached-top { - margin-top: 10px; -} - -.popover.popover-bottom::before, -.popover.popover-bottom::after, -.popover.bs-tether-element-attached-top::before, -.popover.bs-tether-element-attached-top::after { - left: 50%; - border-top-width: 0; -} - -.popover.popover-bottom::before, -.popover.bs-tether-element-attached-top::before { - top: -11px; - margin-left: -11px; - border-bottom-color: rgba(0, 0, 0, 0.25); -} - -.popover.popover-bottom::after, -.popover.bs-tether-element-attached-top::after { - top: -10px; - margin-left: -10px; - border-bottom-color: #f7f7f7; -} - -.popover.popover-bottom .popover-title::before, -.popover.bs-tether-element-attached-top .popover-title::before { - position: absolute; - top: 0; - left: 50%; - display: block; - width: 20px; - margin-left: -10px; - content: ''; - border-bottom: 1px solid #f7f7f7; -} - -.popover.popover-left, -.popover.bs-tether-element-attached-right { - margin-left: -10px; -} - -.popover.popover-left::before, -.popover.popover-left::after, -.popover.bs-tether-element-attached-right::before, -.popover.bs-tether-element-attached-right::after { - top: 50%; - border-right-width: 0; -} - -.popover.popover-left::before, -.popover.bs-tether-element-attached-right::before { - right: -11px; - margin-top: -11px; - border-left-color: rgba(0, 0, 0, 0.25); -} - -.popover.popover-left::after, -.popover.bs-tether-element-attached-right::after { - right: -10px; - margin-top: -10px; - border-left-color: #fff; -} - -.popover::before, -.popover::after { - position: absolute; - display: block; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} - -.popover::before { - content: ''; - border-width: 11px; -} - -.popover::after { - content: ''; - border-width: 10px; -} - -// Popover animations - -@-webkit-keyframes fadeInPopover { - from { - opacity: 0; - } - to { - opacity: 1; - } -} -@keyframes fadeInPopover { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -.popover-fadeIn { - -webkit-animation-name: fadeInPopover; - animation-name: fadeInPopover; - -webkit-animation-delay: 0.2s; - -moz-animation-delay: 0.2s; - animation-delay: 0.2s; - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/popover/index.ts b/projects/angular-bootstrap-md/src/lib/free/popover/index.ts deleted file mode 100755 index 234a9258..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/popover/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { PopoverDirective } from './popover.directive'; -export { PopoverModule } from './popover.module'; -export { PopoverConfig } from './popover.config'; -export { PopoverContainerComponent } from './popover-container.component'; diff --git a/projects/angular-bootstrap-md/src/lib/free/popover/licens.md b/projects/angular-bootstrap-md/src/lib/free/popover/licens.md deleted file mode 100755 index 68dc12fd..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/popover/licens.md +++ /dev/null @@ -1 +0,0 @@ -https://github.com/valor-software/ngx-bootstrap/blob/development/LICENSE \ No newline at end of file diff --git a/projects/angular-bootstrap-md/src/lib/free/popover/popover-container.component.html b/projects/angular-bootstrap-md/src/lib/free/popover/popover-container.component.html deleted file mode 100755 index eac5a669..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/popover/popover-container.component.html +++ /dev/null @@ -1,5 +0,0 @@ -
-

{{ title }}

-
- -
diff --git a/projects/angular-bootstrap-md/src/lib/free/popover/popover-container.component.ts b/projects/angular-bootstrap-md/src/lib/free/popover/popover-container.component.ts deleted file mode 100755 index 6366eda5..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/popover/popover-container.component.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - HostBinding, - Input, - OnInit, - ViewEncapsulation, -} from '@angular/core'; -import { PopoverConfig } from './popover.config'; -import { isBs3 } from '../utils/ng2-bootstrap-config'; - -@Component({ - selector: 'mdb-popover-container', - changeDetection: ChangeDetectionStrategy.OnPush, - template: ` -

{{ title }}

-
- -
- `, - styleUrls: ['./popover-module.scss'], - encapsulation: ViewEncapsulation.None, -}) -export class PopoverContainerComponent implements OnInit { - @Input() public placement: string; - @Input() public title: string; - public containerClass: string; - public bodyClass: string; - public headerClass: string; - @HostBinding('class.show') show = '!isBs3'; - @HostBinding('attr.role') role = 'tooltip'; - - @HostBinding('class') class: any; - public get isBs3(): boolean { - return isBs3(); - } - - public constructor(config: PopoverConfig) { - Object.assign(this, config); - } - - ngOnInit() { - this.class = - 'popover-fadeIn popover in popover-' + - this.placement + - ' ' + - this.placement + - ' bs-popover-' + - this.placement + - ' ' + - this.containerClass; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/popover/popover-module.scss b/projects/angular-bootstrap-md/src/lib/free/popover/popover-module.scss deleted file mode 100644 index 8133ac4f..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/popover/popover-module.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'popover'; diff --git a/projects/angular-bootstrap-md/src/lib/free/popover/popover.config.ts b/projects/angular-bootstrap-md/src/lib/free/popover/popover.config.ts deleted file mode 100755 index d52ce229..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/popover/popover.config.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Injectable } from '@angular/core'; - -/** - * Configuration service for the Popover directive. - * You can inject this service, typically in your root component, and customize - * the values of its properties in order to provide default values for all the - * popovers used in the application. - */ - @Injectable() - export class PopoverConfig { - /** - * Placement of a popover. Accepts: "top", "bottom", "left", "right" - */ - public placement = 'top'; - /** - * Specifies events that should trigger. Supports a space separated list of - * event names. - */ - public triggers = 'click'; - /** - * A selector specifying the element the popover should be appended to. - * Currently only supports "body". - */ - public container: string; - } diff --git a/projects/angular-bootstrap-md/src/lib/free/popover/popover.directive.ts b/projects/angular-bootstrap-md/src/lib/free/popover/popover.directive.ts deleted file mode 100755 index 5be90b45..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/popover/popover.directive.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { - Directive, - Input, - Output, - EventEmitter, - OnInit, - OnDestroy, - Renderer2, - ElementRef, - TemplateRef, - ViewContainerRef, - HostListener, -} from '@angular/core'; -import { PopoverConfig } from './popover.config'; -import { ComponentLoaderFactory } from '../utils/component-loader/component-loader.factory'; -import { ComponentLoader } from '../utils/component-loader/component-loader.class'; -import { PopoverContainerComponent } from './popover-container.component'; -import { PositioningService } from '../utils/positioning/positioning.service'; - -/** - * A lightweight, extensible directive for fancy popover creation. - */ -@Directive({ selector: '[mdbPopover]', exportAs: 'bs-mdbPopover' }) -export class PopoverDirective implements OnInit, OnDestroy { - @Input() public containerClass: string; - @Input() public bodyClass: string; - @Input() public headerClass: string; - /** - * Content to be displayed as popover. - */ - @Input() public mdbPopover: string | TemplateRef; - /** - * Title of a popover. - */ - @Input() public mdbPopoverHeader: string; - @Input() public popoverTitle: string; - /** - * Placement of a popover. Accepts: "top", "bottom", "left", "right" - */ - @Input() public placement: 'top' | 'bottom' | 'left' | 'right'; - /** - * Specifies events that should trigger. Supports a space separated list of - * event names. - */ - @Input() public triggers: string; - /** - * A selector specifying the element the popover should be appended to. - * Currently only supports "body". - */ - @Input() public container: string; - - /** - * Returns whether or not the popover is currently being shown - */ - @Input() - public get isOpen(): boolean { - return this._popover.isShown; - } - - public set isOpen(value: boolean) { - if (value) { - this.show(); - } else { - this.hide(); - } - } - - @Input() dynamicPosition = true; - @Input() outsideClick = false; - /** - * Emits an event when the popover is shown - */ - // tslint:disable-next-line:no-output-on-prefix - @Output() public onShown: EventEmitter; - @Output() public shown: EventEmitter; - /** - * Emits an event when the popover is hidden - */ - // tslint:disable-next-line:no-output-on-prefix - @Output() public onHidden: EventEmitter; - @Output() public hidden: EventEmitter; - - private _popover: ComponentLoader; - - public constructor( - _elementRef: ElementRef, - _renderer: Renderer2, - _viewContainerRef: ViewContainerRef, - _config: PopoverConfig, - cis: ComponentLoaderFactory, - private _positionService: PositioningService - ) { - this._popover = cis - .createLoader(_elementRef, _viewContainerRef, _renderer) - .provide({ provide: PopoverConfig, useValue: _config }); - Object.assign(this, _config); - this.onShown = this._popover.onShown; - this.shown = this._popover.onShown; - this.onHidden = this._popover.onHidden; - this.hidden = this._popover.onHidden; - } - - /** - * Opens an element’s popover. This is considered a “manual” triggering of - * the popover. - */ - public show(): void | any { - if (this._popover.isShown) { - return; - } - - this._positionService.setOptions({ - modifiers: { - flip: { - enabled: this.dynamicPosition, - }, - preventOverflow: { - enabled: this.dynamicPosition, - }, - }, - }); - - this._popover - .attach(PopoverContainerComponent) - .to(this.container) - .position({ attachment: this.placement }) - .show({ - content: this.mdbPopover, - placement: this.placement, - title: this.mdbPopoverHeader || this.popoverTitle, - containerClass: this.containerClass ? this.containerClass : '', - bodyClass: this.bodyClass ? this.bodyClass : '', - headerClass: this.headerClass ? this.headerClass : '', - }); - this.isOpen = true; - - if (!this.dynamicPosition) { - this._positionService.calcPosition(); - this._positionService.deletePositionElement(this._popover._componentRef.location); - } - } - - /** - * Closes an element’s popover. This is considered a “manual” triggering of - * the popover. - */ - public hide(): void { - if (this.isOpen) { - this._popover.hide(); - this.isOpen = false; - } - } - - /** - * Toggles an element’s popover. This is considered a “manual” triggering of - * the popover. - */ - public toggle(): void { - if (this.isOpen) { - return this.hide(); - } - - this.show(); - } - - @HostListener('click', ['$event']) onclick(event: any) { - if (this.triggers.toString().includes('focus')) { - event.stopPropagation(); - this.show(); - } - } - - @HostListener('window:click') onblur() { - if (this.triggers.toString().includes('focus') && this.isOpen) { - this.hide(); - } - } - - // fix(popover): popover with outsideClick='true' will now close after clicking in document on iPad Safari - @HostListener('document:touchstart', ['$event']) onTouchStart(event: any) { - if (this.outsideClick && !event.target.classList.contains('popover-body')) { - this.hide(); - } - } - - public ngOnInit(): any { - this._popover.listen({ - triggers: this.triggers, - outsideClick: this.outsideClick, - show: () => this.show(), - }); - } - - public dispose() { - this._popover.dispose(); - } - - public ngOnDestroy(): any { - this._popover.dispose(); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/popover/popover.module.ts b/projects/angular-bootstrap-md/src/lib/free/popover/popover.module.ts deleted file mode 100755 index df35c44f..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/popover/popover.module.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { NgModule, ModuleWithProviders } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -import { ComponentLoaderFactory } from '../utils/component-loader/component-loader.factory'; -import { PositioningService } from '../utils/positioning/positioning.service'; -import { PopoverConfig } from './popover.config'; -import { PopoverDirective } from './popover.directive'; -import { PopoverContainerComponent } from './popover-container.component'; - -@NgModule({ - imports: [CommonModule], - declarations: [PopoverDirective, PopoverContainerComponent], - exports: [PopoverDirective], - entryComponents: [PopoverContainerComponent] -}) -export class PopoverModule { - public static forRoot(): ModuleWithProviders { - return { - ngModule: PopoverModule, - providers: [PopoverConfig, ComponentLoaderFactory, PositioningService] - }; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/sticky-header/index.ts b/projects/angular-bootstrap-md/src/lib/free/sticky-header/index.ts deleted file mode 100644 index 27081b7d..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/sticky-header/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './sticky-header.directive'; -export * from './sticky-header.module'; diff --git a/projects/angular-bootstrap-md/src/lib/free/sticky-header/sticky-header.directive.ts b/projects/angular-bootstrap-md/src/lib/free/sticky-header/sticky-header.directive.ts deleted file mode 100644 index f72bcc8c..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/sticky-header/sticky-header.directive.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { - AfterViewInit, - Directive, - ElementRef, - EventEmitter, - Input, - Output, - Renderer2, - OnDestroy, -} from '@angular/core'; -import { fromEvent, Subject } from 'rxjs'; -import { window } from '../utils/facade/browser'; -import { - distinctUntilChanged, - filter, - map, - pairwise, - share, - skip, - throttleTime, - takeUntil, -} from 'rxjs/operators'; - -enum Direction { - Up = 'Up', - Down = 'Down', -} - -@Directive({ - selector: '[mdbStickyHeader]', - exportAs: 'mdbStickyHeader', -}) -export class StickyHeaderDirective implements AfterViewInit, OnDestroy { - @Input() animationDuration = 200; - @Output() transitionEnd: EventEmitter<{ state: string }> = new EventEmitter<{ state: string }>(); - - private _destroy$: Subject = new Subject(); - - private scrollDown$: any; - private scrollUp$: any; - - constructor(private _renderer: Renderer2, private _el: ElementRef) {} - - ngAfterViewInit() { - const scroll$ = fromEvent(window, 'scroll').pipe( - throttleTime(10), - map(() => window.pageYOffset), - pairwise(), - map(([y1, y2]): Direction => (y2 < y1 ? Direction.Up : Direction.Down)), - distinctUntilChanged(), - share() - ); - - this.scrollUp$ = scroll$.pipe(filter(direction => direction === Direction.Up)); - this.scrollDown$ = scroll$.pipe(filter(direction => direction === Direction.Down)); - - this._renderer.setStyle(this._el.nativeElement, 'position', 'fixed'); - this._renderer.setStyle(this._el.nativeElement, 'top', '0'); - this._renderer.setStyle(this._el.nativeElement, 'width', '100%'); - this._renderer.setStyle(this._el.nativeElement, 'z-index', '1030'); - - setTimeout(() => { - this.scrollUp$ - .pipe( - skip(0), - takeUntil(this._destroy$) - ) - .subscribe(() => { - this._renderer.setStyle( - this._el.nativeElement, - 'transition', - `all ${this.animationDuration}ms ease-in` - ); - this._renderer.setStyle(this._el.nativeElement, 'transform', 'translateY(0%)'); - this.transitionEnd.emit({ state: 'Visible' }); - }); - this.scrollDown$ - .pipe( - skip(0), - takeUntil(this._destroy$) - ) - .subscribe(() => { - this._renderer.setStyle( - this._el.nativeElement, - 'transition', - `all ${this.animationDuration}ms ease-in` - ); - this._renderer.setStyle(this._el.nativeElement, 'transform', 'translateY(-100%)'); - this.transitionEnd.emit({ state: 'Hidden' }); - }); - }, 0); - } - - ngOnDestroy() { - this._destroy$.next(); - this._destroy$.complete(); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/sticky-header/sticky-header.module.ts b/projects/angular-bootstrap-md/src/lib/free/sticky-header/sticky-header.module.ts deleted file mode 100644 index 7c64f5f1..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/sticky-header/sticky-header.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { StickyHeaderDirective } from './sticky-header.directive'; - -@NgModule({ - declarations: [StickyHeaderDirective], - exports: [StickyHeaderDirective], - imports: [CommonModule], -}) -export class StickyHeaderModule {} diff --git a/projects/angular-bootstrap-md/src/lib/free/tables/_tables.scss b/projects/angular-bootstrap-md/src/lib/free/tables/_tables.scss deleted file mode 100644 index 038ed269..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tables/_tables.scss +++ /dev/null @@ -1,71 +0,0 @@ -// Tables -table { - th { - font-size: $table-th-font-size; - font-weight: 400; - } - td { - font-size: $table-td-font-size; - font-weight: 300; - } - &.table { - thead th { - border-top: none; - } - th, - td { - padding-top: $table-th-padding-top; - padding-bottom: $table-td-padding-bottom; - } - .label-table { - margin: 0; - padding: 0; - line-height: $table-label-height; - height: $table-label-line-height; - } - &.btn-table { - td { - vertical-align: middle; - } - } - } - &.table-hover { - tbody { - tr { - &:hover { - transition: $table-hover-transition; - background-color: $table-hover-background-color; - } - } - } - } - .th-lg { - min-width: $table-th-lg-min-width; - } - .th-sm { - min-width: $table-th-sm-min-width; - } - &.table-sm { - th, - td { - padding-top: $table-sm-padding-y; - padding-bottom: $table-sm-padding-y; - } - } -} -.table-scroll-vertical { - max-height: $table-scroll-vertical-max-height; - overflow-y: auto; -} -.table-fixed { - table-layout: fixed; -} -.table-responsive, -.table-responsive-sm, -.table-responsive-md, -.table-responsive-lg, -.table-responsive-xl { - > .table-bordered { - border-top: 1px solid #dee2e6; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/tables/components/mdb-table-pagination.component.html b/projects/angular-bootstrap-md/src/lib/free/tables/components/mdb-table-pagination.component.html deleted file mode 100644 index 9ff60b43..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tables/components/mdb-table-pagination.component.html +++ /dev/null @@ -1,41 +0,0 @@ - -
- diff --git a/projects/angular-bootstrap-md/src/lib/free/tables/components/mdb-table-pagination.component.ts b/projects/angular-bootstrap-md/src/lib/free/tables/components/mdb-table-pagination.component.ts deleted file mode 100644 index ed882c74..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tables/components/mdb-table-pagination.component.ts +++ /dev/null @@ -1,256 +0,0 @@ -import { - Component, - OnInit, - Output, - EventEmitter, - Input, - ChangeDetectorRef, - OnChanges, - SimpleChanges, - AfterViewInit, - OnDestroy, -} from '@angular/core'; -import { Observable, Subject } from 'rxjs'; -import { MdbTableDirective } from '../directives/mdb-table.directive'; -import { takeUntil } from 'rxjs/operators'; - -export interface MdbPaginationIndex { - first: number; - last: number; -} - -@Component({ - selector: 'mdb-table-pagination', - templateUrl: './mdb-table-pagination.component.html', -}) -export class MdbTablePaginationComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy { - @Input() tableEl: MdbTableDirective; - @Input() searchPagination = false; - @Input() searchDataSource: any = null; - @Input() ofKeyword = 'of'; - @Input() dashKeyword = '-'; - @Input() paginationAlign = ''; - @Input() hideDescription = false; - - private _destroy$: Subject = new Subject(); - - maxVisibleItems = 10; - - firstItemIndex = 0; - lastItemIndex: number = this.maxVisibleItems; - lastVisibleItemIndex = 5; - - activePageNumber = 1; - - allItemsLength = 0; - - nextShouldBeDisabled = false; - previousShouldBeDisabled = true; - - searchText = ''; - - pagination: Subject = new Subject(); - - @Output() nextPageClick = new EventEmitter(); - @Output() previousPageClick = new EventEmitter(); - @Output() firstPageClick = new EventEmitter(); - @Output() lastPageClick = new EventEmitter(); - constructor(private cdRef: ChangeDetectorRef) {} - - ngOnInit() { - if (this.tableEl) { - this.allItemsLength = this.tableEl.getDataSource().length; - } - } - - ngAfterViewInit() { - if (this.tableEl) { - this.tableEl - .dataSourceChange() - .pipe(takeUntil(this._destroy$)) - .subscribe((data: any) => { - this.allItemsLength = data.length; - this.lastVisibleItemIndex = data.length; - this.calculateFirstItemIndex(); - this.calculateLastItemIndex(); - this.disableNextButton(data); - - if (this.searchDataSource) { - setTimeout(() => { - if (this.searchDataSource.length !== data.length) { - this.activePageNumber = 1; - this.firstItemIndex = 1; - } - }, 0); - } - }); - } - - this.paginationChange() - .pipe(takeUntil(this._destroy$)) - .subscribe((data: any) => { - this.firstItemIndex = data.first; - this.lastVisibleItemIndex = data.last; - }); - } - - ngOnChanges(changes: SimpleChanges) { - const searchDataSource = changes['searchDataSource']; - if (searchDataSource.currentValue.length !== 0) { - this.allItemsLength = searchDataSource.currentValue.length; - } - - if (this.lastVisibleItemIndex > this.allItemsLength) { - this.lastVisibleItemIndex = this.allItemsLength; - } - - if (searchDataSource.currentValue.length === 0) { - this.firstItemIndex = 0; - this.lastItemIndex = 0; - this.lastVisibleItemIndex = 0; - this.allItemsLength = 0; - } - - if ( - !searchDataSource.isFirstChange() && - searchDataSource.currentValue.length <= this.maxVisibleItems - ) { - this.nextShouldBeDisabled = true; - this.lastVisibleItemIndex = searchDataSource.currentValue.length; - } else { - this.nextShouldBeDisabled = false; - } - } - - setMaxVisibleItemsNumberTo(value: number) { - this.lastItemIndex = value; - this.lastVisibleItemIndex = value; - this.maxVisibleItems = value; - this.cdRef.detectChanges(); - } - - searchTextObs(): Observable { - const observable = new Observable((observer: any) => { - observer.next(this.searchText); - }); - return observable; - } - - disableNextButton(data: any) { - if (data.length <= this.maxVisibleItems) { - this.nextShouldBeDisabled = true; - } else { - this.nextShouldBeDisabled = false; - } - } - - calculateFirstItemIndex() { - this.firstItemIndex = this.activePageNumber * this.maxVisibleItems - this.maxVisibleItems + 1; - this.pagination.next({ first: this.firstItemIndex, last: this.lastItemIndex }); - } - - calculateLastItemIndex() { - this.lastItemIndex = this.activePageNumber * this.maxVisibleItems; - this.lastVisibleItemIndex = this.lastItemIndex; - - if (this.searchDataSource && this.lastItemIndex > this.searchDataSource.length) { - this.lastVisibleItemIndex = this.searchDataSource.length; - } else if (!this.searchDataSource) { - this.lastVisibleItemIndex = this.lastItemIndex; - } - - if (this.lastItemIndex > this.tableEl.getDataSource().length) { - this.lastItemIndex = this.tableEl.getDataSource().length; - this.lastVisibleItemIndex = this.tableEl.getDataSource().length; - } - - this.pagination.next({ first: this.firstItemIndex, last: this.lastItemIndex }); - } - - paginationChange(): Observable { - return this.pagination; - } - - calculateHowManyPagesShouldBe() { - return Math.ceil(this.tableEl.getDataSource().length / this.maxVisibleItems); - } - - previousPage() { - this.activePageNumber--; - this.calculateFirstItemIndex(); - this.calculateLastItemIndex(); - this.previousPageClick.emit({ first: this.firstItemIndex, last: this.lastItemIndex }); - } - - nextPage() { - this.activePageNumber++; - this.calculateFirstItemIndex(); - this.calculateLastItemIndex(); - - if (this.lastItemIndex > this.tableEl.getDataSource().length) { - this.lastItemIndex = this.tableEl.getDataSource().length; - } - - if (this.lastVisibleItemIndex > this.allItemsLength) { - this.lastVisibleItemIndex = this.allItemsLength; - } - - this.nextPageClick.emit({ first: this.firstItemIndex, last: this.lastItemIndex }); - } - - firstPage() { - this.activePageNumber = 1; - this.calculateFirstItemIndex(); - this.calculateLastItemIndex(); - - this.firstPageClick.emit({ first: this.firstItemIndex, last: this.lastItemIndex }); - } - - lastPage() { - const lastPage = Math.ceil(this.allItemsLength / this.maxVisibleItems); - this.activePageNumber = lastPage; - this.calculateFirstItemIndex(); - this.calculateLastItemIndex(); - - this.lastPageClick.emit({ first: this.firstItemIndex, last: this.lastItemIndex }); - } - - nextPageObservable(): Observable { - const obs = new Observable((observer: any) => { - observer.next(this.firstItemIndex); - }); - return obs; - } - - previousPageObservable(): Observable { - const obs = new Observable((observer: any) => { - observer.next(this.lastVisibleItemIndex); - }); - return obs; - } - - checkIfNextShouldBeDisabled() { - if (this.searchDataSource && this.lastVisibleItemIndex === this.searchDataSource.length) { - return true; - } - - if (this.activePageNumber >= this.calculateHowManyPagesShouldBe()) { - return true; - } - - if (this.nextShouldBeDisabled) { - return this.nextShouldBeDisabled; - } - } - - checkIfPreviousShouldBeDisabled() { - if (this.activePageNumber === 1) { - return true; - } - } - - ngOnDestroy() { - this._destroy$.next(); - this._destroy$.complete(); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/tables/directives/mdb-table-row.directive.ts b/projects/angular-bootstrap-md/src/lib/free/tables/directives/mdb-table-row.directive.ts deleted file mode 100644 index c4cfbb2b..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tables/directives/mdb-table-row.directive.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Directive, Output, EventEmitter, OnInit, OnDestroy, ElementRef } from '@angular/core'; - -@Directive({ - selector: '[mdbTableRow]' -}) -export class MdbTableRowDirective implements OnInit, OnDestroy { - - @Output() rowCreated = new EventEmitter(); - @Output() rowRemoved = new EventEmitter(); - - constructor(private el: ElementRef) { - } - - ngOnInit() { - this.rowCreated.emit({ created: true, el: this.el.nativeElement }); - } - - ngOnDestroy() { - this.rowRemoved.emit({ removed: true }); - } - -} diff --git a/projects/angular-bootstrap-md/src/lib/free/tables/directives/mdb-table-scroll.directive.ts b/projects/angular-bootstrap-md/src/lib/free/tables/directives/mdb-table-scroll.directive.ts deleted file mode 100644 index 846101cd..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tables/directives/mdb-table-scroll.directive.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Directive, ElementRef, Renderer2, OnInit, Input } from '@angular/core'; - -@Directive({ - selector: '[mdbTableScroll]', -}) -export class MdbTableScrollDirective implements OnInit { - @Input() scrollY = false; - @Input() maxHeight: any = null; - - @Input() scrollX = false; - @Input() maxWidth: any = null; - - constructor(private renderer: Renderer2, private el: ElementRef) {} - - wrapTableWithVerticalScrollingWrapper(tableWrapper: ElementRef) { - this.renderer.setStyle(tableWrapper, 'max-height', this.maxHeight + 'px'); - this.renderer.setStyle(tableWrapper, 'overflow-y', 'auto'); - this.renderer.setStyle(tableWrapper, 'display', 'block'); - } - - wrapTableWithHorizontalScrollingWrapper(tableWrapper: ElementRef) { - this.renderer.setStyle(tableWrapper, 'max-width', this.maxWidth + 'px'); - this.renderer.setStyle(tableWrapper, 'overflow-x', 'auto'); - this.renderer.setStyle(tableWrapper, 'display', 'block'); - } - - wrapTableWithHorizontalAndVerticalScrollingWrapper(tableWrapper: ElementRef) { - this.renderer.setStyle(tableWrapper, 'max-height', this.maxHeight + 'px'); - this.renderer.setStyle(tableWrapper, 'max-width', this.maxWidth + 'px'); - this.renderer.setStyle(tableWrapper, 'overflow-x', 'auto'); - this.renderer.setStyle(tableWrapper, 'display', 'block'); - } - - ngOnInit() { - const parent = this.el.nativeElement.parentNode; - const tableWrapper = this.renderer.createElement('div'); - - if (this.scrollY && this.scrollX && this.maxHeight && this.maxWidth) { - this.wrapTableWithHorizontalAndVerticalScrollingWrapper(tableWrapper); - } - - if (this.scrollY && this.maxHeight) { - this.wrapTableWithVerticalScrollingWrapper(tableWrapper); - } - - if (this.scrollX && this.maxWidth) { - this.wrapTableWithHorizontalScrollingWrapper(tableWrapper); - } - - this.renderer.insertBefore(parent, tableWrapper, this.el.nativeElement); - this.renderer.removeChild(parent, this.el.nativeElement); - this.renderer.appendChild(tableWrapper, this.el.nativeElement); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/tables/directives/mdb-table-sort.directive.ts b/projects/angular-bootstrap-md/src/lib/free/tables/directives/mdb-table-sort.directive.ts deleted file mode 100644 index 9c2afa61..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tables/directives/mdb-table-sort.directive.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { - Directive, - EventEmitter, - HostListener, - Input, - Output, - ElementRef, - Renderer2, - OnInit, -} from '@angular/core'; - -enum SortDirection { - ASC = 'ascending', - DESC = 'descending', - CONST = 'constant', -} - -export interface SortedData { - data: any[]; - sortOrder: string; - sortBy: string; -} - -@Directive({ - selector: '[mdbTableSort]', -}) -export class MdbTableSortDirective implements OnInit { - sortedInto = true; - order: string; - - @Input('mdbTableSort') dataSource: Array = []; - @Input() sortBy: string; - - @Output() sortEnd: EventEmitter = new EventEmitter(); - @Output() sorted: EventEmitter = new EventEmitter(); - - constructor(private el: ElementRef, private renderer: Renderer2) {} - - @HostListener('click') onclick() { - this.sortDataBy(this.trimWhiteSigns(this.sortBy.toString())); - this.sortEnd.emit(this.dataSource); - this.sorted.emit({ - data: this.dataSource, - sortOrder: this.order, - sortBy: this.sortBy, - }); - } - - trimWhiteSigns(headElement: any): string { - return headElement.replace(/ /g, ''); - } - - public moveArrayItem(arr: any, oldIndex: number, newIndex: number) { - while (oldIndex < 0) { - oldIndex += arr.length; - } - while (newIndex < 0) { - newIndex += arr.length; - } - if (newIndex >= arr.length) { - let k = newIndex - arr.length; - while (k-- + 1) { - arr.push(null); - } - } - arr.splice(newIndex, 0, arr.splice(oldIndex, 1)[0]); - return arr; - } - - sortDataBy(key: string | any) { - let ariaPass = true; - - const setAria = (sort: 'ascending' | 'descending', id: any) => { - if (ariaPass) { - const inverse = sort === 'ascending' ? 'descending' : 'ascending'; - - this.renderer.setAttribute(this.el.nativeElement, 'aria-sort', sort); - this.renderer.setAttribute( - this.el.nativeElement, - 'aria-label', - `${id}: activate to sort column ${inverse}` - ); - ariaPass = false; - } - }; - - key = key.split('.'); - - this.dataSource.sort((a: any, b: any) => { - let i = 0; - while (i < key.length) { - a = a[key[i]]; - b = b[key[i]]; - i++; - } - - if (a < b) { - setAria('ascending', key); - this.order = SortDirection.ASC; - - return this.sortedInto ? 1 : -1; - } else if (a > b) { - setAria('descending', key); - this.order = SortDirection.DESC; - - return this.sortedInto ? -1 : 1; - } else if (a == null || b == null) { - this.order = SortDirection.CONST; - return 1; - } else { - this.order = SortDirection.CONST; - return 0; - } - }); - - this.sortedInto = !this.sortedInto; - } - - ngOnInit() { - const key = this.trimWhiteSigns(this.sortBy.toString()).split('.'); - this.renderer.setAttribute( - this.el.nativeElement, - 'aria-label', - `${key}: activate to sort column descending` - ); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/tables/directives/mdb-table.directive.ts b/projects/angular-bootstrap-md/src/lib/free/tables/directives/mdb-table.directive.ts deleted file mode 100644 index 0cd33a44..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tables/directives/mdb-table.directive.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { - AfterViewInit, - Component, - ElementRef, - HostBinding, - Input, - OnInit, - Renderer2, - ViewEncapsulation, -} from '@angular/core'; -import { Observable, Subject } from 'rxjs'; - -@Component({ - // tslint:disable-next-line:component-selector - selector: '[mdbTable]', - exportAs: 'mdbTable', - template: '', - styleUrls: ['./../tables-module.scss'], - encapsulation: ViewEncapsulation.None, -}) -// tslint:disable-next-line:component-class-suffix -export class MdbTableDirective implements OnInit, AfterViewInit { - @Input() - @HostBinding('class.table-striped') - striped: boolean; - - @Input() - @HostBinding('class.table-bordered') - bordered: boolean; - - @Input() - @HostBinding('class.table-borderless') - borderless: boolean; - - @Input() - @HostBinding('class.table-hover') - hover: boolean; - - @Input() - @HostBinding('class.table-sm') - small: boolean; - - @Input() - @HostBinding('class.table-responsive') - responsive: boolean; - - @Input() stickyHeader = false; - @Input() stickyHeaderBgColor = ''; - @Input() stickyHeaderTextColor = ''; - - constructor(private el: ElementRef, private renderer: Renderer2) {} - - private _dataSource: any = []; - private _dataSourceChanged: Subject = new Subject(); - - addRow(newRow: any) { - this.getDataSource().push(newRow); - } - - addRowAfter(index: number, row: any) { - this.getDataSource().splice(index, 0, row); - } - - removeRow(index: number) { - this.getDataSource().splice(index, 1); - } - - rowRemoved(): Observable { - const rowRemoved = new Observable((observer: any) => { - observer.next(true); - }); - return rowRemoved; - } - - removeLastRow() { - this.getDataSource().pop(); - } - - getDataSource() { - return this._dataSource; - } - - setDataSource(data: any) { - this._dataSource = data; - this._dataSourceChanged.next(this.getDataSource()); - } - - dataSourceChange(): Observable { - return this._dataSourceChanged; - } - - filterLocalDataBy(searchKey: string) { - return this.getDataSource().filter((obj: Array) => { - return Object.keys(obj).some((key: any) => { - if (obj[key]) { - // Fix(tableSearch): table search will now able to filter through nested data - - return JSON.stringify(obj) - .toLowerCase() - .includes(searchKey) as any; - } - }); - }); - } - - filterLocalDataByFields(searchKey: string, keys: string[]) { - return this.getDataSource().filter((obj: Array) => { - return Object.keys(obj).some((key: any) => { - if (obj[key]) { - if (keys.includes(key)) { - if (obj[key].toLowerCase().includes(searchKey)) { - return obj[key]; - } - } - } - }); - }); - } - filterLocalDataByMultipleFields(searchKey: string, keys?: string[]) { - const items = searchKey.split(' ').map((x: { toLowerCase: () => void }) => x.toLowerCase()); - return this.getDataSource().filter((x: Array) => { - for (const item of items) { - let flag = false; - - if (keys !== undefined) { - for (const prop in x) { - if (x[prop]) { - if (keys.includes(prop)) { - if (x[prop].toLowerCase().indexOf(item) !== -1) { - flag = true; - break; - } - } - } - } - } - if (keys === undefined) { - for (const prop in x) { - if (x[prop].toLowerCase().indexOf(item) !== -1) { - flag = true; - break; - } - } - } - if (!flag) { - return false; - } - } - return true; - }); - } - searchLocalDataBy(searchKey: string) { - if (!searchKey) { - return this.getDataSource(); - } - - if (searchKey) { - return this.filterLocalDataBy(searchKey.toLowerCase()); - } - } - - searchLocalDataByFields(searchKey: string, keys: string[]) { - if (!searchKey) { - return this.getDataSource(); - } - - if (searchKey && keys.length > 0) { - return this.filterLocalDataByFields(searchKey.toLowerCase(), keys); - } - if (!keys || keys.length === 0) { - return this.filterLocalDataBy(searchKey.toLowerCase()); - } - } - searchLocalDataByMultipleFields(searchKey: string, keys?: string[]) { - if (!searchKey) { - return this.getDataSource(); - } - if (searchKey && keys !== undefined) { - return this.filterLocalDataByMultipleFields(searchKey.toLowerCase(), keys); - } - } - searchDataObservable(searchKey: string): Observable { - const observable = new Observable((observer: any) => { - observer.next(this.searchLocalDataBy(searchKey)); - }); - return observable; - } - - ngOnInit() { - this.renderer.addClass(this.el.nativeElement, 'table'); - } - - ngAfterViewInit() { - // Fix(stickyHeader): resolved problem with not working stickyHeader="true" on Chrome - if (this.stickyHeader) { - const tableHead = this.el.nativeElement.querySelector('thead'); - - Array.from(tableHead.firstElementChild.children).forEach((child: any) => { - this.renderer.addClass(child, 'sticky-top'); - if (this.stickyHeaderBgColor) { - this.renderer.setStyle(child, 'background-color', this.stickyHeaderBgColor); - } else { - this.renderer.setStyle(child, 'background-color', '#f2f2f2'); - } - if (this.stickyHeaderTextColor) { - this.renderer.setStyle(child, 'color', this.stickyHeaderTextColor); - } else { - this.renderer.setStyle(child, 'color', '#000000'); - } - }); - } - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/tables/index.ts b/projects/angular-bootstrap-md/src/lib/free/tables/index.ts deleted file mode 100644 index 5e6bcc09..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tables/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { TableModule } from './tables.module'; -export { MdbTablePaginationComponent } from './components/mdb-table-pagination.component'; -export { MdbTableRowDirective } from './directives/mdb-table-row.directive'; -export { MdbTableScrollDirective } from './directives/mdb-table-scroll.directive'; -export { MdbTableSortDirective } from './directives/mdb-table-sort.directive'; -export { MdbTableDirective } from './directives/mdb-table.directive'; -export { MdbTableService } from './services/mdb-table.service'; diff --git a/projects/angular-bootstrap-md/src/lib/free/tables/services/mdb-table.service.ts b/projects/angular-bootstrap-md/src/lib/free/tables/services/mdb-table.service.ts deleted file mode 100644 index 0cc69ea0..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tables/services/mdb-table.service.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Observable, Subject } from 'rxjs'; -import { Injectable } from '@angular/core'; - -@Injectable({ - providedIn: 'root', -}) -export class MdbTableService { - private _dataSource: any = []; - private _dataSourceChanged: Subject = new Subject(); - constructor() {} - - addRow(newRow: any) { - this.getDataSource().push(newRow); - } - - addRowAfter(index: number, row: any) { - this.getDataSource().splice(index, 0, row); - } - - removeRow(index: number) { - this.getDataSource().splice(index, 1); - } - - rowRemoved(): Observable { - const rowRemoved = new Observable((observer: any) => { - observer.next(true); - }); - return rowRemoved; - } - - removeLastRow() { - this.getDataSource().pop(); - } - - getDataSource() { - return this._dataSource; - } - - setDataSource(data: any) { - this._dataSource = data; - this._dataSourceChanged.next(this.getDataSource()); - } - - dataSourceChange(): Observable { - return this._dataSourceChanged; - } - - filterLocalDataBy(searchKey: any) { - return this.getDataSource().filter((obj: Array) => { - return Object.keys(obj).some((key: any) => { - if (obj[key]) { - return obj[key] - .toString() - .toLowerCase() - .includes(searchKey); - } - }); - }); - } - - searchLocalDataBy(searchKey: any) { - if (!searchKey) { - return this.getDataSource(); - } - - if (searchKey) { - return this.filterLocalDataBy(searchKey.toLowerCase()); - } - } - - searchDataObservable(searchKey: any): Observable { - const observable = new Observable((observer: any) => { - observer.next(this.searchLocalDataBy(searchKey)); - }); - return observable; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/tables/tables-module.scss b/projects/angular-bootstrap-md/src/lib/free/tables/tables-module.scss deleted file mode 100644 index f436c505..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tables/tables-module.scss +++ /dev/null @@ -1,4 +0,0 @@ -@import '../../assets/scss/core/colors'; -@import '../../assets/scss/core/variables'; - -@import 'tables'; diff --git a/projects/angular-bootstrap-md/src/lib/free/tables/tables.module.ts b/projects/angular-bootstrap-md/src/lib/free/tables/tables.module.ts deleted file mode 100644 index dd148503..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tables/tables.module.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { MdbTableDirective } from './directives/mdb-table.directive'; -import { MdbTableSortDirective } from './directives/mdb-table-sort.directive'; -import { MdbTableScrollDirective } from './directives/mdb-table-scroll.directive'; -import { MdbTableRowDirective } from './directives/mdb-table-row.directive'; -import { MdbTableService } from './services/mdb-table.service'; -import { MdbTablePaginationComponent } from './components/mdb-table-pagination.component'; - -@NgModule({ - imports: [CommonModule], - declarations: [ - MdbTablePaginationComponent, - MdbTableRowDirective, - MdbTableScrollDirective, - MdbTableSortDirective, - MdbTableDirective, - ], - exports: [ - MdbTablePaginationComponent, - MdbTableRowDirective, - MdbTableScrollDirective, - MdbTableSortDirective, - MdbTableDirective, - ], - entryComponents: [MdbTablePaginationComponent], - providers: [MdbTableService], -}) -export class TableModule {} diff --git a/projects/angular-bootstrap-md/src/lib/free/tooltip/_tooltip.scss b/projects/angular-bootstrap-md/src/lib/free/tooltip/_tooltip.scss deleted file mode 100644 index 3f5e9083..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tooltip/_tooltip.scss +++ /dev/null @@ -1,229 +0,0 @@ -@mixin reset-text { - font-family: $font-family-base; - // We deliberately do NOT reset font-size or word-wrap. - font-style: normal; - font-weight: $font-weight-normal; - letter-spacing: normal; - line-break: auto; - line-height: $line-height-base; - text-align: left; // Fallback for where `start` is not supported - text-align: start; - text-decoration: none; - text-shadow: none; - text-transform: none; - white-space: normal; - word-break: normal; - word-spacing: normal; -} -a - -// Base class -.tooltip { - position: absolute; - z-index: $zindex-tooltip; - display: block; - // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element. - // So reset our font and text properties to avoid inheriting weird values. - @include reset-text(); - font-size: $font-size-sm; - // Allow breaking very long words so they don't overflow the tooltip's bounds - word-wrap: break-word; - opacity: 0; - - &.show { - opacity: $tooltip-opacity; - } - - &.tooltip-top, - &.bs-tether-element-attached-bottom { - padding: $tooltip-arrow-width 0; - margin-top: -$tooltip-margin; - - .tooltip-inner::before { - bottom: 0; - left: 50%; - margin-left: -$tooltip-arrow-width; - content: ''; - border-width: $tooltip-arrow-width $tooltip-arrow-width 0; - // border-top-color: $tooltip-arrow-color; - } - } - &.tooltip-right, - &.bs-tether-element-attached-left { - padding: 0 $tooltip-arrow-width; - margin-left: $tooltip-margin; - - .tooltip-inner::before { - top: 50%; - left: 0; - margin-top: -$tooltip-arrow-width; - content: ''; - border-width: $tooltip-arrow-width $tooltip-arrow-width $tooltip-arrow-width 0; - // border-right-color: $tooltip-arrow-color; - } - } - &.tooltip-bottom, - &.bs-tether-element-attached-top { - padding: $tooltip-arrow-width 0; - margin-top: $tooltip-margin; - - .tooltip-inner::before { - top: 0; - left: 50%; - margin-left: -$tooltip-arrow-width; - content: ''; - border-width: 0 $tooltip-arrow-width $tooltip-arrow-width; - // border-bottom-color: $tooltip-arrow-color; - } - } - &.tooltip-left, - &.bs-tether-element-attached-right { - padding: 0 $tooltip-arrow-width; - margin-left: -$tooltip-margin; - - .tooltip-inner::before { - top: 50%; - right: 0; - margin-top: -$tooltip-arrow-width; - content: ''; - border-width: $tooltip-arrow-width 0 $tooltip-arrow-width $tooltip-arrow-width; - // border-left-color: $tooltip-arrow-color; - } - } -} - -// Wrapper for the tooltip content -.tooltip-inner { - max-width: $tooltip-max-width; - padding: $tooltip-padding-y $tooltip-padding-x; - // color: $tooltip-color; - text-align: center; - // background-color: $tooltip-bg; - padding: 0.2rem 0.4rem; - box-shadow: $z-depth-1-half; - border-radius: ($border-radius); - - &::before { - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; - } -} - -// Tooltip animations -@-webkit-keyframes fadeInTooltip { - from { - opacity: 0; - } - to { - opacity: 1; - } -} -@keyframes fadeInTooltip { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -.tooltip-fadeIn { - -webkit-animation-name: fadeInTooltip; - animation-name: fadeInTooltip; - -webkit-animation-delay: 0.2s; - -moz-animation-delay: 0.2s; - animation-delay: 0.2s; - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; -} - -//sebfix for tooltips -.single-tooltip { - padding: 0.75rem 0 0 0; - a { - padding: 0 !important; - } -} - -a[tooltip] { - margin-left: 0 !important; - padding: 0 0.5rem; -} - -.tooltip-arrow { - &.left { - position: relative; - margin-right: -0.6rem; - transform: rotate(90deg); - } - &.right { - position: relative; - margin-left: -0.6rem; - transform: rotate(-90deg); - } - &.top { - position: relative; - transform: rotate(-180deg); - } -} - -.tooltip-top { - padding: $tooltip-arrow-height 0; - - .arrow { - bottom: 0; - - &::before { - top: 0; - border-width: $tooltip-arrow-height ($tooltip-arrow-width / 2) 0; - border-top-color: $tooltip-arrow-color; - } - } -} - -.tooltip-right { - padding: 0 $tooltip-arrow-height; - - .arrow { - left: 0; - - &::before { - right: 0; - border-width: ($tooltip-arrow-width / 2) $tooltip-arrow-height ($tooltip-arrow-width / 2) 0; - border-right-color: $tooltip-arrow-color; - } - } -} - -.tooltip-bottom { - padding: $tooltip-arrow-height 0; - - .arrow { - top: 0; - - &::before { - bottom: 0; - border-width: 0 ($tooltip-arrow-width / 2) $tooltip-arrow-height; - border-bottom-color: $tooltip-arrow-color; - } - } -} - -.tooltip-left { - padding: 0 $tooltip-arrow-height; - - .arrow { - right: 0; - - &::before { - left: 0; - border-width: ($tooltip-arrow-width / 2) 0 ($tooltip-arrow-width / 2) $tooltip-arrow-height; - border-left-color: $tooltip-arrow-color; - } - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/tooltip/index.ts b/projects/angular-bootstrap-md/src/lib/free/tooltip/index.ts deleted file mode 100755 index b652a3d4..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tooltip/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { TooltipContainerComponent } from './tooltip.component'; -export { TooltipDirective } from './tooltip.directive'; -export { TooltipModule } from './tooltip.module'; -export { TooltipConfig } from './tooltip.service'; diff --git a/projects/angular-bootstrap-md/src/lib/free/tooltip/licens.md b/projects/angular-bootstrap-md/src/lib/free/tooltip/licens.md deleted file mode 100755 index 68dc12fd..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tooltip/licens.md +++ /dev/null @@ -1 +0,0 @@ -https://github.com/valor-software/ngx-bootstrap/blob/development/LICENSE \ No newline at end of file diff --git a/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip-module.scss b/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip-module.scss deleted file mode 100644 index 88c8f34e..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip-module.scss +++ /dev/null @@ -1,8 +0,0 @@ -@import '../../assets/scss/core/mixins'; -@import '../../assets/scss/core/colors'; -@import '../../assets/scss/core/variables'; -@import '../../assets/scss/core/bootstrap/functions'; -@import '../../assets/scss/core/bootstrap/variables'; - -@import 'tooltip'; - diff --git a/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip.component.html b/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip.component.html deleted file mode 100755 index 5ec56eb6..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip.component.html +++ /dev/null @@ -1,2 +0,0 @@ -
-
\ No newline at end of file diff --git a/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip.component.ts b/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip.component.ts deleted file mode 100755 index dd2dca21..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip.component.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { - AfterViewInit, - ChangeDetectionStrategy, - Component, - ElementRef, - HostBinding, - Input, - ViewChild, - ViewEncapsulation, -} from '@angular/core'; -import { TooltipConfig } from './tooltip.service'; -import { isBs3 } from '../utils/ng2-bootstrap-config'; - -@Component({ - selector: 'mdb-tooltip-container', - changeDetection: ChangeDetectionStrategy.OnPush, - template: ` -
-
- -
- `, - styleUrls: ['tooltip-module.scss'], - encapsulation: ViewEncapsulation.None, -}) -export class TooltipContainerComponent implements AfterViewInit { - public classMap: any; - public placement: string; - public popupClass: string; - public animation: boolean; - - @Input() containerClass = ''; - @ViewChild('tooltipInner', { static: true }) tooltipInner: ElementRef; - @ViewChild('tooltipArrow', { static: true }) tooltipArrow: ElementRef; - @HostBinding('class.show') show = !this.isBs3; - @HostBinding('class') - get tooltipClasses() { - return `tooltip-fadeIn tooltip in tooltip-${this.placement} bs-tooltip-${this.placement} ${this.placement} ${this.containerClass}`; - } - - public get isBs3(): boolean { - return isBs3(); - } - - public constructor(config: TooltipConfig, public elem: ElementRef) { - Object.assign(this, config); - } - - public ngAfterViewInit(): void { - this.classMap = { in: false, fade: false }; - this.classMap[this.placement] = true; - this.classMap['tooltip-' + this.placement] = true; - - this.classMap.in = true; - if (this.animation) { - this.classMap.fade = true; - } - - if (this.popupClass) { - this.classMap[this.popupClass] = true; - } - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip.directive.ts b/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip.directive.ts deleted file mode 100755 index 22c28c02..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip.directive.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { - Directive, - ElementRef, - EventEmitter, - Inject, - Input, - OnChanges, - OnDestroy, - OnInit, - Output, - PLATFORM_ID, - Renderer2, - SimpleChanges, - TemplateRef, - ViewContainerRef, -} from '@angular/core'; -import { TooltipContainerComponent } from './tooltip.component'; -import { TooltipConfig } from './tooltip.service'; -import { ComponentLoaderFactory } from '../utils/component-loader/component-loader.factory'; -import { ComponentLoader } from '../utils/component-loader/component-loader.class'; -import { OnChange } from '../utils/decorators'; -import { isPlatformBrowser } from '@angular/common'; -import { PositioningService } from '../utils/positioning/positioning.service'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; - -@Directive({ - selector: '[mdbTooltip]', - exportAs: 'mdb-tooltip', -}) -export class TooltipDirective implements OnInit, OnDestroy, OnChanges { - /** - * Content to be displayed as tooltip. - */ - @OnChange() - @Input() - public mdbTooltip: string | TemplateRef; - /** Fired when tooltip content changes */ - @Output() public tooltipChange: EventEmitter> = new EventEmitter(); - - /** - * Placement of a tooltip. Accepts: "top", "bottom", "left", "right" - */ - @Input() public placement: string; - /** - * Specifies events that should trigger. Supports a space separated list of - * event names. - */ - @Input() public triggers: string; - /** - * A selector specifying the element the tooltip should be appended to. - * Currently only supports "body". - */ - @Input() public container: string; - - /** - * Returns whether or not the tooltip is currently being shown - */ - @Input() - public get isOpen(): boolean { - return this._tooltip.isShown; - } - - public set isOpen(value: boolean) { - if (value) { - this.show(); - } else { - this.hide(); - } - } - - /** - * Allows to disable tooltip - */ - @Input() public isDisabled: boolean; - - @Input() dynamicPosition = true; - - /** - * Emits an event when the tooltip is shown - */ - // tslint:disable-next-line:no-output-on-prefix - @Output() public onShown: EventEmitter; - @Output() public shown: EventEmitter; - /** - * Emits an event when the tooltip is hidden - */ - // tslint:disable-next-line:no-output-on-prefix - @Output() public onHidden: EventEmitter; - @Output() public hidden: EventEmitter; - - @Input() public delay = 0; - @Input() public customHeight: string; - @Input() public fadeDuration = 150; - - private _destroy$: Subject = new Subject(); - - protected _delayTimeoutId: any; - - private _tooltip: ComponentLoader; - - isBrowser: any = false; - - public constructor( - _renderer: Renderer2, - private _elementRef: ElementRef, - private _positionService: PositioningService, - _viewContainerRef: ViewContainerRef, - cis: ComponentLoaderFactory, - config: TooltipConfig, - @Inject(PLATFORM_ID) private platformId: string - ) { - this.isBrowser = isPlatformBrowser(this.platformId); - this._tooltip = cis - .createLoader(this._elementRef, _viewContainerRef, _renderer) - .provide({ provide: TooltipConfig, useValue: config }); - - Object.assign(this, config); - this.onShown = this._tooltip.onShown; - this.shown = this._tooltip.onShown; - this.onHidden = this._tooltip.onHidden; - this.hidden = this._tooltip.onHidden; - } - - public ngOnInit(): void { - this._tooltip.listen({ - triggers: this.triggers, - show: () => this.show(), - }); - - this.tooltipChange.pipe(takeUntil(this._destroy$)).subscribe((value: any) => { - if (!value) { - this._tooltip.hide(); - } - }); - } - - ngOnChanges(changes: SimpleChanges) { - if (!changes['mdbTooltip'].isFirstChange()) { - this.tooltipChange.emit(this.mdbTooltip); - } - } - - /** - * Toggles an element’s tooltip. This is considered a “manual” triggering of - * the tooltip. - */ - public toggle(): void { - if (this.isOpen) { - return this.hide(); - } - - this.show(); - } - - /** - * Opens an element’s tooltip. This is considered a “manual” triggering of - * the tooltip. - */ - public show(): void { - if (this.isOpen || this.isDisabled || this._delayTimeoutId || !this.mdbTooltip) { - return; - } - - this._positionService.setOptions({ - modifiers: { - flip: { - enabled: this.dynamicPosition, - }, - preventOverflow: { - enabled: this.dynamicPosition, - }, - }, - }); - - const showTooltip = () => { - this._tooltip - .attach(TooltipContainerComponent) - .to(this.container) - .position({ attachment: this.placement }) - .show({ - content: this.mdbTooltip, - placement: this.placement, - }); - }; - - this.showTooltip(showTooltip); - } - - private showTooltip(fn: Function) { - if (this.delay) { - this._delayTimeoutId = setTimeout(() => { - fn(); - }, this.delay); - } else { - fn(); - } - } - - /** - * Closes an element’s tooltip. This is considered a “manual” triggering of - * the tooltip. - */ - public hide(): void { - if (this._delayTimeoutId) { - clearTimeout(this._delayTimeoutId); - this._delayTimeoutId = undefined; - } - - if (!this._tooltip.isShown) { - return; - } - - this._tooltip.instance.classMap.in = false; - setTimeout(() => { - this._tooltip.hide(); - }, this.fadeDuration); - } - - public dispose() { - this._tooltip.dispose(); - } - - public ngOnDestroy(): void { - this._tooltip.dispose(); - this._destroy$.next(); - this._destroy$.complete(); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip.module.ts b/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip.module.ts deleted file mode 100755 index 4eaa2c03..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip.module.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule, ModuleWithProviders } from '@angular/core'; -import { TooltipContainerComponent } from './tooltip.component'; -import { TooltipDirective } from './tooltip.directive'; -import { TooltipConfig } from './tooltip.service'; -import { ComponentLoaderFactory } from '../utils/component-loader/component-loader.factory'; -import { PositioningService } from '../utils/positioning/positioning.service'; - -@NgModule({ - imports: [CommonModule], - declarations: [TooltipDirective, TooltipContainerComponent], - exports: [TooltipDirective], - entryComponents: [TooltipContainerComponent] -}) -export class TooltipModule { - public static forRoot(): ModuleWithProviders { - return { - ngModule: TooltipModule, - providers: [TooltipConfig, ComponentLoaderFactory, PositioningService] - }; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip.service.ts b/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip.service.ts deleted file mode 100755 index 8f798358..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/tooltip/tooltip.service.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Injectable } from '@angular/core'; - -/** Default values provider for tooltip */ -@Injectable() -export class TooltipConfig { - /** tooltip placement, supported positions: 'top', 'bottom', 'left', 'right' */ - public placement = 'top'; - /** array of event names which triggers tooltip opening */ - public triggers = 'hover focus'; - /** a selector specifying the element the tooltip should be appended to. Currently only supports "body" */ - public container: string; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utilities/decorators.ts b/projects/angular-bootstrap-md/src/lib/free/utilities/decorators.ts deleted file mode 100644 index c27344f7..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utilities/decorators.ts +++ /dev/null @@ -1,25 +0,0 @@ -/*tslint:disable:no-invalid-this */ -/* tslint:disable-next-line: no-any */ -export function OnChange(): any { - const sufix = 'Change'; - - /* tslint:disable-next-line: no-any */ - return function OnChangeHandler(target: any, propertyKey: string): void { - const _key = ` __${propertyKey}Value`; - Object.defineProperty(target, propertyKey, { - /* tslint:disable-next-line: no-any */ - get(): any { - return this[_key]; - }, - /* tslint:disable-next-line: no-any */ - set(value: any): void { - const prevValue = this[_key]; - this[_key] = value; - if (prevValue !== value && this[propertyKey + sufix]) { - this[propertyKey + sufix].emit(value); - } - } - }); - }; -} -/* tslint:enable */ diff --git a/projects/angular-bootstrap-md/src/lib/free/utilities/facade/browser.ts b/projects/angular-bootstrap-md/src/lib/free/utilities/facade/browser.ts deleted file mode 100644 index bd78bbc2..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utilities/facade/browser.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -/** - * JS version of browser APIs. This library can only run in the browser. - */ -const win = (typeof window !== 'undefined' && window) || {} as any; - -export { win as window }; -export const document = win.document; -export const location = win.location; -export const gc = win.gc ? () => win.gc() : (): any => null; -export const performance = win.performance ? win.performance : null; -export const Event = win.Event; -export const MouseEvent = win.MouseEvent; -export const KeyboardEvent = win.KeyboardEvent; -export const EventTarget = win.EventTarget; -export const History = win.History; -export const Location = win.Location; -export const EventListener = win.EventListener; diff --git a/projects/angular-bootstrap-md/src/lib/free/utilities/index.ts b/projects/angular-bootstrap-md/src/lib/free/utilities/index.ts deleted file mode 100644 index 56c5e091..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utilities/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -export * from './triggers'; -export { isBs3 } from './theme-provider'; -export { LinkedList } from './linked-list.class'; - -export { - listenToTriggersV2, - registerOutsideClick, - registerEscClick -} from './triggers'; - -export { OnChange } from './decorators'; -export { setTheme } from './theme-provider'; -export { Trigger } from './trigger.class'; -export { Utils } from './utils.class'; -export { window, document } from './facade/browser'; -export { warnOnce }from './warn-once'; diff --git a/projects/angular-bootstrap-md/src/lib/free/utilities/licens.md b/projects/angular-bootstrap-md/src/lib/free/utilities/licens.md deleted file mode 100755 index 68dc12fd..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utilities/licens.md +++ /dev/null @@ -1 +0,0 @@ -https://github.com/valor-software/ngx-bootstrap/blob/development/LICENSE \ No newline at end of file diff --git a/projects/angular-bootstrap-md/src/lib/free/utilities/linked-list.class.ts b/projects/angular-bootstrap-md/src/lib/free/utilities/linked-list.class.ts deleted file mode 100644 index da4546c4..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utilities/linked-list.class.ts +++ /dev/null @@ -1,283 +0,0 @@ -export class LinkedList { - length = 0; - /* tslint:disable-next-line: no-any*/ - protected head: any; - /* tslint:disable-next-line: no-any*/ - protected tail: any; - /* tslint:disable-next-line: no-any*/ - protected current: any; - protected asArray: T[] = []; - - get(position: number) { - if (this.length === 0 || position < 0 || position >= this.length) { - return void 0; - } - - let current = this.head; - - for (let index = 0; index < position; index++) { - current = current.next; - } - - return current.value; - } - - add(value: T, position: number = this.length): void { - if (position < 0 || position > this.length) { - throw new Error('Position is out of the list'); - } - - /* tslint:disable-next-line: no-any*/ - const node: any = { - value, - next: undefined, - previous: undefined - }; - - if (this.length === 0) { - this.head = node; - this.tail = node; - this.current = node; - } else { - if (position === 0) { - // first node - node.next = this.head; - this.head.previous = node; - this.head = node; - } else if (position === this.length) { - // last node - this.tail.next = node; - node.previous = this.tail; - this.tail = node; - } else { - // node in middle - const currentPreviousNode = this.getNode(position - 1); - const currentNextNode = currentPreviousNode.next; - - currentPreviousNode.next = node; - currentNextNode.previous = node; - - node.previous = currentPreviousNode; - node.next = currentNextNode; - } - } - this.length++; - this.createInternalArrayRepresentation(); - } - - remove(position = 0): void { - if (this.length === 0 || position < 0 || position >= this.length) { - throw new Error('Position is out of the list'); - } - - if (position === 0) { - // first node - this.head = this.head.next; - - if (this.head) { - // there is no second node - this.head.previous = undefined; - } else { - // there is no second node - this.tail = undefined; - } - } else if (position === this.length - 1) { - // last node - this.tail = this.tail.previous; - this.tail.next = undefined; - } else { - // middle node - const removedNode = this.getNode(position); - removedNode.next.previous = removedNode.previous; - removedNode.previous.next = removedNode.next; - } - - this.length--; - this.createInternalArrayRepresentation(); - } - - set(position: number, value: T): void { - if (this.length === 0 || position < 0 || position >= this.length) { - throw new Error('Position is out of the list'); - } - - const node = this.getNode(position); - node.value = value; - this.createInternalArrayRepresentation(); - } - - toArray(): T[] { - return this.asArray; - } - - /* tslint:disable-next-line: no-any*/ - findAll(fn: any): any[] { - let current = this.head; - /* tslint:disable-next-line: no-any*/ - const result: any[] = []; - for (let index = 0; index < this.length; index++) { - if (fn(current.value, index)) { - result.push({index, value: current.value}); - } - current = current.next; - } - - return result; - } - - // Array methods overriding start - push(...args: T[]): number { - /* tslint:disable-next-line: no-any*/ - args.forEach((arg: any) => { - this.add(arg); - }); - - return this.length; - } - - pop() { - if (this.length === 0) { - return undefined; - } - const last = this.tail; - this.remove(this.length - 1); - - return last.value; - } - - unshift(...args: T[]): number { - args.reverse(); - /* tslint:disable-next-line: no-any*/ - args.forEach((arg: any) => { - this.add(arg, 0); - }); - - return this.length; - } - - shift() { - if (this.length === 0) { - return undefined; - } - const lastItem = this.head.value; - this.remove(); - - return lastItem; - } - - /* tslint:disable-next-line: no-any*/ - forEach(fn: any): void { - let current = this.head; - for (let index = 0; index < this.length; index++) { - fn(current.value, index); - current = current.next; - } - } - - indexOf(value: T): number { - let current = this.head; - let position = 0; - - for (let index = 0; index < this.length; index++) { - if (current.value === value) { - position = index; - break; - } - current = current.next; - } - - return position; - } - - /* tslint:disable-next-line: no-any*/ - some(fn: any): boolean { - let current = this.head; - let result = false; - while (current && !result) { - if (fn(current.value)) { - result = true; - break; - } - current = current.next; - } - - return result; - } - - /* tslint:disable-next-line: no-any*/ - every(fn: any): boolean { - let current = this.head; - let result = true; - while (current && result) { - if (!fn(current.value)) { - result = false; - } - current = current.next; - } - - return result; - } - - toString(): string { - return '[Linked List]'; - } - - /* tslint:disable-next-line: no-any*/ - find(fn: any) { - let current = this.head; - let result; - for (let index = 0; index < this.length; index++) { - if (fn(current.value, index)) { - result = current.value; - break; - } - current = current.next; - } - - return result; - } - - /* tslint:disable-next-line: no-any*/ - findIndex(fn: any) { - let current = this.head; - let result; - for (let index = 0; index < this.length; index++) { - if (fn(current.value, index)) { - result = index; - break; - } - current = current.next; - } - - return result; - } - - /* tslint:disable-next-line: no-any*/ - protected getNode(position: number): any { - if (this.length === 0 || position < 0 || position >= this.length) { - throw new Error('Position is out of the list'); - } - - let current = this.head; - - for (let index = 0; index < position; index++) { - current = current.next; - } - - return current; - } - - protected createInternalArrayRepresentation(): void { - /* tslint:disable-next-line: no-any*/ - const outArray: any[] = []; - let current = this.head; - - while (current) { - outArray.push(current.value); - current = current.next; - } - this.asArray = outArray; - } - - // Array methods overriding END -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utilities/theme-provider.ts b/projects/angular-bootstrap-md/src/lib/free/utilities/theme-provider.ts deleted file mode 100644 index f94e8ff8..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utilities/theme-provider.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { window } from './facade/browser'; - -let guessedVersion: 'bs3' | 'bs4' | null; - -function _guessBsVersion(): 'bs3' | 'bs4' | null { - if (typeof document === 'undefined') { - return null; - } - const spanEl = document.createElement('span'); - spanEl.innerText = 'test bs version'; - document.body.appendChild(spanEl); - spanEl.classList.add('d-none'); - const rect = spanEl.getBoundingClientRect(); - document.body.removeChild(spanEl); - if (!rect) { - return 'bs3'; - } - - return rect.top === 0 ? 'bs4' : 'bs3'; -} - -export function setTheme(theme: 'bs3' | 'bs4'): void { - guessedVersion = theme; -} - -// todo: in ngx-bootstrap, bs4 will became a default one -export function isBs3(): boolean { - if (typeof window === 'undefined') { - return true; - } - - if (typeof window.__theme === 'undefined') { - if (guessedVersion) { - return guessedVersion === 'bs3'; - } - guessedVersion = _guessBsVersion(); - - return guessedVersion === 'bs3'; - } - - return window.__theme !== 'bs4'; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utilities/trigger.class.ts b/projects/angular-bootstrap-md/src/lib/free/utilities/trigger.class.ts deleted file mode 100644 index 0ee7d29f..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utilities/trigger.class.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @copyright Valor Software - * @copyright Angular ng-bootstrap team - */ - -export class Trigger { - open: string; - close?: any; - - constructor(open: string, close?: string) { - this.open = open; - this.close = close || open; - } - - isManual(): boolean { - return this.open === 'manual' || this.close === 'manual'; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utilities/triggers.ts b/projects/angular-bootstrap-md/src/lib/free/utilities/triggers.ts deleted file mode 100644 index ee572878..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utilities/triggers.ts +++ /dev/null @@ -1,176 +0,0 @@ -/** - * @copyright Valor Software - * @copyright Angular ng-bootstrap team - */ -import { Renderer2 } from '@angular/core'; -import { Trigger } from './trigger.class'; - -/* tslint:disable-next-line: no-any */ -export type BsEventCallback = (event?: any) => boolean | void; - -export interface ListenOptions { - target?: HTMLElement; - targets?: HTMLElement[]; - triggers?: any; - outsideClick?: boolean; - outsideEsc?: boolean; - show?: any; - hide?: any; - toggle?: BsEventCallback; -} - -const DEFAULT_ALIASES = { - hover: ['mouseover', 'mouseout'], - focus: ['focusin', 'focusout'] -}; - -/* tslint:disable-next-line: no-any */ -export function parseTriggers(triggers: string, aliases: any = DEFAULT_ALIASES): Trigger[] { - const trimmedTriggers = (triggers || '').trim(); - - if (trimmedTriggers.length === 0) { - return []; - } - - const parsedTriggers = trimmedTriggers - .split(/\s+/) - .map((trigger: string) => trigger.split(':')) - .map((triggerPair: string[]) => { - const alias = aliases[triggerPair[0]] || triggerPair; - - return new Trigger(alias[0], alias[1]); - }); - - const manualTriggers = parsedTriggers.filter((triggerPair: Trigger) => - triggerPair.isManual() - ); - - if (manualTriggers.length > 1) { - throw new Error('Triggers parse error: only one manual trigger is allowed'); - } - - if (manualTriggers.length === 1 && parsedTriggers.length > 1) { - throw new Error('Triggers parse error: manual trigger can\'t be mixed with other triggers'); - } - - return parsedTriggers; -} - -export function listenToTriggers(renderer: Renderer2, - /* tslint:disable-next-line: no-any */ - target: any, - triggers: string, - showFn: BsEventCallback, - hideFn: BsEventCallback, - toggleFn: BsEventCallback): Function { - const parsedTriggers = parseTriggers(triggers); - /* tslint:disable-next-line: no-any */ - const listeners: any[] = []; - - if (parsedTriggers.length === 1 && parsedTriggers[0].isManual()) { - return Function.prototype; - } - - parsedTriggers.forEach((trigger: Trigger) => { - if (trigger.open === trigger.close) { - listeners.push(renderer.listen(target, trigger.open, toggleFn)); - - return; - } - - listeners.push( - renderer.listen(target, trigger.open, showFn), - renderer.listen(target, trigger.close, hideFn) - ); - }); - - return () => { - listeners.forEach((unsubscribeFn: Function) => unsubscribeFn()); - }; -} - -export function listenToTriggersV2(renderer: Renderer2, - options: ListenOptions): Function { - const parsedTriggers = parseTriggers(options.triggers); - const target = options.target; - // do nothing - if (parsedTriggers.length === 1 && parsedTriggers[0].isManual()) { - return Function.prototype; - } - - // all listeners - /* tslint:disable-next-line: no-any */ - const listeners: any[] = []; - - // lazy listeners registration - const _registerHide: Function[] = []; - const registerHide = () => { - // add hide listeners to unregister array - _registerHide.forEach((fn: Function) => listeners.push(fn())); - // register hide events only once - _registerHide.length = 0; - }; - - // register open\close\toggle listeners - parsedTriggers.forEach((trigger: Trigger) => { - const useToggle = trigger.open === trigger.close; - const showFn = useToggle ? options.toggle : options.show; - - if (!useToggle) { - _registerHide.push(() => - renderer.listen(target, trigger.close, options.hide) - ); - } - - listeners.push( - renderer.listen(target, trigger.open, () => showFn(registerHide)) - ); - }); - - return () => { - listeners.forEach((unsubscribeFn: Function) => unsubscribeFn()); - }; -} - -export function registerOutsideClick(renderer: Renderer2, - options: ListenOptions) { - if (!options.outsideClick) { - return Function.prototype; - } - - /* tslint:disable-next-line: no-any */ - return renderer.listen('document', 'click', (event: any) => { - if (options.target && options.target.contains(event.target)) { - return undefined; - } - if ( - options.targets && - options.targets.some(target => target.contains(event.target)) - ) { - return undefined; - } - - options.hide(); - }); -} - -export function registerEscClick(renderer: Renderer2, - options: ListenOptions) { - if (!options.outsideEsc) { - return Function.prototype; - } - - return renderer.listen('document', 'keyup.esc', (event: any) => { - if (options.target && options.target.contains(event.target)) { - return undefined; - } - if ( - options.targets && - options.targets.some(target => target.contains(event.target)) - ) { - return undefined; - } - - options.hide(); - }); -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utilities/utils.class.ts b/projects/angular-bootstrap-md/src/lib/free/utilities/utils.class.ts deleted file mode 100644 index 41926a3e..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utilities/utils.class.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { window } from './facade/browser'; - -export class Utils { - /* tslint:disable-next-line: no-any */ - static reflow(element: any): void { - /* tslint:disable-next-line: no-any */ - ((bs: any): void => bs)(element.offsetHeight); - } - - // source: https://github.com/jquery/jquery/blob/master/src/css/var/getStyles.js - /* tslint:disable-next-line: no-any */ - static getStyles(elem: any): any { - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - let view = elem.ownerDocument.defaultView; - - if (!view || !view.opener) { - view = window; - } - - return view.getComputedStyle(elem); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utilities/warn-once.ts b/projects/angular-bootstrap-md/src/lib/free/utilities/warn-once.ts deleted file mode 100644 index d9a84629..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utilities/warn-once.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { isDevMode } from '@angular/core'; -const _messagesHash: { [key: string]: boolean } = {}; -const _hideMsg = typeof console === 'undefined' || !('warn' in console); - -export function warnOnce(msg: string): void { - if (!isDevMode() || _hideMsg || msg in _messagesHash) { - return; - } - - _messagesHash[msg] = true; - /*tslint:disable-next-line*/ - console.warn(msg); -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/bs-component-ref.class.ts b/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/bs-component-ref.class.ts deleted file mode 100644 index 14c212ee..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/bs-component-ref.class.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { TemplateRef, ViewContainerRef } from '@angular/core'; - -export class BsComponentRef { - templateRef: TemplateRef; - viewContainer: ViewContainerRef; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/component-loader.class.ts b/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/component-loader.class.ts deleted file mode 100644 index 16f1a453..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/component-loader.class.ts +++ /dev/null @@ -1,420 +0,0 @@ -// tslint:disable:max-file-line-count -// todo: add delay support -// todo: merge events onShow, onShown, etc... -// todo: add global positioning configuration? -import { - ApplicationRef, - ComponentFactory, - ComponentFactoryResolver, - ComponentRef, - ElementRef, - EmbeddedViewRef, - EventEmitter, - Injector, - NgZone, - Renderer2, - StaticProvider, - TemplateRef, - Type, - ViewContainerRef -} from '@angular/core'; - -import { PositioningOptions, PositioningService} from './../positioning/positioning.service'; - -import { - listenToTriggersV2, - registerEscClick, - registerOutsideClick -} from './../../utilities'; - -import { ContentRef } from './content-ref.class'; -import { ListenOptions } from './listen-options.model'; - -export class ComponentLoader { - onBeforeShow: EventEmitter = new EventEmitter(); - /* tslint:disable-next-line: no-any*/ - onShown: EventEmitter = new EventEmitter(); - /* tslint:disable-next-line: no-any*/ - onBeforeHide: EventEmitter = new EventEmitter(); - onHidden: EventEmitter = new EventEmitter(); - shown: EventEmitter = new EventEmitter(); - hidden: EventEmitter = new EventEmitter(); - - instance: T; - _componentRef: ComponentRef | any; - _inlineViewRef: EmbeddedViewRef; - - private _providers: StaticProvider[] = []; - private _componentFactory: ComponentFactory; - private _zoneSubscription: any; - private _contentRef: ContentRef | any; - private _innerComponent: ComponentRef | any; - - private _unregisterListenersFn: Function; - - get isShown(): boolean { - if (this._isHiding) { - return false; - } - - return !!this._componentRef; - } - - private _isHiding = false; - - /** - * Placement of a component. Accepts: "top", "bottom", "left", "right" - */ - private attachment: string; - - /** - * A selector specifying the element the popover should be appended to. - */ - /* tslint:disable-next-line: no-any*/ - private container: string | ElementRef | any; - - /** - * A selector used if container element was not found - */ - private containerDefaultSelector = 'body'; - - /** - * Specifies events that should trigger. Supports a space separated list of - * event names. - */ - private triggers: string; - - private _listenOpts: any = {}; - private _globalListener: Function | null = Function.prototype; - - /** - * Do not use this directly, it should be instanced via - * `ComponentLoadFactory.attach` - * @internal - */ - // tslint:disable-next-line - public constructor( - private _viewContainerRef: ViewContainerRef, - private _renderer: Renderer2, - private _elementRef: ElementRef, - private _injector: Injector, - private _componentFactoryResolver: ComponentFactoryResolver, - private _ngZone: NgZone, - private _applicationRef: ApplicationRef, - private _posService: PositioningService - ) {} - - attach(compType: Type): ComponentLoader { - this._componentFactory = this._componentFactoryResolver - .resolveComponentFactory(compType); - - return this; - } - - // todo: add behaviour: to target element, `body`, custom element - to(container?: string | ElementRef): ComponentLoader { - this.container = container || this.container; - - return this; - } - - position(opts?: PositioningOptions | any): ComponentLoader { - this.attachment = opts.attachment || this.attachment; - this._elementRef = (opts.target as ElementRef) || this._elementRef; - - return this; - } - - provide(provider: StaticProvider): ComponentLoader { - this._providers.push(provider); - - return this; - } - - // todo: appendChild to element or document.querySelector(this.container) - - show(opts: { - /* tslint:disable-next-line: no-any*/ - content?: string | TemplateRef; - /* tslint:disable-next-line: no-any*/ - data?: any; - /* tslint:disable-next-line: no-any*/ - [key: string]: any; - } = {} - ): ComponentRef { - - this._subscribePositioning(); - this._innerComponent = null; - - if (!this._componentRef) { - this.onBeforeShow.emit(); - this._contentRef = this._getContentRef(opts.content, opts.data); - - const injector = Injector.create({ - providers: this._providers, - parent: this._injector - }); - - this._componentRef = this._componentFactory.create(injector, this._contentRef.nodes); - - this._applicationRef.attachView(this._componentRef.hostView); - // this._componentRef = this._viewContainerRef - // .createComponent(this._componentFactory, 0, injector, this._contentRef.nodes); - this.instance = this._componentRef.instance; - - Object.assign(this._componentRef.instance, opts); - - if (this.container instanceof ElementRef) { - this.container.nativeElement.appendChild( - this._componentRef.location.nativeElement - ); - } - - if (typeof this.container === 'string' && typeof document !== 'undefined') { - const selectedElement = document.querySelector(this.container) || - document.querySelector(this.containerDefaultSelector); - - if (selectedElement) { - selectedElement.appendChild(this._componentRef.location.nativeElement); - } - } - - if ( - !this.container && - this._elementRef && - this._elementRef.nativeElement.parentElement - ) { - this._elementRef.nativeElement.parentElement.appendChild( - this._componentRef.location.nativeElement - ); - } - - // we need to manually invoke change detection since events registered - // via - // Renderer::listen() are not picked up by change detection with the - // OnPush strategy - if (this._contentRef.componentRef) { - this._innerComponent = this._contentRef.componentRef.instance; - this._contentRef.componentRef.changeDetectorRef.markForCheck(); - this._contentRef.componentRef.changeDetectorRef.detectChanges(); - } - this._componentRef.changeDetectorRef.markForCheck(); - this._componentRef.changeDetectorRef.detectChanges(); - this.onShown.emit(this._componentRef.instance); - } - - this._registerOutsideClick(); - - return this._componentRef; - } - - hide(): ComponentLoader { - if (!this._componentRef) { - return this; - } - - this._posService.deletePositionElement(this._componentRef.location); - - this.onBeforeHide.emit(this._componentRef.instance); - - const componentEl = this._componentRef.location.nativeElement; - componentEl.parentNode.removeChild(componentEl); - if (this._contentRef.componentRef) { - this._contentRef.componentRef.destroy(); - } - this._componentRef.destroy(); - if (this._viewContainerRef && this._contentRef.viewRef) { - this._viewContainerRef.remove( - this._viewContainerRef.indexOf(this._contentRef.viewRef) - ); - } - if (this._contentRef.viewRef) { - this._contentRef.viewRef.destroy(); - } - - this._contentRef = null; - this._componentRef = null; - this._removeGlobalListener(); - - this.onHidden.emit(); - - return this; - } - - toggle(): void { - if (this.isShown) { - this.hide(); - - return; - } - - this.show(); - } - - dispose(): void { - if (this.isShown) { - this.hide(); - } - - this._unsubscribePositioning(); - - if (this._unregisterListenersFn) { - this._unregisterListenersFn(); - } - } - - listen(listenOpts: ListenOptions | any): ComponentLoader { - this.triggers = listenOpts.triggers || this.triggers; - this._listenOpts.outsideClick = listenOpts.outsideClick; - this._listenOpts.outsideEsc = listenOpts.outsideEsc; - listenOpts.target = listenOpts.target || this._elementRef.nativeElement; - - const hide = (this._listenOpts.hide = () => - listenOpts.hide ? listenOpts.hide() : void this.hide()); - const show = (this._listenOpts.show = (registerHide: Function) => { - listenOpts.show ? listenOpts.show(registerHide) : this.show(registerHide); - registerHide(); - }); - - const toggle = (registerHide: Function) => { - this.isShown ? hide() : show(registerHide); - }; - - this._unregisterListenersFn = listenToTriggersV2(this._renderer, { - target: listenOpts.target, - triggers: listenOpts.triggers, - show, - hide, - toggle - }); - - return this; - } - - _removeGlobalListener() { - if (this._globalListener) { - this._globalListener(); - this._globalListener = null; - } - } - - attachInline( - vRef: ViewContainerRef, - /* tslint:disable-next-line: no-any*/ - template: TemplateRef - ): ComponentLoader { - this._inlineViewRef = vRef.createEmbeddedView(template); - - return this; - } - - _registerOutsideClick(): void { - if (!this._componentRef || !this._componentRef.location) { - return; - } - // why: should run after first event bubble - if (this._listenOpts && this._listenOpts.outsideClick) { - const target = this._componentRef.location.nativeElement; - setTimeout(() => { - this._globalListener = registerOutsideClick(this._renderer, { - targets: [target, this._elementRef.nativeElement], - outsideClick: this._listenOpts.outsideClick, - hide: () => this._listenOpts.hide() - }); - }); - } - if (this._listenOpts.outsideEsc) { - const target = this._componentRef.location.nativeElement; - this._globalListener = registerEscClick(this._renderer, { - targets: [target, this._elementRef.nativeElement], - outsideEsc: this._listenOpts.outsideEsc, - hide: () => this._listenOpts.hide() - }); - } - } - - getInnerComponent(): ComponentRef { - return this._innerComponent; - } - - private _subscribePositioning(): void { - if (this._zoneSubscription || !this.attachment) { - return; - } - - this.onShown.subscribe(() => { - this._posService.position({ - element: this._componentRef.location, - target: this._elementRef, - attachment: this.attachment, - appendToBody: this.container === 'body' - }); - }); - - this._zoneSubscription = this._ngZone.onStable.subscribe(() => { - if (!this._componentRef) { - return; - } - - this._posService.calcPosition(); - }); - } - - private _unsubscribePositioning(): void { - if (!this._zoneSubscription) { - return; - } - - this._zoneSubscription.unsubscribe(); - this._zoneSubscription = null; - } - - private _getContentRef( - /* tslint:disable-next-line: no-any*/ - content: string | TemplateRef | any, - /* tslint:disable-next-line: no-any*/ - data?: any, - /* tslint:disable-next-line: no-any*/ - ): ContentRef { - if (!content) { - return new ContentRef([]); - } - - if (content instanceof TemplateRef) { - if (this._viewContainerRef) { - const _viewRef = this._viewContainerRef - .createEmbeddedView>(content); - _viewRef.markForCheck(); - - return new ContentRef([_viewRef.rootNodes], _viewRef); - } - const viewRef = content.createEmbeddedView({}); - this._applicationRef.attachView(viewRef); - - return new ContentRef([viewRef.rootNodes], viewRef); - } - - if (typeof content === 'function') { - const contentCmptFactory = this._componentFactoryResolver.resolveComponentFactory( - content - ); - - const modalContentInjector = Injector.create({ - providers: this._providers, - parent: this._injector - }); - - const componentRef = contentCmptFactory.create(modalContentInjector); - Object.assign(componentRef.instance, data); - this._applicationRef.attachView(componentRef.hostView); - - return new ContentRef( - [[componentRef.location.nativeElement]], - componentRef.hostView, - componentRef - ); - } - - return new ContentRef([[this._renderer.createText(`${content}`)]]); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/component-loader.factory.ts b/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/component-loader.factory.ts deleted file mode 100644 index 0f244f6e..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/component-loader.factory.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - ApplicationRef, ComponentFactoryResolver, ElementRef, Injectable, Injector, - NgZone, Renderer2, ViewContainerRef -} from '@angular/core'; -import { ComponentLoader } from './component-loader.class'; -import { PositioningService } from './../positioning/positioning.service'; - -@Injectable() -export class ComponentLoaderFactory { - constructor(private _componentFactoryResolver: ComponentFactoryResolver, - private _ngZone: NgZone, - private _injector: Injector, - private _posService: PositioningService, - private _applicationRef: ApplicationRef) {} - - createLoader(_elementRef: ElementRef, - _viewContainerRef: ViewContainerRef, - _renderer: Renderer2): ComponentLoader { - return new ComponentLoader( - _viewContainerRef, - _renderer, - _elementRef, - this._injector, - this._componentFactoryResolver, - this._ngZone, - this._applicationRef, - this._posService - ); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/content-ref.class.ts b/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/content-ref.class.ts deleted file mode 100644 index 2c973c8c..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/content-ref.class.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @copyright Valor Software - * @copyright Angular ng-bootstrap team - */ - -import { ComponentRef, ViewRef } from '@angular/core'; - -export class ContentRef { - /* tslint:disable-next-line: no-any */ - nodes: any[]; - viewRef?: ViewRef; - /* tslint:disable-next-line: no-any */ - componentRef?: ComponentRef; - - constructor( - /* tslint:disable-next-line: no-any */ - nodes: any[], - viewRef?: ViewRef, - /* tslint:disable-next-line: no-any */ - componentRef?: ComponentRef - ) { - this.nodes = nodes; - this.viewRef = viewRef; - this.componentRef = componentRef; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/index.ts b/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/index.ts deleted file mode 100644 index 91dc3fc1..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { BsComponentRef } from './bs-component-ref.class'; -export { ComponentLoader } from './component-loader.class'; -export { ComponentLoaderFactory } from './component-loader.factory'; -export { ContentRef } from './content-ref.class'; diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/listen-options.model.ts b/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/listen-options.model.ts deleted file mode 100644 index 004c8db0..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/component-loader/listen-options.model.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* tslint:disable-next-line: no-any */ -export type BsEventCallback = (event?: any) => boolean | void; - -export interface ListenOptions { - target?: HTMLElement; - targets?: HTMLElement[]; - triggers?: string; - outsideClick?: boolean; - outsideEsc?: boolean; - show?: BsEventCallback; - hide?: BsEventCallback; - toggle?: BsEventCallback; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/decorators.ts b/projects/angular-bootstrap-md/src/lib/free/utils/decorators.ts deleted file mode 100755 index 607a27bf..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/decorators.ts +++ /dev/null @@ -1,18 +0,0 @@ -/*tslint:disable:no-invalid-this */ -export function OnChange(): any { - const sufix = 'Change'; - return function OnChangeHandler(target: any, propertyKey: string): void { - const _key = ` __${propertyKey}Value`; - Object.defineProperty(target, propertyKey, { - get(): any { return this[_key]; }, - set(value: any): void { - const prevValue = this[_key]; - this[_key] = value; - if (prevValue !== value && this[propertyKey + sufix]) { - this[propertyKey + sufix].emit(value); - } - } - }); - }; -} -/* tslint:enable */ diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/facade/browser.ts b/projects/angular-bootstrap-md/src/lib/free/utils/facade/browser.ts deleted file mode 100755 index e6bbfdc8..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/facade/browser.ts +++ /dev/null @@ -1,27 +0,0 @@ -/*tslint:disable */ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -/** - * JS version of browser APIs. This library can only run in the browser. - */ -var win = typeof window !== 'undefined' && window || {}; - -export {win as window}; -export var document = win.document; -export var location = win.location; -export var gc = win['gc'] ? () => win['gc']() : (): any => null; -export var performance = win['performance'] ? win['performance'] : null; -export const Event = win['Event']; -export const MouseEvent = win['MouseEvent']; -export const KeyboardEvent = win['KeyboardEvent']; -export const EventTarget = win['EventTarget']; -export const History = win['History']; -export const Location = win['Location']; -export const EventListener = win['EventListener']; -export const navigator = win['navigator']; diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/index.ts b/projects/angular-bootstrap-md/src/lib/free/utils/index.ts deleted file mode 100755 index 3ddf6c52..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { OnChange } from './decorators'; -export { LinkedList } from './linked-list.class'; -export { isBs3 } from './ng2-bootstrap-config'; -export { Trigger } from './trigger.class'; -export { Utils } from './utils.class'; diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/keyboard-navigation.ts b/projects/angular-bootstrap-md/src/lib/free/utils/keyboard-navigation.ts deleted file mode 100644 index ea5d737e..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/keyboard-navigation.ts +++ /dev/null @@ -1,126 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -export const MAC_ENTER = 3; -export const BACKSPACE = 8; -export const TAB = 9; -export const NUM_CENTER = 12; -export const ENTER = 13; -export const SHIFT = 16; -export const CONTROL = 17; -export const ALT = 18; -export const PAUSE = 19; -export const CAPS_LOCK = 20; -export const ESCAPE = 27; -export const SPACE = 32; -export const PAGE_UP = 33; -export const PAGE_DOWN = 34; -export const END = 35; -export const HOME = 36; -export const LEFT_ARROW = 37; -export const UP_ARROW = 38; -export const RIGHT_ARROW = 39; -export const DOWN_ARROW = 40; -export const PLUS_SIGN = 43; -export const PRINT_SCREEN = 44; -export const INSERT = 45; -export const DELETE = 46; -export const ZERO = 48; -export const ONE = 49; -export const TWO = 50; -export const THREE = 51; -export const FOUR = 52; -export const FIVE = 53; -export const SIX = 54; -export const SEVEN = 55; -export const EIGHT = 56; -export const NINE = 57; -export const FF_SEMICOLON = 59; // Firefox (Gecko) fires this for semicolon instead of 186 -export const FF_EQUALS = 61; // Firefox (Gecko) fires this for equals instead of 187 -export const QUESTION_MARK = 63; -export const AT_SIGN = 64; -export const A = 65; -export const B = 66; -export const C = 67; -export const D = 68; -export const E = 69; -export const F = 70; -export const G = 71; -export const H = 72; -export const I = 73; -export const J = 74; -export const K = 75; -export const L = 76; -export const M = 77; -export const N = 78; -export const O = 79; -export const P = 80; -export const Q = 81; -export const R = 82; -export const S = 83; -export const T = 84; -export const U = 85; -export const V = 86; -export const W = 87; -export const X = 88; -export const Y = 89; -export const Z = 90; -export const META = 91; // WIN_KEY_LEFT -export const MAC_WK_CMD_LEFT = 91; -export const MAC_WK_CMD_RIGHT = 93; -export const CONTEXT_MENU = 93; -export const NUMPAD_ZERO = 96; -export const NUMPAD_ONE = 97; -export const NUMPAD_TWO = 98; -export const NUMPAD_THREE = 99; -export const NUMPAD_FOUR = 100; -export const NUMPAD_FIVE = 101; -export const NUMPAD_SIX = 102; -export const NUMPAD_SEVEN = 103; -export const NUMPAD_EIGHT = 104; -export const NUMPAD_NINE = 105; -export const NUMPAD_MULTIPLY = 106; -export const NUMPAD_PLUS = 107; -export const NUMPAD_MINUS = 109; -export const NUMPAD_PERIOD = 110; -export const NUMPAD_DIVIDE = 111; -export const F1 = 112; -export const F2 = 113; -export const F3 = 114; -export const F4 = 115; -export const F5 = 116; -export const F6 = 117; -export const F7 = 118; -export const F8 = 119; -export const F9 = 120; -export const F10 = 121; -export const F11 = 122; -export const F12 = 123; -export const NUM_LOCK = 144; -export const SCROLL_LOCK = 145; -export const FIRST_MEDIA = 166; -export const FF_MINUS = 173; -export const MUTE = 173; // Firefox (Gecko) fires 181 for MUTE -export const VOLUME_DOWN = 174; // Firefox (Gecko) fires 182 for VOLUME_DOWN -export const VOLUME_UP = 175; // Firefox (Gecko) fires 183 for VOLUME_UP -export const FF_MUTE = 181; -export const FF_VOLUME_DOWN = 182; -export const LAST_MEDIA = 183; -export const FF_VOLUME_UP = 183; -export const SEMICOLON = 186; // Firefox (Gecko) fires 59 for SEMICOLON -export const EQUALS = 187; // Firefox (Gecko) fires 61 for EQUALS -export const COMMA = 188; -export const DASH = 189; // Firefox (Gecko) fires 173 for DASH/MINUS -export const SLASH = 191; -export const APOSTROPHE = 192; -export const TILDE = 192; -export const OPEN_SQUARE_BRACKET = 219; -export const BACKSLASH = 220; -export const CLOSE_SQUARE_BRACKET = 221; -export const SINGLE_QUOTE = 222; -export const MAC_META = 224; diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/licens.md b/projects/angular-bootstrap-md/src/lib/free/utils/licens.md deleted file mode 100755 index 68dc12fd..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/licens.md +++ /dev/null @@ -1 +0,0 @@ -https://github.com/valor-software/ngx-bootstrap/blob/development/LICENSE \ No newline at end of file diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/linked-list.class.ts b/projects/angular-bootstrap-md/src/lib/free/utils/linked-list.class.ts deleted file mode 100755 index 075d20c9..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/linked-list.class.ts +++ /dev/null @@ -1,265 +0,0 @@ -export class LinkedList { - - // public length: = 0; - public length: any = 0; - protected head: any; - protected tail: any; - protected current: any; - protected asArray: T[] = []; - - protected getNode(position: number): any { - if (this.length === 0 || position < 0 || position >= this.length) { - throw new Error('Position is out of the list'); - } - - let current = this.head; - - for (let index = 0; index < position; index++) { - current = current.next; - } - return current; - } - - protected createInternalArrayRepresentation(): void { - const outArray: any[] = []; - let current = this.head; - - while (current) { - outArray.push(current.value); - current = current.next; - } - this.asArray = outArray; - } - - // public get(position: number): T { - public get(position: number): T | any { - if (this.length === 0 || position < 0 || position >= this.length) { - return void 0; - } - - let current = this.head; - - for (let index = 0; index < position; index++) { - current = current.next; - } - return current.value; - } - - public add(value: T, position: number = this.length): void { - if (position < 0 || position > this.length) { - throw new Error('Position is out of the list'); - } - - const node = { - value: value as any, - next: undefined as any, - previous: undefined as any - }; - - if (this.length === 0) { - this.head = node; - this.tail = node; - this.current = node; - } else { - if (position === 0) { - // first node - node.next = this.head; - this.head.previous = node; - this.head = node; - } else if (position === this.length) { - // last node - this.tail.next = node; - node.previous = this.tail; - this.tail = node; - } else { - // node in middle - const currentPreviousNode = this.getNode(position - 1); - const currentNextNode = currentPreviousNode.next; - - currentPreviousNode.next = node; - currentNextNode.previous = node; - - node.previous = currentPreviousNode; - node.next = currentNextNode; - } - - } - this.length++; - this.createInternalArrayRepresentation(); - } - - public remove(position: number = 0): void { - if (this.length === 0 || position < 0 || position >= this.length) { - throw new Error('Position is out of the list'); - } - - if (position === 0) { - // first node - this.head = this.head.next; - - if (this.head) { - // there is no second node - this.head.previous = undefined; - } else { - // there is no second node - this.tail = undefined; - } - } else if (position === this.length - 1) { - // last node - this.tail = this.tail.previous; - this.tail.next = undefined; - } else { - // middle node - const removedNode = this.getNode(position); - removedNode.next.previous = removedNode.previous; - removedNode.previous.next = removedNode.next; - } - - this.length--; - this.createInternalArrayRepresentation(); - } - - public set(position: number, value: T): void { - if (this.length === 0 || position < 0 || position >= this.length) { - throw new Error('Position is out of the list'); - } - - const node = this.getNode(position); - node.value = value; - this.createInternalArrayRepresentation(); - } - - public toArray(): T[] { - return this.asArray; - } - - public findAll(fn: any): any[] { - let current = this.head; - const result: any[] = []; - for (let index = 0; index < this.length; index++) { - if (fn(current.value, index)) { - result.push({index, value: current.value}); - } - current = current.next; - } - return result; - } - // Array methods overriding start - public push(...args: T[]): number { - args.forEach((arg: any) => { - this.add(arg); - }); - return this.length; - } - - // public pop(): T { - public pop(): T | any { - if (this.length === 0) { - return undefined; - } - const last = this.tail; - this.remove(this.length - 1); - return last.value; - } - - public unshift(...args: T[]): number { - args.reverse(); - args.forEach((arg: any) => { - this.add(arg, 0); - }); - return this.length; - } - - // public shift(): T { - public shift(): T | any { - if (this.length === 0) { - return undefined; - } - const lastItem = this.head.value; - this.remove(); - return lastItem; - } - - public forEach(fn: any): void { - let current = this.head; - for (let index = 0; index < this.length; index++) { - fn(current.value, index); - current = current.next; - } - } - - public indexOf(value: T): number { - let current = this.head; - let position = 0; - - for (let index = 0; index < this.length; index++) { - if (current.value === value) { - - position = index; - break; - } - current = current.next; - } - return position; - } - - public some(fn: any): boolean { - let current = this.head; - let result = false; - while (current && !result) { - if (fn(current.value)) { - result = true; - break; - } - current = current.next; - } - return result; - } - - public every(fn: any): boolean { - let current = this.head; - let result = true; - while (current && result) { - if (!fn(current.value)) { - result = false; - } - current = current.next; - } - return result; - } - - public toString(): string { - return '[Linked List]'; - } - - // public find(fn: any): T { - public find(fn: any): T | any { - let current = this.head; - // let result: T; - let result: T | any; - for (let index = 0; index < this.length; index++) { - if (fn(current.value, index)) { - result = current.value; - break; - } - current = current.next; - } - return result; - } - - public findIndex(fn: any): number { - let current = this.head; - // let result: number; - let result: number | any; - for (let index = 0; index < this.length; index++) { - if (fn(current.value, index)) { - result = index; - break; - } - current = current.next; - } - return result; - } - - // Array methods overriding END -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/ng2-bootstrap-config.ts b/projects/angular-bootstrap-md/src/lib/free/utils/ng2-bootstrap-config.ts deleted file mode 100755 index 3e6f0432..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/ng2-bootstrap-config.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { window } from './facade/browser'; - -export function isBs3(): boolean { - return window.__theme === 'bs4'; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/index.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/index.ts deleted file mode 100644 index 8b7a4ef0..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { positionElements, Positioning } from './ng-positioning'; -export { PositioningService, PositioningOptions } from './positioning.service'; diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/models/index.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/models/index.ts deleted file mode 100644 index 22c3d864..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/models/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -export interface Offsets { - bottom?: number; - height: number; - left?: number; - right?: number; - top?: number; - width: number; - marginTop?: number; - marginLeft?: number; -} - -export interface Data { - options: Options; - instance: { - target: HTMLElement; - host: HTMLElement; - arrow: any; - }; - offsets: { - target: any; - host: any; - arrow: any; - }; - positionFixed: boolean; - placement: string; - placementAuto: boolean; -} - -export interface Options { - placement?: string; - modifiers: { - flip?: { - enabled: boolean; - }; - preventOverflow?: { - enabled: boolean; - }; - }; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/arrow.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/arrow.ts deleted file mode 100644 index 510fc51b..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/arrow.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { getClientRect, getOuterSizes, getStyleComputedProperty } from '../utils/index'; -import { Data } from '../models/index'; - -export function arrow(data: Data) { - let targetOffsets = data.offsets.target; - // if arrowElement is a string, suppose it's a CSS selector - const arrowElement: HTMLElement | null = data.instance.target.querySelector('.arrow'); - - // if arrowElement is not found, don't run the modifier - if (!arrowElement) { - return data; - } - - const isVertical = ['left', 'right'].indexOf(data.placement) !== -1; - - const len = isVertical ? 'height' : 'width'; - const sideCapitalized = isVertical ? 'Top' : 'Left'; - const side = sideCapitalized.toLowerCase(); - const altSide = isVertical ? 'left' : 'top'; - const opSide = isVertical ? 'bottom' : 'right'; - const arrowElementSize = getOuterSizes(arrowElement)[len]; - - // top/left side - if (data.offsets.host[opSide] - arrowElementSize < (targetOffsets as any)[side]) { - (targetOffsets as any)[side] -= - (targetOffsets as any)[side] - (data.offsets.host[opSide] - arrowElementSize); - } - // bottom/right side - if (Number((data as any).offsets.host[side]) + Number(arrowElementSize) > (targetOffsets as any)[opSide]) { - (targetOffsets as any)[side] += - Number((data as any).offsets.host[side]) + Number(arrowElementSize) - Number((targetOffsets as any)[opSide]); - } - targetOffsets = getClientRect(targetOffsets); - - // compute center of the target - const center = Number((data as any).offsets.host[side]) + Number(data.offsets.host[len] / 2 - arrowElementSize / 2); - - // Compute the sideValue using the updated target offsets - // take target margin in account because we don't have this info available - const css = getStyleComputedProperty(data.instance.target); - - const targetMarginSide = parseFloat(css[`margin${sideCapitalized}`]); - const targetBorderSide = parseFloat(css[`border${sideCapitalized}Width`]); - let sideValue = - center - (targetOffsets as any)[side] - targetMarginSide - targetBorderSide; - - // prevent arrowElement from being placed not contiguously to its target - sideValue = Math.max(Math.min(targetOffsets[len] - arrowElementSize, sideValue), 0); - - data.offsets.arrow = { - [side]: Math.round(sideValue), - [altSide]: '' // make sure to unset any eventual altSide value from the DOM node - }; - - data.instance.arrow = arrowElement; - - return data; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/flip.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/flip.ts deleted file mode 100644 index b1cda1ff..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/flip.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { - computeAutoPlacement, - getBoundaries, - getClientRect, - getOppositeVariation, - getTargetOffsets, - isModifierEnabled -} from '../utils/index'; - -import { Data } from '../models/index'; - -export function flip(data: Data): Data { - data.offsets.target = getClientRect(data.offsets.target); - - if (!isModifierEnabled(data.options, 'flip')) { - - data.offsets.target = { - ...data.offsets.target, - ...getTargetOffsets( - data.instance.target, - data.offsets.host, - data.placement - ) - }; - - return data; - } - - const boundaries = getBoundaries( - data.instance.target, - data.instance.host, - 0, // padding - 'viewport', - false // positionFixed - ); - - let placement = data.placement.split(' ')[0]; - let variation = data.placement.split(' ')[1] || ''; - - const offsetsHost = data.offsets.host; - const target = data.instance.target; - const host = data.instance.host; - - const adaptivePosition = variation - ? computeAutoPlacement('auto', offsetsHost, target, host, ['top', 'bottom']) - : computeAutoPlacement('auto', offsetsHost, target, host); - - const flipOrder = [placement, adaptivePosition]; - - /* tslint:disable-next-line: cyclomatic-complexity */ - flipOrder.forEach((step, index) => { - if (placement !== step || flipOrder.length === index + 1) { - return data; - } - - placement = data.placement.split(' ')[0]; - - // using floor because the host offsets may contain decimals we are not going to consider here - const overlapsRef = - (placement === 'left' && - Math.floor(data.offsets.target.right) > Math.floor(data.offsets.host.left)) || - (placement === 'right' && - Math.floor(data.offsets.target.left) < Math.floor(data.offsets.host.right)) || - (placement === 'top' && - Math.floor(data.offsets.target.bottom) > Math.floor(data.offsets.host.top)) || - (placement === 'bottom' && - Math.floor(data.offsets.target.top) < Math.floor(data.offsets.host.bottom)); - - const overflowsLeft = Math.floor(data.offsets.target.left) < Math.floor(boundaries.left); - const overflowsRight = Math.floor(data.offsets.target.right) > Math.floor(boundaries.right); - const overflowsTop = Math.floor(data.offsets.target.top) < Math.floor(boundaries.top); - const overflowsBottom = Math.floor(data.offsets.target.bottom) > Math.floor(boundaries.bottom); - - const overflowsBoundaries = - (placement === 'left' && overflowsLeft) || - (placement === 'right' && overflowsRight) || - (placement === 'top' && overflowsTop) || - (placement === 'bottom' && overflowsBottom); - - // flip the variation if required - const isVertical = ['top', 'bottom'].indexOf(placement) !== -1; - const flippedVariation = - ((isVertical && variation === 'left' && overflowsLeft) || - (isVertical && variation === 'right' && overflowsRight) || - (!isVertical && variation === 'left' && overflowsTop) || - (!isVertical && variation === 'right' && overflowsBottom)); - - if (overlapsRef || overflowsBoundaries || flippedVariation) { - if (overlapsRef || overflowsBoundaries) { - placement = flipOrder[index + 1]; - } - - if (flippedVariation) { - variation = getOppositeVariation(variation); - } - - data.placement = placement + (variation ? ` ${variation}` : ''); - - data.offsets.target = { - ...data.offsets.target, - ...getTargetOffsets( - data.instance.target, - data.offsets.host, - data.placement - ) - }; - } - }); - - return data; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/index.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/index.ts deleted file mode 100644 index 94492244..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { arrow } from './arrow'; -export { flip } from './flip'; -export { initData } from './initData'; -export { preventOverflow } from './preventOverflow'; -export { shift } from './shift'; diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/initData.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/initData.ts deleted file mode 100644 index 823e0475..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/initData.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { - computeAutoPlacement, - getReferenceOffsets, - getTargetOffsets -} from '../utils/index'; - -import { Data, Options } from '../models/index'; - -export function initData( - targetElement: HTMLElement, hostElement: HTMLElement, position: string, options: Options -): Data { - - const hostElPosition = getReferenceOffsets(targetElement, hostElement); - const placementAuto = !!position.match(/auto/g); - - // support old placements 'auto left|right|top|bottom' - let placement = !!position.match(/auto\s(left|right|top|bottom)/g) - ? position.split(' ')[1] || '' - : position; - - const targetOffset = getTargetOffsets(targetElement, hostElPosition, placement); - placement = computeAutoPlacement(placement, hostElPosition, targetElement, hostElement); - - return { - options, - instance: { - target: targetElement, - host: hostElement, - arrow: null - }, - offsets: { - target: targetOffset, - host: hostElPosition, - arrow: null - }, - positionFixed: false, - placement, - placementAuto - }; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/preventOverflow.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/preventOverflow.ts deleted file mode 100644 index b061fe4d..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/preventOverflow.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { getBoundaries, isModifierEnabled } from '../utils/index'; -import { Data } from '../models/index'; - -export function preventOverflow(data: Data) { - - if (!isModifierEnabled(data.options, 'preventOverflow')) { - return data; - } - - // NOTE: DOM access here - // resets the targetOffsets's position so that the document size can be calculated excluding - // the size of the targetOffsets element itself - const transformProp = 'transform'; - const targetStyles = data.instance.target.style; // assignment to help minification - const { top, left, [transformProp]: transform } = targetStyles; - targetStyles.top = ''; - targetStyles.left = ''; - targetStyles[transformProp] = ''; - - const boundaries = getBoundaries( - data.instance.target, - data.instance.host, - 0, // padding - 'scrollParent', - false // positionFixed - ); - - // NOTE: DOM access here - // restores the original style properties after the offsets have been computed - targetStyles.top = top; - targetStyles.left = left; - targetStyles[transformProp] = transform; - - const order = ['left', 'right', 'top', 'bottom']; - - const check = { - primary(placement: string) { - let value = (data as any).offsets.target[placement]; - if ( - (data as any).offsets.target[placement] < boundaries[placement] && - !false // options.escapeWithReference - ) { - value = Math.max((data as any).offsets.target[placement], boundaries[placement]); - } - - return { [placement]: value }; - }, - secondary(placement: string) { - const mainSide = placement === 'right' ? 'left' : 'top'; - let value = data.offsets.target[mainSide]; - if ( - (data as any).offsets.target[placement] > boundaries[placement] && - !false // escapeWithReference - ) { - value = Math.min( - data.offsets.target[mainSide], - boundaries[placement] - - (placement === 'right' ? data.offsets.target.width : data.offsets.target.height) - ); - } - - return { [mainSide]: value }; - } - }; - - let side: string; - - order.forEach(placement => { - side = ['left', 'top'] - .indexOf(placement) !== -1 - ? 'primary' - : 'secondary'; - - data.offsets.target = { ...data.offsets.target, ...(check as any)[side](placement) }; - - }); - - return data; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/shift.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/shift.ts deleted file mode 100644 index ed040ac7..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/modifiers/shift.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Data } from '../models/index'; - -export function shift(data: Data): Data { - const placement = data.placement; - const basePlacement = placement.split(' ')[0]; - const shiftvariation = placement.split(' ')[1]; - - if (shiftvariation) { - const { host, target } = data.offsets; - const isVertical = ['bottom', 'top'].indexOf(basePlacement) !== -1; - const side = isVertical ? 'left' : 'top'; - const measurement = isVertical ? 'width' : 'height'; - - const shiftOffsets = { - left: { [side]: host[side] }, - right: { - [side]: host[side] + host[measurement] - host[measurement] - } - }; - - data.offsets.target = { ...target, ...(shiftOffsets as any)[shiftvariation] }; - } - - return data; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/ng-positioning.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/ng-positioning.ts deleted file mode 100644 index b1fb5106..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/ng-positioning.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @copyright Valor Software - * @copyright Federico Zivolo and contributors - */ -import { Renderer2 } from '@angular/core'; - -import { getReferenceOffsets, setAllStyles } from './utils/index'; - -import { arrow, flip, preventOverflow, shift, initData } from './modifiers/index'; -import { Data, Offsets, Options } from './models/index'; - - -export class Positioning { - position(hostElement: HTMLElement, targetElement: HTMLElement): Offsets { - return this.offset(hostElement, targetElement); - } - - offset(hostElement: HTMLElement, targetElement: HTMLElement): Offsets { - return getReferenceOffsets(targetElement, hostElement); - } - - positionElements( - hostElement: HTMLElement, - targetElement: HTMLElement, - position: string, - _appendToBody?: boolean, - options?: any - ): Data { - const chainOfModifiers = [flip, shift, preventOverflow, arrow]; - - return chainOfModifiers.reduce( - (modifiedData, modifier) => modifier(modifiedData), - initData(targetElement, hostElement, position, options) - ); - } -} - -const positionService = new Positioning(); - -export function positionElements( - hostElement: HTMLElement, - targetElement: HTMLElement, - placement: string, - appendToBody?: boolean, - options?: Options, - renderer?: Renderer2 -): void { - - const data = positionService.positionElements( - hostElement, - targetElement, - placement, - appendToBody, - options - ); - - setAllStyles(data, renderer); -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/positioning.service.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/positioning.service.ts deleted file mode 100644 index 2cd1fef0..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/positioning.service.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { - Injectable, - ElementRef, - RendererFactory2, - Inject, - PLATFORM_ID, - NgZone, -} from '@angular/core'; -import { isPlatformBrowser } from '@angular/common'; - -import { positionElements } from './ng-positioning'; - -import { fromEvent, merge, of, animationFrameScheduler, Subject } from 'rxjs'; -import { Options } from './models/index'; - -export interface PositioningOptions { - /** The DOM element, ElementRef, or a selector string of an element which will be moved */ - element?: any; - - /** The DOM element, ElementRef, or a selector string of an element which the element will be attached to */ - target?: any; - - /** - * A string of the form 'vert-attachment horiz-attachment' or 'placement' - * - placement can be "top", "bottom", "left", "right" - * not yet supported: - * - vert-attachment can be any of 'top', 'middle', 'bottom' - * - horiz-attachment can be any of 'left', 'center', 'right' - */ - attachment?: any; - - /** A string similar to `attachment`. The one difference is that, if it's not provided, - * `targetAttachment` will assume the mirror image of `attachment`. - */ - targetAttachment?: string; - - /** A string of the form 'vert-offset horiz-offset' - * - vert-offset and horiz-offset can be of the form "20px" or "55%" - */ - offset?: string; - - /** A string similar to `offset`, but referring to the offset of the target */ - targetOffset?: string; - - /** If true component will be attached to body */ - appendToBody?: boolean; -} - -@Injectable() -export class PositioningService { - options: Options; - private update$$ = new Subject(); - private positionElements = new Map(); - - constructor( - rendererFactory: RendererFactory2, - @Inject(PLATFORM_ID) platformId: number, - private _ngZone: NgZone - ) { - if (isPlatformBrowser(platformId)) { - this._ngZone.runOutsideAngular(() => { - merge( - fromEvent(window, 'scroll'), - fromEvent(window, 'resize'), - // tslint:disable-next-line: deprecation - of(0, animationFrameScheduler), - this.update$$ - ).subscribe(() => { - this.positionElements.forEach((positionElement: PositioningOptions) => { - positionElements( - _getHtmlElement(positionElement.target), - _getHtmlElement(positionElement.element), - positionElement.attachment, - positionElement.appendToBody, - this.options, - rendererFactory.createRenderer(null, null) - ); - }); - }); - }); - } - } - - position(options: PositioningOptions): void { - this.addPositionElement(options); - } - - addPositionElement(options: PositioningOptions): void { - this.positionElements.set(_getHtmlElement(options.element), options); - } - - calcPosition(): void { - this.update$$.next(); - } - - deletePositionElement(elRef: ElementRef): void { - this.positionElements.delete(_getHtmlElement(elRef)); - } - - setOptions(options: Options) { - this.options = options; - } -} - -function _getHtmlElement(element: HTMLElement | ElementRef | string): any { - // it means that we got a selector - if (element && typeof element === 'string') { - return document.querySelector(element); - } - - if (element instanceof ElementRef) { - return element.nativeElement; - } - - return element; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/computeAutoPlacement.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/computeAutoPlacement.ts deleted file mode 100644 index ea303110..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/computeAutoPlacement.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Utility used to transform the `auto` placement to the placement with more - * available space. - */ -import { getBoundaries } from './getBoundaries'; - -function getArea({ width, height }: { [key: string]: number }) { - return width * height; -} - -export function computeAutoPlacement( - placement: string, - refRect: any, - target: HTMLElement, - host: HTMLElement, - allowedPositions: any[] = ['top', 'left', 'bottom', 'right'], - boundariesElement = 'viewport', - padding = 0 -) { - if (placement.indexOf('auto') === -1) { - return placement; - } - - const boundaries = getBoundaries(target, host, padding, boundariesElement); - - const rects: any = { - top: { - width: boundaries.width, - height: refRect.top - boundaries.top - }, - right: { - width: boundaries.right - refRect.right, - height: boundaries.height - }, - bottom: { - width: boundaries.width, - height: boundaries.bottom - refRect.bottom - }, - left: { - width: refRect.left - boundaries.left, - height: boundaries.height - } - }; - - const sortedAreas = Object.keys(rects) - .map(key => ({ - key, - ...rects[key], - area: getArea(rects[key]) - })) - .sort((a, b) => b.area - a.area); - - let filteredAreas: any[] = sortedAreas.filter( - ({ width, height }) => - width >= target.clientWidth && height >= target.clientHeight - ); - - filteredAreas = allowedPositions - .reduce((obj, key) => { - return { ...obj, [key]: filteredAreas[key] }; - }, {}); - - const computedPlacement: string = filteredAreas.length > 0 - ? filteredAreas[0].key - : sortedAreas[0].key; - - const variation = placement.split(' ')[1]; - - target.className = target.className.replace(/auto/g, computedPlacement); - - return computedPlacement + (variation ? `-${variation}` : ''); -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/findCommonOffsetParent.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/findCommonOffsetParent.ts deleted file mode 100644 index 278ed8de..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/findCommonOffsetParent.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Finds the offset parent common to the two provided nodes - */ -import { isOffsetContainer } from './isOffsetContainer'; -import { getRoot } from './getRoot'; -import { getOffsetParent } from './getOffsetParent'; - -export function findCommonOffsetParent(element1: HTMLElement, element2: HTMLElement): any { - // This check is needed to avoid errors in case one of the elements isn't defined for any reason - if (!element1 || !element1.nodeType || !element2 || !element2.nodeType) { - return document.documentElement; - } - - // Here we make sure to give as "start" the element that comes first in the DOM - /* tslint:disable-next-line: no-bitwise */ - const order = element1.compareDocumentPosition(element2) & Node.DOCUMENT_POSITION_FOLLOWING; - - const start = order ? element1 : element2; - const end = order ? element2 : element1; - - // Get common ancestor container - const range = document.createRange(); - range.setStart(start, 0); - range.setEnd(end, 0); - const { commonAncestorContainer } = range; - - // Both nodes are inside #document - if ( - (element1 !== commonAncestorContainer && - element2 !== commonAncestorContainer) || - start.contains(end) - ) { - if (isOffsetContainer(commonAncestorContainer)) { - return commonAncestorContainer; - } - - return getOffsetParent(commonAncestorContainer); - } - - // one of the nodes is inside shadowDOM, find which one - const element1root = getRoot(element1); - if (element1root.host) { - return findCommonOffsetParent(element1root.host, element2); - } else { - return findCommonOffsetParent(element1, getRoot(element2).host); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getBordersSize.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getBordersSize.ts deleted file mode 100644 index 52da2c5a..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getBordersSize.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Helper to detect borders of a given element - */ - -export function getBordersSize(styles: CSSStyleDeclaration, axis: string) { - const sideA = axis === 'x' ? 'Left' : 'Top'; - const sideB = sideA === 'Left' ? 'Right' : 'Bottom'; - - return ( - parseFloat(styles[`border${sideA}Width` as any]) + - parseFloat(styles[`border${sideB}Width` as any]) - ); -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getBoundaries.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getBoundaries.ts deleted file mode 100644 index 897acd8d..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getBoundaries.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Computed the boundaries limits and return them - */ -import { getScrollParent } from './getScrollParent'; -import { getParentNode } from './getParentNode'; -import { findCommonOffsetParent } from './findCommonOffsetParent'; -import { getOffsetRectRelativeToArbitraryNode } from './getOffsetRectRelativeToArbitraryNode'; -import { getViewportOffsetRectRelativeToArtbitraryNode } from './getViewportOffsetRectRelativeToArtbitraryNode'; -import { getWindowSizes } from './getWindowSizes'; -import { isFixed } from './isFixed'; -import { getFixedPositionOffsetParent } from './getFixedPositionOffsetParent'; - -export function getBoundaries( - target: any, - host: HTMLElement, - padding = 0, - boundariesElement: string, - fixedPosition = false -) { - // NOTE: 1 DOM access here - - let boundaries: any = { top: 0, left: 0 }; - const offsetParent = fixedPosition ? getFixedPositionOffsetParent(target) : findCommonOffsetParent(target, host); - - // Handle viewport case - if (boundariesElement === 'viewport') { - boundaries = getViewportOffsetRectRelativeToArtbitraryNode(offsetParent, fixedPosition); - } else { - // Handle other cases based on DOM element used as boundaries - let boundariesNode; - if (boundariesElement === 'scrollParent') { - boundariesNode = getScrollParent(getParentNode(host)); - if (boundariesNode.nodeName === 'BODY') { - boundariesNode = target.ownerDocument.documentElement; - } - } else if (boundariesElement === 'window') { - boundariesNode = target.ownerDocument.documentElement; - } else { - boundariesNode = boundariesElement; - } - - const offsets = getOffsetRectRelativeToArbitraryNode( - boundariesNode, - offsetParent, - fixedPosition - ); - - // In case of HTML, we need a different computation - if (boundariesNode.nodeName === 'HTML' && !isFixed(offsetParent)) { - const { height, width } = getWindowSizes(target.ownerDocument); - boundaries.top += offsets.top - offsets.marginTop; - boundaries.bottom = Number(height) + Number(offsets.top); - boundaries.left += offsets.left - offsets.marginLeft; - boundaries.right = Number(width) + Number(offsets.left); - } else { - // for all the other DOM elements, this one is good - boundaries = offsets; - } - } - - // Add paddings - boundaries.left += padding; - boundaries.top += padding; - boundaries.right -= padding; - boundaries.bottom -= padding; - - return boundaries; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getBoundingClientRect.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getBoundingClientRect.ts deleted file mode 100644 index 057d696c..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getBoundingClientRect.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Get bounding client rect of given element - */ -import { getStyleComputedProperty } from './getStyleComputedProperty'; -import { getBordersSize } from './getBordersSize'; -import { getWindowSizes } from './getWindowSizes'; -import { getScroll } from './getScroll'; -import { getClientRect } from './getClientRect'; -import { isIE } from './isIE'; - -export function getBoundingClientRect(element: HTMLElement): any { - let rect: any = {}; - - // IE10 10 FIX: Please, don't ask, the element isn't - // considered in DOM in some circumstances... - // This isn't reproducible in IE10 compatibility mode of IE11 - try { - if (isIE(10)) { - rect = element.getBoundingClientRect(); - const scrollTop = getScroll(element, 'top'); - const scrollLeft = getScroll(element, 'left'); - rect.top += scrollTop; - rect.left += scrollLeft; - rect.bottom += scrollTop; - rect.right += scrollLeft; - } else { - rect = element.getBoundingClientRect(); - } - } catch (e) { - return undefined; - } - - const result: any = { - left: rect.left, - top: rect.top, - width: rect.right - rect.left, - height: rect.bottom - rect.top - }; - - // subtract scrollbar size from sizes - const sizes: any = element.nodeName === 'HTML' ? getWindowSizes(element.ownerDocument) : {}; - const width = - sizes.width || element.clientWidth || result.right - result.left; - const height = - sizes.height || element.clientHeight || result.bottom - result.top; - - let horizScrollbar = element.offsetWidth - width; - let vertScrollbar = element.offsetHeight - height; - - // if an hypothetical scrollbar is detected, we must be sure it's not a `border` - // we make this check conditional for performance reasons - if (horizScrollbar || vertScrollbar) { - const styles = getStyleComputedProperty(element); - horizScrollbar -= getBordersSize(styles, 'x'); - vertScrollbar -= getBordersSize(styles, 'y'); - - result.width -= horizScrollbar; - result.height -= vertScrollbar; - } - - return getClientRect(result); -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getClientRect.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getClientRect.ts deleted file mode 100644 index c86a4510..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getClientRect.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Given element offsets, generate an output similar to getBoundingClientRect - */ -import { Offsets } from '../models/index'; - -export function getClientRect(offsets: any): Offsets { - return { - ...offsets, - right: offsets.left + offsets.width, - bottom: offsets.top + offsets.height - }; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getFixedPositionOffsetParent.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getFixedPositionOffsetParent.ts deleted file mode 100644 index ca1f2336..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getFixedPositionOffsetParent.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Finds the first parent of an element that has a transformed property defined - */ - -import { getStyleComputedProperty } from './getStyleComputedProperty'; -import { isIE } from './isIE'; - -export function getFixedPositionOffsetParent(element: any): any { - // This check is needed to avoid errors in case one of the elements isn't defined for any reason - if (!element || !element.parentElement || isIE()) { - return document.documentElement; - } - - let el: any = element.parentElement; - - while (el && getStyleComputedProperty(el, 'transform') === 'none') { - el = el.parentElement; - } - - return el || document.documentElement; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOffsetParent.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOffsetParent.ts deleted file mode 100644 index 01eaf3dc..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOffsetParent.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Returns the offset parent of the given element - */ -import { getStyleComputedProperty } from './getStyleComputedProperty'; -import { isIE } from './isIE'; - -export function getOffsetParent(element: any): any { - if (!element) { - return document.documentElement; - } - - const noOffsetParent = isIE(10) ? document.body : null; - - // NOTE: 1 DOM access here - let offsetParent = element.offsetParent || null; - // Skip hidden elements which don't have an offsetParent - - let sibling: any; - - while (offsetParent === noOffsetParent && element.nextElementSibling && element.nodeName !== 'BODY') { - sibling = element.nextElementSibling; - offsetParent = sibling.offsetParent; - } - - const nodeName = offsetParent && offsetParent.nodeName; - - if (!nodeName || nodeName === 'BODY' || nodeName === 'HTML') { - return sibling ? sibling.ownerDocument.documentElement : document.documentElement; - } - - // .offsetParent will return the closest TH, TD or TABLE in case - // no offsetParent is present, I hate this job... - if ( - ['TH', 'TD', 'TABLE'].indexOf(offsetParent.nodeName) !== -1 && - getStyleComputedProperty(offsetParent, 'position') === 'static' - ) { - return getOffsetParent(offsetParent); - } - - return offsetParent; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOffsetRectRelativeToArbitraryNode.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOffsetRectRelativeToArbitraryNode.ts deleted file mode 100644 index 5e8b5a36..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOffsetRectRelativeToArbitraryNode.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { getBoundingClientRect } from './getBoundingClientRect'; -import { getClientRect } from './getClientRect'; -import { getScrollParent } from './getScrollParent'; -import { getStyleComputedProperty } from './getStyleComputedProperty'; -import { includeScroll } from './includeScroll'; -import { isIE as runIsIE } from './isIE'; - -export function getOffsetRectRelativeToArbitraryNode( - children: HTMLElement, - parent: HTMLElement, - fixedPosition = false -): any { - const isIE10 = runIsIE(10); - const isHTML = parent.nodeName === 'HTML'; - const childrenRect: any = getBoundingClientRect(children); - const parentRect: any = getBoundingClientRect(parent); - const scrollParent = getScrollParent(children); - - const styles = getStyleComputedProperty(parent); - const borderTopWidth = parseFloat(styles.borderTopWidth); - const borderLeftWidth = parseFloat(styles.borderLeftWidth); - - // In cases where the parent is fixed, we must ignore negative scroll in offset calc - if (fixedPosition && isHTML) { - parentRect.top = Math.max(parentRect.top, 0); - parentRect.left = Math.max(parentRect.left, 0); - } - - let offsets: any = getClientRect({ - top: childrenRect.top - parentRect.top - borderTopWidth, - left: childrenRect.left - parentRect.left - borderLeftWidth, - width: childrenRect.width, - height: childrenRect.height - }); - - offsets.marginTop = 0; - offsets.marginLeft = 0; - - // Subtract margins of documentElement in case it's being used as parent - // we do this only on HTML because it's the only element that behaves - // differently when margins are applied to it. The margins are included in - // the box of the documentElement, in the other cases not. - if (!isIE10 && isHTML) { - const marginTop = parseFloat(styles.marginTop); - const marginLeft = parseFloat(styles.marginLeft); - - offsets.top -= borderTopWidth - marginTop; - offsets.bottom -= borderTopWidth - marginTop; - offsets.left -= borderLeftWidth - marginLeft; - offsets.right -= borderLeftWidth - marginLeft; - - // Attach marginTop and marginLeft because in some circumstances we may need them - offsets.marginTop = marginTop; - offsets.marginLeft = marginLeft; - } - - if ( - isIE10 && !fixedPosition - ? parent.contains(scrollParent) - : parent === scrollParent && scrollParent.nodeName !== 'BODY' - ) { - offsets = includeScroll(offsets, parent); - } - - return offsets; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOffsets.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOffsets.ts deleted file mode 100644 index cc14e088..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOffsets.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Data, Offsets } from '../models/index'; - -export function getOffsets(data: Data): Offsets { - return { - width: data.offsets.target.width, - height: data.offsets.target.height, - left: Math.floor(data.offsets.target.left), - top: Math.round(data.offsets.target.top), - bottom: Math.round(data.offsets.target.bottom), - right: Math.floor(data.offsets.target.right) - }; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOppositePlacement.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOppositePlacement.ts deleted file mode 100644 index d34fe453..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOppositePlacement.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Get the opposite placement of the given one - */ -export function getOppositePlacement(placement: string) { - const hash = { left: 'right', right: 'left', bottom: 'top', top: 'bottom' }; - - return placement.replace(/left|right|bottom|top/g, matched => (hash as any)[matched]); -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOppositeVariation.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOppositeVariation.ts deleted file mode 100644 index ea32bcb6..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOppositeVariation.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Get the opposite placement variation of the given one - */ -export function getOppositeVariation(variation: string) { - if (variation === 'right') { - return 'left'; - } else if (variation === 'left') { - return 'right'; - } - - return variation; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOuterSizes.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOuterSizes.ts deleted file mode 100644 index 5f475a03..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getOuterSizes.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Get the outer sizes of the given element (offset size + margins) - */ -export function getOuterSizes(element: any) { - const window = element.ownerDocument.defaultView; - const styles = window.getComputedStyle(element); - const x = parseFloat(styles.marginTop || 0) + parseFloat(styles.marginBottom || 0); - const y = parseFloat(styles.marginLeft || 0) + parseFloat(styles.marginRight || 0); - - return { - width: Number(element.offsetWidth) + y, - height: Number(element.offsetHeight) + x - }; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getParentNode.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getParentNode.ts deleted file mode 100644 index b0ef5780..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getParentNode.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Returns the parentNode or the host of the element - */ -export function getParentNode(element: any): any { - if (element.nodeName === 'HTML') { - return element; - } - - return element.parentNode || element.host; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getReferenceOffsets.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getReferenceOffsets.ts deleted file mode 100644 index 983a9978..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getReferenceOffsets.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Get offsets to the reference element - */ -import { findCommonOffsetParent } from './findCommonOffsetParent'; -import { getOffsetRectRelativeToArbitraryNode } from './getOffsetRectRelativeToArbitraryNode'; -import { getFixedPositionOffsetParent } from './getFixedPositionOffsetParent'; -import { Offsets } from '../models/index'; - -export function getReferenceOffsets( - target: HTMLElement, - host: HTMLElement, - fixedPosition: any = null -): Offsets { - const commonOffsetParent = fixedPosition - ? getFixedPositionOffsetParent(target) - : findCommonOffsetParent(target, host); - - return getOffsetRectRelativeToArbitraryNode(host, commonOffsetParent, fixedPosition); -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getRoot.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getRoot.ts deleted file mode 100644 index dda10ed1..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getRoot.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Finds the root node (document, shadowDOM root) of the given element - */ -export function getRoot(node: Node): any { - if (node.parentNode !== null) { - return getRoot(node.parentNode); - } - - return node; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getScroll.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getScroll.ts deleted file mode 100644 index f7f751e6..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getScroll.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Gets the scroll value of the given element in the given side (top and left) - */ -export function getScroll(element: any, side = 'top') { - const upperSide = side === 'top' ? 'scrollTop' : 'scrollLeft'; - const nodeName = element.nodeName; - - if (nodeName === 'BODY' || nodeName === 'HTML') { - const html = element.ownerDocument.documentElement; - const scrollingElement = element.ownerDocument.scrollingElement || html; - - return scrollingElement[upperSide]; - } - - return element[upperSide]; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getScrollParent.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getScrollParent.ts deleted file mode 100644 index 59ede938..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getScrollParent.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Returns the scrolling parent of the given element - */ -import { getStyleComputedProperty } from './getStyleComputedProperty'; -import { getParentNode } from './getParentNode'; - -export function getScrollParent(element: any): any { - // Return body, `getScroll` will take care to get the correct `scrollTop` from it - if (!element) { - return document.body; - } - - switch (element.nodeName) { - case 'HTML': - case 'BODY': - return element.ownerDocument.body; - case '#document': - return element.body; - default: - } - - // Firefox want us to check `-x` and `-y` variations as well - const { overflow, overflowX, overflowY } = getStyleComputedProperty(element); - if (/(auto|scroll|overlay)/.test(String(overflow) + String(overflowY) + String(overflowX))) { - return element; - } - - return getScrollParent(getParentNode(element)); -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getStyleComputedProperty.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getStyleComputedProperty.ts deleted file mode 100644 index 280f039b..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getStyleComputedProperty.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Get CSS computed property of the given element - */ -export function getStyleComputedProperty(element: any, property?: string): any { - if (element.nodeType !== 1) { - return []; - } - // NOTE: 1 DOM access here - const window = element.ownerDocument.defaultView; - const css = window.getComputedStyle(element, null); - - return property ? css[property as any] : css; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getTargetOffsets.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getTargetOffsets.ts deleted file mode 100644 index 5400cc51..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getTargetOffsets.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Get offsets to the target - */ -import { getOppositePlacement } from './getOppositePlacement'; -import { getOuterSizes } from './getOuterSizes'; -import { Offsets } from '../models/index'; - -export function getTargetOffsets( - target: HTMLElement, - hostOffsets: any, - position: string -): Offsets { - const placement = position.split(' ')[0]; - - // Get target node sizes - const targetRect = getOuterSizes(target); - - // Add position, width and height to our offsets object - const targetOffsets = { - width: targetRect.width, - height: targetRect.height - }; - - // depending by the target placement we have to compute its offsets slightly differently - const isHoriz = ['right', 'left'].indexOf(placement) !== -1; - const mainSide = isHoriz ? 'top' : 'left'; - const secondarySide = isHoriz ? 'left' : 'top'; - const measurement = isHoriz ? 'height' : 'width'; - const secondaryMeasurement = !isHoriz ? 'height' : 'width'; - - (targetOffsets as any)[mainSide] = - hostOffsets[mainSide] + - hostOffsets[measurement] / 2 - - targetRect[measurement] / 2; - - (targetOffsets as any)[secondarySide] = placement === secondarySide - ? hostOffsets[secondarySide] - targetRect[secondaryMeasurement] - : (hostOffsets as any)[getOppositePlacement(secondarySide)]; - - return targetOffsets; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getViewportOffsetRectRelativeToArtbitraryNode.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getViewportOffsetRectRelativeToArtbitraryNode.ts deleted file mode 100644 index d59764d7..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getViewportOffsetRectRelativeToArtbitraryNode.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { getClientRect } from './getClientRect'; -import { getOffsetRectRelativeToArbitraryNode } from './getOffsetRectRelativeToArbitraryNode'; -import { getScroll } from './getScroll'; -import { Offsets } from '../models/index'; - -export function getViewportOffsetRectRelativeToArtbitraryNode(element: any, excludeScroll = false): Offsets { - const html = element.ownerDocument.documentElement; - const relativeOffset = getOffsetRectRelativeToArbitraryNode(element, html); - const width = Math.max(html.clientWidth, window.innerWidth || 0); - const height = Math.max(html.clientHeight, window.innerHeight || 0); - - const scrollTop = !excludeScroll ? getScroll(html) : 0; - const scrollLeft = !excludeScroll ? getScroll(html, 'left') : 0; - - const offset = { - top: scrollTop - Number(relativeOffset.top) + Number(relativeOffset.marginTop), - left: scrollLeft - Number(relativeOffset.left) + Number(relativeOffset.marginLeft), - width, - height - }; - - return getClientRect(offset); -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getWindowSizes.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getWindowSizes.ts deleted file mode 100644 index 1e555525..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/getWindowSizes.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { isIE } from './isIE'; - -function getSize(axis: string, body: HTMLElement, html: HTMLElement, computedStyle: CSSStyleDeclaration) { - return Math.max( - (body as any)[`offset${axis}`], - (body as any)[`scroll${axis}`], - (html as any)[`client${axis}`], - (html as any)[`offset${axis}`], - (html as any)[`scroll${axis}`], - isIE(10) - ? (parseInt((html as any)[`offset${axis}`], 10) + - parseInt(computedStyle[`margin${axis === 'Height' ? 'Top' : 'Left'}` as any], 10) + - parseInt(computedStyle[`margin${axis === 'Height' ? 'Bottom' : 'Right'}` as any], 10)) - : 0 - ); -} - -export function getWindowSizes(document: any) { - const body = document.body; - const html = document.documentElement; - const computedStyle: any = isIE(10) && getComputedStyle(html); - - return { - height: getSize('Height', body, html, computedStyle), - width: getSize('Width', body, html, computedStyle) - }; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/includeScroll.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/includeScroll.ts deleted file mode 100644 index 9e98bad5..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/includeScroll.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Sum or subtract the element scroll values (left and top) from a given rect object - */ -import { getScroll } from './getScroll'; - -export function includeScroll(rect: any, element: HTMLElement, subtract = false) { - const scrollTop = getScroll(element, 'top'); - const scrollLeft = getScroll(element, 'left'); - const modifier = subtract ? -1 : 1; - rect.top += scrollTop * modifier; - rect.bottom += scrollTop * modifier; - rect.left += scrollLeft * modifier; - rect.right += scrollLeft * modifier; - - return rect; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/index.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/index.ts deleted file mode 100644 index d2071654..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -export { computeAutoPlacement } from './computeAutoPlacement'; -export { getBordersSize } from './getBordersSize'; -export { getBoundaries } from './getBoundaries'; -export { getBoundingClientRect } from './getBoundingClientRect'; -export { getClientRect } from './getClientRect'; -export { getOffsetParent } from './getOffsetParent'; -export { getOffsetRectRelativeToArbitraryNode } from './getOffsetRectRelativeToArbitraryNode'; -export { getOffsets } from './getOffsets'; -export { getOppositePlacement } from './getOppositePlacement'; -export { getOppositeVariation } from './getOppositeVariation'; -export { getOuterSizes } from './getOuterSizes'; -export { getParentNode } from './getParentNode'; -export { getReferenceOffsets } from './getReferenceOffsets'; -export { getScroll } from './getScroll'; -export { getScrollParent } from './getScrollParent'; -export { getStyleComputedProperty } from './getStyleComputedProperty'; -export { getTargetOffsets } from './getTargetOffsets'; -export { getWindowSizes } from './getWindowSizes'; -export { isFixed } from './isFixed'; -export { isModifierEnabled } from './isModifierEnabled'; -export { isNumeric } from './isNumeric'; -export { setAllStyles } from './setAllStyles'; -export { setStyles } from './setStyles'; diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isBrowser.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isBrowser.ts deleted file mode 100644 index bb2cd0b7..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isBrowser.ts +++ /dev/null @@ -1 +0,0 @@ -export const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'; diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isFixed.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isFixed.ts deleted file mode 100644 index c3be266e..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isFixed.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Check if the given element is fixed or is inside a fixed parent - */ -import { getStyleComputedProperty } from './getStyleComputedProperty'; -import { getParentNode } from './getParentNode'; - -export function isFixed(element: HTMLElement): boolean { - const nodeName = element.nodeName; - if (nodeName === 'BODY' || nodeName === 'HTML') { - return false; - } - if (getStyleComputedProperty(element, 'position') === 'fixed') { - return true; - } - - return isFixed(getParentNode(element)); -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isIE.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isIE.ts deleted file mode 100644 index fc624605..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isIE.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Determines if the browser is Internet Explorer - */ -import { isBrowser } from './isBrowser'; - -const isIE11 = isBrowser && !!((window as any).MSInputMethodContext && (document as any).documentMode); -const isIE10 = isBrowser && !!((window as any).MSInputMethodContext && /MSIE 10/.test((navigator as any).userAgent)); - -export function isIE(version?: number) { - if (version === 11) { - return isIE11; - } - if (version === 10) { - return isIE10; - } - - return isIE11 || isIE10; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isModifierEnabled.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isModifierEnabled.ts deleted file mode 100644 index 1f203aec..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isModifierEnabled.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Helper used to know if the given modifier is enabled. - */ -export function isModifierEnabled(options: any, modifierName: string) { - return options - && options.modifiers - && options.modifiers[modifierName] - && options.modifiers[modifierName].enabled; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isNumeric.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isNumeric.ts deleted file mode 100644 index 5a237c85..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isNumeric.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Tells if a given input is a number - */ -export function isNumeric(n: any) { - return n !== '' && !isNaN(parseFloat(n)) && isFinite(n); -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isOffsetContainer.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isOffsetContainer.ts deleted file mode 100644 index 1e847bf5..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/isOffsetContainer.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { getOffsetParent } from './getOffsetParent'; - -export function isOffsetContainer(element: any) { - const { nodeName } = element; - if (nodeName === 'BODY') { - return false; - } - - return ( - nodeName === 'HTML' || getOffsetParent(element.firstElementChild) === element - ); -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/setAllStyles.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/setAllStyles.ts deleted file mode 100644 index 6f588594..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/setAllStyles.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Set the style to the given popper - */ -import { Renderer2 } from '@angular/core'; - -import { Data } from '../models/index'; -import { setStyles } from './setStyles'; -import { getOffsets } from './getOffsets'; - -export function setAllStyles(data: Data, renderer?: Renderer2): void { - const target = data.instance.target; - - const offsets = getOffsets(data); - - setStyles(target, { - 'will-change': 'transform', - top: '0px', - left: '0px', - transform: `translate3d(${offsets.left}px, ${offsets.top}px, 0px)` - }, renderer); - - if (data.instance.arrow) { - setStyles(data.instance.arrow, data.offsets.arrow, renderer); - } - - if (data.placementAuto) { - if (renderer) { - renderer.setAttribute(target, 'class', - target.className.replace(/bs-popover-auto/g, `bs-popover-${data.placement}`) - ); - renderer.setAttribute(target, 'class', - target.className.replace(/bs-tooltip-auto/g, `bs-tooltip-${data.placement}`) - ); - - renderer.setAttribute(target, 'class', - target.className.replace(/\sauto/g, `\s${data.placement}`) - ); - - if (target.className.match(/popover/g)) { - renderer.addClass(target, 'popover-auto'); - } - - if (target.className.match(/tooltip/g)) { - renderer.addClass(target, 'tooltip-auto'); - } - - - } else { - target.className = target.className.replace(/bs-popover-auto/g, `bs-popover-${data.placement}`); - target.className = target.className.replace(/bs-tooltip-auto/g, `bs-tooltip-${data.placement}`); - target.className = target.className.replace(/\sauto/g, `\s${data.placement}`); - - if (target.className.match(/popover/g)) { - target.classList.add('popover-auto'); - } - - if (target.className.match(/tooltip/g)) { - target.classList.add('tooltip-auto'); - } - } - } - - if (renderer) { - renderer.setAttribute( - target, - 'class', - target.className.replace(/left|right|top|bottom/g, `${data.placement.split(' ')[0]}`) - ); - } else { - target.className = target.className.replace(/left|right|top|bottom/g, `${data.placement.split(' ')[0]}`); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/setStyles.ts b/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/setStyles.ts deleted file mode 100644 index e73c69f0..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/positioning/utils/setStyles.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Set the style to the given popper - */ -import { Renderer2 } from '@angular/core'; - -import { isNumeric } from './isNumeric'; - -export function setStyles(element: HTMLElement, styles: any, renderer?: Renderer2) { - Object.keys(styles).forEach((prop: any) => { - let unit = ''; - // add unit if the value is numeric and is one of the following - if (['width', 'height', 'top', 'right', 'bottom', 'left'].indexOf(prop) !== -1 && - isNumeric(styles[prop])) { - unit = 'px'; - } - - if (renderer) { - renderer.setStyle(element, prop, `${String(styles[prop])}${unit}`); - - return; - } - - element.style[prop] = String(styles[prop]) + unit; - }); -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/trigger.class.ts b/projects/angular-bootstrap-md/src/lib/free/utils/trigger.class.ts deleted file mode 100755 index 12df1274..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/trigger.class.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @copyright Valor Software - * @copyright Angular ng-bootstrap team - */ - - export class Trigger { - public open: string; - public close?: string; - - public constructor(open: string, close?: string) { - this.open = open; - this.close = close || open; - } - - public isManual(): boolean { return this.open === 'manual' || this.close === 'manual'; } - } diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/triggers.ts b/projects/angular-bootstrap-md/src/lib/free/utils/triggers.ts deleted file mode 100755 index 2fd43509..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/triggers.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @copyright Valor Software - * @copyright Angular ng-bootstrap team - */ -import { Renderer2 } from '@angular/core'; -import { Trigger } from './trigger.class'; - -const DEFAULT_ALIASES = { - hover: ['mouseover', 'mouseout'], - focus: ['focusin', 'focusout'] -}; - -export function parseTriggers(triggers: string, aliases: any = DEFAULT_ALIASES): Trigger[] { - const trimmedTriggers = (triggers || '').trim(); - - if (trimmedTriggers.length === 0) { - return []; - } - - const parsedTriggers = trimmedTriggers.split(/\s+/) - .map((trigger: string) => trigger.split(':')) - .map((triggerPair: string[]) => { - const alias = aliases[triggerPair[0]] || triggerPair; - return new Trigger(alias[0], alias[1]); - }); - - const manualTriggers = parsedTriggers - .filter((triggerPair: Trigger) => triggerPair.isManual()); - - if (manualTriggers.length > 1) { - throw new Error('Triggers parse error: only one manual trigger is allowed'); - } - - if (manualTriggers.length === 1 && parsedTriggers.length > 1) { - throw new Error('Triggers parse error: manual trigger can\'t be mixed with other triggers'); - } - - return parsedTriggers; -} - -export function listenToTriggers(renderer: Renderer2, target: any, triggers: string, - showFn: Function, hideFn: Function, toggleFn: Function): Function { - const parsedTriggers = parseTriggers(triggers); - const listeners: any[] = []; - - if (parsedTriggers.length === 1 && parsedTriggers[0].isManual()) { - return Function.prototype; - } - - // parsedTriggers.forEach((trigger: Trigger) => { - parsedTriggers.forEach((trigger: Trigger | any) => { - if (trigger.open === trigger.close) { - listeners.push(renderer.listen(target, trigger.open, () => { - toggleFn(); - })); - // listeners.push(renderer.listen(target, trigger.open, toggleFn)); - return; - } - - listeners.push( - renderer.listen(target, trigger.open, () => { - showFn(); - }), - // renderer.listen(target, trigger.open, showFn), - renderer.listen(target, trigger.close, () => { - hideFn(); - })); - // renderer.listen(target, trigger.close, hideFn)); - }); - - return () => { listeners.forEach((unsubscribeFn: Function) => unsubscribeFn()); }; -} diff --git a/projects/angular-bootstrap-md/src/lib/free/utils/utils.class.ts b/projects/angular-bootstrap-md/src/lib/free/utils/utils.class.ts deleted file mode 100755 index d31e13c4..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/utils/utils.class.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { window, document } from './facade/browser'; -import {ElementRef} from '@angular/core'; - -export class Utils { - constructor() { - - } - public static reflow(element: any): void { - ((bs: any): void => bs)(element.offsetHeight); - } - - // source: https://github.com/jquery/jquery/blob/master/src/css/var/getStyles.js - public static getStyles(elem: any): any { - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - let view = elem.ownerDocument.defaultView; - - if (!view || !view.opener) { - view = window; - } - - return view.getComputedStyle(elem); - } - - public focusTrapModal(event: KeyboardEvent | any, el: ElementRef) { - let focusableElements: any; - let firstFocusableElement: any; - let lastFocusableElement: any; - - const KEYCODE_TAB = 9; - /*tslint:disable-next-line:max-line-length */ - focusableElements = el.nativeElement.querySelectorAll('a[href], button, textarea, input, select, form, mdb-select, mdb-auto-completer, mdb-checkbox, mdb-range-input'); - firstFocusableElement = focusableElements[0]; - lastFocusableElement = focusableElements[focusableElements.length - 1]; - - if (event.key === 'Tab' || event.keyCode === KEYCODE_TAB) { - if (event.shiftKey) { - if (document && document.activeElement === firstFocusableElement) { - lastFocusableElement.focus(); - event.preventDefault(); - } - } else { - if (document && document.activeElement === lastFocusableElement) { - firstFocusableElement.focus(); - event.preventDefault(); - } - } - } - } - - public getClosestEl(el: any, selector: string) { - for (; el && el !== document; el = el.parentNode) { - if (el.matches && el.matches(selector)) { - return el; - } - } - return null; - } - - public getCoords(elem: any): any { - const box: ClientRect = elem.getBoundingClientRect(); - const body: any = document.body; - const docEl: any = document.documentElement; - - const scrollTop: number = window.pageYOffset || docEl.scrollTop || body.scrollTop; - const scrollLeft: number = window.pageXOffset || docEl.scrollLeft || body.scrollLeft; - - const clientTop: number = docEl.clientTop || body.clientTop || 0; - const clientLeft: number = docEl.clientLeft || body.clientLeft || 0; - - const top: number = box.top + scrollTop - clientTop; - const left: number = box.left + scrollLeft - clientLeft; - - return {top: Math.round(top), left: Math.round(left)}; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/waves/index.ts b/projects/angular-bootstrap-md/src/lib/free/waves/index.ts deleted file mode 100755 index 056949c6..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/waves/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { WavesDirective } from './waves-effect.directive'; -export { WavesModule } from './waves.module'; diff --git a/projects/angular-bootstrap-md/src/lib/free/waves/waves-effect.directive.ts b/projects/angular-bootstrap-md/src/lib/free/waves/waves-effect.directive.ts deleted file mode 100755 index 47589f09..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/waves/waves-effect.directive.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Directive, ElementRef, HostListener } from '@angular/core'; -@Directive({ - selector: '[mdbWavesEffect]', -}) -export class WavesDirective { - constructor(public el: ElementRef) {} - - @HostListener('click', ['$event']) - public click(event: any) { - if (!this.el.nativeElement.classList.contains('disabled')) { - const button = this.el.nativeElement; - if (!button.classList.contains('waves-effect')) { - button.className += ' waves-effect'; - } - - const xPos = event.clientX - button.getBoundingClientRect().left; - const yPos = event.clientY - button.getBoundingClientRect().top; - - const tmp = document.createElement('div'); - tmp.className += 'waves-ripple waves-rippling'; - const ripple = button.appendChild(tmp); - - const top = yPos + 'px'; - const left = xPos + 'px'; - - tmp.style.top = top; - tmp.style.left = left; - - const scale = 'scale(' + (button.clientWidth / 100) * 3 + ') translate(0,0)'; - - // tslint:disable-next-line: deprecation - tmp.style.webkitTransform = scale; - tmp.style.transform = scale; - tmp.style.opacity = '1'; - - const duration = 750; - - // tslint:disable-next-line: deprecation - tmp.style.webkitTransitionDuration = duration + 'ms'; - tmp.style.transitionDuration = duration + 'ms'; - - this.removeRipple(button, ripple); - } - } - - removeRipple(button: any, ripple: any) { - ripple.classList.remove('waves-rippling'); - - setTimeout(() => { - ripple.style.opacity = '0'; - - setTimeout(() => { - button.removeChild(ripple); - }, 750); - }, 200); - } -} diff --git a/projects/angular-bootstrap-md/src/lib/free/waves/waves.module.ts b/projects/angular-bootstrap-md/src/lib/free/waves/waves.module.ts deleted file mode 100755 index dd6edf91..00000000 --- a/projects/angular-bootstrap-md/src/lib/free/waves/waves.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule, ModuleWithProviders } from '@angular/core'; -import { WavesDirective } from './waves-effect.directive'; - -@NgModule({ - declarations: [WavesDirective], - exports: [WavesDirective] -}) - -export class WavesModule { - public static forRoot(): ModuleWithProviders { - return {ngModule: WavesModule, providers: []}; - } -} diff --git a/projects/angular-bootstrap-md/src/lib/img/lightbox/default-skin.png b/projects/angular-bootstrap-md/src/lib/img/lightbox/default-skin.png deleted file mode 100755 index 441c502c..00000000 Binary files a/projects/angular-bootstrap-md/src/lib/img/lightbox/default-skin.png and /dev/null differ diff --git a/projects/angular-bootstrap-md/src/lib/img/lightbox/default-skin.svg b/projects/angular-bootstrap-md/src/lib/img/lightbox/default-skin.svg deleted file mode 100755 index 9d5f0c6a..00000000 --- a/projects/angular-bootstrap-md/src/lib/img/lightbox/default-skin.svg +++ /dev/null @@ -1 +0,0 @@ -default-skin 2 \ No newline at end of file diff --git a/projects/angular-bootstrap-md/src/lib/img/lightbox/preloader.gif b/projects/angular-bootstrap-md/src/lib/img/lightbox/preloader.gif deleted file mode 100755 index b8faa697..00000000 Binary files a/projects/angular-bootstrap-md/src/lib/img/lightbox/preloader.gif and /dev/null differ diff --git a/projects/angular-bootstrap-md/src/lib/img/overlays/01.png b/projects/angular-bootstrap-md/src/lib/img/overlays/01.png deleted file mode 100755 index f9b60ee7..00000000 Binary files a/projects/angular-bootstrap-md/src/lib/img/overlays/01.png and /dev/null differ diff --git a/projects/angular-bootstrap-md/src/lib/img/overlays/02.png b/projects/angular-bootstrap-md/src/lib/img/overlays/02.png deleted file mode 100755 index acce7a66..00000000 Binary files a/projects/angular-bootstrap-md/src/lib/img/overlays/02.png and /dev/null differ diff --git a/projects/angular-bootstrap-md/src/lib/img/overlays/03.png b/projects/angular-bootstrap-md/src/lib/img/overlays/03.png deleted file mode 100755 index c11a355b..00000000 Binary files a/projects/angular-bootstrap-md/src/lib/img/overlays/03.png and /dev/null differ diff --git a/projects/angular-bootstrap-md/src/lib/img/overlays/04.png b/projects/angular-bootstrap-md/src/lib/img/overlays/04.png deleted file mode 100755 index 89b85338..00000000 Binary files a/projects/angular-bootstrap-md/src/lib/img/overlays/04.png and /dev/null differ diff --git a/projects/angular-bootstrap-md/src/lib/img/overlays/04.png~HEAD b/projects/angular-bootstrap-md/src/lib/img/overlays/04.png~HEAD deleted file mode 100755 index 89b85338..00000000 Binary files a/projects/angular-bootstrap-md/src/lib/img/overlays/04.png~HEAD and /dev/null differ diff --git a/projects/angular-bootstrap-md/src/lib/img/overlays/04.png~ecacfc5c730e25dcad92fe8f4a80cba6c23a4b6b b/projects/angular-bootstrap-md/src/lib/img/overlays/04.png~ecacfc5c730e25dcad92fe8f4a80cba6c23a4b6b deleted file mode 100755 index 89b85338..00000000 Binary files a/projects/angular-bootstrap-md/src/lib/img/overlays/04.png~ecacfc5c730e25dcad92fe8f4a80cba6c23a4b6b and /dev/null differ diff --git a/projects/angular-bootstrap-md/src/lib/img/overlays/05.png b/projects/angular-bootstrap-md/src/lib/img/overlays/05.png deleted file mode 100755 index 082bda8c..00000000 Binary files a/projects/angular-bootstrap-md/src/lib/img/overlays/05.png and /dev/null differ diff --git a/projects/angular-bootstrap-md/src/lib/img/overlays/06.png b/projects/angular-bootstrap-md/src/lib/img/overlays/06.png deleted file mode 100755 index 9c9006a2..00000000 Binary files a/projects/angular-bootstrap-md/src/lib/img/overlays/06.png and /dev/null differ diff --git a/projects/angular-bootstrap-md/src/lib/img/overlays/07.png b/projects/angular-bootstrap-md/src/lib/img/overlays/07.png deleted file mode 100755 index 218be609..00000000 Binary files a/projects/angular-bootstrap-md/src/lib/img/overlays/07.png and /dev/null differ diff --git a/projects/angular-bootstrap-md/src/lib/img/overlays/08.png b/projects/angular-bootstrap-md/src/lib/img/overlays/08.png deleted file mode 100755 index 1b9dffc9..00000000 Binary files a/projects/angular-bootstrap-md/src/lib/img/overlays/08.png and /dev/null differ diff --git a/projects/angular-bootstrap-md/src/lib/img/overlays/09.png b/projects/angular-bootstrap-md/src/lib/img/overlays/09.png deleted file mode 100755 index b9ed2ff1..00000000 Binary files a/projects/angular-bootstrap-md/src/lib/img/overlays/09.png and /dev/null differ diff --git a/projects/angular-bootstrap-md/src/lib/img/svg/arrow_left.svg b/projects/angular-bootstrap-md/src/lib/img/svg/arrow_left.svg deleted file mode 100755 index a88d2997..00000000 --- a/projects/angular-bootstrap-md/src/lib/img/svg/arrow_left.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/projects/angular-bootstrap-md/src/lib/img/svg/arrow_right.svg b/projects/angular-bootstrap-md/src/lib/img/svg/arrow_right.svg deleted file mode 100755 index f62d8852..00000000 --- a/projects/angular-bootstrap-md/src/lib/img/svg/arrow_right.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/projects/angular-bootstrap-md/src/public_api.ts b/projects/angular-bootstrap-md/src/public_api.ts deleted file mode 100644 index 5b31b0d8..00000000 --- a/projects/angular-bootstrap-md/src/public_api.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Public API Surface of ng-uikit-pro-standard-compile - */ -// MDB Angular Free -export * from './lib/free/badge/index'; -export * from './lib/free/breadcrumbs/index'; -export * from './lib/free/buttons/index'; -export * from './lib/free/cards/index'; -export * from './lib/free/carousel/index'; -export * from './lib/free/charts/index'; -export * from './lib/free/checkbox/index'; -export * from './lib/free/collapse/index'; -export * from './lib/free/dropdown/index'; -export * from './lib/free/icons/index'; -export * from './lib/free/input-utilities/index'; -export * from './lib/free/inputs/index'; -export * from './lib/free/modals/index'; -export * from './lib/free/navbars/index'; -export * from './lib/free/popover/index'; -export * from './lib/free/tables/index'; -export * from './lib/free/tooltip/index'; -export * from './lib/free/waves/index'; -export * from './lib/free/mdb-free.module'; - - - diff --git a/projects/angular-bootstrap-md/src/test.ts b/projects/angular-bootstrap-md/src/test.ts deleted file mode 100644 index e11ff1c9..00000000 --- a/projects/angular-bootstrap-md/src/test.ts +++ /dev/null @@ -1,22 +0,0 @@ -// This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import 'core-js/es7/reflect'; -import 'zone.js/dist/zone'; -import 'zone.js/dist/zone-testing'; -import { getTestBed } from '@angular/core/testing'; -import { - BrowserDynamicTestingModule, - platformBrowserDynamicTesting -} from '@angular/platform-browser-dynamic/testing'; - -declare const require: any; - -// 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); diff --git a/projects/angular-bootstrap-md/tsconfig.schematics.json b/projects/angular-bootstrap-md/tsconfig.schematics.json deleted file mode 100644 index 6057d1cd..00000000 --- a/projects/angular-bootstrap-md/tsconfig.schematics.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "lib": [ - "es2018", - "dom" - ], - "declaration": true, - "module": "commonjs", - "moduleResolution": "node", - "noEmitOnError": true, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": true, - "noImplicitThis": true, - "noUnusedParameters": true, - "noUnusedLocals": true, - "rootDir": "schematics", - "outDir": "../../dist/angular-bootstrap-md/schematics", - "skipDefaultLibCheck": true, - "skipLibCheck": true, - "sourceMap": true, - "strictNullChecks": true, - "target": "es6", - "types": [ - "jasmine", - "node" - ] - }, - "include": [ - "schematics/" - ], - "exclude": [ - ] - } \ No newline at end of file diff --git a/projects/angular-bootstrap-md/tsconfig.spec.json b/projects/angular-bootstrap-md/tsconfig.spec.json deleted file mode 100644 index 16da33db..00000000 --- a/projects/angular-bootstrap-md/tsconfig.spec.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "../../out-tsc/spec", - "types": [ - "jasmine", - "node" - ] - }, - "files": [ - "src/test.ts" - ], - "include": [ - "**/*.spec.ts", - "**/*.d.ts" - ] -} diff --git a/projects/mdb-angular-ui-kit/CHANGELOG.md b/projects/mdb-angular-ui-kit/CHANGELOG.md new file mode 100644 index 00000000..29e567ec --- /dev/null +++ b/projects/mdb-angular-ui-kit/CHANGELOG.md @@ -0,0 +1,1037 @@ +## 9.0.0 (21.09.2025) + +This version requires Angular v20. Follow the [Angular Update Guide](https://angular.dev/update-guide) to migrate your project to Angular 20. + +### Breaking changes: + +- Updated Angular to v20, this version is required in MDB Angular v9 + +### Fixes and improvements: + +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) + - Improved blocking of months and years cells in some edge cases + - Resolved problems with errors when parsing invalid date formats +- [File upload](https://mdbootstrap.com/docs/angular/plugins/file-upload/) + - Added error handling for `maxFileQuantity` + - Improved extensions validation logic to handle problems with uploading files with extensions defined in `mimeTypes` input +- [Input fields](https://mdbootstrap.com/docs/angular/forms/input-fields/) - resolved problem with border gap updates for dynamically rendered label +- [Select](https://mdbootstrap.com/docs/angular/forms/select/) - resolved issue where clicking the arrow icon in one Select input would not close the dropdown of another Select component + +### New features: + +- Added new SCSS and CSS variables for plugins styles +- [Autocomplete](https://mdbootstrap.com/docs/angular/forms/autocomplete/) - added new `dropdownWidth` input that allows to set custom width for the dropdown menu +- [Datatables](https://mdbootstrap.com/docs/angular/data/datatables/) - added new `defaultSortDirection` input that allows to set default sort direction for the table header + +--- + +## 8.0.0 (07.04.2025) + +This version requires Angular v19. Follow the [Angular Update Guide](https://angular.dev/update-guide) to migrate your project to Angular 19. + +### Breaking changes: + +- Updated Angular to v19, this version is required in MDB Angular v8 +- Older theming styles are no longer supported, use new [color modes](https://mdbootstrap.com/docs/angular/content-styles/theme/) instead +- Slightly increased cell width in [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) +- The `.navbar-light` class is no longer used in [Navbar](https://mdbootstrap.com/docs/angular/navigation/navbar), use [color modes](https://mdbootstrap.com/docs/angular/content-styles/theme/) instead + +### Design updates: + +Introduced a new theming system that allows setting the theme for the entire page, its parts, or selected elements using data attributes. + +Read [Colors modes](https://mdbootstrap.com/docs/angular/content-styles/theme/) page to learn more about new theming. + +### Fixes and improvements: + +- [Modal](https://mdbootstrap.com/docs/angular/components/modal/) - resolved problem with opening animation +- [Select](https://mdbootstrap.com/docs/angular/forms/select/) - resolved problem with not hiding option groups labels when using filter +- [Popconfirm](https://mdbootstrap.com/docs/angular/components/popconfirm/) - added default offset to the component +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) - fixed date parsing bug for `yy` year format +- [Stepper](https://mdbootstrap.com/docs/angular/components/stepper/) - added 'Optional' text to the steps that use `optional` input +- [Onboarding](https://mdbootstrap.com/docs/angular/plugins/onboarding/) - added gap between the buttons and fixed border styles +- Fixed [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) and [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) toggle button padding in Firefox browser +- Removed unnecessary `BrowserAnimationsModule` imports from [Onboarding](https://mdbootstrap.com/docs/angular/plugins/onboarding/), [Ecommerce gallery](https://mdbootstrap.com/docs/angular/plugins/ecommerce-gallery/) and [Organization chart](https://mdbootstrap.com/docs/angular/plugins/organization-chart/) plugins + +### New features: + +- Added new SCSS and CSS variables for plugins styles +- [File upload](https://mdbootstrap.com/docs/angular/plugins/file-upload/) - added new `mimeTypes` input that allow to define a list of mime types for supported file types +- [Multi item carousel](https://mdbootstrap.com/docs/angular/plugins/multi-item-carousel/) - added new `(slideClick)` event +- [Color picker](https://mdbootstrap.com/docs/angular/plugins/color-picker/) - added new `color-picker-next-format-button`, `color-picker-previous-format-button` and `color-picker-copy-button` classes for the buttons + +--- + +## 7.1.0 (18.11.2024) + +### Fixes and improvements: + +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) + - Resolved problem with `close` method being called twice on component close + - Fixed dark theme styles in inline mode +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) + - Added `aria-disabled` attributes to elements that display disabled dates + - Fixed `aria-label` attribute value in the element used to display day value + - Resolved problem with adding `aria-selected` attribute to the element that display day value +- [Select](https://mdbootstrap.com/docs/angular/forms/select/) + - Fixed disabled options styles in custom theme + - Resolved problem with opening dropdown on `space` key press + - Added `aria-label` and `aria-labelledby` attributes to the component + - Fixed value returned by `(deselect)` event +- [Autocomplete](https://mdbootstrap.com/docs/angular/forms/autocomplete/) + - Fixed `aria-expended` attribute values for opened and closed menu + - Fixed problem where component menu was opening even when input was disbled +- [Transfer](https://mdbootstrap.com/docs/angular/plugins/transfer/) + - Fixed events output for target container + - Fixed checkboxes styles +- [Range](https://mdbootstrap.com/docs/angular/forms/range/) - fixed thumb position on component init +- [Onboarding](https://mdbootstrap.com/docs/angular/plugins/onboarding/) - added fix to prevent memory leak after component destroy +- [Input mask](https://mdbootstrap.com/docs/angular/plugins/input-mask/) - fixed a problem with value formatting when pasting all content into input at once +- [Vector maps](https://mdbootstrap.com/docs/angular/plugins/vector-maps/) - fixed shadow styles in zoom buttons +- [Transfer](https://mdbootstrap.com/docs/angular/plugins/wysiwyg-editor/) - fixed dropdown menu alignment +- [Dropdown](https://mdbootstrap.com/docs/angular/component/dropdowns/) - fixed `aria-expended` attribute values for opened and closed menu +- [Sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) - fixed problem with focus trap when the last focused element is inside the component content + +### New features: + +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) + - Added new `showClearBtn` input + - Added new `(clear)` event that will be fired after using Clear button +- [File upload](https://mdbootstrap.com/docs/angular/plugins/file-upload/) + - Added `svg` and `webp` extensions to the list of allowed file types for default preview + - Added new `datepickerOptions` that allow to define options for the date pickers used by the plugin +- [Select](https://mdbootstrap.com/docs/angular/forms/select/) - added new `(search)` event that will be fired after using search input +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) - added new `(viewChanged)` event that will be fired on component view change +- [Progress](https://mdbootstrap.com/docs/angular/components/progress/) - added new circular version of the component + +--- + +## 7.0.0 (16.09.2024) + +This version requires Angular v18. Follow the [Angular Update Guide](https://angular.dev/update-guide) to migrate your project to Angular 18. + +### Breaking changes: + +- Updated Angular to v18, this version is required in MDB Angular v7. +- [Checkbox](https://mdbootstrap.com/docs/angular/forms/checkbox/) - changed `margin-right` style from `4px` to `6px` in `.form-check-input` element. +- [Forms](https://mdbootstrap.com/docs/angular/forms/overview/) - added `padding-left: 0.15rem` style to `.form-check-label` element. +- [Switch](https://mdbootstrap.com/docs/angular/forms/switch/) - changed `margin-right` style from `4px` to `8px` in `.form-check-input` element. +- [Progress](https://mdbootstrap.com/docs/angular/components/progress/) - added `box-shadow: none` style to `.progress` element. +- [Input group](https://mdbootstrap.com/docs/angular/forms/input-group/) - added `flex-wrap: nowrap` style to `.input-group` element. +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) - changed SCSS variable `$datepicker-small-cell-content-width` value from `36px` to `40px`. +- [Range](https://mdbootstrap.com/docs/angular/forms/range/): + - Added `box-shadow: none` style to `.form-range ::-webkit-slider-runnable-track` element. + - Added `box-shadow: none` style to `.form-range ::-moz-range-track` element. +- [Captcha](https://mdbootstrap.com/docs/angular/plugins/captcha/): + - Changed `error` event name to `captchaError`. + - Changed `expire` event name to `captchaExpire`. + - Changed `success` event name to `captchaSuccess`. +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/): + - Redesigned clock's page HTML structure and styles. + - Arrow icons are now displayed when hour/minute buttons are hovered in inline mode. +- [Treeview](https://mdbootstrap.com/docs/angular/plugins/tree-view/): + - Redesigned entire HTML structure. + - Replaced `li` element with `mdb-treeview-item`. + - Removed the `
` wrapper element from the entire component. + - Removed the `checkboxesField` input. + - Added a public `MdbTreeviewColor` type for the color input. + - Added a new mechanism for setting the arrow icon with the `collapseIcon` property. + - Added keyboard navigation handling. + +### Fixes and improvements: + +- [Multi range](https://mdbootstrap.com/docs/angular/forms/multi-range-slider/) - resolved the issue with `TouchEvent` in Firefox. +- [Select](https://mdbootstrap.com/docs/angular/forms/select/) - resolved the issue with unhandled `tabindex` input. +- [Onboarding](https://mdbootstrap.com/docs/angular/plugins/onboarding/) - resolved the issue with initializing onboarding with a delay after navigating to another page. +- [Input fields](https://mdbootstrap.com/docs/angular/forms/input-fields/) - resolved the issue with displaying the value after setting it programmatically in all inputs with built-in placeholders (e.g., `datetime-local` or `time`). +- [Datatable](https://mdbootstrap.com/docs/angular/data/datatables/) - resolved the issue with the `showAllEntries` input not working properly with the `entries` input. +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) - resolved the issue with `ArrowUp` and `ArrowDown` key presses not working upon opening the timepicker. +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/): + - Resolved the issue with returned form control values for empty or invalid input values. + - Resolved the issue with closing the datepicker using the input toggle. +- [Calendar](https://mdbootstrap.com/docs/angular/plugins/calendar/): + - Resolved the issue with unpreserved event IDs on edit. + - Resolved the issue with dragging in `readonly` mode. + - Resolved the issue with view selection when non-default captions are used. + - Resolved the issue with rendering the period in the correct format in Month view. + +### New features: + +- [Dropdown](https://mdbootstrap.com/docs/angular/components/dropdown/) - added `MdbDropdownPositionClass` type to the public API. +- [Modal](https://mdbootstrap.com/docs/angular/components/modal/) - added `focusElementSelector` property to the `open` method's options for specifying the element to focus on when the modal opens. +- [Calendar](https://mdbootstrap.com/docs/angular/plugins/calendar/): + - Added `addEventButtonCaption` property to the `options` input for setting a custom caption for the add event button. + - Added `MdbCalendarViews` Enum to the public API. + +--- + +## 6.1.0 (27.05.2024) + +### Fixes and improvements: + +- [Multi range](https://mdbootstrap.com/docs/angular/forms/multi-range-slider/) + - Fixed problem with thumb limiting logic when using custom step + - Fixed problem with updating thumb positions via form controls +- [Popconfirm](https://mdbootstrap.com/docs/angular/components/popconfirm/) - added focus trap +- [Autocomplete](https://mdbootstrap.com/docs/angular/forms/autocomplete/) - restored native `shift + home` and `shift + end` keys behavior (open/close dropdown) +- [Select](https://mdbootstrap.com/docs/angular/forms/select/) - added support for opening and closing dropdown with `alt + arrow-up` and `alt + arrow-down` keys + +### New: + +- [Table pagination](https://mdbootstrap.com/docs/angular/data/datatables/) - added new `page` input that allows to set page number +- [Multi range](https://mdbootstrap.com/docs/angular/forms/multi-range-slider/) - added new `highlightRange` input that allows to highlight range +- [Parallax](https://mdbootstrap.com/docs/angular/plugins/parallax/) - added new `container` input that allows to set wrapper element for parallax effect + +--- + +## 6.0.0 (15.01.2024) + +This version requires Angular v17. Follow the [Angular Update Guide](https://update.angular.io/?l=3&v=16.0-17.0) to migrate your project to Angular 17. + +### Breaking changes: + +- Updated Angular to v17, this version is required in MDB Angular v6 +- [Calendar](https://mdbootstrap.com/docs/angular/plugins/calendar/) - changed type of `defaultView` input from `string` to `MdbCalendarView` +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) - changed type of `options` input from `any` to `MdbDatepickerOptions` +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) + - Changed type of `options` input from `Options` to `MdbTimepickerOptions` and made all parameters optional + - Changed `SelectedTime` type name to `MdbTimepickerSelectedTime` and added this type to public exports +- [Popover](https://mdbootstrap.com/docs/angular/components/popover/) - removed unused `template` input +- [Sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) + - Changed return type of all events from `MdbSidenavComponent` to `void` + - Removed redundant `li` element from `MdbSidenavItemComponent` template +- [Transfer](https://mdbootstrap.com/docs/angular/plugins/transfer/) + - Changed `onSearchOutput` event name to `searchOutput` + - Changed `selectOutput` event name to `selectOutput` + - Changed `onChange` event name to `listChange` + - Changed `onSearch` event name to `itemSearch` + - Changed `onSelect` event name to `itemSelect` + +### Fixes and improvements: + +- [Sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) - removed height animation transition +- [Select](https://mdbootstrap.com/docs/angular/forms/select/) - blocked input clearing in disabled component +- [Input fields](https://mdbootstrap.com/docs/angular/forms/input-fields/) - resolved problem with default label position in all inputs with built-in placeholder (like `datetime-local` or `time`) +- [Lightbox](https://mdbootstrap.com/docs/angular/components/lightbox/) - resolved problem with component removal from DOM after using browser's back button +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) - resolved problem with font size in landscape view + +### New fetures: + +- [Select](https://mdbootstrap.com/docs/angular/forms/select/) - added new `inputId` and `inputFilterId` inputs that allow to declare ids for input elements + +--- + +## 5.2.0 (04.12.2023) + +### Fixes and improvements: + +- Resolved problem with components rendering when using Server Side Rendering +- Resolved problem with overlay when using `menuPositionClass` in [Datatable](https://mdbootstrap.com/docs/angular/components/dropdowns/) +- Replaced hardcoded `padding-left` value in [Sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) link with a value from CSS variable +- Replaced hardcoded `box-shadow`, `border-color` and `background-color` values in [Buttons](https://mdbootstrap.com/docs/angular/components/buttons/) with a values from CSS variables +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) + - Fixed the button press behavior to consider the duration of the press + - Removed the default scroll effect from the arrow keydown events in inline mode +- Fixed events types for `opened` and `closed` events in [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) +- Resolved problem with initial value in [Rating](https://mdbootstrap.com/docs/angular/components/rating) +- [Multi Range Slider](https://mdbootstrap.com/docs/angular/forms/multi-range-slider/) + - Resolved problem with thumbs position updates on `ngModel` or `formControl` value changes + - Added thumbs position constraints so that the position of a given thumb is limited by its counterpart +- Resolved problem with the `Host already has a portal attached` error in [Wysiwyg](https://mdbootstrap.com/docs/angular/plugins/wysiwyg-editor/) + +### New: + +- A new `MdbSidenavMenuDirective` directive has been added to [Sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) allowing to create multiple menus within one component +- A new `size` input has been added to [Select](https://mdbootstrap.com/docs/angular/orms/select/) allowing to change input size to `sm` or `lg` + +--- + +## 5.1.0 (09.10.2023) + +### Fixes and improvements: + +- [Datatable](https://mdbootstrap.com/docs/angular/data/datatables/) + - Added missing `cursor: pointer` styles to clickable rows + - Resolved problems with pagination width styles + - Resolved problems with page number calculation in pagination +- [Sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) + - Resolved problems with accessibility + - Removed the need to define template variables in HTML template + - Adjusted padding in slim version to correctly display link icon and arrow +- [Tabs](https://mdbootstrap.com/docs/angular/navigation/tabs/) + - Improved animation smoothness + - Added `MdbTabChange` event type to public exports +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) + - Resolved problem with `disabled` input + - Resolved problem with disabling and enabling component via Reactive Forms methods + - Removed border styles from focused buttons +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) + - Resolved problem with border radius styles + - Resolved problem with disabling and enabling component via Reactive Forms methods +- [Autocomplete](https://mdbootstrap.com/docs/angular/forms/autocomplete/) + - Removed auto highlight from first option + - Resolved problems with input and dropdown keyboard navigation when using `HOME` and `END` keys +- [Multi range](https://mdbootstrap.com/docs/angular/forms/multi-range-slider/) + - Resolved problem with component render in apps using Angular 16 + - Resolved problem with unhandled `endDrag` event +- [Onboarding](https://mdbootstrap.com/docs/angular/plugins/onboarding/) + - Resolved problem with component render in apps using Angular 16 + - Resolved problems with popover styles + - Fixed event types + - Fixed event emitted when jumping to next step +- [Treeview](/docs/angular/plugins/tree-view/) + - Improved animation smoothness + - Added correct types to public events + - Resolved problem with `accordion` option + - Resolved problem with `openOnClick` option + - Improved accessibility +- Resolved problem with styles of anchor elements used as [floating buttons](https://mdbootstrap.com/docs/angular/components/buttons/#section-floating) +- Resolved problem with adding new [Chips](https://mdbootstrap.com/docs/angular/components/chips/) on blur event +- Resolved problem with [Dropdown](https://mdbootstrap.com/docs/angular/components/dropdowns/) menu position +- Fixed focus styles in [Select](https://mdbootstrap.com/docs/angular/forms/select/) with `form-white` class +- Resolved problem with position of bottom frame [non-invasive Modal](https://mdbootstrap.com/docs/angular/components/modal/#section-non-invasive-modal) +- Fixed type of `infiniteScrollCompleted` event in [Infinite scroll](https://mdbootstrap.com/docs/angular/methods/infinite-scroll/) +- Added mechanism to handle dynamic updates in [Input mask](https://mdbootstrap.com/docs/angular/plugins/input-mask/) plugin +- Resolved problems with [Color picker](https://mdbootstrap.com/docs/angular/plugins/color-picker/) plugin styles and slider in Firefox browser +- Resolved problem with [Parallax](https://mdbootstrap.com/docs/angular/plugins/parallax/) plugin render in apps using Angular 16 +- Fixed event types and unhandled events in [Drag and drop](https://mdbootstrap.com/docs/angular/plugins/drag-and-drop/) plugin +- Resolved problem with reverting lists transformation in [WYSIWYG editor](https://mdbootstrap.com/docs/angular/plugins/wysiwyg-editor/) plugin +- Resolved problem with `changeView` method in [Calendar](https://mdbootstrap.com/docs/angular/plugins/calendar/) plugin +- Added types to public exports in [Data parser](https://mdbootstrap.com/docs/angular/plugins/data-parser/) plugin + +### New: + +- Added new [Treetable](https://mdbootstrap.com/docs/angular/plugins/treetable/) plugin +- Added mechanism that allow to add context for `ng-template` template in [Popover](https://mdbootstrap.com/docs/angular/components/popovers/) +- Added new `showAllEntries` option to [Datatable pagination](https://mdbootstrap.com/docs/angular/data/datatables/) +- Added new `filterFn` option to [Select](https://mdbootstrap.com/docs/angular/forms/select/) +- Added new directive that allow to create a custom header in [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) +- Added new `positionClass` and `menuPositionClass` options to [Dropdown](https://mdbootstrap.com/docs/angular/components/dropdowns/) +- Added new `disabled` input that allow to disable [Accordion](https://mdbootstrap.com/docs/angular/components/accordion/)Accordion items +- Added mechanism that allow to define custom icon template with `ng-template` in [Datepicker](/docs/angular/forms/datepicker/) and [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) +- Added mechanism that allow to define custom header template with `ng-template` in [Stepper](https://mdbootstrap.com/docs/angular/components/stepper/) +- Added new `$link-decoration` and `--mdb-link-decoration` variables to make it easier to customize `text-decoration` styles for anchor elements +- Added new inputs for disabling specific features in [Calendar](https://mdbootstrap.com/docs/angular/plugins/calendar/) plugin + +--- + +## 5.0.0 (26.06.2023) + +This version requires Angular v16. Follow the [Angular update guide](https://update.angular.io/?l=3&v=15.0-16.0) to migrate your project to Angular 16. + +### Dependencies: + +- Updated Angular to v16, this version is required in MDB Angular v5 +- Updated Bootstrap to [5.2.3](https://github.com/twbs/bootstrap/releases/tag/v5.2.3) version. + +### Design changes: + +- Changed arrow styles in [Select](https://mdbootstrap.com/docs/angular/forms/select/) input +- Slightly changed hover styles in [outline buttons](https://mdbootstrap.com/docs/angular/components/buttons/#section-outline) to make them more elegant and subtle + +### Fixes and improvements: + +- Fixed problems with schematics installation in MDB Angular Free version +- Fixed problem with display of [Sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) item when its content is translated with the `translate` pipe from the `@ngx-translate` library +- Fixed position of smaller icons in relation to the text in [Rating](https://mdbootstrap.com/docs/angular/components/rating/) + +### New: + +- Converted MDB components to CSS variables +- Added SCSS and CSS variables for `mdb-option` and `mdb-option-group` components +- Added access to the underlying component instance from ref element in [Modal](https://mdbootstrap.com/docs/angular/components/modal/), [Popconfirm](https://mdbootstrap.com/docs/angular/components/popconfirm/), [Alerts](https://mdbootstrap.com/docs/angular/components/alerts/) and [Toasts](https://mdbootstrap.com/docs/angular/components/toasts/) + +--- + +## 4.1.0 (24.01.2023) + +### Fixes and improvements: + +- Fixed default value display in [Autocomplete](https://mdbootstrap.com/docs/angular/forms/autocomplete/) when the value is an object +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) + - Fixed focus trap + - Fixed keyboard navigation in inline mode + - Fixed the problem with minTime and maxTime range +- Fixed [Ripple effect](https://mdbootstrap.com/docs/angular/methods/ripple/) on inputs styled as buttons +- Fixed background colors of [Toasts](https://mdbootstrap.com/docs/angular/components/toasts/) and [Alerts](https://mdbootstrap.com/docs/angular/components/alerts/) in MDB theme +- [Modal](https://mdbootstrap.com/docs/angular/components/modal/) + - Fixed the problem with scrollbar on bottom frame modal init + - Removed rounded corners from frame modals + - Removed unnecessary body scroll when using `scrollable` modal +- [Datatable](https://mdbootstrap.com/docs/angular/data/datatables/) + - Removed ability to focus disabled buttons in pagination + - Fixed the problem with case-sensitive sorting +- Fixed the problem with hiding buttons in the [Wysiwyg](https://mdbootstrap.com/docs/angular/plugins/wysiwyg-editor/) toolbar +- Fixed problem with event types in [Select](https://mdbootstrap.com/docs/angular/forms/select/) +- Fixed problem with `Rxjs operators` import paths in all the components and plugins + +### New: + +- Added new [Data Parser](https://mdbootstrap.com/docs/angular/plugins/data-parser/) plugin +- Added new [Organization Chart](https://mdbootstrap.com/docs/angular/plugins/organization-chart/) plugin +- Added new [Captcha](https://mdbootstrap.com/docs/angular/plugins/captcha/) plugin +- Added new [Chips](https://mdbootstrap.com/docs/angular/components/chips/) component +- Added new `[collapsible]` input to [Scrollspy](https://mdbootstrap.com/docs/angular/navigation/scrollspy/) +- Added new `[disableWindowScroll]` input to the [Sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) +- Added new [non-invasive Modal](https://mdbootstrap.com/docs/angular/components/modal/#section-non-invasive-modal) +- [Datatable](https://mdbootstrap.com/docs/angular/data/datatables/) + - Added new `[forceSort]` input that allow to disable sort reset on third click + - Added new `[disableSort]` input that allow to disable a specific sort header + - Added new `[disabled]` input to pagination component +- [Datepicker](https://mdbootstrap.com/docs/angular/forms/datepicker/) +- Added new `[removeOkBtn]`, `[removeCancelBtn]` and `[removeClearBtn]` inputs that allow to remove specific buttons from the component footer +- Addew new `[confirmDateOnSelect]` input that allow to select date without a confirmation by click on `Ok` button + +--- + +## 4.0.0 (09.01.2023) + +### Design updates: + +Our basic color palette has been updated. We toned down our colors to be less flashy and more elegant and subtle. This affects virtually all of our components, so be aware of this before upgrading your project to v4.0.0. + +Read [colors docs](https://mdbootstrap.com/docs/angular/content-styles/colors/) to learn more about new palette. + +### Breaking changes: + +- Added support for Angular 15, this Angular version is now required, +- Improved [buttons](https://mdbootstrap.com/docs/angular/components/buttons/) +- Improved existing [accordion](https://mdbootstrap.com/docs/angular/components/accordion/) and added new examples +- Improved [stepper](https://mdbootstrap.com/docs/angular/components/stepper/) design +- Improved [badges](https://mdbootstrap.com/docs/angular/components/badges/) design and added new examples +- Improved [popovers](https://mdbootstrap.com/docs/angular/components/popovers/) and [popconfirm](https://mdbootstrap.com/docs/angular/components/popconfirm/) design +- Removed default configuration of `chartjs-plugin-datalabels` from [charts](https://mdbootstrap.com/docs/angular/data/charts/), all plugins must be now registered before use + +### Fixes and improvements: + +- Resolved problem with [scrollbar](https://mdbootstrap.com/docs/angular/methods/scrollbar/) initialization on element with a `mdbScrollbar` directive +- Removed unnecessary border animation on initialization of `mdb-form-control` component +- Resolved problem with global registration of controllers and plugins in [charts](https://mdbootstrap.com/docs/angular/data/charts/) +- Improved types in `mdbChart` directive inputs +- Added some fixes to the [transfer plugin](https://mdbootstrap.com/docs/angular/plugins/transfer/) + - Improved 'select all' option implementation + - Resolved problems with value updates in search bar input + - Resolved problems with component view updates when using pagination +- Improved theme styles in the following components: + - List group + - Pagination + - Datepicker + +### New: + +- Addew new [color picker plugin](https://mdbootstrap.com/docs/angular/plugins/color-picker/) plugin +- Addew new [multi item carousel plugin](https://mdbootstrap.com/docs/angular/plugins/multi-item-carousel/) +- Addew new [ecommerce gallery plugin](https://mdbootstrap.com/docs/angular/plugins/ecommerce-gallery/) +- Addew new `[borderless]` input to [accordion](https://mdbootstrap.com/docs/angular/components/accordion/) +- Added new `[withPush]` input to [dropdown](https://mdbootstrap.com/docs/angular/components/dropdown/) +- Added new `[plugins]` input to [charts](https://mdbootstrap.com/docs/angular/data/charts/) +- Added public access to the chart instance in `mdbChart` directive +- Added new `[ofText]` input to [datatables](https://mdbootstrap.com/docs/angular/data/datatables/) +- Added new `[titleSource]` and `[titleTarget]` inputs to [transfer plugin](https://mdbootstrap.com/docs/angular/plugins/transfer/) + +--- + +## 3.0.1 (05.12.2022) + +### Fixes and improvements: + +- [Timepicker](https://mdbootstrap.com/docs/angular/forms/timepicker/) + - Removed border styles displayed on focused elements + - Resolved problems with keyboard navigation +- It will be now possible to jump to any step in [linear stepper](https://mdbootstrap.com/docs/angular/components/stepper/#section-linear-stepper-example/), as long as all previous steps are completed +- Resolved problems with `acceptedExtensions` in [file upload plugin](https://mdbootstrap.com/docs/angular/plugins/file-upload/) +- Select all option will now select/deselect only filtered options when used inside a [select component with filter](https://mdbootstrap.com/docs/angular/forms/select/#section-search/) +- Events `itemShown` and `itemHidden` in [accordion](https://mdbootstrap.com/docs/angular/components/accordion/) will be now correctly emitted after animation end +- Resolved problem with close animation in [popconfirm](https://mdbootstrap.com/docs/angular/components/popconfirm/) +- Resolved problem with value returned to [autocomplete](https://mdbootstrap.com/docs/angular/forms/autocomplete/) form control on option selection +- Resolved problem with wrong page value returned by `(paginationChange)` event in [datatable](https://mdbootstrap.com/docs/angular/data/datatables/) +- Increased backdrop z-index in [onboarding plugin](https://mdbootstrap.com/docs/angular/plugins/onboarding/) +- Resolved problem with `autohide` option in [toast](https://mdbootstrap.com/docs/angular/components/toasts/), notification will be removed only if it is not hovered +- Added default padding to the content container in [WYSIWYG editor plugin](https://mdbootstrap.com/docs/angular/plugins/wysiwyg-editor/) +- Resolved problem with Angular dependencies versions in schematics installation + +### New: + +- Addew new [color picker plugin](https://mdbootstrap.com/docs/angular/plugins/color-picker/) +- Addew new [scroll status plugin](https://mdbootstrap.com/docs/angular/plugins/scroll-status/) + +--- + +## 3.0.0 (10.10.2022) + +This version requires Angular v14 and Node 14.15.0 (or later). Follow the [Angular update guide](https://update.angular.io/?l=3&v=13.0-14.0) to migrate your project to Angular 14: + +### Breaking changes: + +- Added support for Angular 14, this Angular version is now required, +- Removed `~` from styles imports, this syntax is now deprecated +- Updated [calendar](https://mdbootstrap.com/docs/angular/plugins/calendar/) plugin: + - redesigned toolbar, events, views and modals + - replaced view toggle buttons with select + - created an `Add event` button + - added [blur](https://mdbootstrap.com/docs/angular/plugins/calendar/#section-blur/) option to style past events + - improved long events styling + - improved responsiveness +- Design changes: + - Changed shadows for components such as [card](https://mdbootstrap.com/docs/angular/components/cards/), [popover](https://mdbootstrap.com/docs/angular/components/popovers/), [toast](https://mdbootstrap.com/docs/angular/components/toasts/), [modal](https://mdbootstrap.com/docs/angular/components/modal/), [image hoverable](https://mdbootstrap.com/docs/angular/content-styles/images/), [dropdown menu](https://mdbootstrap.com/docs/angular/components/dropdowns/), [popconfirm](https://mdbootstrap.com/docs/angular/components/popconfirm/) + - Changed styling of border for [card](https://mdbootstrap.com/docs/angular/components/cards/), [modal](https://mdbootstrap.com/docs/angular/components/modal/), header and footer + - Changed [table](https://mdbootstrap.com/docs/angular/data/tables/) font weight and text color + - Changed [checkbox](https://mdbootstrap.com/docs/angular/forms/checkbox/) and [radio](https://mdbootstrap.com/docs/angular/forms/radio/) border color + - Changed [switch](https://mdbootstrap.com/docs/angular/forms/switch/) background color + - Changed [checkbox](https://mdbootstrap.com/docs/angular/forms/checkbox/) border radius size + - Changed [list group](https://mdbootstrap.com/docs/angular/components/list-group/), [pagination](https://mdbootstrap.com/docs/angular/navigation/pagination/) and [dropdown](https://mdbootstrap.com/docs/angular/components/dropdowns/) text color as it is in the body + - Changed [toast](https://mdbootstrap.com/docs/angular/components/toasts/) color palette + - Changed [datatables](https://mdbootstrap.com/docs/angular/data/datatables/) striped and hover background color as it is in the usual table + - Changed [select](https://mdbootstrap.com/docs/angular/forms/select/) states background colors + - Changed [sidenav](https://mdbootstrap.com/docs/angular/navigation/sidenav/) icons colors and width of the slim version + - Added new [toast](https://mdbootstrap.com/docs/angular/components/toasts/) color classes that replaced background color classes. Old: `toast bg-primary`. New: `toast toast-primary` + +### Fixes and improvements: + +- [Lightbox](https://mdbootstrap.com/docs/angular/components/lightbox/) + - Resolved problems with zoom + - Resolved problems with swipe on mobile devices + - Resolved problem with display of smaller images + - Fixed image position in fullscreen mode + - Disabled elements will no longer be displayed inside the component modal +- Fixed problems with `rebuild` method in [charts](https://mdbootstrap.com/docs/angular/data/charts/) +- Replaced hardcoded color values with SCSS variables in [autocomplete](https://mdbootstrap.com/docs/angular/forms/autocomplete/) and [select](https://mdbootstrap.com/docs/angular/forms/select/) +- Resolved problem with [carousel](https://mdbootstrap.com/docs/angular/components/carousel/) animations inside a component with OnPush change detection strategy +- Position of dropdown menus in all components will be now correctly updated on scroll event +- Resolved problem with fade animation in [tabs](https://mdbootstrap.com/docs/angular/components/tabs/) +- Label values in [select](https://mdbootstrap.com/docs/angular/forms/select/) will be now dynamically updated on option label change +- All event listeners in the [WYSIWYG](https://mdbootstrap.com/docs/angular/plugins/wysiwyg-editor/) plugin will be now correctly removed when component is destroyed +- Resolved problem with [input](https://mdbootstrap.com/docs/angular/forms/input-fields/) label position when browser autofill is used + +### New: + +- Addew new [countdown plugin](https://mdbootstrap.com/docs/angular/plugins/countdown/) +- Addew new [input mask plugin](https://mdbootstrap.com/docs/angular/plugins/input-mask/) +- Addew new [parallax plugin](https://mdbootstrap.com/docs/angular/plugins/parallax/) +- Addew new [multi range component](https://mdbootstrap.com/docs/angular/components/multi-range-slider/) +- Added new `[fade]` input that allow to toggle fade animations in [tabs](https://mdbootstrap.com/docs/angular/components/tabs/) + +### Design updates: + +- Updated icon colors of basic light navbar and footer with secondary color +- Added new horizontal dividers classes `.hr` and `.hr-blurry` +- Updated styles of vertical divider class `.vr` and add new class `.vr-blurry` +- Added new sidenav with menu categories and class `.sidenav-sm` +- Added new `object-fit` and `object-position` utilities + +### Removed: + +- Deprecated button close classes. Old: `.close`. New: `.btn-close` and `.btn-close-white` +- Deprecated embed classes. Old: `.embed`. New: `.ratio` +- Deprecated flag classes. Check [flags](https://mdbootstrap.com/docs/angular/content-styles/flags/) docs +- Deprecated utils + +### Deprecated: + +- `.divider-horizontal` and `.divider-horizontal-blurry` +- `.divider-vertical` and `.divider-vertical-blurry` + +--- + +## 2.3.0 (27.06.2022) + +### Fixes and improvements + +- [Sidenav](https://mdbootstrap.com/docs/b5/angular/navigation/sidenav/) + - Resolved problems with arrow position updates in slim mode and accordion mode + - Resolved problem with initialization of component with `[right]="true"` and `[hidden]="false"` options + - Fixed problem with long content display in component with `[right]="true"` option +- Fixed problems with long label positioning in [checkbox](https://mdbootstrap.com/docs/b5/angular/forms/checkbox/), [switch](https://mdbootstrap.com/docs/b5/angular/forms/switch/) and [radio](https://mdbootstrap.com/docs/b5/angular/forms/radio/) +- Resolved problem with multiple `paginationChange` events emitted on [datatable](https://mdbootstrap.com/docs/b5/angular/data/datatables/) initialization +- Resolved problems with [pagination](https://mdbootstrap.com/docs/b5/angular/navigation/pagination/) and [accordion](https://mdbootstrap.com/docs/b5/angular/components/accordion/) styles when using [theme](https://mdbootstrap.com/docs/b5/angular/content-styles/theme/) +- Fixed problem with max file quantity in [file upload](https://mdbootstrap.com/docs/b5/angular/plugins/file-upload/) plugin with `multiple` mode +- Resolved problem with first option highlight in [select](https://mdbootstrap.com/docs/b5/angular/forms/select/) with a `[highlightFirst]="false"` option +- Added `type="button"` to the 'insert horizontal line' button in [WYSIWYG](https://mdbootstrap.com/docs/b5/angular/plugins/wysiwyg-editor/) to resolve problem with form submit +- Zero-length [tooltip](https://mdbootstrap.com/docs/b5/angular/components/tooltips/) and [popover](https://mdbootstrap.com/docs/b5/angular/components/popovers/) will no longer be displayed +- Fixed problem with multiple `(selected)` events emitted after click on [autocomplete](https://mdbootstrap.com/docs/b5/angular/forms/autocomplete/) option + +### New + +- Addew new [onboarding plugin](https://mdbootstrap.com/docs/b5/angular/plugins/onboarding/) +- [Stepper](https://mdbootstrap.com/docs/b5/angular/components/stepper/) + - Added possibility to block step navigation on step header click + - Added possibility to edit buttons and header text in mobile mode +- Added new `--mdb-bg-opacity` CSS variable +- Added optional auto select on tab-out in [select](https://mdbootstrap.com/docs/b5/angular/forms/select/) and [autocomplete](https://mdbootstrap.com/docs/b5/angular/forms/autocomplete/) +- Added list group new variant with `.list-group-light` class +- Added `.table-group-divider` and `.table-divider-color` classes to emphasize the separation of thead from tbody +- Added new `.divider-horizontal`, `.divider-vertical`, `.divider-horizontal-blurry` and `.divider-vertical-blurry` classes + +--- + +## 2.2.0 (16.05.2022) + +### Fixes and improvements: + +- Datepicker - resolved problem with returned month value when `m` format is used, +- Treeview - resolved problem with `(selected)` event emit when selecting checkbox, +- Select - resolved problem with keyboard navigation and option highlight after filter input is used. +- Charts - resolved problem with chart options being overriden by options defined for other charts, +- Range - resolved problem with thumb position update after change in `ngModel` or `formControl` + +### New: + +- [Added filter plugin](https://mdbootstrap.com/docs/b5/angular/plugins/filters/) +- Dropdown - added keyboard navigation + +--- + +## 2.1.0 (11.04.2022) + +### Fixes and improvements: + +- Datepicker - resolved problem with validation of date typed into input, +- Sidenav - removed unnecessary transition animation on initialization in slim mode, +- File upload plugin - fixed typo in main error message, +- Carousel/Lightbox - updated icons styles for Font Awesome v6. + +### New: + +- [Cookies management](https://mdbootstrap.com/docs/b5/angular/plugins/cookies-management/) +- [Storage management](https://mdbootstrap.com/docs/b5/angular/plugins/storage-management/) +- [Mention](https://mdbootstrap.com/docs/b5/angular/plugins/mention/) +- [Transfer](https://mdbootstrap.com/docs/b5/angular/plugins/transfer/) + +--- + +## 2.0.0 (28.02.2022) + +### Breaking changes: + +- Added support for Angular 13, this Angular version is now required, +- Sidenav - removed support for automatic item expansion based on an active link ([in our documentation](https://mdbootstrap.com/docs/b5/angular/navigation/sidenav/) you can find information on how to achieve this effect using methods provided by Angular Router). + +### Dependencies: + +- Updated Font Awesome to v6.0.0 + +### Fixes and improvements: + +- Toasts/Alerts - resolved problem with positioning when stacking and position bottom options are used, +- Select/Datepicker - resolved problems with input, label and icons styles when `form-white` class is used on `mdb-form-control` component, +- Select - resolved problem with selection when multiple options have the same label (in some cases component incorrectly displayed option value instead of option label in input), +- Datatable pagination - component will now display correct information when data source is empty. + +### New features: + +- Tabs - added new `[navColumnClass]` and `[contentColumnClass]` inputs that allow to customize width of the navigation and content sections in vertical mode. + +--- + +## 1.6.1 (24.01.2022) + +### Optimization: + +- Documentation migration from Wordpress to Hugo, +- Updated code in snippets in documentation to work properly with tsconfig strict settings. + +### Fixes and improvements: + +- Input - resolved problem with label position in input with type="date", +- Datepicker/Timepicker - improved backdrop animation (removed unnecessary delay), +- Datepicker - resolved problem with navigation using previous/next arrows when min and max date is specified, +- Sidenav - animation of the collapsed item in slim mode will be now in sync with animation of the menu (previously there was unnecessary delay) +- Select - list of filtered options will be now correctly reset after the dropdown menu is closed, +- Treeview plugin - click on checkbox will no longer change collapsed state of the node, +- Treeview plugin - checked state of the checkox in parent node will be now in sync with the checkboxes in child nodes. + +--- + +## 1.6.0 (27.12.2021) + +### Dependencies: + +- Updated Bootstrap to 5.1.3 version. + +### Fixes and improvements: + +- Charts - resolved problem with `chartjs-plugin-datalabels` configuration, +- Carousel - component should now work correctly inside components with `OnPush` change detection strategy, +- Table - updated `dataSource` type to resolve problem with asynchronous data and async pipe, +- File upload plugin - resolved problem with extensions handled by the `acceptedExtensions` input, +- Popconfirm - target element will be now optional in modal display mode, +- Sidenav - resolved problem with `child.querySelector is not a function` error when using `ngFor` directive to render sidenav items, +- Popover - `mdbPopover` input will now correctly accept value with `TemplateRef` type. + +### New: + +- Dropdown - added new `closeOnOutsideClick`, `closeOnItemClick`, `closeOnEsc` inputs that allow to configure menu closing actions, +- File upload plugin - added a new `reset` method that allow to reset component state to default settings. + +--- + +## 1.5.1 (22.11.2021) + +### Fixes and improvements + +- Toast/Alert - resolved problem with stacking and close animation, +- Modal - resolved problem with closing when mouseup event is detected outside the component, +- Sidenav - setting `hidden` input to `false` will no longer trigger component animation, +- Sidenav - resolved problem with arrow rotation update when `[collapsed]="false"` is used, +- Sidenav - removed focus trap in side and push modes, +- Sidenav - default position will be now correctly set to `fixed`, +- Input - resolved problem with border top gap recalculation when used inside a dynamically loaded component (such as tabs), +- Overlay - resolved problem with z-index in components using overlay (e.g. modal, popconfirm, tooltip, components with dropdown menus). The components will be correctly displayed above the elements with sticky/fixed styles, +- Charts - fixed default options and resolved problem with custom options merge. + +### Vector maps 1.1.0: + +- resolved problem with automatic updates of colors defined in `colorMap`, +- resolved problem with tooltip display when `[hover]="false"` is used, +- added possibility to display custom tooltips. + +--- + +## 1.5.0 (02.11.2021) + +### New + +- [File upload](https://mdbootstrap.com/docs/b5/angular/plugins/file-upload) +- [Treeview](https://mdbootstrap.com/docs/b5/angular/plugins/tree-view) + +--- + +## 1.4.0 (18.10.2021) + +### New + +- [Drag and drop](https://mdbootstrap.com/docs/b5/angular/plugins/drag-and-drop) +- [Vector maps](https://mdbootstrap.com/docs/b5/angular/plugins/vector-maps) + +--- + +## 1.3.0 (04.10.2021) + +### New + +- [Wysiwyg](https://mdbootstrap.com/docs/b5/angular/plugins/wysiwyg-editor) + +### Fixes and improvements: + +- Popover/Tooltip - resolved problem with closing component when quickly moving mouse over trigger element + +--- + +## 1.2.0 (20.09.2021) + +### New + +- [Calendar](https://mdbootstrap.com/docs/b5/angular/plugins/calendar) +- [Table Editor](https://mdbootstrap.com/docs/b5/angular/plugins/table-editor) + +--- + +## 1.1.0 (06.09.2021) + +### Fixes and improvements: + +- Table pagination - resolved problem with disabled state of next button, +- Input - resolved problem with disabled state updates using Angular form control methods, +- Table - resolved problem with default filter function, +- Datepicker - resolved problem with disabled state of toggle button, +- Timepicker - resolved problem with setting default value in component with 24h format, +- Sidenav - resolved problem with `Cannot read property destroy of undefined` error, +- Select - resolved problem with disabled state of checkboxes in options, +- Select - resolved problem with closing modal on clear button click, +- Dropdown - menu will be now closed correctly on item click. + +### New components: + +- [Theming](https://mdbootstrap.com/docs/b5/angular/content-styles/theme) + +### New features: + +- Table pagination - added new `rowsPerPageText` input that allow to change default 'Rows per page' text + +--- + +## 1.0.0 (09.08.2021) + +In this version we introduced some breaking changes, please check `Breaking changes` section and update your application accordingly. + +### Breaking changes: + +- Inputs - removed `margin-bottom` styles from inputs with validation classes. + +### Fixes and improvements: + +- Select - dropdown will be correctly removed on component destroy, +- Select - resolved problem with select-all option state on component initialization, +- Select - resolved problem with selection of options with false values, +- Dropdown - resolved problem with opening component on icon click, +- Toasts/Alerts - resolved problem with z-index, +- Popconfirm - resolved problem with `onClose` and `onConfirm` events, +- Loading management - backdrop will be correctly removed on component destroy when fullscreen option is used, +- Timepicker - resolved problem with setting default value using Angular form controls, +- Datepicker - previous/next button disabled state will be now correctly updated on component initialization, +- Datepicker/Timepicker - click on toggle button will no longer submit form, +- Datepicker/Timepicker - resolved problems with `valueChanges` event and validation status updates, +- Datatables - resolved problem with scroll position when component is rendered inside a tab. + +### New components: + +- [Accordion](https://mdbootstrap.com/docs/b5/angular/components/accordion/) +- [Charts advanced](https://mdbootstrap.com/docs/b5/angular/data/charts-advanced/) +- [Lightbox](https://mdbootstrap.com/docs/b5/angular/components/lightbox/) +- [Smooth scroll](https://mdbootstrap.com/docs/b5/angular/methods/smooth-scroll/) + +--- + +## 1.0.0-beta8 (12.07.2021) + +In this version we introduced some breaking changes, please check `Breaking changes` section and update your application accordingly. + +### Breaking changes: + +- Popover - `[template]` input will now accept value of type `TemplateRef` and can be used to display `ng-template` content. + +### Fixes and improvements: + +- Toast - component will no longer throw error after reopening, +- Toast - stacked components will now slide up automatically, +- Sidenav - resolved problem with auto expand when route has route parameters, +- Dropdown - opened menu will be now correctly destroyed on route change, +- Table pagination - resolved problem with data automatic updates after change in `[entryOptions]` input. + +### New components: + +- [Popconfirm](https://mdbootstrap.com/docs/b5/angular/components/popconfirm/) +- [Lazy loading](https://mdbootstrap.com/docs/b5/angular/methods/lazy-loading/) +- [Loading management](https://mdbootstrap.com/docs/b5/angular/methods/loading-management/) + +### New features: + +- Popover - `[template]` input will now accept value of type `TemplateRef` and can be used to display `ng-template` content. + +--- + +## 1.0.0-beta7 (28.06.2021) + +In this version we introduced some breaking changes, please check `Breaking changes` section and update your application accordingly. + +### Breaking changes: + +- Changed `mdb-select-option` selector to `mdb-option`, +- Removed `select-` prefix from option and option group class names, +- Moved option and option group styles to individual file. + +### Fixes and improvements: + +- Sidenav - resolved problem with arrow icons in collapsed items, +- Sidenav - resolved problem with z-index, +- Select - resolved problem with dropdown toggle on arrow icon click, +- Input - resolved problem with label position when setting value dynamically using Angular form controls. + +### New components: + +- [Autcomplete](https://mdbootstrap.com/docs/b5/angular/forms/autocomplete/) +- [Infinite scroll](https://mdbootstrap.com/docs/b5/angular/methods/infinite-scroll/) +- [Touch](https://mdbootstrap.com/docs/b5/angular/methods/touch/) + +### New features: + +- Select - added new `[filterPlaceholder]` input that allow to change filter input placeholder. + +--- + +## 1.0.0-beta6 (14.06.2021) + +In this version we introduced some breaking changes, please check `Breaking changes` section and update your application accordingly. The list of all individual modules and entry points can be found here: + +[MDB Angular UI Kit Free Modules And Imports](https://mdbootstrap.com/docs/b5/angular/getting-started/modules-and-imports/) + +[MDB Angular UI Kit Pro Essential Modules And Imports](https://mdbootstrap.com/docs/b5/angular/pro/modules-and-imports/) + +### Breaking changes: + +- Updated Angular to v12 (this version is now required), +- Components, modules and types can no longer be imported from `mdb-angular-ui-kit` entry point. Use the newly added secondary entry points, such as `mdb-angular-ui-kit/checkbox` to import individual elements, +- Removed main `MdbModule`, import individual modules from its entry points, for example: `import { MdbCheckboxModule } from 'mdb-angular-ui-kit/checkbox'`, +- Renamed `MdbTimePickerComponent` to `MdbTimepickerComponent`, +- Renamed `MdbTimePickerDirective` to `MdbTimepickerDirective`, +- Renamed `MdbTimePickerModule` to `MdbTimepickerModule`, +- Updated Bootstrap styles to the latest stable version. + +### Components redesign: + +- Redesigned shadows for components: Cards, Dropdowns, Modal, Popover, Toasts, Buttons, Button Group, Navbar, Pagination, Pills, Sidenav, +- Redesigned padding for components: Alerts, Cards, List Group, +- Redesigned border radius to 0.5rem for components: Alerts, Cards, Dropdowns, Modal, List group, Popover, Toasts, Dateipcker, Timepicker. + +### Fixes and improvements: + +- Sidenav - resolved problem with height of the element with `.sidenav-menu` class, +- Range - resolved problem with a hardcoded `Example label` text, +- Datepicker - `dateChanged` event will be now correctly emited on date change, +- Datepicker - resolved problem with components updates on Angular form control changes, +- File input - updated styles to Material Design styles, +- Pills - fixed width of pills when they're filled or justified, +- Checkbox/Switch/Radio - fix margin styles and positioning. + +### New components: + +- [Stepper](https://mdbootstrap.com/docs/b5/angular/components/stepper/) +- [Sticky](https://mdbootstrap.com/docs/b5/angular/components/sticky/) + +### New features: + +- Navbar - added a new `.navbar-nav-scroll` class to enable vertical scrolling when a collapsed navbar is opened, +- Navbar - re-added `flex-grow` to the `.navbar-collapse` to restore the flexbox behaviors from v4 and prevent some content from being inadvertently squished, +- List group - added a new `.list-group-numbered` variation to list groups that uses pseudo-elements for numbering list group items, +- Shadows - added a new styles design: shadows soft, shadows standard, shadows strong, +- Added color-scheme mixin. + +--- + +## 1.0.0-beta5 (31.05.2021) + +### New components: + +- [Datatables](https://mdbootstrap.com/docs/b5/angular/data/datatables/) +- [Rating](https://mdbootstrap.com/docs/b5/angular/components/rating/) + +--- + +## 1.0.0-beta4 (04.05.2021) + +### New components: + +- [Charts](https://mdbootstrap.com/docs/b5/angular/data/charts/) + +### Bug fixes: + +- Animations - resolved problem with parameters in HTML template, +- Sidenav - resolved problems with `mode` and `hidden` inputs, +- Sidenav - resolved problem with `show` method. + +--- + +## 1.0.0-beta3 (19.04.2021) + +### New components: + +- [Alerts](https://mdbootstrap.com/docs/b5/angular/components/alerts/) +- [Carousel](https://mdbootstrap.com/docs/b5/angular/components/carousel) +- [Toasts](https://mdbootstrap.com/docs/b5/angular/components/toasts) + +### Bug fixes: + +- Datepicker - resolved problem with keyboard navigation when using `DownArrow` key, +- Datepicker - resolved problem with selecting dates using `Enter/Space` keys in component with date filter, +- Datepicker - added correct aria-labels to the previous/next buttons in the days view. + +--- + +## 1.0.0-beta2 (06.04.2021) + +### New components: + +- [Datepicker](https://mdbootstrap.com/docs/b5/angular/forms/datepicker/) +- [Timepicker](https://mdbootstrap.com/docs/b5/angular/forms/timepicker) + +--- + +## 1.0.0-beta1 (22.03.2021) + +### New components: + +- [Range](https://mdbootstrap.com/docs/b5/angular/forms/range/) +- [File](https://mdbootstrap.com/docs/b5/angular/forms/file) +- [Switch](https://mdbootstrap.com/docs/b5/angular/forms/switch/) +- [Input group](https://mdbootstrap.com/docs/b5/angular/forms-input-group/) +- [Pills](https://mdbootstrap.com/docs/b5/angular/navigation/pills/) +- [Tabs](https://mdbootstrap.com/docs/b5/angular/navigation/tabs/) + +### Bug fixes: + +- Scrollspy - added `cursor: pointer` styles to scrollspy links, +- Sidenav - resolved problem with errors when `RouterModule` is not imported, +- Sidenav - component will be correctly updated on inputs changes, +- Sidenav - resolved problem with scroll position, +- Sidenav - added components and module exports to main library index. + +### New features: + +- Animations - added new animations: `slideLeft`, `slideRight`, `slideUp`, `slideDown`, +- Sidenav - added focus trap, +- Sidenav - escape button will now close the component. + +--- + +## 1.0.0-alpha4 (08.03.2021) + +### New components: + +- [Animations](https://mdbootstrap.com/docs/b5/angular/content-styles/animations/) +- [Ripple](https://mdbootstrap.com/docs/b5/angular/methods/ripple/) +- [Sidenav](https://mdbootstrap.com/docs/b5/angular/navigation/sidenav/) +- [Scrollspy](https://mdbootstrap.com/docs/b5/angular/navigation/scrollbar/) +- [Validation](https://mdbootstrap.com/docs/b5/angular/forms/validation/) + +### Bug fixes: + +- Select - `x options selected` text will be displayed correctly when more than 5 options have been selected, +- Select - fixed clear button focusing issue. + +### New features: + +- Select - added new `displayedLabels` input that allows to change maximum number of comma-separated options labels displayed in the multiselect input, +- Select - added new `optionsSelectedLabel` input that allows to customize x options selected text, +- Select - added new `filterDebounce` input that allows to add delay to the options list updates when using filter input + +--- + +## 1.0.0-alpha3 (22.02.2021) + +### New components: + +- [Dropdown](https://mdbootstrap.com/docs/b5/angular/components/dropdowns/) +- [Modal](https://mdbootstrap.com/docs/b5/angular/components/modal/) +- [Select](https://mdbootstrap.com/docs/b5/angular/forms/select/) +- [Scrollbar](https://mdbootstrap.com/docs/b5/angular/methods/scrollbar/) + +--- + +## 1.0.0-alpha2 (25.01.2021) + +### New components: + +- [Popover](https://mdbootstrap.com/docs/b5/angular/components/popovers/) +- [Tooltip](https://mdbootstrap.com/docs/b5/angular/components/tooltips/) +- [Checkbox](https://mdbootstrap.com/docs/b5/angular/forms/checkbox/) +- [Input](https://mdbootstrap.com/docs/b5/angular/forms/input-fields/) +- [Radio](https://mdbootstrap.com/docs/b5/angular/forms/radio/) + +--- + +## 1.0.0-alpha1 (11.01.2021) + +The initial release of MDB 5 Angular Alpha 1. + +### New components: + +- [Badges](https://mdbootstrap.com/docs/b5/angular/components/badges/) +- [Buttons](https://mdbootstrap.com/docs/b5/angular/components/buttons/) +- [Button Group](https://mdbootstrap.com/docs/b5/angular/components/button-group/) +- [Cards](https://mdbootstrap.com/docs/b5/angular/components/cards/) +- [Collapse](https://mdbootstrap.com/docs/b5/angular/components/collapse/) +- [List Group](https://mdbootstrap.com/docs/b5/angular/components/list-group/) +- [Progress](https://mdbootstrap.com/docs/b5/angular/components/progress/) +- [Spinners](https://mdbootstrap.com/docs/b5/angular/components/spinners/) +- [Tables](https://mdbootstrap.com/docs/b5/angular/data/tables/) +- [Breadcrumb](https://mdbootstrap.com/docs/b5/angular/navigation/breadcrumb/) +- [Footer](https://mdbootstrap.com/docs/b5/angular/navigation/footer/) +- [Headers](https://mdbootstrap.com/docs/b5/angular/navigation/headers/) +- [Navbar](https://mdbootstrap.com/docs/b5/angular/navigation/navbar/) +- [Pagination](https://mdbootstrap.com/docs/b5/angular/navigation/pagination/) + +### New sections: + +- Layout +- Utilities +- Content & styles diff --git a/projects/mdb-angular-ui-kit/LICENSE b/projects/mdb-angular-ui-kit/LICENSE new file mode 100644 index 00000000..b7512314 --- /dev/null +++ b/projects/mdb-angular-ui-kit/LICENSE @@ -0,0 +1,19 @@ +MIT license for MDB Free + +Free packages are available under the MIT License. + +-- Highlights + +● Free for personal use + +● Free for commercial use + +● No attribution required + +-- Copyright notice + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions. + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +The software is provided "As is", without warranty of any kind, express or implied, including but not limited To the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall The authors or copyright holders be liable for any claim, damages or other liability, whether in an action of Contract, tort or otherwise, arising from, out of or in connection with the software or the use or other Dealings in the software. \ No newline at end of file diff --git a/projects/mdb-angular-ui-kit/README.md b/projects/mdb-angular-ui-kit/README.md new file mode 100644 index 00000000..2b958ead --- /dev/null +++ b/projects/mdb-angular-ui-kit/README.md @@ -0,0 +1,238 @@ +Bootstrap 5 & Angular 20 UI KIT - 700+ components, MIT license, simple installation. + +MDB is a collection of free Bootstrap templates, themes, design tools & resources. + +--- + +# Get started + +### [>> Get Started in 1 minute](https://mdbootstrap.com/docs/angular/getting-started/installation/) +Simple installation via .zip, npm or cdnjs. + +### [>> Install with MDBGO](https://mdbgo.com/) + Free Hosting, WordPress support, custom domains, SSL support, free database, frontend & backend templates, webpack starter included, git repostiory, FTP & jenkins support. + +### [>> Install with MDBGO + e-commerce shop integration](https://mdbgo.com/wordpress-shop/) +One click setup! MDB GO allows you to create a WordPress page with a single click. +Regardless whether you want to create a Travel Blog or an e-commerce shop to sell your product you can easily do that. You can even combine both into single page. + + +## About Material Design for Bootstrap 5 & Angular 20 + +

Created by + Downloads +License + +YouTube Video Views

+ +Trusted by 2 000 000+ developers & designers. Used by companies & institutions like + + + + + + + + + +
+ + + + + + + + +
+ + + + + + + + +
+ + + + +
    +
  • 700+ UI components
  • +
  • Super simple, 1 minute installation
  • +
  • Detailed docs & practical examples
  • +
  • Lots of tutorials
  • +
  • Huge and active community
  • +
  • MIT license - free for personal & commercial use
  • +
+
+ +___ + +# Bootstrap 5 tutorial + +**[>> Learn more about Bootstrap 5](https://mdbootstrap.com/docs/standard/bootstrap-5/)** + + +**[>> Bootstrap 5 Tutorial](https://mdbootstrap.com/docs/standard/bootstrap-5-tutorial/)** + +**[>> Subscribe to our YouTube channel with dozens of Bootstrap tutorials](https://www.youtube.com/c/Mdbootstrap?sub_confirmation=1)** + + + + + + + + + + + + +
+ + + + + + + +
+

Start learning from Basics

+ + + +
+

Learn Bootstrap 5 | Crash Course for Beginners in 1.5H

+ + + +
+ +--- + +# Demo + +#### Simplicity and ease of use are key features of MDB 5 Angular UI Kit. You need only one minute to install and run it. + +### Buttons + +

Use MDB custom button styles for actions in forms, dialogs, and more with support for multiple sizes, states, and more.

+ + +

+ +

+
+ + +

+ +

+
+ + +

+ +

+
+ + +

+ +

+
+ +### Spinners + +

Indicate the loading state of a component or page with MDB spinners, built entirely with HTML, CSS, and no JavaScript.

+ + +

+ +

+
+ + +

+ +

+
+ +### Cards + +

A card is a flexible and extensible content container. It includes options for headers and footers, a wide variety of content, contextual background colors, and powerful display options.

+ + +

+ +

+
+ +### Footer + +

A footer is an additional navigation component. It can hold links, buttons, company info, copyrights, forms, and many other elements.

+ + +

+ +

+
+ +### Hover + +

MDB hover effect appears when the user positions the computer cursor over an element without activating it. Hover effects make a website more interactive.

+ + +

+ +

+
+ + +

+ +

+
+ +### Notes + +

Notes are small components very helpful in inserting an additional piece of information.

+ + +

+ +

+
+ + + +___ + +# Extended documentation + + diff --git a/projects/mdb-angular-ui-kit/accordion/accordion-item-content.directive.ts b/projects/mdb-angular-ui-kit/accordion/accordion-item-content.directive.ts new file mode 100644 index 00000000..9eac351b --- /dev/null +++ b/projects/mdb-angular-ui-kit/accordion/accordion-item-content.directive.ts @@ -0,0 +1,15 @@ +import { Directive, InjectionToken, TemplateRef } from '@angular/core'; + +export const MDB_ACCORDION_ITEM_BODY = new InjectionToken( + 'MdbAccordionItemBodyDirective' +); + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbAccordionItemBody]', + providers: [{ provide: MDB_ACCORDION_ITEM_BODY, useExisting: MdbAccordionItemBodyDirective }], + standalone: false, +}) +export class MdbAccordionItemBodyDirective { + constructor(public template: TemplateRef) {} +} diff --git a/projects/mdb-angular-ui-kit/accordion/accordion-item-header.directive.ts b/projects/mdb-angular-ui-kit/accordion/accordion-item-header.directive.ts new file mode 100644 index 00000000..647f341c --- /dev/null +++ b/projects/mdb-angular-ui-kit/accordion/accordion-item-header.directive.ts @@ -0,0 +1,15 @@ +import { Directive, InjectionToken, TemplateRef } from '@angular/core'; + +export const MDB_ACCORDION_ITEM_HEADER = new InjectionToken( + 'MdbAccordionItemHeaderDirective' +); + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbAccordionItemHeader]', + providers: [{ provide: MDB_ACCORDION_ITEM_HEADER, useExisting: MdbAccordionItemHeaderDirective }], + standalone: false, +}) +export class MdbAccordionItemHeaderDirective { + constructor(public template: TemplateRef) {} +} diff --git a/projects/mdb-angular-ui-kit/accordion/accordion-item.component.html b/projects/mdb-angular-ui-kit/accordion/accordion-item.component.html new file mode 100644 index 00000000..9c2a2856 --- /dev/null +++ b/projects/mdb-angular-ui-kit/accordion/accordion-item.component.html @@ -0,0 +1,27 @@ +

+ +

+
+
+ +
+
diff --git a/projects/mdb-angular-ui-kit/accordion/accordion-item.component.ts b/projects/mdb-angular-ui-kit/accordion/accordion-item.component.ts new file mode 100644 index 00000000..c5777d0c --- /dev/null +++ b/projects/mdb-angular-ui-kit/accordion/accordion-item.component.ts @@ -0,0 +1,135 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ContentChild, + EventEmitter, + HostBinding, + Input, + OnInit, + Output, + TemplateRef, + ViewChild, +} from '@angular/core'; +import { MdbCollapseDirective } from 'mdb-angular-ui-kit/collapse'; +import { Subject } from 'rxjs'; +import { MDB_ACCORDION_ITEM_BODY } from './accordion-item-content.directive'; +import { MDB_ACCORDION_ITEM_HEADER } from './accordion-item-header.directive'; +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; + +let uniqueHeaderId = 0; +let uniqueId = 0; + +@Component({ + selector: 'mdb-accordion-item', + templateUrl: './accordion-item.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +export class MdbAccordionItemComponent implements OnInit { + @ContentChild(MDB_ACCORDION_ITEM_HEADER, { read: TemplateRef, static: true }) + _headerTemplate: TemplateRef; + + @ContentChild(MDB_ACCORDION_ITEM_BODY, { read: TemplateRef, static: true }) + _bodyTemplate: TemplateRef; + + @ViewChild(MdbCollapseDirective, { static: true }) collapse: MdbCollapseDirective; + + @Input() + get disabled(): boolean { + return this._disabled; + } + set disabled(value: boolean) { + this._disabled = coerceBooleanProperty(value); + } + private _disabled = false; + + @Input() header: string; + @Input() + set collapsed(value: boolean) { + if (!this._isInitialized) { + if (!value) { + this._shouldOpenOnInit = true; + } + return; + } + + if (value) { + this.hide(); + } else { + this.show(); + } + } + + @Input() id = `mdb-accordion-item-${uniqueId++}`; + + _headerId = `mdb-accordion-item-header-${uniqueHeaderId++}`; + + private _isInitialized = false; + private _shouldOpenOnInit = false; + + @Output() itemShow: EventEmitter = new EventEmitter(); + @Output() itemShown: EventEmitter = new EventEmitter(); + @Output() itemHide: EventEmitter = new EventEmitter(); + @Output() itemHidden: EventEmitter = new EventEmitter(); + + @HostBinding('class.accordion-item') accordionItem = true; + @HostBinding('class.d-block') accordionItemDisplayBlock = true; + + ngOnInit(): void { + this._isInitialized = true; + + if (this._shouldOpenOnInit) { + this.show(); + } + } + + show$ = new Subject(); + + _collapsed = true; + _addCollapsedClass = true; + + constructor(private _cdRef: ChangeDetectorRef) {} + + toggle(): void { + if (this.disabled) { + return; + } + + this.collapse.toggle(); + } + + show(): void { + this.collapse.show(); + this._cdRef.markForCheck(); + } + + hide(): void { + this.collapse.hide(); + this._cdRef.markForCheck(); + } + + onShow(): void { + this._addCollapsedClass = false; + this.itemShow.emit(this); + + this.show$.next(this); + } + + onHide(): void { + this._addCollapsedClass = true; + this.itemHide.emit(this); + } + + onShown(): void { + this._collapsed = false; + this.itemShown.emit(this); + } + + onHidden(): void { + this._collapsed = true; + this.itemHidden.emit(this); + } + + static ngAcceptInputType_disabled: BooleanInput; +} diff --git a/projects/angular-bootstrap-md/src/lib/free/cards/mdb-card-body.component.html b/projects/mdb-angular-ui-kit/accordion/accordion.component.html similarity index 100% rename from projects/angular-bootstrap-md/src/lib/free/cards/mdb-card-body.component.html rename to projects/mdb-angular-ui-kit/accordion/accordion.component.html diff --git a/projects/mdb-angular-ui-kit/accordion/accordion.component.ts b/projects/mdb-angular-ui-kit/accordion/accordion.component.ts new file mode 100644 index 00000000..6d462f0a --- /dev/null +++ b/projects/mdb-angular-ui-kit/accordion/accordion.component.ts @@ -0,0 +1,91 @@ +import { + AfterContentInit, + ChangeDetectionStrategy, + Component, + ContentChildren, + HostBinding, + Input, + QueryList, +} from '@angular/core'; +import { startWith, switchMap } from 'rxjs/operators'; +import { merge } from 'rxjs'; +import { MdbAccordionItemComponent } from './accordion-item.component'; +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; + +@Component({ + selector: 'mdb-accordion', + templateUrl: './accordion.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +export class MdbAccordionComponent implements AfterContentInit { + @ContentChildren(MdbAccordionItemComponent) items: QueryList; + + @Input() + get borderless(): boolean { + return this._borderless; + } + set borderless(value: boolean) { + this._borderless = coerceBooleanProperty(value); + } + private _borderless = false; + + @Input() + get flush(): boolean { + return this._flush; + } + set flush(value: boolean) { + this._flush = coerceBooleanProperty(value); + } + private _flush = false; + + @Input() + get multiple(): boolean { + return this._multiple; + } + set multiple(value: boolean) { + this._multiple = coerceBooleanProperty(value); + } + private _multiple = false; + + @HostBinding('class.accordion') accordion = true; + + @HostBinding('class.accordion-borderless') + get addBorderlessClass(): boolean { + return this.borderless; + } + + @HostBinding('class.accordion-flush') + get addFlushClass(): boolean { + return this.flush; + } + + constructor() {} + + ngAfterContentInit(): void { + this.items.changes + .pipe( + startWith(this.items), + switchMap((items: QueryList) => { + return merge(...items.map((item: MdbAccordionItemComponent) => item.show$)); + }) + ) + .subscribe((clickedItem: MdbAccordionItemComponent) => + this._handleMultipleItems(clickedItem) + ); + } + + private _handleMultipleItems(clickedItem: MdbAccordionItemComponent): void { + if (!this.multiple) { + const itemsToClose = this.items.filter( + (item: MdbAccordionItemComponent) => item !== clickedItem && !item._collapsed + ); + + itemsToClose.forEach((item: MdbAccordionItemComponent) => item.hide()); + } + } + + static ngAcceptInputType_borderless: BooleanInput; + static ngAcceptInputType_flush: BooleanInput; + static ngAcceptInputType_multiple: BooleanInput; +} diff --git a/projects/mdb-angular-ui-kit/accordion/accordion.module.ts b/projects/mdb-angular-ui-kit/accordion/accordion.module.ts new file mode 100644 index 00000000..cf8a59df --- /dev/null +++ b/projects/mdb-angular-ui-kit/accordion/accordion.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MdbAccordionComponent } from './accordion.component'; +import { MdbAccordionItemComponent } from './accordion-item.component'; +import { MdbAccordionItemHeaderDirective } from './accordion-item-header.directive'; +import { MdbAccordionItemBodyDirective } from './accordion-item-content.directive'; +import { MdbCollapseModule } from 'mdb-angular-ui-kit/collapse'; + +@NgModule({ + declarations: [ + MdbAccordionComponent, + MdbAccordionItemComponent, + MdbAccordionItemHeaderDirective, + MdbAccordionItemBodyDirective, + ], + imports: [CommonModule, MdbCollapseModule], + exports: [ + MdbAccordionComponent, + MdbAccordionItemComponent, + MdbAccordionItemHeaderDirective, + MdbAccordionItemBodyDirective, + ], +}) +export class MdbAccordionModule {} diff --git a/projects/mdb-angular-ui-kit/accordion/accordion.spec.ts b/projects/mdb-angular-ui-kit/accordion/accordion.spec.ts new file mode 100644 index 00000000..5248eb2e --- /dev/null +++ b/projects/mdb-angular-ui-kit/accordion/accordion.spec.ts @@ -0,0 +1,241 @@ +import { Component, QueryList, ViewChildren } from '@angular/core'; +import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing'; +import { MdbAccordionItemComponent } from './accordion-item.component'; +import { MdbAccordionModule } from './accordion.module'; + +const ANIMATION_TIME = 350; // animation time from collapse directive + +const template = ` + + + Accordion Item #1 + + This is the first item's accordion body. It is hidden by default, + until the collapse plugin adds the appropriate classes that we use to style each + element. These classes control the overall appearance, as well as the showing and + hiding via CSS transitions. You can modify any of this with custom CSS or overriding + our default variables. It's also worth noting that just about any HTML can go within + the .accordion-body, though the transition does limit overflow. + + + + + Accordion Item #2 + + This is the second item's accordion body. It is hidden by default, + until the collapse plugin adds the appropriate classes that we use to style each + element. These classes control the overall appearance, as well as the showing and + hiding via CSS transitions. You can modify any of this with custom CSS or overriding + our default variables. It's also worth noting that just about any HTML can go within + the .accordion-body, though the transition does limit overflow. + + + + + Accordion Item #3 + + This is the third item's accordion body. It is hidden by default, + until the collapse plugin adds the appropriate classes that we use to style each + element. These classes control the overall appearance, as well as the showing and + hiding via CSS transitions. You can modify any of this with custom CSS or overriding + our default variables. It's also worth noting that just about any HTML can go within + the .accordion-body, though the transition does limit overflow. + + + +`; + +@Component({ + selector: 'mdb-accordion-test', + template, + standalone: false, +}) +class TestAccordionComponent { + @ViewChildren(MdbAccordionItemComponent) _accordionItems: QueryList; + multiple = false; + flush = false; + borderless = false; + disabled = false; + + get accordionItems(): MdbAccordionItemComponent[] { + return this._accordionItems.toArray(); + } +} + +describe('MDB Accordion', () => { + let fixture: ComponentFixture; + let element: any; + let component: any; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestAccordionComponent], + imports: [MdbAccordionModule], + teardown: { destroyAfterEach: false }, + }); + fixture = TestBed.createComponent(TestAccordionComponent); + fixture.detectChanges(); + component = fixture.componentInstance; + element = fixture.nativeElement; + }); + + it('should toggle item on click', fakeAsync(() => { + const item = document.querySelector('.accordion-item') as HTMLElement; + const button = item.querySelector('.accordion-button') as HTMLElement; + + button.click(); + fixture.detectChanges(); + flush(); + + const itemCollapse = item.querySelector('.collapse'); + + expect(button.classList).not.toContain('collapsed'); + expect(itemCollapse.classList).toContain('show'); + + button.click(); + fixture.detectChanges(); + flush(); + + expect(button.classList).toContain('collapsed'); + expect(itemCollapse.classList).not.toContain('show'); + })); + + it('should toggle item when toggle method is used', fakeAsync(() => { + const item = document.querySelector('.accordion-item') as HTMLElement; + const button = item.querySelector('.accordion-button') as HTMLElement; + component.accordionItems[0].toggle(); + + fixture.detectChanges(); + flush(); + + const itemCollapse = item.querySelector('.collapse'); + + expect(button.classList).not.toContain('collapsed'); + expect(itemCollapse.classList).toContain('show'); + + component.accordionItems[0].toggle(); + fixture.detectChanges(); + flush(); + + expect(button.classList).toContain('collapsed'); + expect(itemCollapse.classList).not.toContain('show'); + })); + + it('should allow only one item to be opened if multiple is set to false', fakeAsync(() => { + const buttons = document.querySelectorAll('.accordion-button'); + const contents = document.querySelectorAll('.collapse'); + + component.accordionItems[0].toggle(); + fixture.detectChanges(); + flush(); + + expect(buttons[0].classList).not.toContain('collapsed'); + expect(contents[0].classList).toContain('show'); + + component.accordionItems[1].toggle(); + fixture.detectChanges(); + flush(); + + expect(buttons[0].classList).toContain('collapsed'); + expect(contents[0].classList).not.toContain('show'); + expect(buttons[1].classList).not.toContain('collapsed'); + expect(contents[1].classList).toContain('show'); + })); + + it('should allow multiple items to be opened if multiple is set to true', fakeAsync(() => { + component.multiple = true; + fixture.detectChanges(); + + const buttons = document.querySelectorAll('.accordion-button'); + const contents = document.querySelectorAll('.collapse'); + + component.accordionItems[0].toggle(); + fixture.detectChanges(); + flush(); + + expect(buttons[0].classList).not.toContain('collapsed'); + expect(contents[0].classList).toContain('show'); + + component.accordionItems[1].toggle(); + fixture.detectChanges(); + flush(); + + expect(buttons[0].classList).not.toContain('collapsed'); + expect(contents[0].classList).toContain('show'); + expect(buttons[1].classList).not.toContain('collapsed'); + expect(contents[1].classList).toContain('show'); + })); + + it('should add accordion-flush class if flush is set to true', () => { + component.flush = true; + fixture.detectChanges(); + + const accordion = document.querySelector('.accordion'); + + expect(accordion.classList).toContain('accordion-flush'); + }); + + it('should add accordion-borderless class if borderless is set to true', () => { + const accordion = document.querySelector('.accordion'); + + expect(accordion.classList).not.toContain('accordion-borderless'); + + component.borderless = true; + fixture.detectChanges(); + + expect(accordion.classList).toContain('accordion-borderless'); + }); + + it('should emit correct events on item collapse and expand', fakeAsync(() => { + const item = component.accordionItems[0]; + + const showSpy = jest.spyOn(item.itemShow, 'emit'); + const shownSpy = jest.spyOn(item.itemShown, 'emit'); + const hideSpy = jest.spyOn(item.itemHide, 'emit'); + const hiddenSpy = jest.spyOn(item.itemHidden, 'emit'); + + item.show(); + fixture.detectChanges(); + + expect(showSpy).toHaveBeenCalled(); + expect(shownSpy).not.toHaveBeenCalled(); + + tick(ANIMATION_TIME); + flush(); + fixture.detectChanges(); + + expect(shownSpy).toHaveBeenCalled(); + + item.hide(); + fixture.detectChanges(); + + expect(hideSpy).toHaveBeenCalled(); + expect(hiddenSpy).not.toHaveBeenCalled(); + + tick(ANIMATION_TIME); + flush(); + fixture.detectChanges(); + + expect(hiddenSpy).toHaveBeenCalled(); + })); + + it('should not toggle item on click when disabled input is set to true', fakeAsync(() => { + component.disabled = true; + fixture.detectChanges(); + + const item = document.querySelector('.accordion-item') as HTMLElement; + const button = item.querySelector('.accordion-button') as HTMLButtonElement; + const itemCollapse = item.querySelector('.collapse') as HTMLDivElement; + + expect(button.hasAttribute('disabled')).toBe(true); + expect(button.classList).toContain('collapsed'); + expect(itemCollapse.classList).not.toContain('show'); + + button.click(); + fixture.detectChanges(); + flush(); + + expect(button.classList).toContain('collapsed'); + expect(itemCollapse.classList).not.toContain('show'); + })); +}); diff --git a/projects/mdb-angular-ui-kit/accordion/index.ts b/projects/mdb-angular-ui-kit/accordion/index.ts new file mode 100644 index 00000000..4aaf8f92 --- /dev/null +++ b/projects/mdb-angular-ui-kit/accordion/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/projects/mdb-angular-ui-kit/accordion/ng-package.json b/projects/mdb-angular-ui-kit/accordion/ng-package.json new file mode 100644 index 00000000..ecef3ed8 --- /dev/null +++ b/projects/mdb-angular-ui-kit/accordion/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "index.ts" + } +} \ No newline at end of file diff --git a/projects/mdb-angular-ui-kit/accordion/public_api.ts b/projects/mdb-angular-ui-kit/accordion/public_api.ts new file mode 100644 index 00000000..4a8239b3 --- /dev/null +++ b/projects/mdb-angular-ui-kit/accordion/public_api.ts @@ -0,0 +1,5 @@ +export { MdbAccordionComponent } from './accordion.component'; +export { MdbAccordionItemComponent } from './accordion-item.component'; +export { MdbAccordionItemHeaderDirective } from './accordion-item-header.directive'; +export { MdbAccordionItemBodyDirective } from './accordion-item-content.directive'; +export { MdbAccordionModule } from './accordion.module'; diff --git a/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_accordion.scss b/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_accordion.scss new file mode 100644 index 00000000..75588a5a --- /dev/null +++ b/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_accordion.scss @@ -0,0 +1,158 @@ +// +// Base styles +// + +.accordion { + // scss-docs-start accordion-css-vars + --#{$prefix}accordion-color: #{$accordion-color}; + --#{$prefix}accordion-bg: #{$accordion-bg}; + --#{$prefix}accordion-transition: #{$accordion-transition}; + --#{$prefix}accordion-border-color: #{$accordion-border-color}; + --#{$prefix}accordion-border-width: #{$accordion-border-width}; + --#{$prefix}accordion-border-radius: #{$accordion-border-radius}; + --#{$prefix}accordion-inner-border-radius: #{$accordion-inner-border-radius}; + --#{$prefix}accordion-btn-padding-x: #{$accordion-button-padding-x}; + --#{$prefix}accordion-btn-padding-y: #{$accordion-button-padding-y}; + --#{$prefix}accordion-btn-color: #{$accordion-button-color}; + --#{$prefix}accordion-btn-bg: #{$accordion-button-bg}; + --#{$prefix}accordion-btn-icon: #{escape-svg($accordion-button-icon)}; + --#{$prefix}accordion-btn-icon-width: #{$accordion-icon-width}; + --#{$prefix}accordion-btn-icon-transform: #{$accordion-icon-transform}; + --#{$prefix}accordion-btn-icon-transition: #{$accordion-icon-transition}; + --#{$prefix}accordion-btn-active-icon: #{escape-svg($accordion-button-active-icon)}; + --#{$prefix}accordion-btn-focus-border-color: #{$accordion-button-focus-border-color}; + --#{$prefix}accordion-btn-focus-box-shadow: #{$accordion-button-focus-box-shadow}; + --#{$prefix}accordion-body-padding-x: #{$accordion-body-padding-x}; + --#{$prefix}accordion-body-padding-y: #{$accordion-body-padding-y}; + --#{$prefix}accordion-active-color: #{$accordion-button-active-color}; + --#{$prefix}accordion-active-bg: #{$accordion-button-active-bg}; + // scss-docs-end accordion-css-vars +} + +.accordion-button { + position: relative; + display: flex; + align-items: center; + width: 100%; + padding: var(--#{$prefix}accordion-btn-padding-y) var(--#{$prefix}accordion-btn-padding-x); + @include font-size($font-size-base); + color: var(--#{$prefix}accordion-btn-color); + text-align: left; // Reset button style + background-color: var(--#{$prefix}accordion-btn-bg); + border: 0; + @include border-radius(0); + overflow-anchor: none; + @include transition(var(--#{$prefix}accordion-transition)); + + &:not(.collapsed) { + color: var(--#{$prefix}accordion-active-color); + background-color: var(--#{$prefix}accordion-active-bg); + box-shadow: inset 0 calc(-1 * var(--#{$prefix}accordion-border-width)) 0 var(--#{$prefix}accordion-border-color); // stylelint-disable-line function-disallowed-list + + &::after { + background-image: var(--#{$prefix}accordion-btn-active-icon); + transform: var(--#{$prefix}accordion-btn-icon-transform); + } + } + + // Accordion icon + &::after { + flex-shrink: 0; + width: var(--#{$prefix}accordion-btn-icon-width); + height: var(--#{$prefix}accordion-btn-icon-width); + margin-left: auto; + content: ""; + background-image: var(--#{$prefix}accordion-btn-icon); + background-repeat: no-repeat; + background-size: var(--#{$prefix}accordion-btn-icon-width); + @include transition(var(--#{$prefix}accordion-btn-icon-transition)); + } + + &:hover { + z-index: 2; + } + + &:focus { + z-index: 3; + border-color: var(--#{$prefix}accordion-btn-focus-border-color); + outline: 0; + box-shadow: var(--#{$prefix}accordion-btn-focus-box-shadow); + } +} + +.accordion-header { + margin-bottom: 0; +} + +.accordion-item { + color: var(--#{$prefix}accordion-color); + background-color: var(--#{$prefix}accordion-bg); + border: var(--#{$prefix}accordion-border-width) solid var(--#{$prefix}accordion-border-color); + + &:first-of-type { + @include border-top-radius(var(--#{$prefix}accordion-border-radius)); + + .accordion-button { + @include border-top-radius(var(--#{$prefix}accordion-inner-border-radius)); + } + } + + &:not(:first-of-type) { + border-top: 0; + } + + // Only set a border-radius on the last item if the accordion is collapsed + &:last-of-type { + @include border-bottom-radius(var(--#{$prefix}accordion-border-radius)); + + .accordion-button { + &.collapsed { + @include border-bottom-radius(var(--#{$prefix}accordion-inner-border-radius)); + } + } + + .accordion-collapse { + @include border-bottom-radius(var(--#{$prefix}accordion-border-radius)); + } + } +} + +.accordion-body { + padding: var(--#{$prefix}accordion-body-padding-y) var(--#{$prefix}accordion-body-padding-x); +} + + +// Flush accordion items +// +// Remove borders and border-radius to keep accordion items edge-to-edge. + +.accordion-flush { + .accordion-collapse { + border-width: 0; + } + + .accordion-item { + border-right: 0; + border-left: 0; + @include border-radius(0); + + &:first-child { border-top: 0; } + &:last-child { border-bottom: 0; } + + .accordion-button { + &, + &.collapsed { + @include border-radius(0); + } + } + } +} + +@if $enable-dark-mode { + @include color-mode(dark) { + .accordion-button::after { + --#{$prefix}accordion-btn-icon: #{escape-svg($accordion-button-icon-dark)}; + --#{$prefix}accordion-btn-active-icon: #{escape-svg($accordion-button-active-icon-dark)}; + } + } +} diff --git a/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_alert.scss b/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_alert.scss new file mode 100644 index 00000000..b8cff9b7 --- /dev/null +++ b/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_alert.scss @@ -0,0 +1,68 @@ +// +// Base styles +// + +.alert { + // scss-docs-start alert-css-vars + --#{$prefix}alert-bg: transparent; + --#{$prefix}alert-padding-x: #{$alert-padding-x}; + --#{$prefix}alert-padding-y: #{$alert-padding-y}; + --#{$prefix}alert-margin-bottom: #{$alert-margin-bottom}; + --#{$prefix}alert-color: inherit; + --#{$prefix}alert-border-color: transparent; + --#{$prefix}alert-border: #{$alert-border-width} solid var(--#{$prefix}alert-border-color); + --#{$prefix}alert-border-radius: #{$alert-border-radius}; + --#{$prefix}alert-link-color: inherit; + // scss-docs-end alert-css-vars + + position: relative; + padding: var(--#{$prefix}alert-padding-y) var(--#{$prefix}alert-padding-x); + margin-bottom: var(--#{$prefix}alert-margin-bottom); + color: var(--#{$prefix}alert-color); + background-color: var(--#{$prefix}alert-bg); + border: var(--#{$prefix}alert-border); + @include border-radius(var(--#{$prefix}alert-border-radius)); +} + +// Headings for larger alerts +.alert-heading { + // Specified to prevent conflicts of changing $headings-color + color: inherit; +} + +// Provide class for links that match alerts +.alert-link { + font-weight: $alert-link-font-weight; + color: var(--#{$prefix}alert-link-color); +} + + +// Dismissible alerts +// +// Expand the right padding and account for the close button's positioning. + +.alert-dismissible { + padding-right: $alert-dismissible-padding-r; + + // Adjust close link position + .btn-close { + position: absolute; + top: 0; + right: 0; + z-index: $stretched-link-z-index + 1; + padding: $alert-padding-y * 1.25 $alert-padding-x; + } +} + + +// scss-docs-start alert-modifiers +// Generate contextual modifier classes for colorizing the alert +@each $state in map-keys($theme-colors) { + .alert-#{$state} { + --#{$prefix}alert-color: var(--#{$prefix}#{$state}-text-emphasis); + --#{$prefix}alert-bg: var(--#{$prefix}#{$state}-bg-subtle); + --#{$prefix}alert-border-color: var(--#{$prefix}#{$state}-border-subtle); + --#{$prefix}alert-link-color: var(--#{$prefix}#{$state}-text-emphasis); + } +} +// scss-docs-end alert-modifiers diff --git a/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_badge.scss b/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_badge.scss new file mode 100644 index 00000000..cc3d2695 --- /dev/null +++ b/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_badge.scss @@ -0,0 +1,38 @@ +// Base class +// +// Requires one of the contextual, color modifier classes for `color` and +// `background-color`. + +.badge { + // scss-docs-start badge-css-vars + --#{$prefix}badge-padding-x: #{$badge-padding-x}; + --#{$prefix}badge-padding-y: #{$badge-padding-y}; + @include rfs($badge-font-size, --#{$prefix}badge-font-size); + --#{$prefix}badge-font-weight: #{$badge-font-weight}; + --#{$prefix}badge-color: #{$badge-color}; + --#{$prefix}badge-border-radius: #{$badge-border-radius}; + // scss-docs-end badge-css-vars + + display: inline-block; + padding: var(--#{$prefix}badge-padding-y) var(--#{$prefix}badge-padding-x); + @include font-size(var(--#{$prefix}badge-font-size)); + font-weight: var(--#{$prefix}badge-font-weight); + line-height: 1; + color: var(--#{$prefix}badge-color); + text-align: center; + white-space: nowrap; + vertical-align: baseline; + @include border-radius(var(--#{$prefix}badge-border-radius)); + @include gradient-bg(); + + // Empty badges collapse automatically + &:empty { + display: none; + } +} + +// Quick fix for badges in buttons +.btn .badge { + position: relative; + top: -1px; +} diff --git a/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_breadcrumb.scss b/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_breadcrumb.scss new file mode 100644 index 00000000..b8252ff2 --- /dev/null +++ b/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_breadcrumb.scss @@ -0,0 +1,40 @@ +.breadcrumb { + // scss-docs-start breadcrumb-css-vars + --#{$prefix}breadcrumb-padding-x: #{$breadcrumb-padding-x}; + --#{$prefix}breadcrumb-padding-y: #{$breadcrumb-padding-y}; + --#{$prefix}breadcrumb-margin-bottom: #{$breadcrumb-margin-bottom}; + @include rfs($breadcrumb-font-size, --#{$prefix}breadcrumb-font-size); + --#{$prefix}breadcrumb-bg: #{$breadcrumb-bg}; + --#{$prefix}breadcrumb-border-radius: #{$breadcrumb-border-radius}; + --#{$prefix}breadcrumb-divider-color: #{$breadcrumb-divider-color}; + --#{$prefix}breadcrumb-item-padding-x: #{$breadcrumb-item-padding-x}; + --#{$prefix}breadcrumb-item-active-color: #{$breadcrumb-active-color}; + // scss-docs-end breadcrumb-css-vars + + display: flex; + flex-wrap: wrap; + padding: var(--#{$prefix}breadcrumb-padding-y) var(--#{$prefix}breadcrumb-padding-x); + margin-bottom: var(--#{$prefix}breadcrumb-margin-bottom); + @include font-size(var(--#{$prefix}breadcrumb-font-size)); + list-style: none; + background-color: var(--#{$prefix}breadcrumb-bg); + @include border-radius(var(--#{$prefix}breadcrumb-border-radius)); +} + +.breadcrumb-item { + // The separator between breadcrumbs (by default, a forward-slash: "/") + + .breadcrumb-item { + padding-left: var(--#{$prefix}breadcrumb-item-padding-x); + + &::before { + float: left; // Suppress inline spacings and underlining of the separator + padding-right: var(--#{$prefix}breadcrumb-item-padding-x); + color: var(--#{$prefix}breadcrumb-divider-color); + content: var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider)) #{"/* rtl:"} var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider-flipped)) #{"*/"}; + } + } + + &.active { + color: var(--#{$prefix}breadcrumb-item-active-color); + } +} diff --git a/projects/angular-bootstrap-md/src/lib/assets/scss/bootstrap/_button-group.scss b/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_button-group.scss similarity index 51% rename from projects/angular-bootstrap-md/src/lib/assets/scss/bootstrap/_button-group.scss rename to projects/mdb-angular-ui-kit/assets/scss/bootstrap/_button-group.scss index 9951e927..55ae3f65 100644 --- a/projects/angular-bootstrap-md/src/lib/assets/scss/bootstrap/_button-group.scss +++ b/projects/mdb-angular-ui-kit/assets/scss/bootstrap/_button-group.scss @@ -1,5 +1,3 @@ -// stylelint-disable selector-no-qualifying-type - // Make the div behave like a button .btn-group, .btn-group-vertical { @@ -10,17 +8,17 @@ > .btn { position: relative; flex: 1 1 auto; + } - // Bring the hover, focused, and "active" buttons to the front to overlay - // the borders properly - @include hover { - z-index: 1; - } - &:focus, - &:active, - &.active { - z-index: 1; - } + // Bring the hover, focused, and "active" buttons to the front to overlay + // the borders properly + > .btn-check:checked + .btn, + > .btn-check:focus + .btn, + > .btn:hover, + > .btn:focus, + > .btn:active, + > .btn.active { + z-index: 1; } } @@ -36,21 +34,29 @@ } .btn-group { + @include border-radius($btn-border-radius); + // Prevent double borders when buttons are next to each other - > .btn:not(:first-child), + > :not(.btn-check:first-child) + .btn, > .btn-group:not(:first-child) { - margin-left: -$btn-border-width; + margin-left: calc(#{$btn-border-width} * -1); // stylelint-disable-line function-disallowed-list } // Reset rounded corners > .btn:not(:last-child):not(.dropdown-toggle), + > .btn.dropdown-toggle-split:first-child, > .btn-group:not(:last-child) > .btn { - @include border-right-radius(0); + @include border-end-radius(0); } - > .btn:not(:first-child), + // The left radius should be 0 if the button is: + // - the "third or more" child + // - the second child and the previous element isn't `.btn-check` (making it the first child visually) + // - part of a btn-group which isn't the first child + > .btn:nth-child(n + 3), + > :not(.btn-check) + .btn, > .btn-group:not(:first-child) > .btn { - @include border-left-radius(0); + @include border-start-radius(0); } } @@ -58,42 +64,40 @@ // // Remix the default button sizing classes into new ones for easier manipulation. -.btn-group-sm > .btn { - @extend .btn-sm; -} -.btn-group-lg > .btn { - @extend .btn-lg; -} +.btn-group-sm > .btn { @extend .btn-sm; } +.btn-group-lg > .btn { @extend .btn-lg; } + // // Split button dropdowns // .dropdown-toggle-split { - padding-right: $btn-padding-x * 0.75; - padding-left: $btn-padding-x * 0.75; + padding-right: $btn-padding-x * .75; + padding-left: $btn-padding-x * .75; &::after, .dropup &::after, - .dropright &::after { + .dropend &::after { margin-left: 0; } - .dropleft &::before { + .dropstart &::before { margin-right: 0; } } .btn-sm + .dropdown-toggle-split { - padding-right: $btn-padding-x-sm * 0.75; - padding-left: $btn-padding-x-sm * 0.75; + padding-right: $btn-padding-x-sm * .75; + padding-left: $btn-padding-x-sm * .75; } .btn-lg + .dropdown-toggle-split { - padding-right: $btn-padding-x-lg * 0.75; - padding-left: $btn-padding-x-lg * 0.75; + padding-right: $btn-padding-x-lg * .75; + padding-left: $btn-padding-x-lg * .75; } + // The clickable button for toggling the menu // Set the same inset shadow as the :active state .btn-group.show .dropdown-toggle { @@ -105,6 +109,7 @@ } } + // // Vertical button groups // @@ -121,7 +126,7 @@ > .btn:not(:first-child), > .btn-group:not(:first-child) { - margin-top: -$btn-border-width; + margin-top: calc(#{$btn-border-width} * -1); // stylelint-disable-line function-disallowed-list } // Reset rounded corners @@ -130,34 +135,8 @@ @include border-bottom-radius(0); } - > .btn:not(:first-child), + > .btn ~ .btn, > .btn-group:not(:first-child) > .btn { @include border-top-radius(0); } } - -// Checkbox and radio options -// -// In order to support the browser's form validation feedback, powered by the -// `required` attribute, we have to "hide" the inputs via `clip`. We cannot use -// `display: none;` or `visibility: hidden;` as that also hides the popover. -// Simply visually hiding the inputs via `opacity` would leave them clickable in -// certain cases which is prevented by using `clip` and `pointer-events`. -// This way, we ensure a DOM element is visible to position the popover from. -// -// See https://github.com/twbs/bootstrap/pull/12794 and -// https://github.com/twbs/bootstrap/pull/14559 for more information. - -.btn-group-toggle { - > .btn, - > .btn-group > .btn { - margin-bottom: 0; // Override default `
diff --git a/projects/mdb-angular-ui-kit/carousel/carousel.component.ts b/projects/mdb-angular-ui-kit/carousel/carousel.component.ts new file mode 100644 index 00000000..8bb45cc4 --- /dev/null +++ b/projects/mdb-angular-ui-kit/carousel/carousel.component.ts @@ -0,0 +1,395 @@ +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ContentChildren, + ElementRef, + EventEmitter, + HostBinding, + HostListener, + Input, + OnDestroy, + Output, + QueryList, +} from '@angular/core'; +import { fromEvent, Subject } from 'rxjs'; +import { take, takeUntil } from 'rxjs/operators'; +import { MdbCarouselItemComponent } from './carousel-item.component'; + +export enum Direction { + UNKNOWN, + NEXT, + PREV, +} + +@Component({ + selector: 'mdb-carousel', + templateUrl: './carousel.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +export class MdbCarouselComponent implements AfterViewInit, OnDestroy { + @ContentChildren(MdbCarouselItemComponent) _items: QueryList; + get items(): MdbCarouselItemComponent[] { + return this._items && this._items.toArray(); + } + + @Input() animation: 'slide' | 'fade' = 'slide'; + + @Input() + get controls(): boolean { + return this._controls; + } + set controls(value: boolean) { + this._controls = coerceBooleanProperty(value); + } + private _controls = false; + + @Input() + get dark(): boolean { + return this._dark; + } + set dark(value: boolean) { + this._dark = coerceBooleanProperty(value); + } + private _dark = false; + + @Input() + get indicators(): boolean { + return this._indicators; + } + set indicators(value: boolean) { + this._indicators = coerceBooleanProperty(value); + } + private _indicators = false; + + @Input() + get ride(): boolean { + return this._ride; + } + set ride(value: boolean) { + this._ride = coerceBooleanProperty(value); + } + private _ride = true; + + @Input() + get interval(): number { + return this._interval; + } + set interval(value: number) { + this._interval = value; + + if (this.items) { + this._restartInterval(); + } + } + private _interval = 5000; + + @Input() keyboard = true; + @Input() pause = true; + @Input() wrap = true; + + @Output() slide: EventEmitter = new EventEmitter(); + @Output() slideChange: EventEmitter = new EventEmitter(); + + get activeSlide(): number { + return this._activeSlide; + } + + set activeSlide(index: number) { + if (this.items.length && this._activeSlide !== index) { + this._activeSlide = index; + this._restartInterval(); + } + } + private _activeSlide = 0; + + private _lastInterval: any; + private _isPlaying = false; + private _isSliding = false; + + private readonly _destroy$: Subject = new Subject(); + + @HostListener('mouseenter') + onMouseEnter(): void { + if (this.pause && this._isPlaying) { + this.stop(); + } + } + + @HostListener('mouseleave') + onMouseLeave(): void { + if (this.pause && !this._isPlaying) { + this.play(); + } + } + + @HostBinding('class.d-block') display = true; + + constructor(private _elementRef: ElementRef, private _cdRef: ChangeDetectorRef) {} + + ngAfterViewInit(): void { + Promise.resolve().then(() => { + this._setActiveSlide(this._activeSlide); + + if (this.interval > 0 && this.ride) { + this.play(); + } + this._cdRef.markForCheck(); + }); + + if (this.keyboard) { + fromEvent(this._elementRef.nativeElement, 'keydown') + .pipe(takeUntil(this._destroy$)) + .subscribe((event: KeyboardEvent) => { + if (event.key === 'ArrowRight') { + this.next(); + } else if (event.key === 'ArrowLeft') { + this.prev(); + } + }); + } + } + + ngOnDestroy(): void { + this._destroy$.next(); + this._destroy$.complete(); + } + + private _setActiveSlide(index: number): void { + const currentSlide = this.items[this._activeSlide]; + currentSlide.active = false; + + const newSlide = this.items[index]; + newSlide.active = true; + this._activeSlide = index; + } + + private _restartInterval(): void { + this._resetInterval(); + const activeElement = this.items[this.activeSlide]; + const interval = activeElement.interval ? activeElement.interval : this.interval; + + if (!isNaN(interval) && interval > 0) { + this._lastInterval = setInterval(() => { + const nInterval = +interval; + if (this._isPlaying && !isNaN(nInterval) && nInterval > 0) { + this.next(); + this._cdRef.markForCheck(); + } else { + this.stop(); + } + }, interval); + } + } + + private _resetInterval(): void { + if (this._lastInterval) { + clearInterval(this._lastInterval); + this._lastInterval = null; + } + } + + play(): void { + if (!this._isPlaying) { + this._isPlaying = true; + this._restartInterval(); + } + } + + stop(): void { + if (this._isPlaying) { + this._isPlaying = false; + this._resetInterval(); + } + } + + to(index: number): void { + if (index > this.items.length - 1 || index < 0) { + return; + } + + if (this.activeSlide === index) { + this.stop(); + this.play(); + return; + } + + const direction = index > this.activeSlide ? Direction.NEXT : Direction.PREV; + + this._animateSlides(direction, this.activeSlide, index); + this.activeSlide = index; + } + + next(): void { + if (!this._isSliding) { + this._slide(Direction.NEXT); + } + } + + prev(): void { + if (!this._isSliding) { + this._slide(Direction.PREV); + } + } + + private _slide(direction: Direction): void { + const isFirst = this._activeSlide === 0; + const isLast = this._activeSlide === this.items.length - 1; + + if (!this.wrap) { + if ((direction === Direction.NEXT && isLast) || (direction === Direction.PREV && isFirst)) { + return; + } + } + + const newSlideIndex = this._getNewSlideIndex(direction); + + this._animateSlides(direction, this.activeSlide, newSlideIndex); + this.activeSlide = newSlideIndex; + + this.slide.emit(); + } + + private _animateSlides(direction: Direction, currentIndex: number, nextIndex: number): void { + const currentItem = this.items[currentIndex]; + const nextItem = this.items[nextIndex]; + const currentEl = currentItem.host; + const nextEl = nextItem.host; + + this._isSliding = true; + + if (this._isPlaying) { + this.stop(); + } + + if (direction === Direction.NEXT) { + nextItem.next = true; + + setTimeout(() => { + this._reflow(nextEl); + currentItem.start = true; + nextItem.start = true; + this._cdRef.markForCheck(); + }, 0); + + const transitionDuration = 600; + + fromEvent(currentEl, 'transitionend') + .pipe(take(1)) + .subscribe(() => { + nextItem.next = false; + nextItem.start = false; + nextItem.active = true; + + currentItem.active = false; + currentItem.start = false; + currentItem.next = false; + + this.slideChange.emit(); + this._isSliding = false; + }); + + this._emulateTransitionEnd(currentEl, transitionDuration); + } else if (direction === Direction.PREV) { + nextItem.prev = true; + + setTimeout(() => { + this._reflow(nextEl); + currentItem.end = true; + nextItem.end = true; + this._cdRef.markForCheck(); + }, 0); + + const transitionDuration = 600; + + fromEvent(currentEl, 'transitionend') + .pipe(take(1)) + .subscribe(() => { + nextItem.prev = false; + nextItem.end = false; + nextItem.active = true; + + currentItem.active = false; + currentItem.end = false; + currentItem.prev = false; + + this.slideChange.emit(); + this._isSliding = false; + }); + + this._emulateTransitionEnd(currentEl, transitionDuration); + } + + if (!this._isPlaying && this.interval > 0) { + this.play(); + } + } + + private _reflow(element: HTMLElement): number { + return element.offsetHeight; + } + + private _emulateTransitionEnd(element: HTMLElement, duration: number): void { + let eventEmitted = false; + const durationPadding = 5; + const emulatedDuration = duration + durationPadding; + + fromEvent(element, 'transitionend') + .pipe(take(1)) + .subscribe(() => { + eventEmitted = true; + }); + + setTimeout(() => { + if (!eventEmitted) { + element.dispatchEvent(new Event('transitionend')); + } + }, emulatedDuration); + } + + private _getNewSlideIndex(direction: Direction): number { + let newSlideIndex: number; + + if (direction === Direction.NEXT) { + newSlideIndex = this._getNextSlideIndex(); + } + + if (direction === Direction.PREV) { + newSlideIndex = this._getPrevSlideIndex(); + } + + return newSlideIndex; + } + + private _getNextSlideIndex(): number { + const isLast = this._activeSlide === this.items.length - 1; + + if (!isLast) { + return this._activeSlide + 1; + } else if (this.wrap && isLast) { + return 0; + } else { + return this._activeSlide; + } + } + + private _getPrevSlideIndex(): number { + const isFirst = this._activeSlide === 0; + + if (!isFirst) { + return this._activeSlide - 1; + } else if (this.wrap && isFirst) { + return this.items.length - 1; + } else { + return this._activeSlide; + } + } + + static ngAcceptInputType_controls: BooleanInput; + static ngAcceptInputType_dark: BooleanInput; + static ngAcceptInputType_indicators: BooleanInput; + static ngAcceptInputType_ride: BooleanInput; +} diff --git a/projects/mdb-angular-ui-kit/carousel/carousel.module.ts b/projects/mdb-angular-ui-kit/carousel/carousel.module.ts new file mode 100644 index 00000000..67a70093 --- /dev/null +++ b/projects/mdb-angular-ui-kit/carousel/carousel.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { MdbCarouselComponent } from './carousel.component'; +import { MdbCarouselItemComponent } from './carousel-item.component'; + +@NgModule({ + declarations: [MdbCarouselComponent, MdbCarouselItemComponent], + exports: [MdbCarouselComponent, MdbCarouselItemComponent], + imports: [CommonModule], +}) +export class MdbCarouselModule {} diff --git a/projects/mdb-angular-ui-kit/carousel/carousel.spec.ts b/projects/mdb-angular-ui-kit/carousel/carousel.spec.ts new file mode 100644 index 00000000..7e7f5786 --- /dev/null +++ b/projects/mdb-angular-ui-kit/carousel/carousel.spec.ts @@ -0,0 +1,218 @@ +import { Component, ViewChild } from '@angular/core'; +import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing'; +import { MdbCarouselComponent } from './carousel.component'; +import { MdbCarouselModule } from './carousel.module'; + +const carouselTemplate = ` + + + ... + + + + ... + + + + ... + + +`; + +@Component({ + template: carouselTemplate, + standalone: false, +}) +export class CarouselTestComponent { + @ViewChild(MdbCarouselComponent, { static: true }) carousel: MdbCarouselComponent; + controls = false; + indicators = false; + wrap = true; + dark = false; + animation = 'slide'; +} + +describe('MDB Carousel', () => { + let fixture: ComponentFixture; + let component: CarouselTestComponent; + let carousel: MdbCarouselComponent; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [CarouselTestComponent], + imports: [MdbCarouselModule], + teardown: { destroyAfterEach: false }, + }); + + fixture = TestBed.createComponent(CarouselTestComponent); + component = fixture.componentInstance; + carousel = component.carousel; + + fixture.detectChanges(); + }); + + it('should set first slide as active by default', fakeAsync(() => { + flush(); + fixture.detectChanges(); + const items = fixture.nativeElement.querySelectorAll('.carousel-item'); + expect(items[0].classList.contains('active')).toBe(true); + })); + + it('should show indicators if indicators input is set to true', () => { + component.indicators = true; + fixture.detectChanges(); + const indicators = fixture.nativeElement.querySelectorAll('.carousel-indicators'); + expect(indicators).toBeDefined(); + }); + + it('should show controls if controls input is set to true', () => { + component.controls = true; + fixture.detectChanges(); + const prevArrow = fixture.nativeElement.querySelector('.carousel-control-prev'); + const nextArrow = fixture.nativeElement.querySelector('.carousel-control-next'); + expect(prevArrow).toBeDefined(); + expect(nextArrow).toBeDefined(); + }); + + it('should add carousel-fade class if animation type is set to fade', () => { + component.animation = 'fade'; + fixture.detectChanges(); + const carouselEl = fixture.nativeElement.querySelector('.carousel'); + expect(carouselEl.classList.contains('carousel-fade')).toBe(true); + }); + + it('should add carousel-dark class if dark input is set to true', () => { + component.dark = true; + fixture.detectChanges(); + const carouselEl = fixture.nativeElement.querySelector('.carousel'); + expect(carouselEl.classList.contains('carousel-dark')).toBe(true); + }); + + it('should set corresponding indicator as active', fakeAsync(() => { + component.indicators = true; + fixture.detectChanges(); + const indicators = fixture.nativeElement.querySelectorAll('.carousel-indicators > button'); + expect(indicators[0].classList.contains('active')).toBe(true); + })); + + it('should change active slide on indicator click', fakeAsync(() => { + component.indicators = true; + fixture.detectChanges(); + const items = fixture.nativeElement.querySelectorAll('.carousel-item'); + const indicators = fixture.nativeElement.querySelectorAll('.carousel-indicators > button'); + expect(indicators[0].classList.contains('active')).toBe(true); + expect(items[0].classList.contains('active')).toBe(true); + + indicators[1].click(); + tick(1000); + fixture.detectChanges(); + + expect(indicators[1].classList.contains('active')).toBe(true); + expect(items[1].classList.contains('active')).toBe(true); + })); + + it('should change slide on previous arrow click', fakeAsync(() => { + component.controls = true; + component.wrap = true; + fixture.detectChanges(); + const items = fixture.nativeElement.querySelectorAll('.carousel-item'); + const prevArrow = fixture.nativeElement.querySelector('.carousel-control-prev'); + expect(items[0].classList.contains('active')).toBe(true); + + prevArrow.click(); + tick(1000); + fixture.detectChanges(); + + expect(items[2].classList.contains('active')).toBe(true); + + prevArrow.click(); + tick(1000); + fixture.detectChanges(); + + expect(items[1].classList.contains('active')).toBe(true); + })); + + it('should change slide on next arrow click', fakeAsync(() => { + component.controls = true; + component.wrap = true; + fixture.detectChanges(); + const items = fixture.nativeElement.querySelectorAll('.carousel-item'); + const nextArrow = fixture.nativeElement.querySelector('.carousel-control-next'); + expect(items[0].classList.contains('active')).toBe(true); + + nextArrow.click(); + tick(1000); + fixture.detectChanges(); + + expect(items[1].classList.contains('active')).toBe(true); + + nextArrow.click(); + tick(1000); + fixture.detectChanges(); + + expect(items[2].classList.contains('active')).toBe(true); + })); + + it('should not go to previous slide if first slide is active and wrap option is disabled', fakeAsync(() => { + component.controls = true; + component.wrap = false; + fixture.detectChanges(); + const items = fixture.nativeElement.querySelectorAll('.carousel-item'); + const prevArrow = fixture.nativeElement.querySelector('.carousel-control-prev'); + expect(items[0].classList.contains('active')).toBe(true); + + prevArrow.click(); + tick(1000); + fixture.detectChanges(); + + expect(items[0].classList.contains('active')).toBe(true); + expect(items[2].classList.contains('active')).toBe(false); + })); + + it('should not go to next slide if last slide is active and wrap option is disabled', fakeAsync(() => { + component.controls = true; + component.wrap = false; + fixture.detectChanges(); + const items = fixture.nativeElement.querySelectorAll('.carousel-item'); + const nextArrow = fixture.nativeElement.querySelector('.carousel-control-next'); + expect(items[0].classList.contains('active')).toBe(true); + + nextArrow.click(); + tick(1000); + fixture.detectChanges(); + + expect(items[1].classList.contains('active')).toBe(true); + + nextArrow.click(); + tick(1000); + fixture.detectChanges(); + + expect(items[2].classList.contains('active')).toBe(true); + + nextArrow.click(); + tick(1000); + fixture.detectChanges(); + + expect(items[2].classList.contains('active')).toBe(true); + expect(items[0].classList.contains('active')).toBe(false); + })); +}); diff --git a/projects/mdb-angular-ui-kit/carousel/index.ts b/projects/mdb-angular-ui-kit/carousel/index.ts new file mode 100644 index 00000000..4aaf8f92 --- /dev/null +++ b/projects/mdb-angular-ui-kit/carousel/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/projects/mdb-angular-ui-kit/carousel/ng-package.json b/projects/mdb-angular-ui-kit/carousel/ng-package.json new file mode 100644 index 00000000..ecef3ed8 --- /dev/null +++ b/projects/mdb-angular-ui-kit/carousel/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "index.ts" + } +} \ No newline at end of file diff --git a/projects/mdb-angular-ui-kit/carousel/public_api.ts b/projects/mdb-angular-ui-kit/carousel/public_api.ts new file mode 100644 index 00000000..0c661600 --- /dev/null +++ b/projects/mdb-angular-ui-kit/carousel/public_api.ts @@ -0,0 +1,3 @@ +export { MdbCarouselComponent } from './carousel.component'; +export { MdbCarouselItemComponent } from './carousel-item.component'; +export { MdbCarouselModule } from './carousel.module'; diff --git a/projects/mdb-angular-ui-kit/checkbox/checkbox.directive.spec.ts b/projects/mdb-angular-ui-kit/checkbox/checkbox.directive.spec.ts new file mode 100644 index 00000000..c143cc9e --- /dev/null +++ b/projects/mdb-angular-ui-kit/checkbox/checkbox.directive.spec.ts @@ -0,0 +1,196 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Component, DebugElement } from '@angular/core'; +import { MdbCheckboxModule } from './index'; +import { By } from '@angular/platform-browser'; +import { UntypedFormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; + +describe('MDB Checkbox', () => { + let checkbox: BasicCheckboxComponent; + let fixture: ComponentFixture; + let input: HTMLInputElement; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ + BasicCheckboxComponent, + CheckboxWithNgModelComponent, + CheckboxWithFormControlComponent, + ], + imports: [MdbCheckboxModule, FormsModule, ReactiveFormsModule], + teardown: { destroyAfterEach: false }, + }); + + fixture = TestBed.createComponent(BasicCheckboxComponent); + fixture.detectChanges(); + checkbox = fixture.componentInstance; + input = fixture.nativeElement.querySelector('input'); + }); + + it('should be checked if checked input is set to true', () => { + checkbox.checked = true; + fixture.detectChanges(); + expect(input.checked).toBe(true); + }); + + it('should be unchecked if checked input is set to false', () => { + checkbox.checked = false; + fixture.detectChanges(); + expect(input.checked).toBe(false); + }); + + it('should be disabled if disabled input is set to true', () => { + checkbox.disabled = true; + fixture.detectChanges(); + expect(input.disabled).toBe(true); + }); + + it('should be enabled if disabled input is set to false', () => { + checkbox.disabled = false; + fixture.detectChanges(); + expect(input.disabled).toBe(false); + }); + + it('should toggle checked state when clicked', () => { + checkbox.checked = false; + fixture.detectChanges(); + input.click(); + fixture.detectChanges(); + expect(input.checked).toBe(true); + + input.click(); + fixture.detectChanges(); + expect(input.checked).toBe(false); + }); + + it('should not toggle checked state if element is disabled', () => { + checkbox.checked = false; + checkbox.disabled = true; + fixture.detectChanges(); + + expect(input.checked).toBe(false); + + input.click(); + fixture.detectChanges(); + + expect(input.checked).toBe(false); + }); + + describe('Checkbox with ngModel', () => { + let checkbox: CheckboxWithNgModelComponent; + let fixture: ComponentFixture; + let input: HTMLInputElement; + + beforeEach(() => { + fixture = TestBed.createComponent(CheckboxWithNgModelComponent); + fixture.detectChanges(); + checkbox = fixture.componentInstance; + input = fixture.nativeElement.querySelector('input'); + }); + + // it('should use value from ngModel to set default checked state', () => { + // checkbox.checked = true; + // fixture.detectChanges(); + + // expect(input.checked).toBe(true); + // }); + + it('should update ngModel value when checked state change', () => { + checkbox.checked = false; + fixture.detectChanges(); + + input.click(); + fixture.detectChanges(); + + expect(checkbox.checked).toBe(true); + }); + }); + + describe('Checkbox with form control', () => { + let checkbox: CheckboxWithFormControlComponent; + let fixture: ComponentFixture; + let input: HTMLInputElement; + + beforeEach(() => { + fixture = TestBed.createComponent(CheckboxWithFormControlComponent); + fixture.detectChanges(); + checkbox = fixture.componentInstance; + input = fixture.nativeElement.querySelector('input'); + }); + + it('should use value from form control to set default checked state', () => { + expect(input.checked).toBe(false); + + checkbox.control.setValue(true); + fixture.detectChanges(); + + expect(input.checked).toBe(true); + }); + + it('should update form control value when checked state change', () => { + expect(checkbox.control.value).toBe(false); + expect(input.checked).toBe(false); + + input.click(); + fixture.detectChanges(); + + expect(input.checked).toBe(true); + expect(checkbox.control.value).toBe(true); + }); + + it('should disable input when form control disable method is used', () => { + expect(input.disabled).toBe(false); + + checkbox.control.disable(); + fixture.detectChanges(); + + expect(input.disabled).toBe(true); + }); + }); +}); + +const basicTemplate = ` +
+ + +
+`; + +@Component({ + template: basicTemplate, + standalone: false, +}) +class BasicCheckboxComponent { + checked = false; + disabled = false; +} + +const ngModelTemplate = ` +
+ + +
+`; + +@Component({ + template: ngModelTemplate, + standalone: false, +}) +class CheckboxWithNgModelComponent { + checked = false; + disabled = false; +} + +const formControlTemplate = ` +
+ + +
+`; + +@Component({ + template: formControlTemplate, + standalone: false, +}) +class CheckboxWithFormControlComponent { + control = new UntypedFormControl(false); +} diff --git a/projects/mdb-angular-ui-kit/checkbox/checkbox.directive.ts b/projects/mdb-angular-ui-kit/checkbox/checkbox.directive.ts new file mode 100644 index 00000000..e30717fd --- /dev/null +++ b/projects/mdb-angular-ui-kit/checkbox/checkbox.directive.ts @@ -0,0 +1,125 @@ +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; +import { + EventEmitter, + forwardRef, + Input, + Output, + Directive, + HostBinding, + HostListener, +} from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; + +export const MDB_CHECKBOX_VALUE_ACCESSOR: any = { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MdbCheckboxDirective), + multi: true, +}; + +export class MdbCheckboxChange { + element: MdbCheckboxDirective; + checked: boolean; +} + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbCheckbox]', + providers: [MDB_CHECKBOX_VALUE_ACCESSOR], + standalone: false, +}) +export class MdbCheckboxDirective { + @Input('checked') + get checked(): boolean { + return this._checked; + } + set checked(value: boolean) { + this._checked = coerceBooleanProperty(value); + } + private _checked = false; + + @Input('value') + get value(): any { + return this._value; + } + set value(value: any) { + this._value = value; + } + private _value: any = null; + + @Input('disabled') + get disabled(): boolean { + return this._disabled; + } + set disabled(value: boolean) { + this._disabled = coerceBooleanProperty(value); + } + private _disabled = false; + + @Output() checkboxChange: EventEmitter = new EventEmitter(); + + @HostBinding('disabled') + get isDisabled(): boolean { + return this._disabled; + } + + @HostBinding('checked') + get isChecked(): boolean { + return this._checked; + } + + @HostListener('click') + onCheckboxClick(): void { + this.toggle(); + } + + @HostListener('blur') + onBlur(): void { + this.onTouched(); + } + + constructor() {} + + get changeEvent(): MdbCheckboxChange { + const newChangeEvent = new MdbCheckboxChange(); + newChangeEvent.element = this; + newChangeEvent.checked = this.checked; + return newChangeEvent; + } + + toggle(): void { + if (this.disabled) { + return; + } + this._checked = !this._checked; + this.onChange(this.checked); + this.onCheckboxChange(); + } + + onCheckboxChange(): void { + this.checkboxChange.emit(this.changeEvent); + } + + // Control Value Accessor Methods + onChange = (_: any) => {}; + onTouched = () => {}; + + writeValue(value: any): void { + this.value = value; + this.checked = !!value; + } + + registerOnChange(fn: (_: any) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + static ngAcceptInputType_checked: BooleanInput; + static ngAcceptInputType_disabled: BooleanInput; +} diff --git a/projects/mdb-angular-ui-kit/checkbox/checkbox.module.ts b/projects/mdb-angular-ui-kit/checkbox/checkbox.module.ts new file mode 100644 index 00000000..5268ccc6 --- /dev/null +++ b/projects/mdb-angular-ui-kit/checkbox/checkbox.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { MdbCheckboxDirective } from './checkbox.directive'; + +@NgModule({ + declarations: [MdbCheckboxDirective], + exports: [MdbCheckboxDirective], + imports: [CommonModule, FormsModule], +}) +export class MdbCheckboxModule {} diff --git a/projects/mdb-angular-ui-kit/checkbox/index.ts b/projects/mdb-angular-ui-kit/checkbox/index.ts new file mode 100644 index 00000000..4aaf8f92 --- /dev/null +++ b/projects/mdb-angular-ui-kit/checkbox/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/projects/mdb-angular-ui-kit/checkbox/ng-package.json b/projects/mdb-angular-ui-kit/checkbox/ng-package.json new file mode 100644 index 00000000..ecef3ed8 --- /dev/null +++ b/projects/mdb-angular-ui-kit/checkbox/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "index.ts" + } +} \ No newline at end of file diff --git a/projects/mdb-angular-ui-kit/checkbox/public_api.ts b/projects/mdb-angular-ui-kit/checkbox/public_api.ts new file mode 100644 index 00000000..fcbb1357 --- /dev/null +++ b/projects/mdb-angular-ui-kit/checkbox/public_api.ts @@ -0,0 +1,6 @@ +export { + MdbCheckboxDirective, + MdbCheckboxChange, + MDB_CHECKBOX_VALUE_ACCESSOR, +} from './checkbox.directive'; +export { MdbCheckboxModule } from './checkbox.module'; diff --git a/projects/mdb-angular-ui-kit/collapse/collapse.directive.spec.ts b/projects/mdb-angular-ui-kit/collapse/collapse.directive.spec.ts new file mode 100644 index 00000000..95189697 --- /dev/null +++ b/projects/mdb-angular-ui-kit/collapse/collapse.directive.spec.ts @@ -0,0 +1,112 @@ +import { Component, ViewChild } from '@angular/core'; +import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing'; +import { MdbCollapseDirective } from '.'; +import { MdbCollapseModule } from './collapse.module'; + +const template = ` + +
+ Collapse directive content +
+`; + +@Component({ + selector: 'mdb-collapse-test', + template, + standalone: false, +}) +class TestCollapseComponent { + @ViewChild('collapse') collapse: MdbCollapseDirective; + + collapsed = true; +} + +describe('MDB Collapse', () => { + const ANIMATION_TIME = 355; + let fixture: ComponentFixture; + let element: any; + let component: any; + let collapse: any; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestCollapseComponent], + imports: [MdbCollapseModule], + teardown: { destroyAfterEach: false }, + }); + fixture = TestBed.createComponent(TestCollapseComponent); + fixture.detectChanges(); + component = fixture.componentInstance; + element = fixture.nativeElement; + collapse = fixture.nativeElement.querySelector('.collapse'); + }); + + it('should have content collapsed by default', () => { + expect(collapse.classList.contains('show')).toBe(false); + }); + + it('should be expanded if collapsed input is set to false', fakeAsync(() => { + component.collapsed = false; + fixture.detectChanges(); + + tick(ANIMATION_TIME); + flush(); + fixture.detectChanges(); + + expect(collapse.classList).toContain('show'); + })); + + it('should allow toggling component by clicking on another element', fakeAsync(() => { + const button = fixture.nativeElement.querySelector('button'); + + expect(collapse.classList).not.toContain('show'); + + button.click(); + fixture.detectChanges(); + + tick(ANIMATION_TIME); + flush(); + fixture.detectChanges(); + + expect(collapse.classList).toContain('show'); + + button.click(); + fixture.detectChanges(); + + tick(ANIMATION_TIME); + flush(); + fixture.detectChanges(); + + expect(collapse.classList).not.toContain('show'); + })); + + it('should emit events on collapse and expand', fakeAsync(() => { + const button = fixture.nativeElement.querySelector('button'); + const showSpy = jest.spyOn(component.collapse.collapseShow, 'emit'); + const shownSpy = jest.spyOn(component.collapse.collapseShown, 'emit'); + const hideSpy = jest.spyOn(component.collapse.collapseHide, 'emit'); + const hiddenSpy = jest.spyOn(component.collapse.collapseHidden, 'emit'); + + button.click(); + fixture.detectChanges(); + + expect(showSpy).toHaveBeenCalled(); + + tick(ANIMATION_TIME); + flush(); + fixture.detectChanges(); + + expect(shownSpy).toHaveBeenCalled(); + + button.click(); + fixture.detectChanges(); + + expect(hideSpy).toHaveBeenCalled(); + + tick(ANIMATION_TIME); + flush(); + fixture.detectChanges(); + + expect(hiddenSpy).toHaveBeenCalled(); + })); +}); diff --git a/projects/mdb-angular-ui-kit/collapse/collapse.directive.ts b/projects/mdb-angular-ui-kit/collapse/collapse.directive.ts new file mode 100644 index 00000000..5a67952a --- /dev/null +++ b/projects/mdb-angular-ui-kit/collapse/collapse.directive.ts @@ -0,0 +1,149 @@ +import { + Directive, + ElementRef, + EventEmitter, + HostBinding, + Input, + Output, + Renderer2, +} from '@angular/core'; +import { fromEvent } from 'rxjs'; +import { take } from 'rxjs/operators'; + +const TRANSITION_TIME = 350; + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbCollapse]', + exportAs: 'mdbCollapse', + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +export class MdbCollapseDirective { + constructor(private _elementRef: ElementRef, private _renderer: Renderer2) {} + + @HostBinding('class.collapse') collapseClass = true; + + @Output() collapseShow: EventEmitter = new EventEmitter(); + @Output() collapseShown: EventEmitter = new EventEmitter(); + @Output() collapseHide: EventEmitter = new EventEmitter(); + @Output() collapseHidden: EventEmitter = new EventEmitter(); + + @Input() + set collapsed(collapsed: boolean) { + if (collapsed !== this._collapsed) { + collapsed ? this.hide() : this.show(); + this._collapsed = collapsed; + } + } + get collapsed(): boolean { + return this._collapsed; + } + private _collapsed = true; + + get host(): HTMLElement { + return this._elementRef.nativeElement; + } + + private _isTransitioning = false; + + show(): void { + if (this._isTransitioning || !this.collapsed) { + return; + } + + this.collapseShow.emit(this); + + this._renderer.removeClass(this.host, 'collapse'); + this._renderer.addClass(this.host, 'collapsing'); + + this._renderer.setStyle(this.host, 'height', '0px'); + + this._isTransitioning = true; + + const scrollHeight = this.host.scrollHeight; + + fromEvent(this.host, 'transitionend') + .pipe(take(1)) + .subscribe(() => { + this._isTransitioning = false; + this.collapsed = false; + this._renderer.removeClass(this.host, 'collapsing'); + this._renderer.addClass(this.host, 'collapse'); + this._renderer.addClass(this.host, 'show'); + + this._renderer.removeStyle(this.host, 'height'); + + this.collapseShown.emit(this); + }); + + this._emulateTransitionEnd(this.host, TRANSITION_TIME); + + this._renderer.setStyle(this.host, 'height', `${scrollHeight}px`); + } + + hide(): void { + if (this._isTransitioning || this.collapsed) { + return; + } + + this.collapseHide.emit(this); + + const hostHeight = this.host.getBoundingClientRect().height; + + this._renderer.setStyle(this.host, 'height', `${hostHeight}px`); + + this._reflow(this.host); + + this._renderer.addClass(this.host, 'collapsing'); + this._renderer.removeClass(this.host, 'collapse'); + this._renderer.removeClass(this.host, 'show'); + + this._isTransitioning = true; + + fromEvent(this.host, 'transitionend') + .pipe(take(1)) + .subscribe(() => { + this._renderer.removeClass(this.host, 'collapsing'); + this._renderer.addClass(this.host, 'collapse'); + this._isTransitioning = false; + this.collapsed = true; + + this.collapseHidden.emit(this); + }); + + this._renderer.removeStyle(this.host, 'height'); + this._emulateTransitionEnd(this.host, TRANSITION_TIME); + } + + toggle(): void { + if (this._isTransitioning) { + return; + } + + this.collapsed = !this.collapsed; + this.collapsed ? this.hide() : this.show(); + } + + private _reflow(element: HTMLElement): number { + return element.offsetHeight; + } + + private _emulateTransitionEnd(element: HTMLElement, duration: number): void { + let eventEmitted = false; + const durationPadding = 5; + const emulatedDuration = duration + durationPadding; + + fromEvent(element, 'transitionend') + .pipe(take(1)) + .subscribe(() => { + eventEmitted = true; + }); + + setTimeout(() => { + if (!eventEmitted) { + element.dispatchEvent(new Event('transitionend')); + } + }, emulatedDuration); + } +} diff --git a/projects/mdb-angular-ui-kit/collapse/collapse.module.ts b/projects/mdb-angular-ui-kit/collapse/collapse.module.ts new file mode 100755 index 00000000..e5e5b1c9 --- /dev/null +++ b/projects/mdb-angular-ui-kit/collapse/collapse.module.ts @@ -0,0 +1,8 @@ +import { MdbCollapseDirective } from './collapse.directive'; +import { NgModule } from '@angular/core'; + +@NgModule({ + declarations: [MdbCollapseDirective], + exports: [MdbCollapseDirective], +}) +export class MdbCollapseModule {} diff --git a/projects/mdb-angular-ui-kit/collapse/index.ts b/projects/mdb-angular-ui-kit/collapse/index.ts new file mode 100755 index 00000000..4aaf8f92 --- /dev/null +++ b/projects/mdb-angular-ui-kit/collapse/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/projects/mdb-angular-ui-kit/collapse/ng-package.json b/projects/mdb-angular-ui-kit/collapse/ng-package.json new file mode 100644 index 00000000..ecef3ed8 --- /dev/null +++ b/projects/mdb-angular-ui-kit/collapse/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "index.ts" + } +} \ No newline at end of file diff --git a/projects/mdb-angular-ui-kit/collapse/public_api.ts b/projects/mdb-angular-ui-kit/collapse/public_api.ts new file mode 100644 index 00000000..a9c97388 --- /dev/null +++ b/projects/mdb-angular-ui-kit/collapse/public_api.ts @@ -0,0 +1,2 @@ +export { MdbCollapseDirective } from './collapse.directive'; +export { MdbCollapseModule } from './collapse.module'; diff --git a/projects/mdb-angular-ui-kit/dropdown/dropdown-menu.directive.ts b/projects/mdb-angular-ui-kit/dropdown/dropdown-menu.directive.ts new file mode 100644 index 00000000..7ee58e7a --- /dev/null +++ b/projects/mdb-angular-ui-kit/dropdown/dropdown-menu.directive.ts @@ -0,0 +1,53 @@ +import { Directive, ElementRef, EventEmitter, Input, Output, Renderer2 } from '@angular/core'; + +export type MdbDropdownMenuPositionClass = 'dropdown-menu-end'; + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbDropdownMenu]', + exportAs: 'mdbDropdownMenu', + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +export class MdbDropdownMenuDirective { + constructor(public elementRef: ElementRef, private _renderer: Renderer2) {} + @Output() menuPositionClassChanged: EventEmitter = new EventEmitter(); + + @Input() + get menuPositionClass(): string { + return this._menuPositionClass; + } + + set menuPositionClass(newClass: string) { + const host = this.elementRef.nativeElement; + const isSameClass = host.classList.contains(newClass); + if (this._menuPositionClass !== newClass && !isSameClass) { + const menuPositionClasses = [ + 'dropdown-menu-start', + 'dropdown-menu-sm-start', + 'dropdown-menu-md-start', + 'dropdown-menu-lg-start', + 'dropdown-menu-xl-start', + 'dropdown-menu-xxl-start', + 'dropdown-menu-xxl-start', + 'dropdown-menu-xxl-start', + 'dropdown-menu-end', + 'dropdown-menu-sm-end', + 'dropdown-menu-md-end', + 'dropdown-menu-lg-end', + 'dropdown-menu-xl-end', + 'dropdown-menu-xxl-end', + 'dropdown-menu-xxl-end', + 'dropdown-menu-xxl-end', + ]; + + menuPositionClasses.forEach((className) => { + this._renderer.removeClass(host, className); + }); + this._renderer.addClass(host, newClass); + + this.menuPositionClassChanged.emit(this.menuPositionClass); + } + } + private _menuPositionClass: string; +} diff --git a/projects/mdb-angular-ui-kit/dropdown/dropdown-toggle.directive.ts b/projects/mdb-angular-ui-kit/dropdown/dropdown-toggle.directive.ts new file mode 100644 index 00000000..99b17219 --- /dev/null +++ b/projects/mdb-angular-ui-kit/dropdown/dropdown-toggle.directive.ts @@ -0,0 +1,10 @@ +import { Directive } from '@angular/core'; + +@Directive({ + selector: '[mdbDropdownToggle]', + exportAs: 'mdbDropdownToggle', + standalone: false, +}) +export class MdbDropdownToggleDirective { + constructor() {} +} diff --git a/projects/mdb-angular-ui-kit/dropdown/dropdown.component.html b/projects/mdb-angular-ui-kit/dropdown/dropdown.component.html new file mode 100644 index 00000000..1d270978 --- /dev/null +++ b/projects/mdb-angular-ui-kit/dropdown/dropdown.component.html @@ -0,0 +1,7 @@ + + + +
+ +
+
diff --git a/projects/mdb-angular-ui-kit/dropdown/dropdown.directive.spec.ts b/projects/mdb-angular-ui-kit/dropdown/dropdown.directive.spec.ts new file mode 100644 index 00000000..64a97517 --- /dev/null +++ b/projects/mdb-angular-ui-kit/dropdown/dropdown.directive.spec.ts @@ -0,0 +1,371 @@ +import { ComponentFixture, fakeAsync, flush, inject, TestBed, tick } from '@angular/core/testing'; +import { Component } from '@angular/core'; +import { MdbDropdownMenuDirective, MdbDropdownModule } from './index'; +import { MdbDropdownDirective } from './index'; +import { By } from '@angular/platform-browser'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { OverlayContainer } from '@angular/cdk/overlay'; +import { first } from 'rxjs'; + +describe('MDB Dropdown', () => { + let fixture: ComponentFixture; + let testComponent: TestDropdownComponent; + let directive: MdbDropdownDirective; + let overlayContainer: OverlayContainer; + let overlayContainerElement: HTMLElement; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [MdbDropdownModule, NoopAnimationsModule], + declarations: [TestDropdownComponent], + teardown: { destroyAfterEach: false }, + }); + + inject([OverlayContainer], (container: OverlayContainer) => { + overlayContainer = container; + overlayContainerElement = container.getContainerElement(); + })(); + + fixture = TestBed.createComponent(TestDropdownComponent); + testComponent = fixture.componentInstance; + directive = fixture.debugElement + .query(By.directive(MdbDropdownDirective)) + .injector.get(MdbDropdownDirective); + fixture.detectChanges(); + }); + + afterEach(inject([OverlayContainer], (currentOverlayContainer: OverlayContainer) => { + currentOverlayContainer.ngOnDestroy(); + overlayContainer.ngOnDestroy(); + })); + + describe('Opening and closing', () => { + it('should open and close dropdown on click', fakeAsync(() => { + jest.spyOn(directive, 'show'); + jest.spyOn(directive, 'hide'); + + const buttonEl: HTMLButtonElement = fixture.nativeElement.querySelector('.dropdown-toggle'); + + buttonEl.click(); + fixture.detectChanges(); + + expect(directive.show).toHaveBeenCalled(); + expect(overlayContainerElement.textContent).toContain('Action'); + + buttonEl.click(); + fixture.detectChanges(); + + flush(); + fixture.detectChanges(); + + expect(directive.hide).toHaveBeenCalled(); + expect(overlayContainerElement.textContent).toEqual(''); + })); + + it('should close dropdown on outside click', fakeAsync(() => { + directive.show(); + fixture.detectChanges(); + + document.body.click(); + fixture.detectChanges(); + + flush(); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent).toEqual(''); + })); + + it('should close dropdown on dropdown item click', fakeAsync(() => { + directive.show(); + fixture.detectChanges(); + + const item: HTMLElement = document.querySelector('.dropdown-item'); + + item.click(); + fixture.detectChanges(); + + flush(); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent).toEqual(''); + })); + }); + + describe('Accessibility', () => { + it('should update aria-expanded attribute on dropdown open and close', fakeAsync(() => { + const buttonEl: HTMLButtonElement = fixture.nativeElement.querySelector('.dropdown-toggle'); + + buttonEl.click(); + fixture.detectChanges(); + + expect(buttonEl.getAttribute('aria-expanded')).toBe('true'); + + buttonEl.click(); + fixture.detectChanges(); + flush(); + + expect(buttonEl.getAttribute('aria-expanded')).toContain('false'); + })); + }); + + describe('Keyboard navigation', () => { + it('should correctly focus dropdown items when ArrowUp or ArrowDown key is used', () => { + directive.show(); + fixture.detectChanges(); + + const menu = document.querySelector('.dropdown-menu'); + const items = menu.querySelectorAll('.dropdown-item'); + + document.body.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' })); + fixture.detectChanges(); + + expect(document.activeElement).toBe(items[0]); + + document.body.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' })); + fixture.detectChanges(); + + expect(document.activeElement).toBe(items[1]); + + document.body.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' })); + fixture.detectChanges(); + + expect(document.activeElement).toBe(items[0]); + }); + + it('should focus last option if ArrowUp is used and no item is selected', () => { + directive.show(); + fixture.detectChanges(); + + const menu = document.querySelector('.dropdown-menu'); + const items = menu.querySelectorAll('.dropdown-item'); + + document.body.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' })); + fixture.detectChanges(); + + expect(document.activeElement).toBe(items[items.length - 1]); + }); + + it('should close dropdown on ESC keyup', fakeAsync(() => { + directive.show(); + fixture.detectChanges(); + + document.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape' })); + fixture.detectChanges(); + + flush(); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent).toEqual(''); + })); + }); + + describe('Inputs', () => { + it('should not close dropdown on ESC keyup if closeOnEsc input is set to false', fakeAsync(() => { + testComponent.closeOnEsc = false; + directive.show(); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent).toContain('Action'); + + document.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape' })); + fixture.detectChanges(); + + flush(); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent).toContain('Action'); + })); + + it('should not close dropdown on dropdown item click if closeOnItemClick input is set to false', fakeAsync(() => { + testComponent.closeOnItemClick = false; + directive.show(); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent).toContain('Action'); + + const item: HTMLElement = document.querySelector('.dropdown-item'); + + item.click(); + fixture.detectChanges(); + + flush(); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent).toContain('Action'); + })); + + it('should not close dropdown on outside click if closeOnOutsideClick input is set to false', fakeAsync(() => { + testComponent.closeOnOutsideClick = false; + directive.show(); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent).toContain('Action'); + + document.body.click(); + fixture.detectChanges(); + + flush(); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent).toContain('Action'); + })); + + it('should apply appropriate transform style when offset input is set', () => { + testComponent.offset = 43; + fixture.detectChanges(); + + directive.show(); + fixture.detectChanges(); + + let overlayPane: HTMLDivElement = overlayContainerElement.querySelector('.cdk-overlay-pane'); + expect(overlayPane.style.transform).toBe('translateY(43px)'); + }); + + it('should apply appropriate class when positionClass input is set', () => { + expect(directive.host.classList).toContain('dropdown'); + expect(directive.host.classList).not.toContain('dropup'); + + directive.positionClass = 'dropup'; + + expect(directive.host.classList).not.toContain('dropdown'); + expect(directive.host.classList).toContain('dropup'); + }); + + it('should apply appropriate class when menuPositionClass input is set', () => { + const dropdownMenuDirective: MdbDropdownMenuDirective = directive._dropdownMenu; + expect(dropdownMenuDirective.elementRef.nativeElement.classList).toContain( + 'dropdown-menu-start' + ); + expect(dropdownMenuDirective.elementRef.nativeElement.classList).not.toContain( + 'dropdown-menu-end' + ); + + dropdownMenuDirective.menuPositionClass = 'dropdown-menu-end'; + + expect(dropdownMenuDirective.elementRef.nativeElement.classList).not.toContain( + 'dropdown-menu-start' + ); + expect(dropdownMenuDirective.elementRef.nativeElement.classList).toContain( + 'dropdown-menu-end' + ); + }); + }); + + describe('Outputs', () => { + it('should emit events on show and hide', fakeAsync(() => { + let showDropdown: MdbDropdownDirective | undefined; + let shownDropdown: MdbDropdownDirective | undefined; + let hideDropdown: MdbDropdownDirective | undefined; + let hiddenDropdown: MdbDropdownDirective | undefined; + + const showSpy = jest.spyOn(directive.dropdownShow, 'emit'); + const shownSpy = jest.spyOn(directive.dropdownShown, 'emit'); + const hideSpy = jest.spyOn(directive.dropdownHide, 'emit'); + const hiddenSpy = jest.spyOn(directive.dropdownHidden, 'emit'); + + directive.dropdownShow.pipe(first()).subscribe((event) => (showDropdown = event)); + directive.dropdownShown.pipe(first()).subscribe((event) => (shownDropdown = event)); + directive.dropdownHide.pipe(first()).subscribe((event) => (hideDropdown = event)); + directive.dropdownHidden.pipe(first()).subscribe((event) => (hiddenDropdown = event)); + + directive.show(); + fixture.detectChanges(); + + expect(showSpy).toHaveBeenCalledTimes(1); + expect(showDropdown).toEqual(directive); + + tick(); + + expect(shownSpy).toHaveBeenCalledTimes(1); + expect(shownDropdown).toEqual(directive); + + directive.hide(); + fixture.detectChanges(); + + expect(hideSpy).toHaveBeenCalledTimes(1); + expect(hideDropdown).toEqual(directive); + + tick(); + + expect(hiddenSpy).toHaveBeenCalledTimes(1); + expect(hiddenDropdown).toEqual(directive); + })); + }); + + describe('Public methods', () => { + it('should show dropdown when show method is called', fakeAsync(() => { + expect(overlayContainerElement.textContent).not.toContain('Action'); + + directive.show(); + fixture.detectChanges(); + flush(); + + expect(overlayContainerElement.textContent).toContain('Action'); + })); + + it('should hide dropdown when hide method is called', fakeAsync(() => { + expect(overlayContainerElement.textContent).not.toContain('Action'); + + directive.show(); + fixture.detectChanges(); + flush(); + + expect(overlayContainerElement.textContent).toContain('Action'); + + directive.hide(); + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent).toEqual(''); + })); + + it('should toggle dropdown when toggle method is called', fakeAsync(() => { + expect(overlayContainerElement.textContent).not.toContain('Action'); + + directive.toggle(); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent).toContain('Action'); + + directive.toggle(); + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + + expect(overlayContainerElement.textContent).toEqual(''); + })); + }); +}); + +@Component({ + selector: 'mdb-dropdown-test', + template: ` +
+ `, + standalone: false, +}) +class TestDropdownComponent { + closeOnOutsideClick = true; + closeOnItemClick = true; + closeOnEsc = true; + offset = 0; +} diff --git a/projects/mdb-angular-ui-kit/dropdown/dropdown.directive.ts b/projects/mdb-angular-ui-kit/dropdown/dropdown.directive.ts new file mode 100644 index 00000000..77c082e9 --- /dev/null +++ b/projects/mdb-angular-ui-kit/dropdown/dropdown.directive.ts @@ -0,0 +1,462 @@ +import { + AfterContentInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + Input, + OnDestroy, + Output, + Renderer2, + TemplateRef, + ViewChild, + ViewContainerRef, + booleanAttribute, + numberAttribute, +} from '@angular/core'; +import { + ConnectedPosition, + FlexibleConnectedPositionStrategy, + Overlay, + OverlayConfig, + OverlayPositionBuilder, + OverlayRef, +} from '@angular/cdk/overlay'; +import { TemplatePortal } from '@angular/cdk/portal'; +import { fromEvent, Observable, Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; +import { ContentChild } from '@angular/core'; +import { MdbDropdownToggleDirective } from './dropdown-toggle.directive'; +import { MdbDropdownMenuDirective } from './dropdown-menu.directive'; +import { animate, state, style, transition, trigger, AnimationEvent } from '@angular/animations'; +import { BreakpointObserver } from '@angular/cdk/layout'; + +export type MdbDropdownPositionClass = 'dropdown' | 'dropup' | 'dropstart' | 'dropend'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: '[mdbDropdown]', + templateUrl: 'dropdown.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + trigger('fade', [ + state('visible', style({ opacity: 1 })), + state('hidden', style({ opacity: 0 })), + transition('visible => hidden', animate('150ms linear')), + transition('hidden => visible', [style({ opacity: 0 }), animate('150ms linear')]), + ]), + ], + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +export class MdbDropdownDirective implements OnDestroy, AfterContentInit { + @ViewChild('dropdownTemplate') _template: TemplateRef; + @ContentChild(MdbDropdownToggleDirective, { read: ElementRef }) _dropdownToggle: ElementRef; + @ContentChild(MdbDropdownMenuDirective) _dropdownMenu: MdbDropdownMenuDirective; + + @Input({ transform: booleanAttribute }) animation = true; + @Input({ transform: booleanAttribute }) closeOnEsc = true; + @Input({ transform: booleanAttribute }) closeOnItemClick = true; + @Input({ transform: booleanAttribute }) closeOnOutsideClick = true; + @Input({ transform: numberAttribute }) offset = 0; + @Input() + get positionClass(): MdbDropdownPositionClass { + return this._positionClass; + } + set positionClass(newClass: MdbDropdownPositionClass) { + const isSameClass = this.host.classList.contains(newClass); + if (this._positionClass !== newClass && !isSameClass) { + const positionClasses = ['dropdown', 'dropup', 'dropstart', 'dropend']; + positionClasses.forEach((className) => { + this._renderer.removeClass(this.host, className); + }); + this._renderer.addClass(this.host, newClass); + } + this._updateOverlay(); + } + private _positionClass: MdbDropdownPositionClass; + @Input({ transform: booleanAttribute }) withPush = false; + + @Output() dropdownShow: EventEmitter = new EventEmitter(); + @Output() dropdownShown: EventEmitter = new EventEmitter(); + @Output() dropdownHide: EventEmitter = new EventEmitter(); + @Output() dropdownHidden: EventEmitter = new EventEmitter(); + + private _overlayRef: OverlayRef; + private _portal: TemplatePortal; + private _open = false; + private _isDropUp: boolean; + private _isDropStart: boolean; + private _isDropEnd: boolean; + private _isDropdownMenuEnd: boolean; + private _xPosition: string; + private _breakpoints: any; + private _mousedownTarget: HTMLElement | null = null; + + readonly _destroy$: Subject = new Subject(); + + get host(): HTMLElement { + return this._elementRef.nativeElement; + } + + _breakpointSubscription: any; + _animationState = 'hidden'; + + constructor( + private _overlay: Overlay, + private _overlayPositionBuilder: OverlayPositionBuilder, + private _elementRef: ElementRef, + private _vcr: ViewContainerRef, + private _breakpointObserver: BreakpointObserver, + private _cdRef: ChangeDetectorRef, + private _renderer: Renderer2 + ) { + this._breakpoints = { + isSm: this._breakpointObserver.isMatched('(min-width: 576px)'), + isMd: this._breakpointObserver.isMatched('(min-width: 768px)'), + isLg: this._breakpointObserver.isMatched('(min-width: 992px)'), + isXl: this._breakpointObserver.isMatched('(min-width: 1200px)'), + isXxl: this._breakpointObserver.isMatched('(min-width: 1400px)'), + }; + } + + ngAfterContentInit(): void { + this._bindDropdownToggleClick(); + this._listenToMenuPositionClassChange(); + } + + ngOnDestroy(): void { + if (this._overlayRef) { + this._overlayRef.detach(); + this._overlayRef.dispose(); + } + + this._destroy$.next(); + this._destroy$.complete(); + } + + private _bindDropdownToggleClick(): void { + fromEvent(this._dropdownToggle.nativeElement, 'click') + .pipe(takeUntil(this._destroy$)) + .subscribe(() => this.toggle()); + } + + private _listenToMenuPositionClassChange(): void { + this._dropdownMenu.menuPositionClassChanged + .pipe(takeUntil(this._destroy$)) + .subscribe(() => this._updateOverlay()); + } + + private _updateOverlay() { + this._overlayRef?.updatePositionStrategy(this._createPositionStrategy()); + } + + private _createOverlayConfig(): OverlayConfig { + return new OverlayConfig({ + hasBackdrop: false, + scrollStrategy: this._overlay.scrollStrategies.reposition(), + positionStrategy: this._createPositionStrategy(), + }); + } + + private _createOverlay(): void { + this._overlayRef = this._overlay.create(this._createOverlayConfig()); + } + + private _createPositionStrategy(): FlexibleConnectedPositionStrategy { + const positionStrategy = this._overlayPositionBuilder + .flexibleConnectedTo(this._dropdownToggle) + .withPositions(this._getPosition()) + .withFlexibleDimensions(false) + .withPush(this.withPush); + + return positionStrategy; + } + + private _getPosition(): ConnectedPosition[] { + this._isDropUp = this.host.classList.contains('dropup'); + this._isDropStart = this.host.classList.contains('dropstart'); + this._isDropEnd = this.host.classList.contains('dropend'); + this._isDropdownMenuEnd = + this._dropdownMenu.elementRef.nativeElement.classList.contains('dropdown-menu-end'); + this._xPosition = this._isDropdownMenuEnd ? 'end' : 'start'; + + const regex = new RegExp(/dropdown-menu-(sm|md|lg|xl|xxl)-(start|end)/, 'g'); + + const responsiveClass = this._dropdownMenu.elementRef.nativeElement.className.match(regex); + + if (responsiveClass) { + this._subscribeBrakpoints(); + + const positionRegex = new RegExp(/start|end/, 'g'); + const breakpointRegex = new RegExp(/(sm|md|lg|xl|xxl)/, 'g'); + + const dropdownPosition = positionRegex.exec(responsiveClass)[0]; + const breakpoint = breakpointRegex.exec(responsiveClass)[0]; + + switch (true) { + case breakpoint === 'xxl' && this._breakpoints.isXxl: + this._xPosition = dropdownPosition; + break; + case breakpoint === 'xl' && this._breakpoints.isXl: + this._xPosition = dropdownPosition; + break; + case breakpoint === 'lg' && this._breakpoints.isLg: + this._xPosition = dropdownPosition; + break; + case breakpoint === 'md' && this._breakpoints.isMd: + this._xPosition = dropdownPosition; + break; + case breakpoint === 'sm' && this._breakpoints.isSm: + this._xPosition = dropdownPosition; + break; + default: + break; + } + } + + let position; + + const positionDropup = { + originX: this._xPosition, + originY: 'top', + overlayX: this._xPosition, + overlayY: 'bottom', + offsetY: -this.offset, + }; + + const positionDropdown = { + originX: this._xPosition, + originY: 'bottom', + overlayX: this._xPosition, + overlayY: 'top', + offsetY: this.offset, + }; + + const positionDropstart = { + originX: 'start', + originY: 'top', + overlayX: 'end', + overlayY: 'top', + offsetX: this.offset, + }; + + const positionDropend = { + originX: 'end', + originY: 'top', + overlayX: 'start', + overlayY: 'top', + offsetX: -this.offset, + }; + + switch (true) { + case this._isDropEnd: + position = [positionDropend, positionDropstart]; + break; + case this._isDropStart: + position = [positionDropstart, positionDropend]; + break; + case this._isDropUp: + position = [positionDropup, positionDropdown]; + break; + default: + position = [positionDropdown, positionDropup]; + break; + } + + return position; + } + + private _listenToEscKeyup(overlayRef: OverlayRef): Observable { + return fromEvent(document, 'keyup').pipe( + filter((event: KeyboardEvent) => event.key === 'Escape'), + takeUntil(overlayRef.detachments()) + ); + } + + private _listenToMousedown(overlayRef: OverlayRef): Observable { + return fromEvent(document, 'mousedown').pipe(takeUntil(overlayRef.detachments())); + } + + private _listenToClick(overlayRef: OverlayRef, origin: HTMLElement): Observable { + return fromEvent(document, 'click').pipe( + filter((event: MouseEvent) => { + const target = event.target as HTMLElement; + const isInsideMenu = this._dropdownMenu.elementRef.nativeElement.contains(target); + const notTogglerIcon = !this._dropdownToggle.nativeElement.contains(target); + const notCustomContent = + !isInsideMenu || (target.classList && target.classList.contains('dropdown-item')); + const notOrigin = target !== origin; + return notOrigin && notTogglerIcon && notCustomContent; + }), + takeUntil(overlayRef.detachments()) + ); + } + + public onAnimationEnd(event: AnimationEvent): void { + if (event.fromState === 'visible' && event.toState === 'hidden') { + this._overlayRef.detach(); + this._open = false; + + this._renderer.setAttribute(this._dropdownToggle.nativeElement, 'aria-expanded', 'false'); + + this.dropdownHidden.emit(this); + } + + if (event.fromState === 'hidden' && event.toState === 'visible') { + this.dropdownShown.emit(this); + } + } + + private _subscribeBrakpoints(): void { + const brakpoints = [ + '(min-width: 576px)', + '(min-width: 768px)', + '(min-width: 992px)', + '(min-width: 1200px)', + '(min-width: 1400px)', + ]; + + this._breakpointSubscription = this._breakpointObserver + .observe(brakpoints) + .pipe(takeUntil(this._destroy$)) + .subscribe((result) => { + Object.keys(this._breakpoints).forEach((key, index) => { + const brakpointValue = brakpoints[index]; + const newBreakpoint = result.breakpoints[brakpointValue]; + const isBreakpointChanged = newBreakpoint !== this._breakpoints[key]; + + if (!isBreakpointChanged) { + return; + } + + this._breakpoints[key] = newBreakpoint; + + if (this._open) { + this._updateOverlay(); + } + }); + }); + } + + show(): void { + this._cdRef.markForCheck(); + + if (this._open) { + return; + } + + if (!this._overlayRef) { + this._createOverlay(); + } + + this._portal = new TemplatePortal(this._template, this._vcr); + + this.dropdownShow.emit(this); + + this._open = true; + + this._renderer.setAttribute(this._dropdownToggle.nativeElement, 'aria-expanded', 'true'); + + this._overlayRef.attach(this._portal); + + this._listenToEscKeyup(this._overlayRef).subscribe((isEsc) => { + if (isEsc && this.closeOnEsc) { + this.hide(); + } + }); + + this._overlayRef + .keydownEvents() + .pipe(takeUntil(this._overlayRef.detachments())) + .subscribe((event: KeyboardEvent) => { + this._handleKeyboardNavigation(event); + }); + + this._listenToMousedown(this._overlayRef).subscribe((event) => { + this._mousedownTarget = event.target as HTMLElement; + }); + + this._listenToClick(this._overlayRef, this._dropdownToggle.nativeElement).subscribe((event) => { + const target = event.target as HTMLElement; + const isDropdownItem = target.classList && target.classList.contains('dropdown-item'); + + const isOnMousedownDropdownMenu = this._dropdownMenu.elementRef.nativeElement.contains( + this._mousedownTarget + ); + + this._mousedownTarget = null; + + if (this.closeOnItemClick && isDropdownItem) { + this.hide(); + return; + } + if (this.closeOnOutsideClick && !isDropdownItem && !isOnMousedownDropdownMenu) { + this.hide(); + return; + } + }); + + this._animationState = 'visible'; + } + + private _handleKeyboardNavigation(event: KeyboardEvent) { + const items: HTMLElement[] = Array.from( + this._dropdownMenu.elementRef.nativeElement.querySelectorAll('.dropdown-item') + ); + const key = event.key; + const activeElement = this._dropdownMenu.elementRef.nativeElement.ownerDocument.activeElement; + + if (items.length === 0) { + return; + } + + let index = items.indexOf(activeElement); + + switch (key) { + case 'ArrowDown': + event.preventDefault(); + + index = Math.min(index + 1, items.length - 1); + break; + case 'ArrowUp': + event.preventDefault(); + + if (index === -1) { + index = items.length - 1; + break; + } + index = Math.max(index - 1, 0); + break; + } + + const nextActiveElement: HTMLElement = items[index]; + + if (nextActiveElement) { + nextActiveElement.focus(); + } + } + + hide(): void { + this._cdRef.markForCheck(); + + if (!this._open) { + return; + } + + this.dropdownHide.emit(this); + + this._animationState = 'hidden'; + } + + toggle(): void { + this._cdRef.markForCheck(); + + if (this._open) { + this.hide(); + } else { + this.show(); + } + } +} diff --git a/projects/mdb-angular-ui-kit/dropdown/dropdown.module.ts b/projects/mdb-angular-ui-kit/dropdown/dropdown.module.ts new file mode 100644 index 00000000..75211ac9 --- /dev/null +++ b/projects/mdb-angular-ui-kit/dropdown/dropdown.module.ts @@ -0,0 +1,12 @@ +import { MdbDropdownDirective } from './dropdown.directive'; +import { MdbDropdownToggleDirective } from './dropdown-toggle.directive'; +import { MdbDropdownMenuDirective } from './dropdown-menu.directive'; +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { OverlayModule } from '@angular/cdk/overlay'; +@NgModule({ + imports: [CommonModule, OverlayModule], + declarations: [MdbDropdownDirective, MdbDropdownToggleDirective, MdbDropdownMenuDirective], + exports: [MdbDropdownDirective, MdbDropdownToggleDirective, MdbDropdownMenuDirective], +}) +export class MdbDropdownModule {} diff --git a/projects/mdb-angular-ui-kit/dropdown/index.ts b/projects/mdb-angular-ui-kit/dropdown/index.ts new file mode 100644 index 00000000..4aaf8f92 --- /dev/null +++ b/projects/mdb-angular-ui-kit/dropdown/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/projects/mdb-angular-ui-kit/dropdown/ng-package.json b/projects/mdb-angular-ui-kit/dropdown/ng-package.json new file mode 100644 index 00000000..ecef3ed8 --- /dev/null +++ b/projects/mdb-angular-ui-kit/dropdown/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "index.ts" + } +} \ No newline at end of file diff --git a/projects/mdb-angular-ui-kit/dropdown/public_api.ts b/projects/mdb-angular-ui-kit/dropdown/public_api.ts new file mode 100644 index 00000000..2677a6fd --- /dev/null +++ b/projects/mdb-angular-ui-kit/dropdown/public_api.ts @@ -0,0 +1,4 @@ +export { MdbDropdownDirective, MdbDropdownPositionClass } from './dropdown.directive'; +export { MdbDropdownToggleDirective } from './dropdown-toggle.directive'; +export { MdbDropdownMenuDirective } from './dropdown-menu.directive'; +export { MdbDropdownModule } from './dropdown.module'; diff --git a/projects/mdb-angular-ui-kit/forms/form-control.component.html b/projects/mdb-angular-ui-kit/forms/form-control.component.html new file mode 100644 index 00000000..81f48f48 --- /dev/null +++ b/projects/mdb-angular-ui-kit/forms/form-control.component.html @@ -0,0 +1,6 @@ + +
+
+
+
+
diff --git a/projects/mdb-angular-ui-kit/forms/form-control.component.ts b/projects/mdb-angular-ui-kit/forms/form-control.component.ts new file mode 100644 index 00000000..f81ef2aa --- /dev/null +++ b/projects/mdb-angular-ui-kit/forms/form-control.component.ts @@ -0,0 +1,149 @@ +import { + Component, + ChangeDetectionStrategy, + HostBinding, + ViewChild, + ContentChild, + ElementRef, + AfterContentInit, + Renderer2, + OnDestroy, + NgZone, + AfterContentChecked, +} from '@angular/core'; +import { MdbAbstractFormControl } from './form-control'; +import { MdbLabelDirective } from './label.directive'; +import { ContentObserver } from '@angular/cdk/observers'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'mdb-form-control', + templateUrl: './form-control.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +export class MdbFormControlComponent implements AfterContentInit, AfterContentChecked, OnDestroy { + @ViewChild('notchLeading', { static: true }) _notchLeading: ElementRef; + @ViewChild('notchMiddle', { static: true }) _notchMiddle: ElementRef; + @ContentChild(MdbAbstractFormControl, { static: true }) _formControl: MdbAbstractFormControl; + @ContentChild(MdbLabelDirective, { static: false, read: ElementRef }) _label: ElementRef; + + @HostBinding('class.form-outline') outline = true; + @HostBinding('class.d-block') display = true; + + get input(): HTMLInputElement { + return this._formControl.input; + } + + constructor( + private _renderer: Renderer2, + private _contentObserver: ContentObserver, + private _elementRef: ElementRef, + private _ngZone: NgZone + ) {} + + readonly _destroy$: Subject = new Subject(); + + private _notchLeadingLength = 9; + private _labelMarginLeft = 0; + private _labelGapPadding = 8; + private _labelScale = 0.8; + private _recalculateGapWhenVisible = false; + + private _previousLabel: ElementRef | null = null; + + ngAfterContentInit(): void { + if (this.hasLabel) { + setTimeout(() => { + this._updateBorderGap(); + }, 0); + this._previousLabel = this._label; + } else { + this._renderer.addClass(this.input, 'placeholder-active'); + } + this._updateLabelActiveState(); + + if (this.hasLabel) { + this._contentObserver + .observe(this._label.nativeElement) + .pipe(takeUntil(this._destroy$)) + .subscribe(() => { + this._updateBorderGap(); + }); + } + + this._formControl.stateChanges.pipe(takeUntil(this._destroy$)).subscribe(() => { + this._updateLabelActiveState(); + if (this.hasLabel) { + this._updateBorderGap(); + } + }); + + this._ngZone.runOutsideAngular(() => { + this._ngZone.onStable.pipe(takeUntil(this._destroy$)).subscribe(() => { + if (this.hasLabel && this._recalculateGapWhenVisible) { + this._updateBorderGap(); + } + }); + }); + } + + ngAfterContentChecked(): void { + if (!this._previousLabel && this.hasLabel) { + setTimeout(() => this._updateBorderGap()); + } + this._previousLabel = this._label; + } + + ngOnDestroy(): void { + this._destroy$.next(); + this._destroy$.unsubscribe(); + } + + get hasLabel(): boolean { + return !!this._label; + } + + private _getLabelWidth(): number { + return this._label.nativeElement.clientWidth * this._labelScale + this._labelGapPadding; + } + + private _updateBorderGap(): void { + // Element is in DOM but is not visible, we need to recalculate the gap when element + // is displayed. This problem may occur in components such as tabs where content of + // inactive tabs has display:none styles + + if (this._isHidden()) { + this._recalculateGapWhenVisible = true; + return; + } + + const notchLeadingWidth = `${this._labelMarginLeft + this._notchLeadingLength}px`; + const notchMiddleWidth = `${this._getLabelWidth()}px`; + + this._notchLeading.nativeElement.style.width = notchLeadingWidth; + this._notchMiddle.nativeElement.style.width = notchMiddleWidth; + this._label.nativeElement.style.marginLeft = `${this._labelMarginLeft}px`; + + this._recalculateGapWhenVisible = false; + } + + private _updateLabelActiveState(): void { + if (this._isLabelActive()) { + this._renderer.addClass(this.input, 'active'); + } else { + this._renderer.removeClass(this.input, 'active'); + } + } + + private _isLabelActive(): boolean { + return this._formControl && this._formControl.labelActive; + } + + private _isHidden(): boolean { + const el = this._elementRef.nativeElement; + + return !el.offsetHeight && !el.offsetWidth; + } +} diff --git a/projects/mdb-angular-ui-kit/forms/form-control.spec.ts b/projects/mdb-angular-ui-kit/forms/form-control.spec.ts new file mode 100644 index 00000000..5ea204d3 --- /dev/null +++ b/projects/mdb-angular-ui-kit/forms/form-control.spec.ts @@ -0,0 +1,125 @@ +import { ComponentFixture, fakeAsync, flush, TestBed } from '@angular/core/testing'; +import { Component, DebugElement } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { MdbFormsModule } from './index'; +import { MdbFormControlComponent } from './form-control.component'; + +describe('MDB Form Control', () => { + let fixture: ComponentFixture; + let wrapper: DebugElement; + let input: DebugElement; + const labelGapPadding = 8; + const labelScale = 0.8; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [BasicFormControlComponent, WithoutLabelComponent, DynamicLabelComponent], + imports: [MdbFormsModule], + teardown: { destroyAfterEach: false }, + }); + + fixture = TestBed.createComponent(BasicFormControlComponent); + fixture.detectChanges(); + wrapper = fixture.debugElement.query(By.directive(MdbFormControlComponent)); + input = fixture.debugElement.query(By.css('input')); + }); + + beforeAll(() => { + Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { configurable: true, value: 20 }); + Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { configurable: true, value: 20 }); + Object.defineProperty(HTMLElement.prototype, 'clientWidth', { configurable: true, value: 20 }); + }); + + it('should add outline class to the wrapper element', () => { + fixture.detectChanges(); + expect(wrapper.nativeElement.classList.contains('form-outline')).toBe(true); + }); + + it('should toggle input active class on value change', () => { + input.nativeElement.value = 'Test'; + fixture.detectChanges(); + expect(input.nativeElement.classList.contains('active')).toBe(true); + }); + + it('should set placeholder-active class on input if label is not defined', () => { + const fixture = TestBed.createComponent(WithoutLabelComponent); + fixture.detectChanges(); + + const input = fixture.nativeElement.querySelector('input'); + + expect(input.classList).toContain('placeholder-active'); + }); + + it('should set top border gap on component init if label is defined', fakeAsync(() => { + const fixture = TestBed.createComponent(BasicFormControlComponent); + fixture.detectChanges(); + + flush(); + fixture.detectChanges(); + const labelWidth = fixture.nativeElement.querySelector('label').clientWidth; + const middleNotch = fixture.nativeElement.querySelector('.form-notch-middle'); + const expectedBorderGap = labelWidth * labelScale + labelGapPadding + 'px'; + + expect(middleNotch.style.width).toEqual(expectedBorderGap); + })); + + it('should update border gap when label is dynamically rendered with *ngIf', fakeAsync(() => { + const fixture = TestBed.createComponent(DynamicLabelComponent); + fixture.detectChanges(); + + let label = fixture.nativeElement.querySelector('label'); + let middleNotch = fixture.nativeElement.querySelector('.form-notch-middle'); + expect(label).toBeNull(); + expect(middleNotch.style.width).toBe(''); + + fixture.componentInstance.showLabel = true; + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + + label = fixture.nativeElement.querySelector('label'); + middleNotch = fixture.nativeElement.querySelector('.form-notch-middle'); + expect(label).not.toBeNull(); + const expectedBorderGap = label.clientWidth * labelScale + labelGapPadding + 'px'; + expect(middleNotch.style.width).toEqual(expectedBorderGap); + })); +}); + +const dynamicLabelTemplate = ` + + + + +`; +@Component({ + template: dynamicLabelTemplate, + standalone: false, +}) +class DynamicLabelComponent { + showLabel = false; +} + +const basicTemplate = ` + + + + +`; + +@Component({ + template: basicTemplate, + standalone: false, +}) +class BasicFormControlComponent {} + +const withoutLabelTemplate = ` + + + +`; + +@Component({ + template: withoutLabelTemplate, + standalone: false, +}) +class WithoutLabelComponent {} diff --git a/projects/mdb-angular-ui-kit/forms/form-control.ts b/projects/mdb-angular-ui-kit/forms/form-control.ts new file mode 100644 index 00000000..a60ad746 --- /dev/null +++ b/projects/mdb-angular-ui-kit/forms/form-control.ts @@ -0,0 +1,10 @@ +import { Observable } from 'rxjs'; +import { Directive } from '@angular/core'; + +@Directive() +// eslint-disable-next-line @angular-eslint/directive-class-suffix +export abstract class MdbAbstractFormControl { + readonly stateChanges: Observable; + readonly input: HTMLInputElement; + readonly labelActive: boolean; +} diff --git a/projects/mdb-angular-ui-kit/forms/forms.module.ts b/projects/mdb-angular-ui-kit/forms/forms.module.ts new file mode 100644 index 00000000..69d9a85e --- /dev/null +++ b/projects/mdb-angular-ui-kit/forms/forms.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { MdbFormControlComponent } from './form-control.component'; +import { MdbInputDirective } from './input.directive'; +import { MdbLabelDirective } from './label.directive'; + +@NgModule({ + declarations: [MdbFormControlComponent, MdbInputDirective, MdbLabelDirective], + exports: [MdbFormControlComponent, MdbInputDirective, MdbLabelDirective], + imports: [CommonModule, FormsModule], +}) +export class MdbFormsModule {} diff --git a/projects/mdb-angular-ui-kit/forms/index.ts b/projects/mdb-angular-ui-kit/forms/index.ts new file mode 100644 index 00000000..4aaf8f92 --- /dev/null +++ b/projects/mdb-angular-ui-kit/forms/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/projects/mdb-angular-ui-kit/forms/input.directive.ts b/projects/mdb-angular-ui-kit/forms/input.directive.ts new file mode 100644 index 00000000..bdc0280f --- /dev/null +++ b/projects/mdb-angular-ui-kit/forms/input.directive.ts @@ -0,0 +1,169 @@ +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; +import { + AfterViewInit, + DestroyRef, + Directive, + DoCheck, + ElementRef, + HostBinding, + HostListener, + Input, + OnDestroy, + Optional, + Renderer2, + Self, +} from '@angular/core'; +import { NgControl } from '@angular/forms'; +import { Subject } from 'rxjs'; +import { MdbAbstractFormControl } from './form-control'; +import { AutofillEvent, AutofillMonitor } from '@angular/cdk/text-field'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbInput]', + exportAs: 'mdbInput', + providers: [{ provide: MdbAbstractFormControl, useExisting: MdbInputDirective }], + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +export class MdbInputDirective + implements MdbAbstractFormControl, DoCheck, AfterViewInit, OnDestroy +{ + constructor( + private _elementRef: ElementRef, + private _renderer: Renderer2, + @Optional() @Self() private _ngControl: NgControl, + private _autofill: AutofillMonitor, + private _destroyRef: DestroyRef + ) {} + + readonly stateChanges: Subject = new Subject(); + + private _focused = false; + private _autofilled = false; + private _color = ''; + + ngAfterViewInit() { + if (typeof getComputedStyle === 'function') { + this._color = getComputedStyle(this._elementRef.nativeElement).color; + + if (this._hasTypeInterferingPlaceholder()) { + this._updateTextColorForDateType(); + } + } + + this._autofill.monitor(this.input).subscribe((event: AutofillEvent) => { + this._autofilled = event.isAutofilled; + this.stateChanges.next(); + }); + + this.stateChanges.pipe(takeUntilDestroyed(this._destroyRef)).subscribe(() => { + if (this._hasTypeInterferingPlaceholder()) { + this._updateTextColorForDateType(); + } + }); + } + + private _currentNativeValue: any; + + @HostBinding('disabled') + @Input('disabled') + get disabled(): boolean { + if (this._ngControl && this._ngControl.disabled !== null) { + return this._ngControl.disabled; + } + return this._disabled; + } + set disabled(value: boolean) { + this._disabled = coerceBooleanProperty(value); + } + private _disabled = false; + + @Input('readonly') + get readonly(): boolean { + return this._readonly; + } + set readonly(value: boolean) { + if (value) { + this._renderer.setAttribute(this._elementRef.nativeElement, 'readonly', ''); + } else { + this._renderer.removeAttribute(this._elementRef.nativeElement, 'readonly'); + } + this._readonly = coerceBooleanProperty(value); + } + private _readonly = false; + + @Input() + get value(): string { + return this._elementRef.nativeElement.value; + } + set value(value: string) { + if (value !== this.value) { + this._elementRef.nativeElement.value = value; + this._value = value; + this.stateChanges.next(); + } + } + private _value: any; + + private _updateTextColorForDateType() { + const actualColor = getComputedStyle(this._elementRef.nativeElement).color; + this._color = actualColor !== 'rgba(0, 0, 0, 0)' ? actualColor : this._color; + + const color = this.labelActive ? this._color : `transparent`; + + this._renderer.setStyle(this._elementRef.nativeElement, 'color', color); + } + + @HostListener('focus') + _onFocus(): void { + this._focused = true; + this.stateChanges.next(); + } + + @HostListener('blur') + _onBlur(): void { + this._focused = false; + this.stateChanges.next(); + } + + ngDoCheck(): void { + const value = this._elementRef.nativeElement.value; + if (this._currentNativeValue !== value) { + this._currentNativeValue = value; + this.stateChanges.next(); + } + } + + get hasValue(): boolean { + return this._elementRef.nativeElement.value !== ''; + } + + get focused(): boolean { + return this._focused; + } + + get autofilled(): boolean { + return this._autofilled; + } + + get input(): HTMLInputElement { + return this._elementRef.nativeElement; + } + + get labelActive(): boolean { + return this.focused || this.hasValue || this.autofilled; + } + + private _hasTypeInterferingPlaceholder() { + const typesArray = ['date', 'datetime-local', 'time', 'month', 'week']; + return typesArray.includes(this._elementRef.nativeElement.type); + } + + static ngAcceptInputType_disabled: BooleanInput; + static ngAcceptInputType_readonly: BooleanInput; + + ngOnDestroy(): void { + this._autofill.stopMonitoring(this.input); + } +} diff --git a/projects/mdb-angular-ui-kit/forms/input.spec.ts b/projects/mdb-angular-ui-kit/forms/input.spec.ts new file mode 100644 index 00000000..0193f64d --- /dev/null +++ b/projects/mdb-angular-ui-kit/forms/input.spec.ts @@ -0,0 +1,48 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Component, DebugElement } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { MdbFormsModule } from './index'; + +describe('MDB Checkbox', () => { + let component: BasicInputComponent; + let fixture: ComponentFixture; + let input: DebugElement; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [BasicInputComponent], + imports: [MdbFormsModule], + teardown: { destroyAfterEach: false }, + }); + + fixture = TestBed.createComponent(BasicInputComponent); + component = fixture.componentInstance; + input = fixture.debugElement.query(By.css('input')); + }); + + it('Should be disabled if disabled input is set to true', () => { + component.disabled = true; + fixture.detectChanges(); + expect(input.nativeElement.disabled).toBe(true); + }); + + it('Should be readonly if readonly input is set to true', () => { + component.readonly = true; + fixture.detectChanges(); + expect(input.nativeElement.hasAttribute('readonly')).toBe(true); + }); +}); + +const basicTemplate = ` + +`; + +@Component({ + selector: 'mdb-input-test', + template: basicTemplate, + standalone: false, +}) +class BasicInputComponent { + disabled = false; + readonly = false; +} diff --git a/projects/mdb-angular-ui-kit/forms/label.directive.ts b/projects/mdb-angular-ui-kit/forms/label.directive.ts new file mode 100644 index 00000000..c277cda2 --- /dev/null +++ b/projects/mdb-angular-ui-kit/forms/label.directive.ts @@ -0,0 +1,12 @@ +import { Directive, ElementRef } from '@angular/core'; + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbLabel]', + exportAs: 'mdbLabel', + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +export class MdbLabelDirective { + constructor() {} +} diff --git a/projects/mdb-angular-ui-kit/forms/ng-package.json b/projects/mdb-angular-ui-kit/forms/ng-package.json new file mode 100644 index 00000000..ecef3ed8 --- /dev/null +++ b/projects/mdb-angular-ui-kit/forms/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "index.ts" + } +} \ No newline at end of file diff --git a/projects/mdb-angular-ui-kit/forms/public_api.ts b/projects/mdb-angular-ui-kit/forms/public_api.ts new file mode 100644 index 00000000..4fb2cf87 --- /dev/null +++ b/projects/mdb-angular-ui-kit/forms/public_api.ts @@ -0,0 +1,5 @@ +export { MdbFormControlComponent } from './form-control.component'; +export { MdbInputDirective } from './input.directive'; +export { MdbLabelDirective } from './label.directive'; +export { MdbFormsModule } from './forms.module'; +export { MdbAbstractFormControl } from './form-control'; diff --git a/projects/mdb-angular-ui-kit/index.ts b/projects/mdb-angular-ui-kit/index.ts new file mode 100644 index 00000000..ff8b4c56 --- /dev/null +++ b/projects/mdb-angular-ui-kit/index.ts @@ -0,0 +1 @@ +export default {}; diff --git a/projects/mdb-angular-ui-kit/jest.config.js b/projects/mdb-angular-ui-kit/jest.config.js new file mode 100644 index 00000000..32df13e3 --- /dev/null +++ b/projects/mdb-angular-ui-kit/jest.config.js @@ -0,0 +1,10 @@ +const baseConfig = require('../../jest.config'); + +module.exports = { + ...baseConfig, + globals: { + 'ts-jest': { + tsConfig: '/projects/mdb-angular-ui-kit/tsconfig.spec.json', + }, + }, +}; diff --git a/projects/mdb-angular-ui-kit/modal/index.ts b/projects/mdb-angular-ui-kit/modal/index.ts new file mode 100644 index 00000000..4aaf8f92 --- /dev/null +++ b/projects/mdb-angular-ui-kit/modal/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/projects/mdb-angular-ui-kit/modal/modal-config.ts b/projects/mdb-angular-ui-kit/modal/modal-config.ts new file mode 100644 index 00000000..a8cf3deb --- /dev/null +++ b/projects/mdb-angular-ui-kit/modal/modal-config.ts @@ -0,0 +1,14 @@ +import { ViewContainerRef } from '@angular/core'; + +export class MdbModalConfig { + animation? = true; + backdrop? = true; + ignoreBackdropClick? = false; + keyboard? = true; + modalClass? = ''; + containerClass? = ''; + viewContainerRef?: ViewContainerRef; + data?: D | null = null; + nonInvasive? = false; + focusElementSelector? = ''; +} diff --git a/projects/mdb-angular-ui-kit/modal/modal-container.component.html b/projects/mdb-angular-ui-kit/modal/modal-container.component.html new file mode 100644 index 00000000..c5c752e1 --- /dev/null +++ b/projects/mdb-angular-ui-kit/modal/modal-container.component.html @@ -0,0 +1,9 @@ +
+ +
diff --git a/projects/mdb-angular-ui-kit/modal/modal-container.component.ts b/projects/mdb-angular-ui-kit/modal/modal-container.component.ts new file mode 100644 index 00000000..4ba207b6 --- /dev/null +++ b/projects/mdb-angular-ui-kit/modal/modal-container.component.ts @@ -0,0 +1,338 @@ +import { CdkPortalOutlet, ComponentPortal, TemplatePortal } from '@angular/cdk/portal'; +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + ComponentRef, + ElementRef, + EmbeddedViewRef, + HostBinding, + HostListener, + Inject, + NgZone, + OnDestroy, + OnInit, + Renderer2, + ViewChild, + DOCUMENT, +} from '@angular/core'; +import { MdbModalConfig } from './modal-config'; +import { ConfigurableFocusTrapFactory, ConfigurableFocusTrap } from '@angular/cdk/a11y'; +import { fromEvent, Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; + +// width below which, according to css rules, modal position changes - modal gets position relative instead of absolute. +const MODAL_CSS_BREAKPOINT = 992; +const MODAL_OPEN_CLASS = 'modal-open'; +const NON_INVASIVE_CLASS = 'modal-non-invasive-open'; +const NON_INVASIVE_SHOW_CLASS = 'modal-non-invasive-show'; + +@Component({ + selector: 'mdb-modal-container', + templateUrl: 'modal-container.component.html', + changeDetection: ChangeDetectionStrategy.Default, + standalone: false, +}) +export class MdbModalContainerComponent implements OnInit, AfterViewInit, OnDestroy { + @ViewChild(CdkPortalOutlet, { static: true }) _portalOutlet: CdkPortalOutlet; + @ViewChild('dialog', { static: true }) modalDialog: ElementRef; + @ViewChild('content', { static: true }) modalContent: ElementRef; + + readonly _destroy$: Subject = new Subject(); + readonly backdropClick$: Subject = new Subject(); + + _config: MdbModalConfig; + + BACKDROP_TRANSITION = 150; + MODAL_TRANSITION = 200; + NON_INVASIVE_TRANSITION = 450; + + private _previouslyFocusedElement: HTMLElement; + private _focusTrap: ConfigurableFocusTrap; + + @HostBinding('class.modal') modal = true; + @HostBinding('class.fade') + get hasAnimation(): boolean { + return this._config.animation; + } + + @HostListener('window:resize', ['$event']) + onWindowResize() { + this._ngZone.runOutsideAngular(() => { + if (this._config.nonInvasive) { + this._handleWindowResize(); + } + }); + } + + get host(): HTMLElement { + return this._elementRef.nativeElement; + } + + private _isScrollable = false; + private _isBottomRight = false; + private _isBottomLeft = false; + private _isTopRight = false; + private _isTopLeft = false; + private _isSideTopModal = false; + private _isSideBottomModal = false; + private _isSideModal = false; + private _isModalBottom = false; + private _modalContentRect: null | DOMRectReadOnly; + private _modalContentComputedStyles: null | CSSStyleDeclaration; + private _modalDialogComputedStyles: null | CSSStyleDeclaration; + private _topOffset = 0; + private _leftOffset = 0; + private _rightOffset = 0; + private _bottomOffset = 0; + + constructor( + @Inject(DOCUMENT) private _document, + public _elementRef: ElementRef, + private _renderer: Renderer2, + private _focusTrapFactory: ConfigurableFocusTrapFactory, + private _ngZone: NgZone + ) {} + + ngOnInit(): void { + this._updateContainerClass(); + this._renderer.setStyle(this.host, 'display', 'block'); + + if (!this._config.nonInvasive) { + this._focusTrap = this._focusTrapFactory.create(this.host); + this._previouslyFocusedElement = this._document.activeElement as HTMLElement; + } + const focusElement = + this._config.focusElementSelector && + (this.modalContent.nativeElement.querySelector( + this._config.focusElementSelector + ) as HTMLElement); + if (this._config.animation) { + setTimeout(() => { + this._renderer.addClass(this.host, 'show'); + if (focusElement) { + setTimeout(() => { + focusElement.focus(); + }, this.MODAL_TRANSITION); + } else { + setTimeout(() => { + this._focusTrap?.focusInitialElementWhenReady(); + }, this.MODAL_TRANSITION); + } + }, this.BACKDROP_TRANSITION); + } else if (focusElement) { + focusElement.focus(); + } else { + this._focusTrap?.focusInitialElementWhenReady(); + } + } + + ngAfterViewInit(): void { + const widthWithVerticalScroll = this._document.body.offsetWidth; + this._renderer.addClass(this._document.body, MODAL_OPEN_CLASS); + + if (this._config.nonInvasive) { + this._renderer.addClass(this._document.body, NON_INVASIVE_CLASS); + setTimeout(() => { + this._onNonInvasiveModalShown(); + }, this.NON_INVASIVE_TRANSITION); + } + + if (!this._config.nonInvasive) { + this._renderer.setStyle(this._document.body, 'overflow', 'hidden'); + } + + const widthWithoutVerticalScroll = this._document.body.offsetWidth; + + if (!this._config.nonInvasive) { + this._renderer.setStyle( + this._document.body, + 'padding-right', + `${widthWithoutVerticalScroll - widthWithVerticalScroll}px` + ); + } + + if (!this._config.ignoreBackdropClick && !this._config.nonInvasive) { + fromEvent(this.host, 'mousedown') + .pipe( + filter((event: MouseEvent) => { + const target = event.target as HTMLElement; + const dialog = this.modalDialog.nativeElement; + const notDialog = target !== dialog; + const notDialogContent = !dialog.contains(target); + return notDialog && notDialogContent; + }), + takeUntil(this._destroy$) + ) + .subscribe((event: MouseEvent) => { + this.backdropClick$.next(event); + }); + } + } + + ngOnDestroy(): void { + this._previouslyFocusedElement?.focus(); + this._focusTrap?.destroy(); + + this._destroy$.next(); + this._destroy$.complete(); + } + + private _updateContainerClass(): void { + if ( + this._config.containerClass === '' || + (this._config.containerClass.length && this._config.containerClass.length === 0) + ) { + return; + } + + const containerClasses = this._config.containerClass.split(' '); + + containerClasses.forEach((containerClass) => { + this._renderer.addClass(this.host, containerClass); + }); + } + + private _onNonInvasiveModalShown() { + this._isScrollable = this._config.modalClass.includes('modal-dialog-scrollable'); + this._isBottomRight = this._config.modalClass.includes('modal-bottom-right'); + this._isBottomLeft = this._config.modalClass.includes('modal-bottom-left'); + this._isTopRight = this._config.modalClass.includes('modal-top-right'); + this._isTopLeft = this._config.modalClass.includes('modal-top-left'); + this._isModalBottom = this._config.modalClass.includes('modal-bottom'); + this._isSideTopModal = this._isTopLeft || this._isTopRight; + this._isSideBottomModal = this._isBottomLeft || this._isBottomRight; + this._isSideModal = this._isSideTopModal || this._isSideBottomModal; + this._modalContentRect = this.modalContent.nativeElement.getBoundingClientRect(); + this._modalContentComputedStyles = window.getComputedStyle(this.modalContent.nativeElement); + this._modalDialogComputedStyles = window.getComputedStyle(this.modalDialog.nativeElement); + this._topOffset = parseInt(this._modalDialogComputedStyles.top, 0); + this._leftOffset = parseInt(this._modalDialogComputedStyles.left, 0); + this._rightOffset = parseInt(this._modalDialogComputedStyles.right, 0); + this._bottomOffset = parseInt(this._modalDialogComputedStyles.bottom, 0); + + this._renderer.addClass(this.host, NON_INVASIVE_SHOW_CLASS); + this._setNonInvasiveStyles(); + } + + private _setNonInvasiveStyles(leftOffset = 0, topOffset = 0) { + const isAboveBreakpoint = window.innerWidth >= MODAL_CSS_BREAKPOINT; + this._renderer.setStyle(this.host, 'left', `${this._modalContentRect.left + leftOffset}px`); + this._renderer.setStyle(this.host, 'width', this._modalContentComputedStyles.width); + + if (!this._isScrollable) { + // If the modal content is not long enough to require scroll shrink the modal wrapper to + // the height of modal content so other elements on site are clickable outside modal + this._renderer.setStyle(this.host, 'height', this._modalContentComputedStyles.height); + this._renderer.setStyle(this.host, 'display', ''); + } + + if (isAboveBreakpoint) { + if (this._isSideBottomModal || this._isModalBottom) { + // Force modal to correct bottom placement. It's needed because modal host has position + // fixed and fixed height. + this._renderer.setStyle(this.host, 'top', `${this._modalContentRect.top + topOffset}px`); + } + + if (this._isSideModal) { + // Enable horizontal scrolling when the content is wider than the modal's fixed width + this._renderer.setStyle(this.host, 'overflowX', 'auto'); + } + } + + if (!isAboveBreakpoint) { + this.host.style.height = ''; + } + } + + _onNonInvasiveModalHidden() { + this._renderer.removeClass(this.host, NON_INVASIVE_SHOW_CLASS); + this._resetNonInvasiveStyles(); + this._removeNonInvasiveClass(); + } + + private _resetNonInvasiveStyles() { + this._renderer.setStyle(this.host, 'left', ''); + this._renderer.setStyle(this.host, 'top', ''); + this._renderer.setStyle(this.host, 'height', ''); + this._renderer.setStyle(this.host, 'width', ''); + + if (!this._isScrollable) { + this._renderer.setStyle(this.host, 'display', ''); + } + + if (this._isSideModal) { + this._renderer.setStyle(this.host, 'overflowX', ''); + } + } + + private _removeNonInvasiveClass() { + const isOtherModalOpen = this._document.body.querySelector( + '.modal.show.modal-non-invasive-show' + ); + if (!isOtherModalOpen) { + this._renderer.removeClass(this._document.body, NON_INVASIVE_CLASS); + } else { + this._renderer.addClass(this._document.body, MODAL_OPEN_CLASS); + } + } + + private _handleWindowResize() { + const modalContent = this.host.querySelector('.modal-content'); + this._resetNonInvasiveStyles(); + + this._modalContentRect = modalContent.getBoundingClientRect(); + this._modalContentComputedStyles = window.getComputedStyle(modalContent); + + if (this._isSideTopModal || this._isSideBottomModal) { + let sideOffset = 0; + let topOffset = 0; + if (this._isBottomRight || this._isBottomLeft) { + topOffset = -this._bottomOffset; + } + if (this._isBottomRight || this._isTopRight) { + sideOffset = -this._rightOffset; + } + if (this._isBottomLeft || this._isTopLeft) { + sideOffset = this._leftOffset; + } + + this._setNonInvasiveStyles(sideOffset, topOffset); + } else { + this._setNonInvasiveStyles(); + } + } + + _close(): void { + if (this._config.animation) { + this._renderer.removeClass(this.host, 'show'); + } + + // Pause iframe/video when closing modal + const iframeElements = Array.from(this.host.querySelectorAll('iframe')); + const videoElements = Array.from(this.host.querySelectorAll('video')); + + iframeElements.forEach((iframe: HTMLIFrameElement) => { + const srcAttribute: any = iframe.getAttribute('src'); + this._renderer.setAttribute(iframe, 'src', srcAttribute); + }); + + videoElements.forEach((video: HTMLVideoElement) => { + video.pause(); + }); + } + + _restoreScrollbar(): void { + this._renderer.removeClass(this._document.body, MODAL_OPEN_CLASS); + this._renderer.removeStyle(this._document.body, 'overflow'); + this._renderer.removeStyle(this._document.body, 'padding-right'); + } + + attachComponentPortal(portal: ComponentPortal): ComponentRef { + return this._portalOutlet.attachComponentPortal(portal); + } + + attachTemplatePortal(portal: TemplatePortal): EmbeddedViewRef { + return this._portalOutlet.attachTemplatePortal(portal); + } +} diff --git a/projects/mdb-angular-ui-kit/modal/modal-ref.ts b/projects/mdb-angular-ui-kit/modal/modal-ref.ts new file mode 100644 index 00000000..8293199a --- /dev/null +++ b/projects/mdb-angular-ui-kit/modal/modal-ref.ts @@ -0,0 +1,27 @@ +import { OverlayRef } from '@angular/cdk/overlay'; +import { Observable, Subject } from 'rxjs'; +import { MdbModalContainerComponent } from './modal-container.component'; + +export class MdbModalRef { + constructor(protected _overlayRef: OverlayRef, private _container: MdbModalContainerComponent) {} + + component: T; + + private readonly onClose$: Subject = new Subject(); + readonly onClose: Observable = this.onClose$.asObservable(); + + close(message?: any): void { + this._container._close(); + + setTimeout(() => { + if (this._container._config.nonInvasive) { + this._container._onNonInvasiveModalHidden(); + } + this._container._restoreScrollbar(); + this.onClose$.next(message); + this.onClose$.complete(); + this._overlayRef.detach(); + this._overlayRef.dispose(); + }, this._container.MODAL_TRANSITION); + } +} diff --git a/projects/mdb-angular-ui-kit/modal/modal.module.ts b/projects/mdb-angular-ui-kit/modal/modal.module.ts new file mode 100644 index 00000000..8775961b --- /dev/null +++ b/projects/mdb-angular-ui-kit/modal/modal.module.ts @@ -0,0 +1,14 @@ +import { CommonModule } from '@angular/common'; +import { OverlayModule } from '@angular/cdk/overlay'; +import { PortalModule } from '@angular/cdk/portal'; +import { NgModule } from '@angular/core'; +import { MdbModalContainerComponent } from './modal-container.component'; +import { MdbModalService } from './modal.service'; + +@NgModule({ + imports: [CommonModule, OverlayModule, PortalModule], + exports: [MdbModalContainerComponent], + declarations: [MdbModalContainerComponent], + providers: [MdbModalService], +}) +export class MdbModalModule {} diff --git a/projects/mdb-angular-ui-kit/modal/modal.service.ts b/projects/mdb-angular-ui-kit/modal/modal.service.ts new file mode 100644 index 00000000..0b28823e --- /dev/null +++ b/projects/mdb-angular-ui-kit/modal/modal.service.ts @@ -0,0 +1,144 @@ +import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal, ComponentType, TemplatePortal } from '@angular/cdk/portal'; + +import { + Inject, + Injectable, + Injector, + StaticProvider, + TemplateRef, + ChangeDetectorRef, + DOCUMENT, +} from '@angular/core'; +import { fromEvent } from 'rxjs'; +import { filter, take } from 'rxjs/operators'; +import { MdbModalConfig } from './modal-config'; +import { MdbModalContainerComponent } from './modal-container.component'; +import { MdbModalRef } from './modal-ref'; + +@Injectable() +export class MdbModalService { + constructor( + @Inject(DOCUMENT) private _document, + private _overlay: Overlay, + private _injector: Injector + ) {} + + open( + componentOrTemplateRef: ComponentType | TemplateRef, + config?: MdbModalConfig + ): MdbModalRef { + const defaultConfig = new MdbModalConfig(); + config = config ? Object.assign(defaultConfig, config) : defaultConfig; + + const overlayRef = this._createOverlay(config); + const container = this._createContainer(overlayRef, config); + const modalRef = this._createContent(componentOrTemplateRef, container, overlayRef, config); + + this._registerListeners(modalRef, config, container); + + return modalRef; + } + + private _createOverlay(config: MdbModalConfig): OverlayRef { + const overlayConfig = this._getOverlayConfig(config); + return this._overlay.create(overlayConfig); + } + + private _getOverlayConfig(modalConfig: MdbModalConfig): OverlayConfig { + const config = new OverlayConfig({ + positionStrategy: this._overlay.position().global(), + scrollStrategy: this._overlay.scrollStrategies.noop(), + hasBackdrop: modalConfig.nonInvasive ? false : modalConfig.backdrop, + backdropClass: 'mdb-backdrop', + }); + + return config; + } + + private _createContainer( + overlayRef: OverlayRef, + config: MdbModalConfig + ): MdbModalContainerComponent { + const portal = new ComponentPortal(MdbModalContainerComponent, null, this._injector); + const containerRef = overlayRef.attach(portal); + containerRef.instance._config = config; + + containerRef.changeDetectorRef.detectChanges(); + + return containerRef.instance; + } + + private _createContent( + componentOrTemplate: ComponentType | TemplateRef, + container: MdbModalContainerComponent, + overlayRef: OverlayRef, + config: MdbModalConfig + ): MdbModalRef { + const modalRef: MdbModalRef = new MdbModalRef(overlayRef, container); + + if (componentOrTemplate instanceof TemplateRef) { + container.attachTemplatePortal( + new TemplatePortal(componentOrTemplate, null, { + $implicit: config.data, + modalRef, + } as any) + ); + } else { + const injector = this._createInjector(config, modalRef, container); + const contentRef = container.attachComponentPortal( + new ComponentPortal(componentOrTemplate, config.viewContainerRef, injector) + ); + + if (config.data) { + Object.assign(contentRef.instance, { ...config.data }); + } + + modalRef.component = contentRef.instance; + } + + return modalRef; + } + + private _createInjector( + config: MdbModalConfig, + modalRef: MdbModalRef, + container: MdbModalContainerComponent + ): Injector { + const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector; + + // The dialog container should be provided as the dialog container and the dialog's + // content are created out of the same `ViewContainerRef` and as such, are siblings + // for injector purposes. To allow the hierarchy that is expected, the dialog + // container is explicitly provided in the injector. + const providers: StaticProvider[] = [ + { provide: MdbModalContainerComponent, useValue: container }, + { provide: MdbModalRef, useValue: modalRef }, + ]; + + return Injector.create({ parent: userInjector || this._injector, providers }); + } + + private _registerListeners( + modalRef: MdbModalRef, + config: MdbModalConfig, + container: MdbModalContainerComponent + ): void { + container.backdropClick$.pipe(take(1)).subscribe(() => { + modalRef.close(); + }); + + if (config.keyboard) { + fromEvent(container._elementRef.nativeElement, 'keydown') + .pipe( + filter((event: KeyboardEvent) => { + return event.key === 'Escape'; + }), + take(1) + ) + .subscribe(() => { + modalRef.close(); + }); + } + } +} diff --git a/projects/mdb-angular-ui-kit/modal/modal.spec.ts b/projects/mdb-angular-ui-kit/modal/modal.spec.ts new file mode 100644 index 00000000..7f67a9c0 --- /dev/null +++ b/projects/mdb-angular-ui-kit/modal/modal.spec.ts @@ -0,0 +1,324 @@ +import { ComponentFixture, fakeAsync, flush, inject, TestBed, tick } from '@angular/core/testing'; +import { OverlayContainer } from '@angular/cdk/overlay'; + +import { MdbModalModule } from './modal.module'; +import { MdbModalService } from './modal.service'; +import { Component, NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +@Component({ + template: ` + + + + `, + providers: [MdbModalService], + standalone: false, +}) +class BasicModalComponent { + constructor(public modal: MdbModalService) {} + + mainView = true; + + setView(isMain) { + this.mainView = isMain; + } +} + +@NgModule({ + declarations: [BasicModalComponent], + imports: [BrowserModule], +}) +class TestModalModule {} + +describe('MDB Modal', () => { + let modal: MdbModalService; + let overlayContainer: OverlayContainer; + let overlayContainerElement: HTMLElement; + let fixture: ComponentFixture; + + beforeEach(fakeAsync(() => { + const module = TestBed.configureTestingModule({ + imports: [MdbModalModule, TestModalModule], + teardown: { destroyAfterEach: false }, + }); + + TestBed.compileComponents(); + fixture = module.createComponent(BasicModalComponent); + })); + + beforeEach(inject( + [MdbModalService, OverlayContainer], + (mdbModal: MdbModalService, oc: OverlayContainer) => { + modal = mdbModal; + overlayContainer = oc; + overlayContainerElement = oc.getContainerElement(); + } + )); + + afterEach(() => { + overlayContainer.ngOnDestroy(); + }); + + it('should open a modal with a specified component', () => { + modal.open(BasicModalComponent); + + expect(overlayContainerElement.textContent).toContain('Modal title'); + + const modalContainer = overlayContainerElement.querySelector('mdb-modal-container'); + expect(modalContainer).not.toBe(null); + }); + + it('should correctly add container classes', fakeAsync(() => { + modal.open(BasicModalComponent, { + containerClass: 'top', + }); + + fixture.detectChanges(); + tick(350); + + const modalContainer = overlayContainerElement.querySelector('mdb-modal-container'); + expect(modalContainer.classList.contains('top')).toBe(true); + })); + + it('should correctly add modal classes', fakeAsync(() => { + modal.open(BasicModalComponent, { + modalClass: 'modal-top-right', + }); + + fixture.detectChanges(); + tick(350); + + const modalContainer = overlayContainerElement.querySelector('mdb-modal-container'); + const modalDialog = modalContainer.querySelector('.modal-dialog'); + expect(modalDialog.classList.contains('modal-top-right')).toBe(true); + })); + + it('should close the modal on backdrop click', fakeAsync(() => { + modal.open(BasicModalComponent); + + fixture.detectChanges(); + tick(350); + + let modalContainer = overlayContainerElement.querySelector( + 'mdb-modal-container' + ) as HTMLElement; + expect(modalContainer).not.toBe(null); + + const event = new MouseEvent('mousedown', { clientX: 0, clientY: 0 }); + modalContainer.dispatchEvent(event); + + fixture.detectChanges(); + tick(700); + + modalContainer = overlayContainerElement.querySelector('mdb-modal-container') as HTMLElement; + + expect(modalContainer).toBe(null); + })); + + it('should not close the modal on mousedown inside modal, move mouse outside modal and mouseup', fakeAsync(() => { + modal.open(BasicModalComponent); + + fixture.detectChanges(); + tick(350); + + let modalContainer = overlayContainerElement.querySelector( + 'mdb-modal-container' + ) as HTMLElement; + let modalContent = overlayContainerElement.querySelector('.modal-content') as HTMLElement; + + expect(modalContainer).not.toBe(null); + expect(modalContent).not.toBe(null); + + const mousedownEvent = new MouseEvent('mousedown', { clientX: 0, clientY: 0 }); + const mouseupEvent = new MouseEvent('mouseup', { clientX: 0, clientY: 0 }); + + modalContent.dispatchEvent(mousedownEvent); + modalContainer.dispatchEvent(mouseupEvent); + + fixture.detectChanges(); + tick(700); + + modalContainer = overlayContainerElement.querySelector('mdb-modal-container') as HTMLElement; + modalContent = overlayContainerElement.querySelector('.modal-content') as HTMLElement; + + expect(modalContent).not.toBe(null); + expect(modalContainer).not.toBe(null); + })); + + it('should not close the modal on backdrop click if ignoreBackdropClick is set to true', fakeAsync(() => { + modal.open(BasicModalComponent, { + ignoreBackdropClick: true, + }); + + fixture.detectChanges(); + tick(350); + + let modalContainer = overlayContainerElement.querySelector('mdb-modal-container'); + expect(modalContainer).not.toBe(null); + + const event = new MouseEvent('mousedown'); + modalContainer.dispatchEvent(event); + + fixture.detectChanges(); + tick(700); + + modalContainer = overlayContainerElement.querySelector('mdb-modal-container'); + + expect(modalContainer).not.toBe(null); + })); + + it('should close on escape press if keyboard option is true', fakeAsync(() => { + modal.open(BasicModalComponent, { + keyboard: true, + }); + + fixture.detectChanges(); + tick(350); + + let modalContainer = overlayContainerElement.querySelector('mdb-modal-container'); + expect(modalContainer).not.toBe(null); + + const event = new KeyboardEvent('keydown', { key: 'Escape' }); + modalContainer.dispatchEvent(event); + + fixture.detectChanges(); + tick(700); + + modalContainer = overlayContainerElement.querySelector('mdb-modal-container'); + + expect(modalContainer).toBe(null); + })); + + it('should not close on escape press if keyboard option is false', fakeAsync(() => { + modal.open(BasicModalComponent, { + keyboard: false, + }); + + let modalContainer = overlayContainerElement.querySelector('mdb-modal-container'); + expect(modalContainer).not.toBe(null); + + const event = new KeyboardEvent('keydown', { key: 'Escape' }); + modalContainer.dispatchEvent(event); + + fixture.detectChanges(); + tick(700); + + modalContainer = overlayContainerElement.querySelector('mdb-modal-container'); + + expect(modalContainer).not.toBe(null); + })); + + it('should not close when click on btn inside modal', fakeAsync(() => { + modal.open(BasicModalComponent); + + fixture.detectChanges(); + tick(700); + + let modalContainer = overlayContainerElement.querySelector('mdb-modal-container'); + let mainView = modalContainer.querySelector('#main-view'); + let notMainView = modalContainer.querySelector('#not-main-view'); + let mainViewToggler = modalContainer.querySelector('#main-view-toggler'); + let notMainViewToggler = modalContainer.querySelector('#not-main-view-toggler'); + + expect(modalContainer).not.toBe(null); + expect(mainView).not.toBe(null); + expect(notMainView).toBe(null); + expect(mainViewToggler).not.toBe(null); + expect(notMainViewToggler).toBe(null); + + mainViewToggler.dispatchEvent(new MouseEvent('click')); + + fixture.detectChanges(); + tick(700); + + modalContainer = overlayContainerElement.querySelector('mdb-modal-container'); + mainView = modalContainer.querySelector('#main-view'); + notMainView = modalContainer.querySelector('#not-main-view'); + mainViewToggler = modalContainer.querySelector('#main-view-toggler'); + notMainViewToggler = modalContainer.querySelector('#not-main-view-toggler'); + + expect(modalContainer).not.toBe(null); + expect(mainView).toBe(null); + expect(notMainView).not.toBe(null); + expect(mainViewToggler).toBe(null); + expect(notMainViewToggler).not.toBe(null); + })); +}); + +describe('MDB Non-invasive Modal', () => { + let modal: MdbModalService; + let overlayContainer: OverlayContainer; + let overlayContainerElement: HTMLElement; + let fixture: ComponentFixture; + + beforeEach(fakeAsync(() => { + const module = TestBed.configureTestingModule({ + imports: [MdbModalModule, TestModalModule], + teardown: { destroyAfterEach: false }, + }); + + TestBed.compileComponents(); + fixture = module.createComponent(BasicModalComponent); + })); + + beforeEach(inject( + [MdbModalService, OverlayContainer], + (mdbModal: MdbModalService, oc: OverlayContainer) => { + modal = mdbModal; + overlayContainer = oc; + overlayContainerElement = oc.getContainerElement(); + } + )); + + afterEach(() => { + overlayContainer.ngOnDestroy(); + }); + + it('should add non-invasive class', fakeAsync(() => { + modal.open(BasicModalComponent, { + nonInvasive: true, + }); + + fixture.detectChanges(); + flush(); + + fixture.detectChanges(); + tick(350); + + const body = document.body; + const modalContainer = overlayContainerElement.querySelector('mdb-modal-container'); + expect(body.classList.contains('modal-non-invasive-open')).toBe(true); + expect(modalContainer.classList.contains('modal-non-invasive-show')).toBe(true); + })); + + it('should not apply padding-right style to document body', fakeAsync(() => { + modal.open(BasicModalComponent, { + nonInvasive: true, + }); + + fixture.detectChanges(); + flush(); + + fixture.detectChanges(); + tick(350); + + const body = document.body; + expect(body.style.paddingRight).toBe('0px'); + })); +}); diff --git a/projects/mdb-angular-ui-kit/modal/ng-package.json b/projects/mdb-angular-ui-kit/modal/ng-package.json new file mode 100644 index 00000000..ecef3ed8 --- /dev/null +++ b/projects/mdb-angular-ui-kit/modal/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "index.ts" + } +} \ No newline at end of file diff --git a/projects/mdb-angular-ui-kit/modal/public_api.ts b/projects/mdb-angular-ui-kit/modal/public_api.ts new file mode 100644 index 00000000..58191971 --- /dev/null +++ b/projects/mdb-angular-ui-kit/modal/public_api.ts @@ -0,0 +1,5 @@ +export { MdbModalConfig } from './modal-config'; +export { MdbModalRef } from './modal-ref'; +export { MdbModalContainerComponent } from './modal-container.component'; +export { MdbModalService } from './modal.service'; +export { MdbModalModule } from './modal.module'; diff --git a/projects/mdb-angular-ui-kit/ng-package.json b/projects/mdb-angular-ui-kit/ng-package.json new file mode 100644 index 00000000..fc74550a --- /dev/null +++ b/projects/mdb-angular-ui-kit/ng-package.json @@ -0,0 +1,8 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../dist/mdb-angular-ui-kit", + "lib": { + "entryFile": "index.ts" + }, + "assets": ["./assets/**/*", "./CHANGELOG.md", "./License.pdf"] +} diff --git a/projects/mdb-angular-ui-kit/package.json b/projects/mdb-angular-ui-kit/package.json new file mode 100644 index 00000000..0fb8a0de --- /dev/null +++ b/projects/mdb-angular-ui-kit/package.json @@ -0,0 +1,24 @@ +{ + "name": "mdb-angular-ui-kit", + "repository": "https://github.com/mdbootstrap/mdb-angular-ui-kit", + "homepage": "https://mdbootstrap.com/docs/b5/angular/", + "author": "MDBootstrap", + "license": "MIT", + "version": "9.0.0", + "peerDependencies": { + "@angular/common": "^20.0.0", + "@angular/core": "^20.0.0", + "@angular/animations": "^20.0.0", + "@angular/forms": "^20.0.0", + "@angular/cdk": "^20.0.0" + }, + "schematics": "./schematics/collection.json", + "dependencies": { + "tslib": "^2.0.0" + }, + "exports": { + "./assets/scss/mdb.scss": { + "sass": "./assets/scss/mdb.scss" + } + } +} \ No newline at end of file diff --git a/projects/mdb-angular-ui-kit/popover/index.ts b/projects/mdb-angular-ui-kit/popover/index.ts new file mode 100644 index 00000000..4aaf8f92 --- /dev/null +++ b/projects/mdb-angular-ui-kit/popover/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/projects/mdb-angular-ui-kit/popover/ng-package.json b/projects/mdb-angular-ui-kit/popover/ng-package.json new file mode 100644 index 00000000..ecef3ed8 --- /dev/null +++ b/projects/mdb-angular-ui-kit/popover/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "index.ts" + } +} \ No newline at end of file diff --git a/projects/mdb-angular-ui-kit/popover/popover.component.html b/projects/mdb-angular-ui-kit/popover/popover.component.html new file mode 100644 index 00000000..99d4df76 --- /dev/null +++ b/projects/mdb-angular-ui-kit/popover/popover.component.html @@ -0,0 +1,19 @@ +
+

+ {{ title }} +

+
+ +
+
+ {{ content }} +
+
diff --git a/projects/mdb-angular-ui-kit/popover/popover.component.ts b/projects/mdb-angular-ui-kit/popover/popover.component.ts new file mode 100644 index 00000000..c3bdfab0 --- /dev/null +++ b/projects/mdb-angular-ui-kit/popover/popover.component.ts @@ -0,0 +1,50 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + TemplateRef, + booleanAttribute, +} from '@angular/core'; +import { trigger, style, animate, transition, state, AnimationEvent } from '@angular/animations'; +import { Subject } from 'rxjs'; +@Component({ + selector: 'mdb-popover', + templateUrl: 'popover.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + trigger('fade', [ + state('visible', style({ opacity: 1 })), + state('hidden', style({ opacity: 0 })), + transition('visible <=> hidden', animate('150ms linear')), + transition(':enter', animate('150ms linear')), + ]), + ], + standalone: false, +}) +export class MdbPopoverComponent { + @Input({ transform: booleanAttribute }) animation: boolean; + @Input() content: string | TemplateRef; + @Input() context: any; + @Input() title: string; + + get isContentTemplate(): boolean { + return this.content instanceof TemplateRef; + } + + readonly _hidden: Subject = new Subject(); + + animationState = 'hidden'; + + constructor(private _cdRef: ChangeDetectorRef) {} + + markForCheck(): void { + this._cdRef.markForCheck(); + } + + onAnimationEnd(event: AnimationEvent): void { + if (event.toState === 'hidden') { + this._hidden.next(); + } + } +} diff --git a/projects/mdb-angular-ui-kit/popover/popover.directive.spec.ts b/projects/mdb-angular-ui-kit/popover/popover.directive.spec.ts new file mode 100644 index 00000000..a7f091ed --- /dev/null +++ b/projects/mdb-angular-ui-kit/popover/popover.directive.spec.ts @@ -0,0 +1,241 @@ +import { ComponentFixture, TestBed, fakeAsync, flush } from '@angular/core/testing'; +import { Component } from '@angular/core'; +import { MdbPopoverModule } from './index'; +import { MdbPopoverDirective } from './popover.directive'; +import { By } from '@angular/platform-browser'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +describe('MDB Popover', () => { + describe('after init', () => { + let fixture: ComponentFixture; + let element: any; + let component: any; + let directive: any; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [MdbPopoverModule, NoopAnimationsModule], + declarations: [TestPopoverComponent], + teardown: { destroyAfterEach: false }, + }); + fixture = TestBed.createComponent(TestPopoverComponent); + component = fixture.componentInstance; + element = fixture.nativeElement; + fixture.detectChanges(); + }); + + it('should create the component', () => { + expect(component).toBeTruthy(); + }); + + it('should open tooltip after mouseenter and close after mouseout', () => { + fixture.detectChanges(); + + directive = fixture.debugElement + .query(By.directive(MdbPopoverDirective)) + .injector.get(MdbPopoverDirective) as MdbPopoverDirective; + + const onOpen = jest.spyOn(directive, 'show'); + const onClose = jest.spyOn(directive, 'hide'); + + const buttonEl = element.querySelector('button'); + + buttonEl.dispatchEvent(new Event('mouseenter')); + fixture.detectChanges(); + + expect(directive.show).toHaveBeenCalled(); + + directive._open = true; + buttonEl.dispatchEvent(new Event('mouseleave')); + fixture.detectChanges(); + + expect(directive.hide).toHaveBeenCalled(); + }); + + it('should set popover header and title', () => { + jest.useFakeTimers(); + const buttonEl = fixture.nativeElement.querySelector('button'); + + buttonEl.dispatchEvent(new Event('mouseenter')); + jest.runAllTimers(); + + fixture.detectChanges(); + const popoverContent = document.querySelector('.popover-body').textContent; + const popoverTitle = document.querySelector('.popover-header').textContent; + + expect(popoverContent).toMatch(component.testMdbPopover); + expect(popoverTitle).toMatch(component.testMdbPopoverTitle); + }); + + it('should set placement', () => { + jest.useFakeTimers(); + const buttonEl = fixture.nativeElement.querySelector('button'); + + buttonEl.dispatchEvent(new Event('mouseenter')); + jest.runAllTimers(); + + fixture.detectChanges(); + directive = fixture.debugElement + .query(By.directive(MdbPopoverDirective)) + .injector.get(MdbPopoverDirective) as MdbPopoverDirective; + + const placement = directive._overlayRef._config.positionStrategy._lastPosition.originY; + expect(placement).toMatch('top'); + }); + }); + + describe('onInit', () => { + it('should open/close tooltip after click', () => { + let fixture: ComponentFixture; + let directive: any; + let component: any; + let element: any; + + TestBed.configureTestingModule({ + imports: [MdbPopoverModule, NoopAnimationsModule], + declarations: [TestPopoverComponent2], + teardown: { destroyAfterEach: false }, + }); + fixture = TestBed.createComponent(TestPopoverComponent2); + component = fixture.componentInstance; + element = fixture.nativeElement; + fixture.detectChanges(); + + directive = fixture.debugElement + .query(By.directive(MdbPopoverDirective)) + .injector.get(MdbPopoverDirective) as MdbPopoverDirective; + + const onOpen = jest.spyOn(directive, 'show'); + const onClose = jest.spyOn(directive, 'hide'); + + const buttonEl = fixture.nativeElement.querySelector('button'); + + buttonEl.dispatchEvent(new Event('click')); + fixture.detectChanges(); + + expect(directive.show).toHaveBeenCalled(); + + directive._open = true; + buttonEl.dispatchEvent(new Event('click')); + fixture.detectChanges(); + + expect(directive.hide).toHaveBeenCalled(); + }); + + it('should prevent open', () => { + let fixture: ComponentFixture; + let directive: any; + let component: any; + let element: any; + + TestBed.configureTestingModule({ + imports: [MdbPopoverModule, NoopAnimationsModule], + declarations: [TestPopoverComponent3], + teardown: { destroyAfterEach: false }, + }); + fixture = TestBed.createComponent(TestPopoverComponent3); + component = fixture.componentInstance; + element = fixture.nativeElement; + fixture.detectChanges(); + + directive = fixture.debugElement + .query(By.directive(MdbPopoverDirective)) + .injector.get(MdbPopoverDirective) as MdbPopoverDirective; + + const onOpen = jest.spyOn(directive, 'show'); + + const buttonEl = fixture.nativeElement.querySelector('button'); + + buttonEl.dispatchEvent(new Event('click')); + fixture.detectChanges(); + + expect(directive.show).not.toHaveBeenCalled(); + }); + }); + + describe('custom template', () => { + let fixture: ComponentFixture; + let element: any; + let component: any; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [MdbPopoverModule, NoopAnimationsModule], + declarations: [TestPopoverComponent4], + teardown: { destroyAfterEach: false }, + }); + fixture = TestBed.createComponent(TestPopoverComponent4); + component = fixture.componentInstance; + element = fixture.nativeElement; + fixture.detectChanges(); + }); + + it('should set custom content with context data', fakeAsync(() => { + const buttonEl = element.querySelector('button'); + buttonEl.click(); + fixture.detectChanges(); + flush(); + const popoverBody = document.querySelector('.popover-body'); + expect(popoverBody.textContent).toBe('Current user: John Doe'); + })); + }); +}); + +@Component({ + selector: 'mdb-test-tooltip', + template: ` `, + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +class TestPopoverComponent { + testTrigger = 'hover'; + testMdbPopover = 'popoverTitle'; + testMdbPopoverTitle = 'popoverTitle'; + testPlacement = 'top'; + testDisabled = false; +} + +@Component({ + selector: 'mdb-test-popover2', + template: ` `, + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +class TestPopoverComponent2 {} + +@Component({ + selector: 'mdb-test-popover2', + template: ` `, + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +class TestPopoverComponent3 {} + +@Component({ + selector: 'mdb-test-popover4', + template: ` + Current user: {{ person.name }} {{ person.surname }}`, + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +class TestPopoverComponent4 {} diff --git a/projects/mdb-angular-ui-kit/popover/popover.directive.ts b/projects/mdb-angular-ui-kit/popover/popover.directive.ts new file mode 100644 index 00000000..e76db104 --- /dev/null +++ b/projects/mdb-angular-ui-kit/popover/popover.directive.ts @@ -0,0 +1,243 @@ +import { + ComponentRef, + Directive, + ElementRef, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, + TemplateRef, + booleanAttribute, + numberAttribute, +} from '@angular/core'; +import { + ConnectedPosition, + Overlay, + OverlayConfig, + OverlayPositionBuilder, + OverlayRef, +} from '@angular/cdk/overlay'; +import { ComponentPortal } from '@angular/cdk/portal'; +import { MdbPopoverComponent } from './popover.component'; +import { fromEvent, Subject } from 'rxjs'; +import { first, takeUntil } from 'rxjs/operators'; +import { MdbPopoverPosition } from './popover.types'; + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbPopover]', + exportAs: 'mdbPopover', + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +export class MdbPopoverDirective implements OnInit, OnDestroy { + @Input({ transform: booleanAttribute }) animation = true; + @Input({ transform: numberAttribute }) delayHide = 0; + @Input({ transform: numberAttribute }) delayShow = 0; + @Input() mdbPopover: TemplateRef | string = ''; + @Input() mdbPopoverData: any; + @Input() mdbPopoverTitle = ''; + @Input({ transform: numberAttribute }) offset = 4; + @Input() placement: MdbPopoverPosition = 'top'; + @Input({ transform: booleanAttribute }) popoverDisabled = false; + @Input() trigger = 'click'; + + @Output() popoverShow = new EventEmitter(); + @Output() popoverShown = new EventEmitter(); + @Output() popoverHide = new EventEmitter(); + @Output() popoverHidden = new EventEmitter(); + + private _overlayRef: OverlayRef; + private _tooltipRef: ComponentRef; + private _open = false; + private _showTimeout: any = 0; + private _hideTimeout: any = 0; + + readonly _destroy$: Subject = new Subject(); + + constructor( + private _overlay: Overlay, + private _overlayPositionBuilder: OverlayPositionBuilder, + private _elementRef: ElementRef + ) {} + + ngOnInit(): void { + if (this.popoverDisabled || (this.mdbPopover === '' && this.mdbPopoverTitle === '')) { + return; + } + + this._bindTriggerEvents(); + } + + ngOnDestroy(): void { + if (this._open) { + this.hide(); + } + + this._destroy$.next(); + this._destroy$.complete(); + } + + private _bindTriggerEvents(): void { + const triggers = this.trigger.split(' '); + + triggers.forEach((trigger) => { + if (trigger === 'click') { + fromEvent(this._elementRef.nativeElement, trigger) + .pipe(takeUntil(this._destroy$)) + .subscribe(() => this.toggle()); + } else if (trigger !== 'manual') { + const evIn = trigger === 'hover' ? 'mouseenter' : 'focusin'; + const evOut = trigger === 'hover' ? 'mouseleave' : 'focusout'; + + fromEvent(this._elementRef.nativeElement, evIn) + .pipe(takeUntil(this._destroy$)) + .subscribe(() => this.show()); + fromEvent(this._elementRef.nativeElement, evOut) + .pipe(takeUntil(this._destroy$)) + .subscribe(() => this.hide()); + } + }); + } + + private _createOverlayConfig(): OverlayConfig { + const positionStrategy = this._overlayPositionBuilder + .flexibleConnectedTo(this._elementRef) + .withPositions(this._getPosition()) + .withPush(false); + const overlayConfig = new OverlayConfig({ + hasBackdrop: false, + scrollStrategy: this._overlay.scrollStrategies.reposition(), + positionStrategy, + }); + + return overlayConfig; + } + + private _createOverlay(): void { + this._overlayRef = this._overlay.create(this._createOverlayConfig()); + } + + private _getPosition(): ConnectedPosition[] { + let position; + + const positionTop = { + originX: 'center', + originY: 'top', + overlayX: 'center', + overlayY: 'bottom', + offsetY: -this.offset, + }; + + const positionBottom = { + originX: 'center', + originY: 'bottom', + overlayX: 'center', + overlayY: 'top', + offsetY: this.offset, + }; + + const positionRight = { + originX: 'end', + originY: 'center', + overlayX: 'start', + overlayY: 'center', + offsetX: this.offset, + }; + + const positionLeft = { + originX: 'start', + originY: 'center', + overlayX: 'end', + overlayY: 'center', + offsetX: -this.offset, + }; + + switch (this.placement) { + case 'top': + position = [positionTop, positionBottom]; + break; + case 'bottom': + position = [positionBottom, positionTop]; + break; + case 'left': + position = [positionLeft, positionRight, positionTop, positionBottom]; + break; + case 'right': + position = [positionRight, positionLeft, positionTop, positionBottom]; + break; + default: + break; + } + + return position; + } + + show(): void { + if (this._hideTimeout) { + this._overlayRef.detach(); + clearTimeout(this._hideTimeout); + this._hideTimeout = null; + } + + this._createOverlay(); + + if (this._hideTimeout) { + clearTimeout(this._hideTimeout); + this._hideTimeout = null; + } + + this._showTimeout = setTimeout(() => { + const tooltipPortal = new ComponentPortal(MdbPopoverComponent); + + this.popoverShow.emit(this); + this._open = true; + + this._tooltipRef = this._overlayRef.attach(tooltipPortal); + + this._tooltipRef.instance.content = this.mdbPopover; + this._tooltipRef.instance.title = this.mdbPopoverTitle; + this._tooltipRef.instance.animation = this.animation; + this._tooltipRef.instance.context = this.mdbPopoverData; + this._tooltipRef.instance.animationState = 'visible'; + this._tooltipRef.instance.markForCheck(); + + this.popoverShown.emit(this); + }, this.delayShow); + } + + hide(): void { + if (this._showTimeout) { + clearTimeout(this._showTimeout); + this._showTimeout = null; + } else { + return; + } + + this._hideTimeout = setTimeout(() => { + this.popoverHide.emit(this); + if (!this._tooltipRef) { + this._overlayRef.detach(); + this._open = false; + this.popoverHidden.emit(this); + } else { + this._tooltipRef.instance._hidden.pipe(first()).subscribe(() => { + this._overlayRef.detach(); + this._open = false; + this.popoverHidden.emit(this); + }); + this._tooltipRef.instance.animationState = 'hidden'; + this._tooltipRef.instance.markForCheck(); + } + }, this.delayHide); + } + + toggle(): void { + if (this._open) { + this.hide(); + } else { + this.show(); + } + } +} diff --git a/projects/mdb-angular-ui-kit/popover/popover.module.ts b/projects/mdb-angular-ui-kit/popover/popover.module.ts new file mode 100644 index 00000000..e5315adc --- /dev/null +++ b/projects/mdb-angular-ui-kit/popover/popover.module.ts @@ -0,0 +1,12 @@ +import { MdbPopoverDirective } from './popover.directive'; +import { NgModule } from '@angular/core'; +import { OverlayModule } from '@angular/cdk/overlay'; +import { CommonModule } from '@angular/common'; +import { MdbPopoverComponent } from './popover.component'; + +@NgModule({ + imports: [CommonModule, OverlayModule], + declarations: [MdbPopoverDirective, MdbPopoverComponent], + exports: [MdbPopoverDirective, MdbPopoverComponent], +}) +export class MdbPopoverModule {} diff --git a/projects/mdb-angular-ui-kit/popover/popover.types.ts b/projects/mdb-angular-ui-kit/popover/popover.types.ts new file mode 100644 index 00000000..4648f6bf --- /dev/null +++ b/projects/mdb-angular-ui-kit/popover/popover.types.ts @@ -0,0 +1 @@ +export type MdbPopoverPosition = 'top' | 'right' | 'bottom' | 'left'; diff --git a/projects/mdb-angular-ui-kit/popover/public_api.ts b/projects/mdb-angular-ui-kit/popover/public_api.ts new file mode 100644 index 00000000..946952a3 --- /dev/null +++ b/projects/mdb-angular-ui-kit/popover/public_api.ts @@ -0,0 +1,4 @@ +export { MdbPopoverDirective } from './popover.directive'; +export { MdbPopoverModule } from './popover.module'; +export { MdbPopoverPosition } from './popover.types'; +export { MdbPopoverComponent } from './popover.component'; diff --git a/projects/mdb-angular-ui-kit/radio/index.ts b/projects/mdb-angular-ui-kit/radio/index.ts new file mode 100644 index 00000000..4aaf8f92 --- /dev/null +++ b/projects/mdb-angular-ui-kit/radio/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/projects/mdb-angular-ui-kit/radio/ng-package.json b/projects/mdb-angular-ui-kit/radio/ng-package.json new file mode 100644 index 00000000..ecef3ed8 --- /dev/null +++ b/projects/mdb-angular-ui-kit/radio/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "index.ts" + } +} \ No newline at end of file diff --git a/projects/mdb-angular-ui-kit/radio/public_api.ts b/projects/mdb-angular-ui-kit/radio/public_api.ts new file mode 100644 index 00000000..67dd43dd --- /dev/null +++ b/projects/mdb-angular-ui-kit/radio/public_api.ts @@ -0,0 +1,3 @@ +export { MdbRadioDirective } from './radio-button.directive'; +export { MdbRadioGroupDirective, MDB_RADIO_GROUP_VALUE_ACCESSOR } from './radio-group.directive'; +export { MdbRadioModule } from './radio.module'; diff --git a/projects/mdb-angular-ui-kit/radio/radio-button.directive.ts b/projects/mdb-angular-ui-kit/radio/radio-button.directive.ts new file mode 100644 index 00000000..a311fcf9 --- /dev/null +++ b/projects/mdb-angular-ui-kit/radio/radio-button.directive.ts @@ -0,0 +1,77 @@ +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; +import { Directive, HostBinding, Input } from '@angular/core'; + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbRadio]', + standalone: false, +}) +export class MdbRadioDirective { + @Input() + get name(): string { + return this._name; + } + set name(value: string) { + this._name = value; + } + private _name: string; + + @Input('checked') + get checked(): boolean { + return this._checked; + } + set checked(value: boolean) { + this._checked = coerceBooleanProperty(value); + } + private _checked = false; + + @Input('value') + get value(): any { + return this._value; + } + set value(value: any) { + this._value = value; + } + private _value: any = null; + + @Input('disabled') + get disabled(): boolean { + return this._disabled; + } + set disabled(value: boolean) { + this._disabled = coerceBooleanProperty(value); + } + private _disabled = false; + + @HostBinding('disabled') + get isDisabled(): boolean { + return this._disabled; + } + + @HostBinding('checked') + get isChecked(): boolean { + return this._checked; + } + + @HostBinding('attr.name') + get nameAttr(): string { + return this.name; + } + + constructor() {} + + _updateName(value: string): void { + this._name = value; + } + + _updateChecked(value: boolean): void { + this._checked = value; + } + + _updateDisabledState(value: boolean): void { + this._disabled = value; + } + + static ngAcceptInputType_checked: BooleanInput; + static ngAcceptInputType_disabled: BooleanInput; +} diff --git a/projects/mdb-angular-ui-kit/radio/radio-group.directive.spec.ts b/projects/mdb-angular-ui-kit/radio/radio-group.directive.spec.ts new file mode 100644 index 00000000..95dce45a --- /dev/null +++ b/projects/mdb-angular-ui-kit/radio/radio-group.directive.spec.ts @@ -0,0 +1,76 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Component } from '@angular/core'; +import { MdbRadioModule } from './index'; + +describe('MDB Checkbox', () => { + let component: BasicRadioGroupComponent; + let fixture: ComponentFixture; + let nativeElement: HTMLElement; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [BasicRadioGroupComponent], + imports: [MdbRadioModule], + teardown: { destroyAfterEach: false }, + }); + + fixture = TestBed.createComponent(BasicRadioGroupComponent); + component = fixture.componentInstance; + nativeElement = fixture.elementRef.nativeElement; + }); + + it('Should disable all inputs if the group is disabled', () => { + component.disabled = true; + fixture.detectChanges(); + const inputs = nativeElement.querySelectorAll('input'); + + expect(inputs[0].disabled).toBe(true); + expect(inputs[1].disabled).toBe(true); + }); + + it('Should set inputs name to the group name', () => { + component.name = 'test name'; + fixture.detectChanges(); + const inputs = nativeElement.querySelectorAll('input'); + + expect(inputs[0].getAttribute('name')).toBe('test name'); + expect(inputs[1].getAttribute('name')).toBe('test name'); + }); +}); + +const basicTemplate = ` +
+
+ + +
+ +
+ + +
+
+`; + +@Component({ + selector: 'mdb-radio-group-test', + template: basicTemplate, + standalone: false, +}) +class BasicRadioGroupComponent { + checked = false; + name = 'mdb-radio-group'; + disabled = false; +} diff --git a/projects/mdb-angular-ui-kit/radio/radio-group.directive.ts b/projects/mdb-angular-ui-kit/radio/radio-group.directive.ts new file mode 100644 index 00000000..fb1bf8fd --- /dev/null +++ b/projects/mdb-angular-ui-kit/radio/radio-group.directive.ts @@ -0,0 +1,128 @@ +import { + AfterContentInit, + ContentChildren, + Directive, + forwardRef, + Input, + OnDestroy, + QueryList, +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { from, Subject } from 'rxjs'; +import { startWith, switchMap, takeUntil } from 'rxjs/operators'; +import { MdbRadioDirective } from './radio-button.directive'; + +export const MDB_RADIO_GROUP_VALUE_ACCESSOR: any = { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MdbRadioGroupDirective), + multi: true, +}; + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbRadioGroup]', + providers: [MDB_RADIO_GROUP_VALUE_ACCESSOR], + standalone: false, +}) +export class MdbRadioGroupDirective implements ControlValueAccessor, AfterContentInit, OnDestroy { + @ContentChildren(MdbRadioDirective, { descendants: true }) radios: QueryList; + + @Input() + get value(): any { + return this._value; + } + set value(value: any) { + this._value = value; + if (this.radios) { + this._updateChecked(); + } + } + private _value: any; + + @Input() + get name(): string { + return this._name; + } + set name(name: string) { + this._name = name; + if (this.radios) { + this._updateNames(); + } + } + private _name: string; + + @Input() + get disabled(): boolean { + return this._disabled; + } + set disabled(disabled: boolean) { + this._disabled = disabled; + + if (this.radios) { + this._updateDisabled(); + } + } + private _disabled = false; + + private _destroy$ = new Subject(); + + onChange = (_: any) => {}; + onTouched = () => {}; + + ngAfterContentInit(): void { + this._updateNames(); + this._updateDisabled(); + + this.radios.changes + .pipe( + startWith(this.radios), + switchMap((radios: QueryList) => from(Promise.resolve(radios))), + takeUntil(this._destroy$) + ) + .subscribe(() => this._updateRadiosState()); + } + + ngOnDestroy(): void { + this._destroy$.next(); + this._destroy$.complete(); + } + + private _updateRadiosState(): void { + this._updateNames(); + this._updateChecked(); + this._updateDisabled(); + } + + private _updateNames(): void { + this.radios.forEach((radio: MdbRadioDirective) => radio._updateName(this.name)); + } + + private _updateChecked(): void { + this.radios.forEach((radio: MdbRadioDirective) => { + const isChecked = radio.value === this._value; + radio._updateChecked(isChecked); + }); + } + + private _updateDisabled(): void { + this.radios.forEach((radio: MdbRadioDirective) => radio._updateDisabledState(this._disabled)); + } + + // Control value accessor methods + registerOnChange(fn: (value: any) => any): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => any): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this._disabled = isDisabled; + this._updateDisabled(); + } + + writeValue(value: any): void { + this.value = value; + } +} diff --git a/projects/mdb-angular-ui-kit/radio/radio.directive.spec.ts b/projects/mdb-angular-ui-kit/radio/radio.directive.spec.ts new file mode 100644 index 00000000..6ba75e5c --- /dev/null +++ b/projects/mdb-angular-ui-kit/radio/radio.directive.spec.ts @@ -0,0 +1,73 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Component } from '@angular/core'; +import { MdbRadioModule } from './index'; + +describe('MDB Checkbox', () => { + let component: BasicRadioComponent; + let fixture: ComponentFixture; + let nativeElement: HTMLElement; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [BasicRadioComponent], + imports: [MdbRadioModule], + teardown: { destroyAfterEach: false }, + }); + + fixture = TestBed.createComponent(BasicRadioComponent); + component = fixture.componentInstance; + nativeElement = fixture.elementRef.nativeElement; + }); + + it('Should disable input element if disabled is set to true', () => { + component.disabled = true; + fixture.detectChanges(); + const input = nativeElement.querySelector('input'); + + expect(input.hasAttribute('disabled')).toBe(true); + }); + + it('Should correctly update name attribute', () => { + component.name = 'test name'; + fixture.detectChanges(); + + const input = nativeElement.querySelector('input'); + + expect(input.getAttribute('name')).toBe('test name'); + }); + + it('Should correctly update input checked state', () => { + component.checked = true; + fixture.detectChanges(); + + const input = nativeElement.querySelector('input'); + + expect(input.checked).toBe(true); + }); +}); + +const basicTemplate = ` +
+ + +
+`; + +@Component({ + selector: 'mdb-radio-test', + template: basicTemplate, + standalone: false, +}) +class BasicRadioComponent { + checked = false; + name = 'mdb-radio'; + disabled = false; +} diff --git a/projects/mdb-angular-ui-kit/radio/radio.module.ts b/projects/mdb-angular-ui-kit/radio/radio.module.ts new file mode 100644 index 00000000..a265ecd4 --- /dev/null +++ b/projects/mdb-angular-ui-kit/radio/radio.module.ts @@ -0,0 +1,12 @@ +import { MdbRadioDirective } from './radio-button.directive'; +import { MdbRadioGroupDirective } from './radio-group.directive'; +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +@NgModule({ + declarations: [MdbRadioDirective, MdbRadioGroupDirective], + exports: [MdbRadioDirective, MdbRadioGroupDirective], + imports: [CommonModule, FormsModule], +}) +export class MdbRadioModule {} diff --git a/projects/mdb-angular-ui-kit/range/index.ts b/projects/mdb-angular-ui-kit/range/index.ts new file mode 100644 index 00000000..4aaf8f92 --- /dev/null +++ b/projects/mdb-angular-ui-kit/range/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/projects/mdb-angular-ui-kit/range/ng-package.json b/projects/mdb-angular-ui-kit/range/ng-package.json new file mode 100644 index 00000000..ecef3ed8 --- /dev/null +++ b/projects/mdb-angular-ui-kit/range/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "index.ts" + } +} \ No newline at end of file diff --git a/projects/mdb-angular-ui-kit/range/public_api.ts b/projects/mdb-angular-ui-kit/range/public_api.ts new file mode 100644 index 00000000..6d843762 --- /dev/null +++ b/projects/mdb-angular-ui-kit/range/public_api.ts @@ -0,0 +1,2 @@ +export { MdbRangeModule } from './range.module'; +export { MdbRangeComponent } from './range.component'; diff --git a/projects/mdb-angular-ui-kit/range/range.component.html b/projects/mdb-angular-ui-kit/range/range.component.html new file mode 100644 index 00000000..9672cce9 --- /dev/null +++ b/projects/mdb-angular-ui-kit/range/range.component.html @@ -0,0 +1,27 @@ + +
+ + + {{ value }} + +
diff --git a/projects/mdb-angular-ui-kit/range/range.component.spec.ts b/projects/mdb-angular-ui-kit/range/range.component.spec.ts new file mode 100644 index 00000000..99d20f5d --- /dev/null +++ b/projects/mdb-angular-ui-kit/range/range.component.spec.ts @@ -0,0 +1,101 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing'; +import { MdbRangeModule } from './range.module'; +import { MdbRangeComponent } from './range.component'; +import { UntypedFormControl, ReactiveFormsModule } from '@angular/forms'; +import { By } from '@angular/platform-browser'; + +const template = ` + +`; + +@Component({ + selector: 'mdb-range-test', + template, + standalone: false, +}) +class TestRangeComponent implements OnInit { + @ViewChild(MdbRangeComponent) _range: MdbRangeComponent; + + rangeControl = new UntypedFormControl(50); + ngOnInit(): void { + this.rangeControl.valueChanges.subscribe((val) => console.log(val)); + } +} + +describe('MDB Range', () => { + let fixture: ComponentFixture; + let component: any; + let mdbRange: any; + let thumb: any; + let valueThumb: any; + let input: any; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestRangeComponent], + imports: [MdbRangeModule, ReactiveFormsModule], + teardown: { destroyAfterEach: false }, + }); + fixture = TestBed.createComponent(TestRangeComponent); + fixture.detectChanges(); + component = fixture.componentInstance; + mdbRange = fixture.debugElement.query(By.css('mdb-range')); + thumb = fixture.debugElement.query(By.css('.thumb')); + valueThumb = fixture.debugElement.query(By.css('.thumb-value')); + input = fixture.debugElement.query(By.css('input')); + }); + + it('should show thumb on mousedown and hide on mauseup', fakeAsync(() => { + expect(thumb.nativeElement.classList.contains('thumb-active')).toBe(false); + + input.nativeElement.dispatchEvent(new MouseEvent('mousedown')); + + fixture.detectChanges(); + flush(); + + expect(thumb.nativeElement.classList.contains('thumb-active')).toBe(true); + + input.nativeElement.dispatchEvent(new MouseEvent('mouseup')); + + fixture.detectChanges(); + flush(); + + expect(thumb.nativeElement.classList.contains('thumb-active')).toBe(false); + })); + + it('should show input value', () => { + fixture.detectChanges(); + + expect(document.querySelector('.thumb')).not.toBe(null); + expect(valueThumb.nativeElement.textContent).toBe(input.nativeElement.value); + }); + + it('should update thumb value after input', () => { + input.nativeElement.value = 24; + input.nativeElement.dispatchEvent(new Event('input')); + fixture.detectChanges(); + + expect(valueThumb.nativeElement.textContent).toBe('24'); + }); + + it('should update value after set new FormControl', () => { + component.rangeControl = new UntypedFormControl(60); + fixture.detectChanges(); + + expect(valueThumb.nativeElement.textContent).toBe('60'); + expect(input.nativeElement.value).toBe('60'); + }); + + it('should update thumb position', fakeAsync(() => { + const initialThumbStyle = { ...component._range.thumbStyle }; + + component.rangeControl = new UntypedFormControl(70); + + fixture.detectChanges(); + flush(); + const newThumbStyle = { ...component._range.thumbStyle }; + + expect(initialThumbStyle.left).not.toBe(newThumbStyle.left); + })); +}); diff --git a/projects/mdb-angular-ui-kit/range/range.component.ts b/projects/mdb-angular-ui-kit/range/range.component.ts new file mode 100644 index 00000000..b1f1f024 --- /dev/null +++ b/projects/mdb-angular-ui-kit/range/range.component.ts @@ -0,0 +1,134 @@ +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + forwardRef, + HostListener, + Input, + Output, + ViewChild, +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; + +export const RANGE_VALUE_ACCESOR: any = { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MdbRangeComponent), + multi: true, +}; +@Component({ + selector: 'mdb-range', + templateUrl: 'range.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [RANGE_VALUE_ACCESOR], + standalone: false, +}) +export class MdbRangeComponent implements ControlValueAccessor, AfterViewInit { + @ViewChild('input') input: ElementRef; + @ViewChild('thumb') thumb: ElementRef; + @ViewChild('thumbValue') thumbValue: ElementRef; + + @Input() id: string; + @Input() required: boolean; + @Input() name: string; + @Input() value: string; + + @Input() + get disabled(): boolean { + return this._disabled; + } + set disabled(value: boolean) { + this._disabled = coerceBooleanProperty(value); + } + private _disabled: boolean; + + @Input() label: string; + @Input() min = 0; + @Input() max = 100; + @Input() step: number; + + @Input() + get default(): boolean { + return this._default; + } + set default(value: boolean) { + this._default = value; + } + private _default: boolean; + + @Input() defaultRangeCounterClass: string; + + @Output() rangeValueChange = new EventEmitter(); + + public visibility = false; + public thumbStyle: any; + + @HostListener('change', ['$event']) onchange(event: any): void { + this.onChange(event.target.value); + } + + @HostListener('input') onInput(): void { + this.rangeValueChange.emit({ value: this.value }); + this.focusRangeInput(); + } + + constructor(private _cdRef: ChangeDetectorRef) {} + + ngAfterViewInit(): void { + this.thumbPositionUpdate(); + this._cdRef.detectChanges(); + } + + focusRangeInput(): void { + this.input.nativeElement.focus(); + this.visibility = true; + } + + blurRangeInput(): void { + this.input.nativeElement.blur(); + this.visibility = false; + } + + thumbPositionUpdate(): void { + const rangeInput = this.input.nativeElement; + const inputValue = rangeInput.value; + const minValue = rangeInput.min ? rangeInput.min : 0; + const maxValue = rangeInput.max ? rangeInput.max : 100; + const newValue = Number(((inputValue - minValue) * 100) / (maxValue - minValue)); + + this.value = inputValue; + this.thumbStyle = { left: `calc(${newValue}% + (${8 - newValue * 0.15}px))` }; + } + + // Control Value Accessor Methods + onChange = (_: any) => {}; + onTouched = () => {}; + + writeValue(value: any): void { + this.value = value; + + this._cdRef.markForCheck(); + + setTimeout(() => { + this.thumbPositionUpdate(); + }, 0); + } + + registerOnChange(fn: (_: any) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + static ngAcceptInputType_default: BooleanInput; + static ngAcceptInputType_disabled: BooleanInput; +} diff --git a/projects/mdb-angular-ui-kit/range/range.module.ts b/projects/mdb-angular-ui-kit/range/range.module.ts new file mode 100644 index 00000000..3347ea45 --- /dev/null +++ b/projects/mdb-angular-ui-kit/range/range.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { MdbRangeComponent } from './range.component'; + +@NgModule({ + imports: [CommonModule, FormsModule], + declarations: [MdbRangeComponent], + exports: [MdbRangeComponent], +}) +export class MdbRangeModule {} diff --git a/projects/mdb-angular-ui-kit/ripple/index.ts b/projects/mdb-angular-ui-kit/ripple/index.ts new file mode 100644 index 00000000..4aaf8f92 --- /dev/null +++ b/projects/mdb-angular-ui-kit/ripple/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/projects/mdb-angular-ui-kit/ripple/ng-package.json b/projects/mdb-angular-ui-kit/ripple/ng-package.json new file mode 100644 index 00000000..ecef3ed8 --- /dev/null +++ b/projects/mdb-angular-ui-kit/ripple/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "index.ts" + } +} \ No newline at end of file diff --git a/projects/mdb-angular-ui-kit/ripple/public_api.ts b/projects/mdb-angular-ui-kit/ripple/public_api.ts new file mode 100644 index 00000000..bcab2457 --- /dev/null +++ b/projects/mdb-angular-ui-kit/ripple/public_api.ts @@ -0,0 +1,2 @@ +export { MdbRippleDirective } from './ripple.directive'; +export { MdbRippleModule } from './ripple.module'; diff --git a/projects/mdb-angular-ui-kit/ripple/ripple-utils.spec.ts b/projects/mdb-angular-ui-kit/ripple/ripple-utils.spec.ts new file mode 100644 index 00000000..a6c57d99 --- /dev/null +++ b/projects/mdb-angular-ui-kit/ripple/ripple-utils.spec.ts @@ -0,0 +1,119 @@ +import { colorToRGB, durationToMsNumber, getDiameter } from './ripple-utils'; + +describe('Ripple utils', () => { + const DEFAULT_RIPPLE_COLOR = [0, 0, 0]; + + it('should convert seconds string to miliseconds in number format', () => { + expect(durationToMsNumber('5s')).toEqual(5000); + }); + + it('should convert miliseconds string to miliseconds in number format', () => { + expect(durationToMsNumber('3000ms')).toEqual(3000); + }); + + it('should convert hex color to rgb', () => { + expect(colorToRGB('#c953d6')).toEqual([201, 83, 214]); + }); + + it('should convert short hex color to rgb', () => { + expect(colorToRGB('#fff')).toEqual([255, 255, 255]); + }); + + it('should convert rgba color to rgb', () => { + expect(colorToRGB('rgba(255,0,0,0.5)')).toEqual([255, 0, 0]); + }); + + it('should return default ripple color for transparent color', () => { + expect(colorToRGB('transparent')).toEqual(DEFAULT_RIPPLE_COLOR); + }); + + it('should return correct diameter for click in the first quadrant', () => { + const height = 50; + const width = 100; + const offsetX = 90; + const offsetY = 10; + + const pythagorean = (sideA: number, sideB: number) => Math.sqrt(sideA ** 2 + sideB ** 2); + + const getCorner = { + topLeft: pythagorean(offsetX, offsetY), + topRight: pythagorean(width - offsetX, offsetY), + bottomLeft: pythagorean(offsetX, height - offsetY), + bottomRight: pythagorean(width - offsetX, height - offsetY), + }; + + expect(getDiameter({ offsetX, offsetY, height, width })).toEqual(getCorner.bottomLeft * 2); + }); + + it('should return correct diameter for click in the second quadrant', () => { + const height = 50; + const width = 100; + const offsetX = 15; + const offsetY = 10; + + const pythagorean = (sideA: number, sideB: number) => Math.sqrt(sideA ** 2 + sideB ** 2); + + const getCorner = { + topLeft: pythagorean(offsetX, offsetY), + topRight: pythagorean(width - offsetX, offsetY), + bottomLeft: pythagorean(offsetX, height - offsetY), + bottomRight: pythagorean(width - offsetX, height - offsetY), + }; + + expect(getDiameter({ offsetX, offsetY, height, width })).toEqual(getCorner.bottomRight * 2); + }); + + it('should return correct diameter for click in the third quadrant', () => { + const height = 50; + const width = 100; + const offsetX = 15; + const offsetY = 90; + + const pythagorean = (sideA: number, sideB: number) => Math.sqrt(sideA ** 2 + sideB ** 2); + + const getCorner = { + topLeft: pythagorean(offsetX, offsetY), + topRight: pythagorean(width - offsetX, offsetY), + bottomLeft: pythagorean(offsetX, height - offsetY), + bottomRight: pythagorean(width - offsetX, height - offsetY), + }; + + expect(getDiameter({ offsetX, offsetY, height, width })).toEqual(getCorner.topRight * 2); + }); + + it('should return correct diameter for click in the fourth quadrant', () => { + const height = 50; + const width = 100; + const offsetX = 90; + const offsetY = 30; + + const pythagorean = (sideA: number, sideB: number) => Math.sqrt(sideA ** 2 + sideB ** 2); + + const getCorner = { + topLeft: pythagorean(offsetX, offsetY), + topRight: pythagorean(width - offsetX, offsetY), + bottomLeft: pythagorean(offsetX, height - offsetY), + bottomRight: pythagorean(width - offsetX, height - offsetY), + }; + + expect(getDiameter({ offsetX, offsetY, height, width })).toEqual(getCorner.topLeft * 2); + }); + + it('should return correct diameter for click in the center', () => { + const height = 50; + const width = 100; + const offsetX = 50; + const offsetY = 25; + + const pythagorean = (sideA: number, sideB: number) => Math.sqrt(sideA ** 2 + sideB ** 2); + + const getCorner = { + topLeft: pythagorean(offsetX, offsetY), + topRight: pythagorean(width - offsetX, offsetY), + bottomLeft: pythagorean(offsetX, height - offsetY), + bottomRight: pythagorean(width - offsetX, height - offsetY), + }; + + expect(getDiameter({ offsetX, offsetY, height, width })).toEqual(getCorner.topLeft * 2); + }); +}); diff --git a/projects/mdb-angular-ui-kit/ripple/ripple-utils.ts b/projects/mdb-angular-ui-kit/ripple/ripple-utils.ts new file mode 100644 index 00000000..bf148847 --- /dev/null +++ b/projects/mdb-angular-ui-kit/ripple/ripple-utils.ts @@ -0,0 +1,100 @@ +const DEFAULT_RIPPLE_COLOR = [0, 0, 0]; + +export function durationToMsNumber(time: string): number { + return Number(time.replace('ms', '').replace('s', '000')); +} + +export function colorToRGB(color: any): number[] { + function hexToRgb(color: any): any { + const HEX_COLOR_LENGTH = 7; + const IS_SHORT_HEX = color.length < HEX_COLOR_LENGTH; + if (IS_SHORT_HEX) { + color = `#${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}`; + } + return [ + parseInt(color.substr(1, 2), 16), + parseInt(color.substr(3, 2), 16), + parseInt(color.substr(5, 2), 16), + ]; + } + + function namedColorsToRgba(color: any): any { + const tempElem = document.body.appendChild(document.createElement('fictum')); + const flag = 'rgb(1, 2, 3)'; + tempElem.style.color = flag; + if (tempElem.style.color !== flag) { + return DEFAULT_RIPPLE_COLOR; + } + tempElem.style.color = color; + if (tempElem.style.color === flag || tempElem.style.color === '') { + return DEFAULT_RIPPLE_COLOR; + } // color parse failed + color = getComputedStyle(tempElem).color; + document.body.removeChild(tempElem); + return color; + } + + function rgbaToRgb(color: any): any { + color = color.match(/[.\d]+/g).map((a) => +Number(a)); + color.length = 3; + return color; + } + + if (color.toLowerCase() === 'transparent') { + return DEFAULT_RIPPLE_COLOR; + } + if (color[0] === '#') { + return hexToRgb(color); + } + if (color.indexOf('rgb') === -1) { + color = namedColorsToRgba(color); + } + if (color.indexOf('rgb') === 0) { + return rgbaToRgb(color); + } + + return DEFAULT_RIPPLE_COLOR; +} + +export function getDiameter({ + offsetX, + offsetY, + height, + width, +}: { + [key: string]: number; +}): number { + const top = offsetY <= height / 2; + const left = offsetX <= width / 2; + const pythagorean = (sideA: number, sideB: number) => Math.sqrt(sideA ** 2 + sideB ** 2); + + const positionCenter = offsetY === height / 2 && offsetX === width / 2; + // mouse position on the quadrants of the coordinate system + const quadrant = { + first: top === true && left === false, + second: top === true && left === true, + third: top === false && left === true, + fourth: top === false && left === false, + }; + + const getCorner = { + topLeft: pythagorean(offsetX, offsetY), + topRight: pythagorean(width - offsetX, offsetY), + bottomLeft: pythagorean(offsetX, height - offsetY), + bottomRight: pythagorean(width - offsetX, height - offsetY), + }; + + let diameter = 0; + + if (positionCenter || quadrant.fourth) { + diameter = getCorner.topLeft; + } else if (quadrant.third) { + diameter = getCorner.topRight; + } else if (quadrant.second) { + diameter = getCorner.bottomRight; + } else if (quadrant.first) { + diameter = getCorner.bottomLeft; + } + + return diameter * 2; +} diff --git a/projects/mdb-angular-ui-kit/ripple/ripple.directive.spec.ts b/projects/mdb-angular-ui-kit/ripple/ripple.directive.spec.ts new file mode 100644 index 00000000..6045bf52 --- /dev/null +++ b/projects/mdb-angular-ui-kit/ripple/ripple.directive.spec.ts @@ -0,0 +1,138 @@ +import { Component } from '@angular/core'; +import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing'; +import { MdbRippleModule } from './ripple.module'; + +const template = ` + +`; + +@Component({ + selector: 'mdb-ripple-test', + template, + standalone: false, +}) +class TestRippleComponent { + rippleCentered = true; + rippleColor = ''; + rippleDuration = '1s'; + rippleRadius = 100; + rippleUnbound = false; +} + +describe('MDB Ripple', () => { + let fixture: ComponentFixture; + let component: any; + let button: any; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestRippleComponent], + imports: [MdbRippleModule], + teardown: { destroyAfterEach: false }, + }); + fixture = TestBed.createComponent(TestRippleComponent); + fixture.detectChanges(); + component = fixture.componentInstance; + button = document.querySelector('.btn'); + }); + + it('should set class ripple-surface on element', () => { + expect(button.classList.contains('ripple-surface')).toBe(true); + }); + + it('should create helper element on click', () => { + button.click(); + + fixture.detectChanges(); + expect(button.children[0]).not.toBe(null); + }); + + it('should add class ripple-wave on helper element', () => { + button.click(); + + fixture.detectChanges(); + + expect(button.children[0]).not.toBe(null); + expect(button.children[0].classList.contains('ripple-wave')).toBe(true); + }); + + it('should set class ripple-surface-unbound on wrapper if rippleUnbound option is true', () => { + component.rippleUnbound = true; + + fixture.detectChanges(); + + button.click(); + + fixture.detectChanges(); + expect(button.classList.contains('ripple-surface-unbound')).toBe(true); + }); + + it('should remove helper after duration', fakeAsync(() => { + button.click(); + + fixture.detectChanges(); + + let helper = button.children[0]; + + expect(helper).not.toBe(null); + + tick(1000); + + helper = button.children[0]; + + expect(helper).toBe(undefined); + })); + + it('should accept Bootstrap colors', () => { + fixture.componentInstance.rippleColor = 'primary'; + fixture.detectChanges(); + + button.click(); + fixture.detectChanges(); + + expect(button.classList).toContain('ripple-surface-primary'); + }); + + it('should add new colors class and remove previous color classes', fakeAsync(() => { + fixture.componentInstance.rippleColor = 'primary'; + fixture.detectChanges(); + + button.click(); + fixture.detectChanges(); + + expect(button.classList).toContain('ripple-surface-primary'); + + fixture.componentInstance.rippleColor = 'secondary'; + fixture.detectChanges(); + + button.click(); + fixture.detectChanges(); + + flush(); + + expect(button.classList).not.toContain('ripple-surface-primary'); + expect(button.classList).toContain('ripple-surface-secondary'); + })); + + it('should add ripple-surface-color class only if Bootstrap color type is used', () => { + const REGEXP_CLASS_COLOR = new RegExp(`${'ripple-surface'}-[a-z]+`, 'gi'); + + fixture.componentInstance.rippleColor = '#c953d6'; + fixture.detectChanges(); + + button.click(); + fixture.detectChanges(); + + expect(REGEXP_CLASS_COLOR.test(button.classList)).toBe(false); + }); +}); diff --git a/projects/mdb-angular-ui-kit/ripple/ripple.directive.ts b/projects/mdb-angular-ui-kit/ripple/ripple.directive.ts new file mode 100644 index 00000000..e9270fe0 --- /dev/null +++ b/projects/mdb-angular-ui-kit/ripple/ripple.directive.ts @@ -0,0 +1,205 @@ +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; +import { Directive, ElementRef, HostBinding, HostListener, Input, Renderer2 } from '@angular/core'; +import { colorToRGB, durationToMsNumber, getDiameter } from './ripple-utils'; + +const TRANSITION_BREAK_OPACITY = 0.5; + +const GRADIENT = + 'rgba({{color}}, 0.2) 0, rgba({{color}}, 0.3) 40%, rgba({{color}}, 0.4) 50%, rgba({{color}}, 0.5) 60%, rgba({{color}}, 0) 70%'; +const BOOTSTRAP_COLORS = [ + 'primary', + 'secondary', + 'success', + 'danger', + 'warning', + 'info', + 'light', + 'dark', +]; + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbRipple]', + exportAs: 'mdbRipple', + standalone: false, +}) +export class MdbRippleDirective { + @Input() + get rippleCentered(): boolean { + return this._rippleCentered; + } + set rippleCentered(value: boolean) { + this._rippleCentered = coerceBooleanProperty(value); + } + private _rippleCentered = false; + + @Input() rippleColor = ''; + @Input() rippleDuration = '500ms'; + @Input() rippleRadius = 0; + + @Input() + get rippleUnbound(): boolean { + return this._rippleUnbound; + } + set rippleUnbound(value: boolean) { + this._rippleUnbound = coerceBooleanProperty(value); + } + private _rippleUnbound = false; + + private _rippleInSpan = false; + + private _rippleTimer = null; + + constructor(private _elementRef: ElementRef, private _renderer: Renderer2) {} + + get host(): HTMLElement { + return this._elementRef.nativeElement; + } + + @HostBinding('class.ripple-surface') ripple = true; + + @HostListener('click', ['$event']) + _createRipple(event: any): void { + const { layerX, layerY } = event; + const offsetX = layerX; + const offsetY = layerY; + const height = this.host.offsetHeight; + const width = this.host.offsetWidth; + const duration = durationToMsNumber(this.rippleDuration); + const diameterOptions = { + offsetX: this.rippleCentered ? height / 2 : offsetX, + offsetY: this.rippleCentered ? width / 2 : offsetY, + height, + width, + }; + const diameter = getDiameter(diameterOptions); + const radiusValue = this.rippleRadius || diameter / 2; + + const opacity = { + delay: duration * TRANSITION_BREAK_OPACITY, + duration: duration - duration * TRANSITION_BREAK_OPACITY, + }; + + const styles = { + left: this.rippleCentered ? `${width / 2 - radiusValue}px` : `${offsetX - radiusValue}px`, + top: this.rippleCentered ? `${height / 2 - radiusValue}px` : `${offsetY - radiusValue}px`, + height: `${this.rippleRadius * 2 || diameter}px`, + width: `${this.rippleRadius * 2 || diameter}px`, + transitionDelay: `0s, ${opacity.delay}ms`, + transitionDuration: `${duration}ms, ${opacity.duration}ms`, + }; + + const rippleHTML = this._renderer.createElement('div'); + + if (this.host.tagName.toLowerCase() === 'input') { + this._createWrapperSpan(); + } + + this._createHTMLRipple(this.host, rippleHTML, styles); + this._removeHTMLRipple(rippleHTML, duration); + } + + private _createWrapperSpan(): void { + const parent = this._renderer.parentNode(this.host); + this._rippleInSpan = true; + if (parent.tagName.toLowerCase() === 'span' && parent.classList.contains('ripple-surface')) { + this._elementRef.nativeElement = parent; + } else { + const wrapper = this._renderer.createElement('span'); + + this._renderer.addClass(wrapper, 'ripple-surface'); + this._renderer.addClass(wrapper, 'input-wrapper'); + + this._renderer.setStyle(wrapper, 'border', 0); + + const shadow = getComputedStyle(this.host).boxShadow; + this._renderer.setStyle(wrapper, 'box-shadow', shadow); + + // Put element as child + parent.replaceChild(wrapper, this.host); + wrapper.appendChild(this.host); + this._elementRef.nativeElement = wrapper; + } + this.host.focus(); + } + + _removeWrapperSpan() { + const child = this.host.firstChild; + this.host.replaceWith(child); + this._elementRef.nativeElement = child; + this.host.focus(); + this._rippleInSpan = false; + } + + private _createHTMLRipple(wrapper: HTMLElement, ripple: HTMLElement, styles: any): void { + Object.keys(styles).forEach((property) => (ripple.style[property] = styles[property])); + this._renderer.addClass(ripple, 'ripple-wave'); + + if (this.rippleColor !== '') { + this._removeOldColorClasses(wrapper); + this._addColor(ripple, wrapper); + } + + this._toggleUnbound(wrapper); + this._appendRipple(ripple, wrapper); + } + + private _removeHTMLRipple(ripple: HTMLElement, duration: number): void { + if (this._rippleTimer) { + clearTimeout(this._rippleTimer); + this._rippleTimer = null; + } + this._rippleTimer = setTimeout(() => { + if (ripple) { + ripple.remove(); + this.host.querySelectorAll('.ripple-wave').forEach((rippleEl) => { + rippleEl.remove(); + }); + if (this._rippleInSpan && this.host.classList.contains('input-wrapper')) { + this._removeWrapperSpan(); + } + } + }, duration); + } + + _appendRipple(target: HTMLElement, parent: HTMLElement): void { + const FIX_ADD_RIPPLE_EFFECT = 50; // delay for active animations + this._renderer.appendChild(parent, target); + setTimeout(() => { + this._renderer.addClass(target, 'active'); + }, FIX_ADD_RIPPLE_EFFECT); + } + + _toggleUnbound(target: HTMLElement): void { + if (this.rippleUnbound) { + this._renderer.addClass(target, 'ripple-surface-unbound'); + } else { + this._renderer.removeClass(target, 'ripple-surface-unbound'); + } + } + + _addColor(target: HTMLElement, parent: HTMLElement): void { + const isBootstrapColor = BOOTSTRAP_COLORS.find( + (color) => color === this.rippleColor.toLowerCase() + ); + + if (isBootstrapColor) { + this._renderer.addClass(parent, `${'ripple-surface'}-${this.rippleColor.toLowerCase()}`); + } else { + const rgbValue = colorToRGB(this.rippleColor).join(','); + const gradientImage = GRADIENT.split('{{color}}').join(`${rgbValue}`); + target.style.backgroundImage = `radial-gradient(circle, ${gradientImage})`; + } + } + + _removeOldColorClasses(target: HTMLElement): void { + const REGEXP_CLASS_COLOR = new RegExp(`${'ripple-surface'}-[a-z]+`, 'gi'); + const PARENT_CLASSS_COLOR = target.classList.value.match(REGEXP_CLASS_COLOR) || []; + PARENT_CLASSS_COLOR.forEach((className) => { + this._renderer.removeClass(target, className); + }); + } + + static ngAcceptInputType_rippleCentered: BooleanInput; + static ngAcceptInputType_rippleUnbound: BooleanInput; +} diff --git a/projects/mdb-angular-ui-kit/ripple/ripple.module.ts b/projects/mdb-angular-ui-kit/ripple/ripple.module.ts new file mode 100644 index 00000000..3ff0a9d7 --- /dev/null +++ b/projects/mdb-angular-ui-kit/ripple/ripple.module.ts @@ -0,0 +1,9 @@ +import { NgModule } from '@angular/core'; +import { MdbRippleDirective } from './ripple.directive'; + +@NgModule({ + declarations: [MdbRippleDirective], + imports: [], + exports: [MdbRippleDirective], +}) +export class MdbRippleModule {} diff --git a/projects/angular-bootstrap-md/schematics/collection.json b/projects/mdb-angular-ui-kit/schematics/collection.json similarity index 73% rename from projects/angular-bootstrap-md/schematics/collection.json rename to projects/mdb-angular-ui-kit/schematics/collection.json index 71b017f9..c7e10c80 100644 --- a/projects/angular-bootstrap-md/schematics/collection.json +++ b/projects/mdb-angular-ui-kit/schematics/collection.json @@ -2,14 +2,14 @@ "$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "ng-add": { - "description": "Add MDB Angular Free to the application.", + "description": "Add MDB Angular Ui Kit to the application.", "factory": "./ng-add/index#ngAdd", "schema": "./ng-add/schema.json" }, "ng-add-mdb-setup": { - "description": "Setup MDB Angular Free.", + "description": "Setup MDB Angular.", "factory": "./ng-add/mdb-setup", "schema": "./ng-add/schema.json" } } -} \ No newline at end of file +} diff --git a/projects/angular-bootstrap-md/schematics/ng-add/index.ts b/projects/mdb-angular-ui-kit/schematics/ng-add/index.ts similarity index 68% rename from projects/angular-bootstrap-md/schematics/ng-add/index.ts rename to projects/mdb-angular-ui-kit/schematics/ng-add/index.ts index c94af168..ad1cda85 100644 --- a/projects/angular-bootstrap-md/schematics/ng-add/index.ts +++ b/projects/mdb-angular-ui-kit/schematics/ng-add/index.ts @@ -6,22 +6,24 @@ import { addPackageToPackageJson } from './package'; // Just return the tree export function ngAdd(options: Schema): Rule { return (tree: Tree, context: SchematicContext) => { - const angularDependencyVersion = '^9.0.0'; + const angularDependencyVersion = '^20.0.0'; addPackageToPackageJson(tree, '@angular/cdk', angularDependencyVersion); addPackageToPackageJson(tree, '@angular/forms', angularDependencyVersion); addPackageToPackageJson(tree, '@angular/animations', angularDependencyVersion); - if (options.externalDependencies) { - addPackageToPackageJson(tree, 'chart.js', '^2.7.2'); - addPackageToPackageJson(tree, '@types/chart.js', '^2.7.40'); - addPackageToPackageJson(tree, '@fortawesome/fontawesome-free', '~5.6.3'); - addPackageToPackageJson(tree, 'hammerjs', '~2.0.8'); - addPackageToPackageJson(tree, 'animate.css', '~3.7.2'); + if (options.fontAwesome) { + addPackageToPackageJson(tree, '@fortawesome/fontawesome-free', '^6.0.0'); + } + + if (options.charts) { + addPackageToPackageJson(tree, 'chart.js', '^3.1.1'); } const installMainDependenciesTask = context.addTask(new NodePackageInstallTask()); - context.addTask(new RunSchematicTask('ng-add-mdb-setup', options), [installMainDependenciesTask]); + context.addTask(new RunSchematicTask('ng-add-mdb-setup', options), [ + installMainDependenciesTask, + ]); }; } diff --git a/projects/mdb-angular-ui-kit/schematics/ng-add/mdb-setup.ts b/projects/mdb-angular-ui-kit/schematics/ng-add/mdb-setup.ts new file mode 100644 index 00000000..056e62e2 --- /dev/null +++ b/projects/mdb-angular-ui-kit/schematics/ng-add/mdb-setup.ts @@ -0,0 +1,238 @@ +import { SchematicContext, Tree, chain } from '@angular-devkit/schematics'; +import { getWorkspace } from '@schematics/angular/utility/workspace'; +import { ProjectType } from '@schematics/angular/utility/workspace-models'; +import { + getProjectMainFile, + addModuleImportToRootModule, + getProjectFromWorkspace, + getProjectIndexFiles, + appendHtmlElementToHead, + getProjectStyleFile, + isStandaloneApp, +} from '@angular/cdk/schematics'; +import { addRootProvider } from '@schematics/angular/utility'; +import { Schema } from './schema'; + +const mdbModules = [ + { name: 'MdbAccordionModule', path: 'mdb-angular-ui-kit/accordion'}, + { name: 'MdbCarouselModule', path: 'mdb-angular-ui-kit/carousel'}, + { name: 'MdbCheckboxModule', path: 'mdb-angular-ui-kit/checkbox'}, + { name: 'MdbCollapseModule', path: 'mdb-angular-ui-kit/collapse'}, + { name: 'MdbDropdownModule', path: 'mdb-angular-ui-kit/dropdown'}, + { name: 'MdbFormsModule', path: 'mdb-angular-ui-kit/forms'}, + { name: 'MdbModalModule', path: 'mdb-angular-ui-kit/modal'}, + { name: 'MdbPopoverModule', path: 'mdb-angular-ui-kit/popover'}, + { name: 'MdbRadioModule', path: 'mdb-angular-ui-kit/radio'}, + { name: 'MdbRangeModule', path: 'mdb-angular-ui-kit/range'}, + { name: 'MdbRippleModule', path: 'mdb-angular-ui-kit/ripple'}, + { name: 'MdbScrollspyModule', path: 'mdb-angular-ui-kit/scrollspy'}, + { name: 'MdbTabsModule', path: 'mdb-angular-ui-kit/tabs'}, + { name: 'MdbTooltipModule', path: 'mdb-angular-ui-kit/tooltip'}, + { name: 'MdbValidationModule', path: 'mdb-angular-ui-kit/validation'}, +]; + +// eslint-disable-next-line space-before-function-paren +export default function (options: Schema): any { + return async (tree: Tree) => { + const workspace: any = await getWorkspace(tree); + const project = getProjectFromWorkspace(workspace, options.project); + + if (project.extensions.projectType === ProjectType.Application) { + return chain([ + addMdbModulesImports(options), + addAngularAnimationsModule(options), + addStylesImports(options), + addChartsToScripts(options), + addRobotoFontToIndexHtml(options), + updateAppComponentContent(), + ]); + } + return; + }; +} + +function addMdbModulesImports(options: Schema): any { + return async (tree: Tree) => { + const workspace: any = await getWorkspace(tree); + const project = getProjectFromWorkspace(workspace, options.project); + const mainFile = getProjectMainFile(project); + + if (isStandaloneApp(tree, mainFile)) { + return; + } + + if (options.modules) { + mdbModules.forEach((module) => { + addModuleImportToRootModule(tree, module.name, module.path, project); + }); + } + + return tree; + }; +} + +function addAngularAnimationsModule(options: Schema): any { + return () => { + return addRootProvider(options.project, ({ code, external }) => { + return code`${external('provideAnimations', '@angular/platform-browser/animations')}(${ + options.animations ? '' : `'noop'` + })`; + }); + }; +} + +function addRobotoFontToIndexHtml(options: Schema): any { + return async (tree: Tree, context: SchematicContext) => { + const fontUrl = 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,600&display=swap'; + const workspace: any = await getWorkspace(tree); + const project: any = getProjectFromWorkspace(workspace, options.project); + const projectIndexFiles = getProjectIndexFiles(project); + const logger = context.logger; + + if (options.robotoFont) { + if (!projectIndexFiles.length) { + logger.error('Index HTML not found'); + logger.info('Add roboto font manually'); + return; + } + + projectIndexFiles.forEach((indexFile: any) => { + appendHtmlElementToHead(tree, indexFile, ``); + }); + } + + return tree; + }; +} + +function addStylesImports(options: Schema): any { + return async (host: Tree, context: SchematicContext) => { + const workspace: any = await getWorkspace(host); + const project = getProjectFromWorkspace(workspace, options.project); + const logger = context.logger; + const styleFilePath = getProjectStyleFile(project); + + if (!styleFilePath) { + logger.error( + `Could not find the default style file for this project. Please add styles imports manually` + ); + return; + } + + const buffer = host.read(styleFilePath); + + if (!buffer) { + logger.error( + `Could not read the default style file for this project. Please add styles imports manually` + ); + return; + } + + const fileContent = buffer.toString(); + + let newContent: string; + + if (options.fontAwesome) { + newContent = + `@import '@fortawesome/fontawesome-free/css/all.css';\n` + + `@import 'mdb-angular-ui-kit/assets/scss/mdb.scss';\n`; + } else { + newContent = `@import 'mdb-angular-ui-kit/assets/scss/mdb.scss';\n`; + } + + if (fileContent.includes(newContent)) { + return; + } + + const recorder = host.beginUpdate(styleFilePath); + + recorder.insertLeft(fileContent.length, newContent); + host.commitUpdate(recorder); + }; +} + +function addChartsToScripts(options: Schema): any { + return async (host: Tree, context: SchematicContext) => { + const logger = context.logger; + + const chartsPath = 'node_modules/chart.js/dist/chart.js'; + + if (options.charts) { + const angularJsonFile = host.read('angular.json'); + + if (angularJsonFile) { + const angularJsonFileObject = JSON.parse(angularJsonFile.toString('utf-8')); + const project = options.project + ? options.project + : Object.keys(angularJsonFileObject.projects)[0]; + const projectObject = angularJsonFileObject.projects[project]; + if (!projectObject.architect.build.options.scripts) { + projectObject.architect.build.options.scripts = []; + } + const scripts = projectObject.architect.build.options.scripts; + + scripts.push(chartsPath); + + host.overwrite('angular.json', JSON.stringify(angularJsonFileObject, null, 2)); + } else { + logger.error('Failed to add charts script to angular.json'); + } + } + }; +} + +function updateAppComponentContent(): any { + return async (host: Tree, context: SchematicContext) => { + const filePath = './src/app/app.html'; + const logger = context.logger; + const buffer = host.read(filePath); + + if (!buffer) { + logger.error('No buffer'); + return; + } + + const fileContent = buffer.toString(); + + const newContent = + `
\n` + + `
\n` + + `
\n` + + ` \n` + + `
Thank you for using our product. We're glad you're with us.
\n` + + `

MDB Team

\n` + + `

\n` + + ` PS. We'll be releasing "How to build your first project with MDB 5 Angular" tutorial soon.\n` + + `

\n` + + ` Join now\n` + + `
\n` + + `
\n` + + `
`; + + const hasNewContent = fileContent.includes(newContent); + const hasDefaultContent = + fileContent.includes('Delete the template below') && + fileContent.includes('to get started with your project!') && + fileContent.includes('Congratulations! Your app is running.'); + + if (hasNewContent || !hasDefaultContent) { + return; + } + + const recorder = host.beginUpdate(filePath); + + recorder.remove(0, fileContent.length); + recorder.insertLeft(0, newContent); + host.commitUpdate(recorder); + }; +} diff --git a/projects/angular-bootstrap-md/schematics/ng-add/package.ts b/projects/mdb-angular-ui-kit/schematics/ng-add/package.ts similarity index 94% rename from projects/angular-bootstrap-md/schematics/ng-add/package.ts rename to projects/mdb-angular-ui-kit/schematics/ng-add/package.ts index 3847562f..3c30a87d 100644 --- a/projects/angular-bootstrap-md/schematics/ng-add/package.ts +++ b/projects/mdb-angular-ui-kit/schematics/ng-add/package.ts @@ -1,6 +1,6 @@ import { Tree } from '@angular-devkit/schematics'; -function sortObjectByKeys(obj: any) { +function sortObjectByKeys(obj: any): any { return Object.keys(obj) .sort() .reduce((result: any, key) => (result[key] = obj[key]) && result, {}); diff --git a/projects/mdb-angular-ui-kit/schematics/ng-add/schema.json b/projects/mdb-angular-ui-kit/schematics/ng-add/schema.json new file mode 100644 index 00000000..937f7f60 --- /dev/null +++ b/projects/mdb-angular-ui-kit/schematics/ng-add/schema.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://json-schema.org/schema", + "$id": "mdb-angular-ng-add", + "title": "MDB Angular ng-add schematic", + "type": "object", + "properties": { + "project": { + "type": "string", + "description": "Name of the project.", + "$default": { + "$source": "projectName" + } + }, + "modules": { + "type": "boolean", + "default": true, + "description": "Whether to import all MDB modules.", + "x-prompt": "Import all MDB modules?" + }, + "robotoFont": { + "type": "boolean", + "default": true, + "description": "Whether to add Roboto Font.", + "x-prompt": "Set up Roboto Font?" + }, + "animations": { + "type": "boolean", + "default": true, + "description": "Whether to set up browser animations.", + "x-prompt": "Set up Angular browser animations?" + }, + "fontAwesome": { + "type": "boolean", + "default": true, + "description": "Whether to install and configure Font Awesome.", + "x-prompt": "Set up Font Awesome?" + }, + "charts": { + "type": "boolean", + "default": true, + "description": "Whether to install and configure Charts.", + "x-prompt": "Set up Charts?" + } + }, + "required": [] +} diff --git a/projects/mdb-angular-ui-kit/schematics/ng-add/schema.ts b/projects/mdb-angular-ui-kit/schematics/ng-add/schema.ts new file mode 100644 index 00000000..783672fa --- /dev/null +++ b/projects/mdb-angular-ui-kit/schematics/ng-add/schema.ts @@ -0,0 +1,8 @@ +export interface Schema { + project: string; + modules: boolean; + robotoFont: boolean; + animations: boolean; + fontAwesome: boolean; + charts: boolean; +} diff --git a/projects/mdb-angular-ui-kit/scrollspy/index.ts b/projects/mdb-angular-ui-kit/scrollspy/index.ts new file mode 100644 index 00000000..4aaf8f92 --- /dev/null +++ b/projects/mdb-angular-ui-kit/scrollspy/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/projects/mdb-angular-ui-kit/scrollspy/ng-package.json b/projects/mdb-angular-ui-kit/scrollspy/ng-package.json new file mode 100644 index 00000000..ecef3ed8 --- /dev/null +++ b/projects/mdb-angular-ui-kit/scrollspy/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "index.ts" + } +} \ No newline at end of file diff --git a/projects/mdb-angular-ui-kit/scrollspy/public_api.ts b/projects/mdb-angular-ui-kit/scrollspy/public_api.ts new file mode 100644 index 00000000..697543bb --- /dev/null +++ b/projects/mdb-angular-ui-kit/scrollspy/public_api.ts @@ -0,0 +1,6 @@ +export { MdbScrollspyDirective } from './scrollspy.directive'; +export { MdbScrollspyElementDirective } from './scrollspy-element.directive'; +export { MdbScrollspyWindowDirective } from './scrollspy-window.directive'; +export { MdbScrollspyLinkDirective } from './scrollspy-link.directive'; +export { MdbScrollspyService } from './scrollspy.service'; +export { MdbScrollspyModule } from './scrollspy.module'; diff --git a/projects/mdb-angular-ui-kit/scrollspy/scrollspy-element.directive.ts b/projects/mdb-angular-ui-kit/scrollspy/scrollspy-element.directive.ts new file mode 100644 index 00000000..0265f064 --- /dev/null +++ b/projects/mdb-angular-ui-kit/scrollspy/scrollspy-element.directive.ts @@ -0,0 +1,101 @@ +import { + Directive, + ElementRef, + OnInit, + Renderer2, + NgZone, + Input, + AfterViewInit, + Inject, + DOCUMENT, +} from '@angular/core'; +import { MdbScrollspyService } from './scrollspy.service'; + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbScrollspyElement]', + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/directive-class-suffix +export class MdbScrollspyElementDirective implements OnInit, AfterViewInit { + private id: string; + + get host(): HTMLElement { + return this._elementRef.nativeElement; + } + + @Input() container: HTMLElement; + + @Input('mdbScrollspyElement') + get scrollSpyId(): string { + return this._scrollSpyId; + } + set scrollSpyId(newId: string) { + if (newId) { + this._scrollSpyId = newId; + } + } + private _scrollSpyId: string; + + @Input() offset = 0; + + constructor( + private _elementRef: ElementRef, + private renderer: Renderer2, + private ngZone: NgZone, + private scrollSpyService: MdbScrollspyService, + @Inject(DOCUMENT) private _document: any + ) {} + + isElementInViewport(): boolean { + const scrollTop = this.container.scrollTop; + const elTop = this.host.offsetTop - this.offset; + const elHeight = this.host.offsetHeight; + + return scrollTop >= elTop && scrollTop < elTop + elHeight; + } + + updateActiveState(scrollSpyId: string, id: string): void { + if (this.isElementInViewport()) { + this.scrollSpyService.removeActiveLinks(scrollSpyId); + this.scrollSpyService.updateActiveState(scrollSpyId, id); + } + } + + onScroll(): void { + this.updateActiveState(this.scrollSpyId, this.id); + } + + listenToScroll(): void { + this.renderer.listen(this.container, 'scroll', () => { + this.onScroll(); + }); + } + + ngOnInit(): void { + this.id = this.host.id; + + if (!this.container) { + this.container = this._getClosestEl(this.host, '.scrollspy-container'); + } + + this.renderer.setStyle(this.container, 'position', 'relative'); + + this.ngZone.runOutsideAngular(this.listenToScroll.bind(this)); + } + + ngAfterViewInit(): void { + setTimeout(() => { + this.updateActiveState(this.scrollSpyId, this.id); + }, 0); + } + + private _getClosestEl(el: any, selector: string): HTMLElement | null { + for (; el && el !== this._document; el = el.parentNode) { + if (el.matches && el.matches(selector)) { + return el; + } + } + return null; + } +} diff --git a/projects/mdb-angular-ui-kit/scrollspy/scrollspy-link.directive.spec.ts b/projects/mdb-angular-ui-kit/scrollspy/scrollspy-link.directive.spec.ts new file mode 100644 index 00000000..3b1e1e7d --- /dev/null +++ b/projects/mdb-angular-ui-kit/scrollspy/scrollspy-link.directive.spec.ts @@ -0,0 +1,28 @@ +import { MdbScrollspyLinkDirective } from './scrollspy-link.directive'; + +describe('ScrollspyDirective', () => { + let scrollspyLink: MdbScrollspyLinkDirective; + const cdRefMock = { + detectChanges: jest.fn(), + }; + + beforeEach(() => { + scrollspyLink = new MdbScrollspyLinkDirective(cdRefMock as any, document); + }); + + it('should get section with id equal to link id', () => { + scrollspyLink.id = 'scrollspy'; + const div = document.createElement('div'); + div.setAttribute('id', scrollspyLink.id); + document.body.appendChild(div); + + scrollspyLink.ngOnInit(); + + expect(scrollspyLink.section).toBe(div); + }); + + it('should activate change detection cycle', () => { + scrollspyLink.detectChanges(); + expect(cdRefMock.detectChanges).toHaveBeenCalled(); + }); +}); diff --git a/projects/mdb-angular-ui-kit/scrollspy/scrollspy-link.directive.ts b/projects/mdb-angular-ui-kit/scrollspy/scrollspy-link.directive.ts new file mode 100644 index 00000000..61cfe605 --- /dev/null +++ b/projects/mdb-angular-ui-kit/scrollspy/scrollspy-link.directive.ts @@ -0,0 +1,74 @@ +import { + Directive, + OnInit, + Input, + HostListener, + HostBinding, + ChangeDetectorRef, + Inject, + DOCUMENT, +} from '@angular/core'; + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbScrollspyLink]', + standalone: false, +}) +export class MdbScrollspyLinkDirective implements OnInit { + @Input() + get scrollIntoView(): boolean { + return this._scrollIntoView; + } + set scrollIntoView(value: boolean) { + this._scrollIntoView = value; + } + private _scrollIntoView = true; + + get section(): HTMLElement { + return this._section; + } + set section(value: HTMLElement) { + if (value) { + this._section = value; + } + } + private _section: HTMLElement; + private _id: string; + + constructor(private cdRef: ChangeDetectorRef, @Inject(DOCUMENT) private document: any) {} + + @Input('mdbScrollspyLink') + get id(): string { + return this._id; + } + set id(newId: string) { + if (newId) { + this._id = newId; + } + } + + @HostBinding('class.scrollspy-link') + scrollspyLink = true; + + @HostBinding('class.active') + active = false; + + @HostListener('click', []) + onClick(): void { + if (this.section && this.scrollIntoView === true) { + this.section.scrollIntoView(); + } + } + + detectChanges(): void { + this.cdRef.detectChanges(); + } + + assignSectionToId(): void { + this.section = this.document.documentElement.querySelector(`#${this.id}`); + } + + ngOnInit(): void { + this.assignSectionToId(); + } +} diff --git a/projects/mdb-angular-ui-kit/scrollspy/scrollspy-window.directive.ts b/projects/mdb-angular-ui-kit/scrollspy/scrollspy-window.directive.ts new file mode 100644 index 00000000..179d99e5 --- /dev/null +++ b/projects/mdb-angular-ui-kit/scrollspy/scrollspy-window.directive.ts @@ -0,0 +1,82 @@ +import { + Directive, + ElementRef, + OnInit, + Inject, + Renderer2, + NgZone, + Input, + AfterViewInit, + DOCUMENT, +} from '@angular/core'; + +import { MdbScrollspyService } from './scrollspy.service'; + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbScrollspyWindow]', + standalone: false, +}) +export class MdbScrollspyWindowDirective implements OnInit, AfterViewInit { + private id: string; + + @Input('mdbScrollspyWindow') + get scrollSpyId(): string { + return this._scrollSpyId; + } + set scrollSpyId(newId: string) { + if (newId) { + this._scrollSpyId = newId; + } + } + private _scrollSpyId: string; + + @Input() offset = 0; + + constructor( + @Inject(DOCUMENT) private document: any, + private el: ElementRef, + private renderer: Renderer2, + private ngZone: NgZone, + private scrollSpyService: MdbScrollspyService + ) {} + + isElementInViewport(): boolean { + const scrollTop = this.document.documentElement.scrollTop || this.document.body.scrollTop; + const elHeight = this.el.nativeElement.offsetHeight; + const elTop = this.el.nativeElement.offsetTop - this.offset; + const elBottom = elTop + elHeight; + + return scrollTop >= elTop && scrollTop <= elBottom; + } + + updateActiveState(scrollSpyId: string, id: string): void { + if (this.isElementInViewport()) { + this.scrollSpyService.updateActiveState(scrollSpyId, id); + } else { + this.scrollSpyService.removeActiveState(scrollSpyId, id); + } + } + + onScroll(): void { + this.updateActiveState(this.scrollSpyId, this.id); + } + + listenToScroll(): void { + this.renderer.listen(window, 'scroll', () => { + this.onScroll(); + }); + } + + ngOnInit(): void { + this.id = this.el.nativeElement.id; + + this.ngZone.runOutsideAngular(this.listenToScroll.bind(this)); + } + + ngAfterViewInit(): void { + setTimeout(() => { + this.updateActiveState(this.scrollSpyId, this.id); + }, 0); + } +} diff --git a/projects/mdb-angular-ui-kit/scrollspy/scrollspy.directive.spec.ts b/projects/mdb-angular-ui-kit/scrollspy/scrollspy.directive.spec.ts new file mode 100644 index 00000000..329406e0 --- /dev/null +++ b/projects/mdb-angular-ui-kit/scrollspy/scrollspy.directive.spec.ts @@ -0,0 +1,37 @@ +import { MdbScrollspyLinkDirective } from './scrollspy-link.directive'; +import { MdbScrollspyDirective } from './scrollspy.directive'; +import { MdbScrollspyService } from './scrollspy.service'; + +describe('ScrollspyDirective', () => { + let scrollspy: MdbScrollspyDirective; + let scrollspyService: MdbScrollspyService; + const cdRefMock = { + detectChanges: jest.fn(), + }; + + beforeEach(() => { + scrollspyService = new MdbScrollspyService(); + // scrollspy = new MdbScrollspyDirective(scrollspyService); + }); + + it('should add new scrollspy to service after content init', () => { + // const spy = jest.spyOn(scrollspyService, 'addScrollspy'); + // scrollspy.ngAfterContentInit(); + // expect(spy).toHaveBeenCalled(); + }); + + it('should remove scrollspy from service on destroy', () => { + // const spy = jest.spyOn(scrollspyService, 'removeScrollspy'); + // scrollspy.ngOnDestroy(); + // expect(spy).toHaveBeenCalled(); + }); + + it('should emit activeLinkChange event when active link change', () => { + // const spy = jest.spyOn(scrollspy.activeLinkChange, 'emit'); + // const document = DOCUMENT; + // const link = new MdbScrollspyLinkDirective(cdRefMock as any, document); + // scrollspy.ngOnInit(); + // scrollspyService.setActiveLink(link); + // expect(spy).toHaveBeenCalled(); + }); +}); diff --git a/projects/mdb-angular-ui-kit/scrollspy/scrollspy.directive.ts b/projects/mdb-angular-ui-kit/scrollspy/scrollspy.directive.ts new file mode 100644 index 00000000..868557a7 --- /dev/null +++ b/projects/mdb-angular-ui-kit/scrollspy/scrollspy.directive.ts @@ -0,0 +1,135 @@ +import { + AfterContentInit, + Component, + ContentChildren, + ElementRef, + EventEmitter, + Inject, + Input, + OnDestroy, + OnInit, + Output, + PLATFORM_ID, + QueryList, + Renderer2, +} from '@angular/core'; +import { MdbScrollspyLinkDirective } from './scrollspy-link.directive'; +import { MdbScrollspyService } from './scrollspy.service'; +import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; +import { Subject, Subscription } from 'rxjs'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { isPlatformBrowser } from '@angular/common'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: '[mdbScrollspy]', + template: '', + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +export class MdbScrollspyDirective implements OnInit, AfterContentInit, OnDestroy { + @ContentChildren(MdbScrollspyLinkDirective, { descendants: true }) + links: QueryList; + + readonly _destroy$: Subject = new Subject(); + + @Input('mdbScrollspy') + get id(): string { + return this._id; + } + + set id(newId: string) { + if (newId) { + this._id = newId; + } + } + + private _id: string; + + @Input() + get collapsible(): boolean { + return this._collapsible; + } + set collapsible(value: boolean) { + this._collapsible = coerceBooleanProperty(value); + } + + private _collapsible = false; + + private _isBrowser: boolean; + + @Output() activeLinkChange: EventEmitter = new EventEmitter(); + + activeSub: Subscription; + + constructor( + private scrollSpyService: MdbScrollspyService, + private _elementRef: ElementRef, + private _renderer: Renderer2, + @Inject(PLATFORM_ID) platformId: Object + ) { + this._isBrowser = isPlatformBrowser(platformId); + } + + get host(): HTMLElement { + return this._elementRef.nativeElement; + } + + collapsibleElementHeight = 0; + + ngOnInit(): void { + if (this._isBrowser) { + this.collapsibleElementHeight = this.host.getBoundingClientRect().height; + } + + this.activeSub = this.scrollSpyService.active$ + .pipe(takeUntil(this._destroy$), distinctUntilChanged()) + .subscribe((activeLink) => { + this.activeLinkChange.emit(activeLink); + if (this.collapsible) { + this.styleCollapsibleElement(); + } + }); + } + + ngAfterContentInit(): void { + this.scrollSpyService.addScrollspy({ id: this.id, links: this.links }); + } + + ngOnDestroy(): void { + this.scrollSpyService.removeScrollspy(this.id); + this._destroy$.next(); + this._destroy$.complete(); + } + + private styleCollapsibleElement(): void { + this._renderer.setStyle(this.host, 'overflow', 'hidden'); + this._renderer.setStyle(this.host, 'transition', 'height 0.2s ease-in-out'); + this._renderer.setStyle(this.host, 'flex-wrap', 'nowrap'); + + const hostSiblings = this.getAllSiblings(this.host); + const isAnySiblingActive = hostSiblings.some((element) => { + return element.classList.contains('active'); + }); + + if (this.collapsible && isAnySiblingActive) { + this._renderer.setStyle(this.host, 'height', `${this.collapsibleElementHeight}px`); + } else if (this.collapsible && !isAnySiblingActive) { + this._renderer.setStyle(this.host, 'height', '0px'); + } + } + + private getAllSiblings(element: HTMLElement) { + let siblings = []; + if (!element.parentNode) { + return siblings; + } + let sibling = element.parentNode.firstElementChild; + do { + if (sibling != element) { + siblings.push(sibling); + } + } while ((sibling = sibling.nextElementSibling)); + return siblings; + } +} diff --git a/projects/mdb-angular-ui-kit/scrollspy/scrollspy.module.ts b/projects/mdb-angular-ui-kit/scrollspy/scrollspy.module.ts new file mode 100644 index 00000000..018f0a0b --- /dev/null +++ b/projects/mdb-angular-ui-kit/scrollspy/scrollspy.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; + +import { MdbScrollspyDirective } from './scrollspy.directive'; +import { MdbScrollspyLinkDirective } from './scrollspy-link.directive'; +import { MdbScrollspyElementDirective } from './scrollspy-element.directive'; +import { MdbScrollspyService } from './scrollspy.service'; +import { MdbScrollspyWindowDirective } from './scrollspy-window.directive'; + +@NgModule({ + declarations: [ + MdbScrollspyDirective, + MdbScrollspyLinkDirective, + MdbScrollspyElementDirective, + MdbScrollspyWindowDirective, + ], + exports: [ + MdbScrollspyDirective, + MdbScrollspyLinkDirective, + MdbScrollspyElementDirective, + MdbScrollspyWindowDirective, + ], + providers: [MdbScrollspyService], +}) +export class MdbScrollspyModule {} diff --git a/projects/mdb-angular-ui-kit/scrollspy/scrollspy.service.spec.ts b/projects/mdb-angular-ui-kit/scrollspy/scrollspy.service.spec.ts new file mode 100644 index 00000000..35d5d912 --- /dev/null +++ b/projects/mdb-angular-ui-kit/scrollspy/scrollspy.service.spec.ts @@ -0,0 +1,82 @@ +import { TestBed, inject } from '@angular/core/testing'; +import { QueryList } from '@angular/core'; + +import { MdbScrollspyService } from './scrollspy.service'; +import { MdbScrollspyLinkDirective } from './scrollspy-link.directive'; + +describe('ScrollspyService', () => { + let scrollspyService: MdbScrollspyService; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [MdbScrollspyService], + teardown: { destroyAfterEach: false }, + }); + + inject([MdbScrollspyService], (service: MdbScrollspyService) => { + scrollspyService = service; + })(); + }); + + it('should add new scrollspy to the list', () => { + const links = new QueryList(); + const id = 'test-scrollspy'; + scrollspyService.addScrollspy({ id, links }); + + expect(scrollspyService.scrollSpys).toEqual([{ id: 'test-scrollspy', links }]); + }); + + it('should remove scrollspy from the list', () => { + const links = new QueryList(); + const id = 'test-scrollspy'; + scrollspyService.addScrollspy({ id, links }); + + expect(scrollspyService.scrollSpys).toEqual([{ id: 'test-scrollspy', links }]); + + scrollspyService.removeScrollspy('test-scrollspy'); + + expect(scrollspyService.scrollSpys).toEqual([]); + }); + + it('should correctly set and remove link active state', () => { + const links = [ + { + id: 'test-link', + active: false, + section: 'test-section', + detectChanges: () => {}, + }, + ]; + + scrollspyService.scrollSpys = [{ id: 'test-scrollspy', links }]; + + expect(links[0].active).toBe(false); + + scrollspyService.updateActiveState('test-scrollspy', 'test-link'); + + expect(links[0].active).toBe(true); + + scrollspyService.removeActiveState('test-scrollspy', 'test-link'); + }); + + it('should correctly remove active state from all links in specific scrollspy', () => { + const links = [ + { + id: 'test-link-1', + active: true, + section: 'test-section-1', + detectChanges: () => {}, + }, + { + id: 'test-link-2', + active: true, + section: 'test-section-2', + detectChanges: () => {}, + }, + ]; + + scrollspyService.scrollSpys = [{ id: 'test-scrollspy', links }]; + + scrollspyService.removeActiveLinks('test-scrollspy'); + }); +}); diff --git a/projects/mdb-angular-ui-kit/scrollspy/scrollspy.service.ts b/projects/mdb-angular-ui-kit/scrollspy/scrollspy.service.ts new file mode 100644 index 00000000..3ee523dc --- /dev/null +++ b/projects/mdb-angular-ui-kit/scrollspy/scrollspy.service.ts @@ -0,0 +1,87 @@ +import { Injectable, QueryList } from '@angular/core'; +import { MdbScrollspyLinkDirective } from './scrollspy-link.directive'; +import { Subject, Observable } from 'rxjs'; + +export interface MdbScrollspy { + id: string; + links: QueryList; +} + +@Injectable() +export class MdbScrollspyService { + scrollSpys: MdbScrollspy[] = []; + + private activeSubject = new Subject(); + active$: Observable = this.activeSubject; + + addScrollspy(scrollSpy: MdbScrollspy): void { + this.scrollSpys.push(scrollSpy); + } + + removeScrollspy(scrollSpyId: string): void { + const scrollSpyIndex = this.scrollSpys.findIndex((spy) => { + return spy.id === scrollSpyId; + }); + this.scrollSpys.splice(scrollSpyIndex, 1); + } + + updateActiveState(scrollSpyId: string, activeLinkId: string): void { + const scrollSpy = this.scrollSpys.find((spy) => { + return spy.id === scrollSpyId; + }); + + if (!scrollSpy) { + return; + } + + const activeLink = scrollSpy.links.find((link) => { + return link.id === activeLinkId; + }); + + this.setActiveLink(activeLink); + } + + removeActiveState(scrollSpyId: string, activeLinkId: string): void { + const scrollSpy = this.scrollSpys.find((spy) => { + return spy.id === scrollSpyId; + }); + + if (!scrollSpy) { + return; + } + + const activeLink = scrollSpy.links.find((link) => { + return link.id === activeLinkId; + }); + + if (!activeLink) { + return; + } + + activeLink.active = false; + activeLink.detectChanges(); + } + + setActiveLink(activeLink: MdbScrollspyLinkDirective | any): void { + if (activeLink) { + activeLink.active = true; + activeLink.detectChanges(); + this.activeSubject.next(activeLink); + } + } + + removeActiveLinks(scrollSpyId: string): void { + const scrollSpy: MdbScrollspy | undefined = this.scrollSpys.find((spy) => { + return spy.id === scrollSpyId; + }); + + if (!scrollSpy) { + return; + } + + scrollSpy.links.forEach((link) => { + link.active = false; + link.detectChanges(); + }); + } +} diff --git a/projects/mdb-angular-ui-kit/tabs/index.ts b/projects/mdb-angular-ui-kit/tabs/index.ts new file mode 100644 index 00000000..4aaf8f92 --- /dev/null +++ b/projects/mdb-angular-ui-kit/tabs/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/projects/mdb-angular-ui-kit/tabs/ng-package.json b/projects/mdb-angular-ui-kit/tabs/ng-package.json new file mode 100644 index 00000000..ecef3ed8 --- /dev/null +++ b/projects/mdb-angular-ui-kit/tabs/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "index.ts" + } +} \ No newline at end of file diff --git a/projects/mdb-angular-ui-kit/tabs/public_api.ts b/projects/mdb-angular-ui-kit/tabs/public_api.ts new file mode 100644 index 00000000..5be2ae78 --- /dev/null +++ b/projects/mdb-angular-ui-kit/tabs/public_api.ts @@ -0,0 +1,7 @@ +export { MdbTabComponent } from './tab.component'; +export { MdbTabContentDirective } from './tab-content.directive'; +export { MdbTabTitleDirective } from './tab-title.directive'; +export { MdbTabsComponent } from './tabs.component'; +export { MdbTabsModule } from './tabs.module'; +export { MdbTabPortalOutlet } from './tab-outlet.directive'; +export { MdbTabChange } from './tabs.component'; diff --git a/projects/mdb-angular-ui-kit/tabs/tab-content.directive.ts b/projects/mdb-angular-ui-kit/tabs/tab-content.directive.ts new file mode 100644 index 00000000..f8e91973 --- /dev/null +++ b/projects/mdb-angular-ui-kit/tabs/tab-content.directive.ts @@ -0,0 +1,13 @@ +import { Directive, InjectionToken, TemplateRef } from '@angular/core'; + +export const MDB_TAB_CONTENT = new InjectionToken('MdbTabContentDirective'); + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbTabContent]', + providers: [{ provide: MDB_TAB_CONTENT, useExisting: MdbTabContentDirective }], + standalone: false, +}) +export class MdbTabContentDirective { + constructor(public template: TemplateRef) {} +} diff --git a/projects/mdb-angular-ui-kit/tabs/tab-outlet.directive.ts b/projects/mdb-angular-ui-kit/tabs/tab-outlet.directive.ts new file mode 100644 index 00000000..fb7f1101 --- /dev/null +++ b/projects/mdb-angular-ui-kit/tabs/tab-outlet.directive.ts @@ -0,0 +1,50 @@ +import { CdkPortalOutlet } from '@angular/cdk/portal'; + +import { + Directive, + Inject, + Input, + OnDestroy, + OnInit, + ViewContainerRef, + DOCUMENT, +} from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { MdbTabComponent } from './tab.component'; + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbTabPortalOutlet]', + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/directive-class-suffix +export class MdbTabPortalOutlet extends CdkPortalOutlet implements OnInit, OnDestroy { + readonly _destroy$: Subject = new Subject(); + + @Input() tab: MdbTabComponent; + + constructor(_vcr: ViewContainerRef, @Inject(DOCUMENT) _document: any) { + super(_vcr, _document); + } + + ngOnInit(): void { + super.ngOnInit(); + + if ((this.tab.shouldAttach || this.tab.active) && !this.hasAttached()) { + this.attach(this.tab.content); + } else { + this.tab.activeStateChange$.pipe(takeUntil(this._destroy$)).subscribe((isActive) => { + if (isActive && !this.hasAttached()) { + this.attach(this.tab.content); + } + }); + } + } + + ngOnDestroy(): void { + this._destroy$.next(); + this._destroy$.complete(); + super.ngOnDestroy(); + } +} diff --git a/projects/mdb-angular-ui-kit/tabs/tab-title.directive.ts b/projects/mdb-angular-ui-kit/tabs/tab-title.directive.ts new file mode 100644 index 00000000..a30be04b --- /dev/null +++ b/projects/mdb-angular-ui-kit/tabs/tab-title.directive.ts @@ -0,0 +1,13 @@ +import { Directive, InjectionToken, TemplateRef } from '@angular/core'; + +export const MDB_TAB_TITLE = new InjectionToken('MdbTabTitleDirective'); + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbTabTitle]', + providers: [{ provide: MDB_TAB_TITLE, useExisting: MdbTabTitleDirective }], + standalone: false, +}) +export class MdbTabTitleDirective { + constructor(public template: TemplateRef) {} +} diff --git a/projects/mdb-angular-ui-kit/tabs/tab.component.html b/projects/mdb-angular-ui-kit/tabs/tab.component.html new file mode 100644 index 00000000..cd48c06b --- /dev/null +++ b/projects/mdb-angular-ui-kit/tabs/tab.component.html @@ -0,0 +1 @@ + diff --git a/projects/mdb-angular-ui-kit/tabs/tab.component.ts b/projects/mdb-angular-ui-kit/tabs/tab.component.ts new file mode 100644 index 00000000..581a8427 --- /dev/null +++ b/projects/mdb-angular-ui-kit/tabs/tab.component.ts @@ -0,0 +1,117 @@ +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; +import { TemplatePortal } from '@angular/cdk/portal'; +import { + Component, + ContentChild, + Input, + OnInit, + TemplateRef, + ViewChild, + ViewContainerRef, +} from '@angular/core'; +import { Subject } from 'rxjs'; +import { MDB_TAB_CONTENT } from './tab-content.directive'; +import { MDB_TAB_TITLE } from './tab-title.directive'; + +const SHOW_TRANSITION_DELAY = 150; // Time of transition taken from styles +const TRANSITION_PADDING = 5; // Value from standard added via executeAfterTransition function + +@Component({ + selector: 'mdb-tab', + templateUrl: './tab.component.html', + standalone: false, +}) +export class MdbTabComponent implements OnInit { + @ContentChild(MDB_TAB_CONTENT, { read: TemplateRef, static: true }) + _lazyContent: TemplateRef; + + @ContentChild(MDB_TAB_TITLE, { read: TemplateRef, static: true }) + _titleContent: TemplateRef; + + @ViewChild(TemplateRef, { static: true }) _content: TemplateRef; + + readonly activeStateChange$: Subject = new Subject(); + + @Input() + get disabled(): boolean { + return this._disabled; + } + set disabled(value: boolean) { + this._disabled = coerceBooleanProperty(value); + } + private _disabled = false; + + @Input() + get fade(): boolean { + return this._fade; + } + set fade(value: boolean) { + this._fade = coerceBooleanProperty(value); + } + private _fade = true; + + @Input() title: string; + + get content(): TemplatePortal | null { + return this._contentPortal; + } + + get titleContent(): TemplatePortal | null { + return this._titlePortal; + } + + get shouldAttach(): boolean { + return this._lazyContent === undefined; + } + + private _contentPortal: TemplatePortal | null = null; + private _titlePortal: TemplatePortal | null = null; + + get active(): boolean { + return this._active; + } + + set active(value: boolean) { + this._active = coerceBooleanProperty(value); + this.activeStateChange$.next(value); + } + + private _active = false; + + get show(): boolean { + return this._show; + } + + set show(value: boolean) { + // We use setTimeout to apply delay for setting show class to reproduce standard library where + // show class is applied after a delay to newly activated item via usage of _queueCallback and + // executeAfterTransition functions which introduce delay equal to transition time taken from + // element styles + setTimeout(() => { + this._show = coerceBooleanProperty(value); + }, SHOW_TRANSITION_DELAY + TRANSITION_PADDING); + } + + private _show = true; + + constructor(private _vcr: ViewContainerRef) {} + + ngOnInit(): void { + this._createContentPortal(); + + if (this._titleContent) { + this._createTitlePortal(); + } + } + + private _createContentPortal(): void { + const content = this._lazyContent || this._content; + this._contentPortal = new TemplatePortal(content, this._vcr); + } + + private _createTitlePortal(): void { + this._titlePortal = new TemplatePortal(this._titleContent, this._vcr); + } + + static ngAcceptInputType_disabled: BooleanInput; +} diff --git a/projects/mdb-angular-ui-kit/tabs/tabs.animations.ts b/projects/mdb-angular-ui-kit/tabs/tabs.animations.ts new file mode 100644 index 00000000..c5504482 --- /dev/null +++ b/projects/mdb-angular-ui-kit/tabs/tabs.animations.ts @@ -0,0 +1,30 @@ +import { + animate, + AnimationTriggerMetadata, + keyframes, + style, + transition, + trigger, + animation, + useAnimation, +} from '@angular/animations'; + +export function fadeInAnimation(): AnimationTriggerMetadata { + return trigger('fadeIn', [ + transition('0 => 1', [ + useAnimation( + animation( + [ + animate( + '500ms 0ms', + keyframes([style({ opacity: 0, offset: 0 }), style({ opacity: 1, offset: 1 })]) + ), + ], + { + delay: 0, + } + ) + ), + ]), + ]); +} diff --git a/projects/mdb-angular-ui-kit/tabs/tabs.component.html b/projects/mdb-angular-ui-kit/tabs/tabs.component.html new file mode 100644 index 00000000..85ef5ad3 --- /dev/null +++ b/projects/mdb-angular-ui-kit/tabs/tabs.component.html @@ -0,0 +1,49 @@ + + +
+ + +
+ +
+
+
diff --git a/projects/mdb-angular-ui-kit/tabs/tabs.component.ts b/projects/mdb-angular-ui-kit/tabs/tabs.component.ts new file mode 100644 index 00000000..ed922aeb --- /dev/null +++ b/projects/mdb-angular-ui-kit/tabs/tabs.component.ts @@ -0,0 +1,156 @@ +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; +import { + AfterContentInit, + Component, + ContentChildren, + EventEmitter, + HostBinding, + Input, + OnDestroy, + Output, + QueryList, +} from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { MdbTabComponent } from './tab.component'; + +export class MdbTabChange { + index: number; + tab: MdbTabComponent; +} + +@Component({ + selector: 'mdb-tabs', + templateUrl: './tabs.component.html', + standalone: false, +}) +export class MdbTabsComponent implements AfterContentInit, OnDestroy { + @ContentChildren(MdbTabComponent) tabs: QueryList; + + readonly _destroy$: Subject = new Subject(); + + @Input() + get fill(): boolean { + return this._fill; + } + set fill(value: boolean) { + this._fill = coerceBooleanProperty(value); + } + private _fill = false; + + @Input() + get justified(): boolean { + return this._justified; + } + set justified(value: boolean) { + this._justified = coerceBooleanProperty(value); + } + private _justified = false; + + @Input() + get pills(): boolean { + return this._pills; + } + set pills(value: boolean) { + this._pills = coerceBooleanProperty(value); + } + private _pills = false; + + @HostBinding('class.row') + @Input() + get vertical(): boolean { + return this._vertical; + } + set vertical(value: boolean) { + this._vertical = coerceBooleanProperty(value); + } + private _vertical = false; + + @Input() navColumnClass = 'col-3'; + @Input() contentColumnClass = 'col-9'; + + get navColClass(): string { + return this.vertical ? this.navColumnClass : ''; + } + + get contentColClass(): string { + return this.vertical ? this.contentColumnClass : ''; + } + + private _selectedIndex: number; + + @Output() activeTabChange: EventEmitter = new EventEmitter(); + + constructor() {} + + ngAfterContentInit(): void { + const firstActiveTabIndex = this.tabs.toArray().findIndex((tab) => !tab.disabled); + + this.setActiveTab(firstActiveTabIndex); + this.tabs.changes.pipe(takeUntil(this._destroy$)).subscribe(() => { + const hasActiveTab = this.tabs.find((tab) => tab.active); + + if (!hasActiveTab) { + const closestTabIndex = this._getClosestTabIndex(this._selectedIndex); + + if (closestTabIndex !== -1) { + this.setActiveTab(closestTabIndex); + } + } + }); + } + + setActiveTab(index: number): void { + const activeTab = this.tabs.toArray()[index]; + + if (!activeTab || (activeTab && activeTab.disabled)) { + return; + } + + this.tabs.forEach((tab) => (tab.active = tab === activeTab)); + this.tabs.forEach((tab) => (tab.show = tab === activeTab)); + + this._selectedIndex = index; + + const tabChangeEvent = this._getTabChangeEvent(index, activeTab); + this.activeTabChange.emit(tabChangeEvent); + } + + private _getTabChangeEvent(index: number, tab: MdbTabComponent): MdbTabChange { + const event = new MdbTabChange(); + event.index = index; + event.tab = tab; + + return event; + } + + private _getClosestTabIndex(index: number): number { + const tabs = this.tabs.toArray(); + const tabsLength = tabs.length; + if (!tabsLength) { + return -1; + } + + for (let i = 1; i <= tabsLength; i += 1) { + const prevIndex = index - i; + const nextIndex = index + i; + if (tabs[prevIndex] && !tabs[prevIndex].disabled) { + return prevIndex; + } + if (tabs[nextIndex] && !tabs[nextIndex].disabled) { + return nextIndex; + } + } + return -1; + } + + ngOnDestroy(): void { + this._destroy$.next(); + this._destroy$.complete(); + } + + static ngAcceptInputType_fill: BooleanInput; + static ngAcceptInputType_justified: BooleanInput; + static ngAcceptInputType_pills: BooleanInput; + static ngAcceptInputType_vertical: BooleanInput; +} diff --git a/projects/mdb-angular-ui-kit/tabs/tabs.module.ts b/projects/mdb-angular-ui-kit/tabs/tabs.module.ts new file mode 100644 index 00000000..679136a3 --- /dev/null +++ b/projects/mdb-angular-ui-kit/tabs/tabs.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MdbTabComponent } from './tab.component'; +import { MdbTabsComponent } from './tabs.component'; +import { PortalModule } from '@angular/cdk/portal'; +import { MdbTabContentDirective } from './tab-content.directive'; +import { MdbTabPortalOutlet } from './tab-outlet.directive'; +import { MdbTabTitleDirective } from './tab-title.directive'; + +@NgModule({ + declarations: [ + MdbTabComponent, + MdbTabContentDirective, + MdbTabTitleDirective, + MdbTabPortalOutlet, + MdbTabsComponent, + ], + imports: [CommonModule, PortalModule], + exports: [ + MdbTabComponent, + MdbTabContentDirective, + MdbTabTitleDirective, + MdbTabPortalOutlet, + MdbTabsComponent, + ], +}) +export class MdbTabsModule {} diff --git a/projects/mdb-angular-ui-kit/tabs/tabs.spec.ts b/projects/mdb-angular-ui-kit/tabs/tabs.spec.ts new file mode 100644 index 00000000..3b9eb2af --- /dev/null +++ b/projects/mdb-angular-ui-kit/tabs/tabs.spec.ts @@ -0,0 +1,287 @@ +import { Component, QueryList, ViewChild, ViewChildren } from '@angular/core'; +import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { MdbTabComponent } from './tab.component'; +import { MdbTabsComponent } from './tabs.component'; +import { MdbTabsModule } from './tabs.module'; + +const tabsTemplate = ` + + Tab 1 content + Tab 2 content + Tab 3 content + + + Tab 4 + + Tab content 4 + + Hidden tab content + +`; + +@Component({ + template: tabsTemplate, + standalone: false, +}) +export class TabsTestComponent { + pills = false; + fill = false; + justified = false; + vertical = false; + navColumnClass = 'col-3'; + contentColumnClass = 'col-9'; + firstTabDisabled = true; + secondTabDisabled = false; + thirdTabDisabled = false; + showHiddenTab = false; + + @ViewChild(MdbTabsComponent) tabsComponent: MdbTabsComponent; + @ViewChildren(MdbTabComponent) tabComponents: QueryList; +} + +describe('MDB Tabs', () => { + let fixture: ComponentFixture; + let component: TabsTestComponent; + let tabsComponent: MdbTabsComponent; + + beforeEach(fakeAsync(() => { + TestBed.configureTestingModule({ + declarations: [TabsTestComponent], + imports: [MdbTabsModule, NoopAnimationsModule], + teardown: { destroyAfterEach: false }, + }); + + fixture = TestBed.createComponent(TabsTestComponent); + component = fixture.componentInstance; + + fixture.detectChanges(); + tick(); + flush(); + + tabsComponent = component.tabsComponent; + })); + + it('should activate first available tab', () => { + fixture.detectChanges(); + + const tabs = component.tabComponents.toArray(); + + expect(tabs[0].active).toBe(false); + expect(tabs[1].active).toBe(true); + }); + + it('should set show to true and apply show class on first available tab', () => { + fixture.detectChanges(); + + const tabs = component.tabComponents.toArray(); + const tabPanes = fixture.debugElement.queryAll(By.css('.tab-pane')); + + expect(tabs[0].show).toBe(false); + expect(tabs[1].show).toBe(true); + expect(tabs[2].show).toBe(false); + expect(tabPanes[0].nativeElement.classList.contains('show')).toBe(false); + expect(tabPanes[1].nativeElement.classList.contains('show')).toBe(true); + expect(tabPanes[2].nativeElement.classList.contains('show')).toBe(false); + }); + + it('should change active tab on tab button click', () => { + fixture.detectChanges(); + + const tabs = component.tabComponents.toArray(); + const tabLinks = fixture.debugElement.queryAll(By.css('.nav-link')); + + expect(tabs[1].active).toBe(true); + + tabLinks[2].nativeElement.click(); + fixture.detectChanges(); + + expect(tabs[2].active).toBe(true); + }); + + it('should apply show class after 155ms delay on tab button click', fakeAsync(() => { + fixture.detectChanges(); + flush(); + + const tabs = component.tabComponents.toArray(); + const tabLinks = fixture.debugElement.queryAll(By.css('.nav-link')); + const tabPanes = fixture.debugElement.queryAll(By.css('.tab-pane')); + + expect(tabs[1].active).toBe(true); + expect(tabs[1].show).toBe(true); + expect(tabPanes[1].nativeElement.classList.contains('show')).toBe(true); + expect(tabs[2].active).toBe(false); + expect(tabs[2].show).toBe(false); + expect(tabPanes[2].nativeElement.classList.contains('show')).toBe(false); + + tabLinks[2].nativeElement.click(); + fixture.detectChanges(); + + expect(tabs[1].active).toBe(false); + expect(tabs[1].show).toBe(true); + expect(tabPanes[1].nativeElement.classList.contains('show')).toBe(true); + expect(tabs[2].active).toBe(true); + expect(tabs[2].show).toBe(false); + expect(tabPanes[2].nativeElement.classList.contains('show')).toBe(false); + + tick(155); + fixture.detectChanges(); + + expect(tabs[1].active).toBe(false); + expect(tabs[1].show).toBe(false); + expect(tabPanes[1].nativeElement.classList.contains('show')).toBe(false); + expect(tabs[2].active).toBe(true); + expect(tabs[2].show).toBe(true); + expect(tabPanes[2].nativeElement.classList.contains('show')).toBe(true); + })); + + it('should add active class to active tab link', () => { + fixture.detectChanges(); + + const tabLinks = fixture.debugElement.queryAll(By.css('.nav-link')); + + expect(tabLinks[1].nativeElement.classList.contains('active')).toBe(true); + + tabLinks[2].nativeElement.click(); + fixture.detectChanges(); + + expect(tabLinks[2].nativeElement.classList.contains('active')).toBe(true); + }); + + it('should add disabled class to disabled tab link', () => { + fixture.detectChanges(); + + const tabLinks = fixture.debugElement.queryAll(By.css('.nav-link')); + + expect(tabLinks[0].nativeElement.classList.contains('disabled')).toBe(true); + }); + + it('should add nav-pills class if pills input is set to true', () => { + component.pills = true; + fixture.detectChanges(); + + const tabNav = fixture.debugElement.query(By.css('.nav')); + + expect(tabNav.nativeElement.classList.contains('nav-pills')).toBe(true); + }); + + it('should add nav-fill class if fill input is set to true', () => { + component.fill = true; + fixture.detectChanges(); + + const tabNav = fixture.debugElement.query(By.css('.nav')); + + expect(tabNav.nativeElement.classList.contains('nav-fill')).toBe(true); + }); + + it('should add nav-justified class if justified input is set to true', () => { + component.justified = true; + fixture.detectChanges(); + + const tabNav = fixture.debugElement.query(By.css('.nav')); + + expect(tabNav.nativeElement.classList.contains('nav-justified')).toBe(true); + }); + + it('should add flex-column and text-center classes if vertical input is set to true', () => { + component.vertical = true; + fixture.detectChanges(); + + const tabNav = fixture.debugElement.query(By.css('.nav')); + + expect(tabNav.nativeElement.classList.contains('flex-column')).toBe(true); + expect(tabNav.nativeElement.classList.contains('text-center')).toBe(true); + }); + + it('should not set nav and content column classes on horizontal tabs', () => { + component.vertical = false; + fixture.detectChanges(); + + const tabNav = fixture.debugElement.query(By.css('.nav')); + const tabContent = fixture.debugElement.query(By.css('.tab-content')); + + expect(tabNav.nativeElement.classList.contains('col-3')).toBe(false); + expect(tabContent.nativeElement.classList.contains('col-9')).toBe(false); + }); + + it('should correctly set and update nav and content column classes on vertical tabs', () => { + component.vertical = true; + fixture.detectChanges(); + + const tabNav = fixture.debugElement.query(By.css('.nav')); + const tabContent = fixture.debugElement.query(By.css('.tab-content')); + + expect(tabNav.nativeElement.classList.contains('col-3')).toBe(true); + expect(tabContent.nativeElement.classList.contains('col-9')).toBe(true); + + component.navColumnClass = 'col-6'; + component.contentColumnClass = 'col-6'; + fixture.detectChanges(); + + expect(tabNav.nativeElement.classList.contains('col-3')).toBe(false); + expect(tabContent.nativeElement.classList.contains('col-9')).toBe(false); + expect(tabNav.nativeElement.classList.contains('col-6')).toBe(true); + expect(tabContent.nativeElement.classList.contains('col-6')).toBe(true); + }); + + it('should not activate disabled tab programmaticaly', () => { + const tabs = component.tabComponents.toArray(); + + expect(tabs[0].disabled).toBe(true); + expect(tabs[0].active).toBe(false); + + component.tabsComponent.setActiveTab(0); + fixture.detectChanges(); + + expect(tabs[0].active).toBe(false); + }); + + it('should not change current active tab when tab list is updated', () => { + const tabs = component.tabComponents.toArray(); + + expect(tabs[1].active).toBe(true); + + component.showHiddenTab = true; + fixture.detectChanges(); + + expect(tabs[1].active).toBe(true); + }); + + it('should activate first available on tab list change if no tab is active', () => { + let tabs = component.tabComponents.toArray(); + + expect(tabs[1].active).toBe(true); + + tabs[1].active = false; + component.secondTabDisabled = true; + fixture.detectChanges(); + + component.showHiddenTab = true; + fixture.detectChanges(); + + tabs = component.tabComponents.toArray(); + + expect(tabs[1].active).toBe(false); + expect(tabs[2].active).toBe(true); + }); + + it('should render custom title content when mdbTabTitle directive and ng-template is used', () => { + const span = fixture.nativeElement.querySelector('span'); + const customTabIcon = span.querySelector('i'); + + expect(customTabIcon).toBeTruthy(); + expect(span.textContent).toEqual('Tab 4'); + }); + + it('should lazy load tab content if mdbTabContent directive and ng-template is used', () => { + const tabPanes = fixture.nativeElement.querySelectorAll('.tab-pane'); + + expect(tabPanes[3].textContent).toEqual(''); + + component.tabsComponent.setActiveTab(3); + fixture.detectChanges(); + + expect(tabPanes[3].textContent).toEqual('Tab content 4'); + }); +}); diff --git a/projects/mdb-angular-ui-kit/tooltip/index.ts b/projects/mdb-angular-ui-kit/tooltip/index.ts new file mode 100644 index 00000000..4aaf8f92 --- /dev/null +++ b/projects/mdb-angular-ui-kit/tooltip/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/projects/mdb-angular-ui-kit/tooltip/ng-package.json b/projects/mdb-angular-ui-kit/tooltip/ng-package.json new file mode 100644 index 00000000..ecef3ed8 --- /dev/null +++ b/projects/mdb-angular-ui-kit/tooltip/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "index.ts" + } +} \ No newline at end of file diff --git a/projects/mdb-angular-ui-kit/tooltip/public_api.ts b/projects/mdb-angular-ui-kit/tooltip/public_api.ts new file mode 100644 index 00000000..8e4beb2b --- /dev/null +++ b/projects/mdb-angular-ui-kit/tooltip/public_api.ts @@ -0,0 +1,4 @@ +export { MdbTooltipDirective } from './tooltip.directive'; +export { MdbTooltipModule } from './tooltip.module'; +export { MdbTooltipPosition } from './tooltip.types'; +export { MdbTooltipComponent } from './tooltip.component'; diff --git a/projects/mdb-angular-ui-kit/tooltip/tooltip.component.html b/projects/mdb-angular-ui-kit/tooltip/tooltip.component.html new file mode 100644 index 00000000..1b58b751 --- /dev/null +++ b/projects/mdb-angular-ui-kit/tooltip/tooltip.component.html @@ -0,0 +1,17 @@ +
+
+ {{ title }} +
diff --git a/projects/mdb-angular-ui-kit/tooltip/tooltip.component.ts b/projects/mdb-angular-ui-kit/tooltip/tooltip.component.ts new file mode 100644 index 00000000..2d09ecac --- /dev/null +++ b/projects/mdb-angular-ui-kit/tooltip/tooltip.component.ts @@ -0,0 +1,46 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + HostBinding, + Input, +} from '@angular/core'; +import { trigger, style, animate, transition, state, AnimationEvent } from '@angular/animations'; +import { Subject } from 'rxjs'; +@Component({ + selector: 'mdb-tooltip', + templateUrl: 'tooltip.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + trigger('fade', [ + state('visible', style({ opacity: 1 })), + state('hidden', style({ opacity: 0 })), + transition('visible => hidden', animate('150ms linear')), + transition(':enter', animate('150ms linear')), + ]), + ], + standalone: false, +}) +export class MdbTooltipComponent { + @Input() title: string; + @Input() html: boolean; + @Input() animation: boolean; + + @HostBinding('class.tooltip') tooltip = true; + + readonly _hidden: Subject = new Subject(); + + animationState = 'hidden'; + + constructor(private _cdRef: ChangeDetectorRef) {} + + markForCheck(): void { + this._cdRef.markForCheck(); + } + + onAnimationEnd(event: AnimationEvent): void { + if (event.toState === 'hidden') { + this._hidden.next(); + } + } +} diff --git a/projects/mdb-angular-ui-kit/tooltip/tooltip.directive.spec.ts b/projects/mdb-angular-ui-kit/tooltip/tooltip.directive.spec.ts new file mode 100644 index 00000000..3e4c063b --- /dev/null +++ b/projects/mdb-angular-ui-kit/tooltip/tooltip.directive.spec.ts @@ -0,0 +1,190 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Component } from '@angular/core'; +import { MdbTooltipModule } from './index'; +import { MdbTooltipDirective } from './tooltip.directive'; +import { By } from '@angular/platform-browser'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +describe('MDB Tooltip', () => { + describe('after init', () => { + let fixture: ComponentFixture; + let element: any; + let component: any; + let directive: any; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [MdbTooltipModule, NoopAnimationsModule], + declarations: [TestTooltipComponent], + teardown: { destroyAfterEach: false }, + }); + fixture = TestBed.createComponent(TestTooltipComponent); + component = fixture.componentInstance; + element = fixture.nativeElement; + fixture.detectChanges(); + }); + + it('should create the component', () => { + expect(component).toBeTruthy(); + }); + + it('should open tooltip after mouseenter and close after mouseout', () => { + fixture.detectChanges(); + + directive = fixture.debugElement + .query(By.directive(MdbTooltipDirective)) + .injector.get(MdbTooltipDirective) as MdbTooltipDirective; + + const onOpen = jest.spyOn(directive, 'show'); + const onClose = jest.spyOn(directive, 'hide'); + + const buttonEl = fixture.nativeElement.querySelector('button'); + + buttonEl.dispatchEvent(new Event('mouseenter')); + fixture.detectChanges(); + + expect(directive.show).toHaveBeenCalled(); + + directive.open = true; + buttonEl.dispatchEvent(new Event('mouseleave')); + fixture.detectChanges(); + + expect(directive.hide).toHaveBeenCalled(); + }); + + it('should set tooltip title', () => { + jest.useFakeTimers(); + const buttonEl = fixture.nativeElement.querySelector('button'); + + buttonEl.dispatchEvent(new Event('mouseenter')); + jest.runAllTimers(); + + fixture.detectChanges(); + const tooltip = document.querySelector('.tooltip-inner'); + expect(tooltip.textContent).toMatch(component.testMdbTooltip); + }); + + it('should set placement', () => { + jest.useFakeTimers(); + const buttonEl = fixture.nativeElement.querySelector('button'); + + buttonEl.dispatchEvent(new Event('mouseenter')); + jest.runAllTimers(); + + fixture.detectChanges(); + directive = fixture.debugElement + .query(By.directive(MdbTooltipDirective)) + .injector.get(MdbTooltipDirective) as MdbTooltipDirective; + + const placement = directive._overlayRef._config.positionStrategy._lastPosition.originY; + expect(placement).toMatch('top'); + }); + }); + + describe('onInit', () => { + it('should open/close tooltip after click', () => { + let fixture: ComponentFixture; + let directive: any; + let component: any; + let element: any; + + TestBed.configureTestingModule({ + imports: [MdbTooltipModule], + declarations: [TestTooltipComponent2], + teardown: { destroyAfterEach: false }, + }); + fixture = TestBed.createComponent(TestTooltipComponent2); + component = fixture.componentInstance; + element = fixture.nativeElement; + fixture.detectChanges(); + + directive = fixture.debugElement + .query(By.directive(MdbTooltipDirective)) + .injector.get(MdbTooltipDirective) as MdbTooltipDirective; + + const onOpen = jest.spyOn(directive, 'show'); + const onClose = jest.spyOn(directive, 'hide'); + + const buttonEl = fixture.nativeElement.querySelector('button'); + + buttonEl.dispatchEvent(new Event('click')); + fixture.detectChanges(); + + expect(directive.show).toHaveBeenCalled(); + + directive._open = true; + buttonEl.dispatchEvent(new Event('click')); + fixture.detectChanges(); + + expect(directive.hide).toHaveBeenCalled(); + }); + }); + + it('should prevent open', () => { + let fixture: ComponentFixture; + let directive: any; + let component: any; + let element: any; + + TestBed.configureTestingModule({ + imports: [MdbTooltipModule], + declarations: [TestTooltipComponent3], + teardown: { destroyAfterEach: false }, + }); + fixture = TestBed.createComponent(TestTooltipComponent3); + component = fixture.componentInstance; + element = fixture.nativeElement; + fixture.detectChanges(); + + directive = fixture.debugElement + .query(By.directive(MdbTooltipDirective)) + .injector.get(MdbTooltipDirective) as MdbTooltipDirective; + + const onOpen = jest.spyOn(directive, 'show'); + + const buttonEl = fixture.nativeElement.querySelector('button'); + + buttonEl.dispatchEvent(new Event('click')); + fixture.detectChanges(); + + expect(directive.show).not.toHaveBeenCalled(); + }); +}); + +@Component({ + selector: 'mdb-test-tooltip', + template: ` `, + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +class TestTooltipComponent { + testTrigger = 'hover'; + testMdbTooltip = 'tooltipTitle'; + testPlacement = 'top'; + testDisabled = false; +} + +@Component({ + selector: 'mdb-test-tooltip2', + template: ` `, + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +class TestTooltipComponent2 {} + +@Component({ + selector: 'mdb-test-tooltip2', + template: ` `, + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +class TestTooltipComponent3 {} diff --git a/projects/mdb-angular-ui-kit/tooltip/tooltip.directive.ts b/projects/mdb-angular-ui-kit/tooltip/tooltip.directive.ts new file mode 100644 index 00000000..265e3bd3 --- /dev/null +++ b/projects/mdb-angular-ui-kit/tooltip/tooltip.directive.ts @@ -0,0 +1,235 @@ +import { + ComponentRef, + Directive, + ElementRef, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, +} from '@angular/core'; +import { + ConnectedPosition, + Overlay, + OverlayConfig, + OverlayPositionBuilder, + OverlayRef, +} from '@angular/cdk/overlay'; +import { ComponentPortal } from '@angular/cdk/portal'; +import { MdbTooltipComponent } from './tooltip.component'; +import { MdbTooltipPosition } from './tooltip.types'; +import { fromEvent, Subject } from 'rxjs'; +import { first, takeUntil } from 'rxjs/operators'; + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbTooltip]', + exportAs: 'mdbTooltip', + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +export class MdbTooltipDirective implements OnInit, OnDestroy { + @Input() mdbTooltip = ''; + @Input() tooltipDisabled = false; + @Input() placement: MdbTooltipPosition = 'top'; + @Input() html = false; + @Input() animation = true; + @Input() trigger = 'hover focus'; + @Input() delayShow = 0; + @Input() delayHide = 0; + @Input() offset = 6; + + @Output() tooltipShow: EventEmitter = new EventEmitter(); + @Output() tooltipShown: EventEmitter = new EventEmitter(); + @Output() tooltipHide: EventEmitter = new EventEmitter(); + @Output() tooltipHidden: EventEmitter = new EventEmitter(); + + private _overlayRef: OverlayRef; + private _tooltipRef: ComponentRef; + private _open = false; + private _showTimeout: any = 0; + private _hideTimeout: any = 0; + + readonly _destroy$: Subject = new Subject(); + + constructor( + private _overlay: Overlay, + private _overlayPositionBuilder: OverlayPositionBuilder, + private _elementRef: ElementRef + ) {} + + ngOnInit(): void { + if (this.tooltipDisabled || this.mdbTooltip === '') { + return; + } + + this._bindTriggerEvents(); + } + + ngOnDestroy(): void { + if (this._open || this._showTimeout) { + this.hide(); + } + + this._destroy$.next(); + this._destroy$.complete(); + } + + private _bindTriggerEvents(): void { + const triggers = this.trigger.split(' '); + + triggers.forEach((trigger) => { + if (trigger === 'click') { + fromEvent(this._elementRef.nativeElement, trigger) + .pipe(takeUntil(this._destroy$)) + .subscribe(() => this.toggle()); + } else if (trigger !== 'manual') { + const evIn = trigger === 'hover' ? 'mouseenter' : 'focusin'; + const evOut = trigger === 'hover' ? 'mouseleave' : 'focusout'; + + fromEvent(this._elementRef.nativeElement, evIn) + .pipe(takeUntil(this._destroy$)) + .subscribe(() => this.show()); + fromEvent(this._elementRef.nativeElement, evOut) + .pipe(takeUntil(this._destroy$)) + .subscribe(() => this.hide()); + } + }); + } + + private _createOverlayConfig(): OverlayConfig { + const positionStrategy = this._overlayPositionBuilder + .flexibleConnectedTo(this._elementRef) + .withPositions(this._getPosition()); + const overlayConfig = new OverlayConfig({ + hasBackdrop: false, + scrollStrategy: this._overlay.scrollStrategies.reposition(), + positionStrategy, + }); + + return overlayConfig; + } + + private _createOverlay(): void { + this._overlayRef = this._overlay.create(this._createOverlayConfig()); + } + + private _getPosition(): ConnectedPosition[] { + let position; + + const positionTop = { + originX: 'center', + originY: 'top', + overlayX: 'center', + overlayY: 'bottom', + offsetY: -this.offset, + }; + + const positionBottom = { + originX: 'center', + originY: 'bottom', + overlayX: 'center', + overlayY: 'top', + offsetY: this.offset, + }; + + const positionRight = { + originX: 'end', + originY: 'center', + overlayX: 'start', + overlayY: 'center', + offsetX: this.offset, + }; + + const positionLeft = { + originX: 'start', + originY: 'center', + overlayX: 'end', + overlayY: 'center', + offsetX: -this.offset, + }; + + switch (this.placement) { + case 'top': + position = [positionTop, positionBottom]; + break; + case 'bottom': + position = [positionBottom, positionTop]; + break; + case 'left': + position = [positionLeft, positionRight]; + break; + case 'right': + position = [positionRight, positionLeft]; + break; + default: + break; + } + + return position; + } + + show(): void { + if (this._hideTimeout || this._open) { + this._overlayRef.detach(); + clearTimeout(this._hideTimeout); + this._hideTimeout = null; + } + + this._createOverlay(); + + this._showTimeout = setTimeout(() => { + if (!this._overlayRef.hasAttached()) { + const tooltipPortal = new ComponentPortal(MdbTooltipComponent); + + this.tooltipShow.emit(this); + this._open = true; + + this._tooltipRef = this._overlayRef.attach(tooltipPortal); + this._tooltipRef.instance.title = this.mdbTooltip; + this._tooltipRef.instance.html = this.html; + this._tooltipRef.instance.animation = this.animation; + this._tooltipRef.instance.animationState = 'visible'; + + this._tooltipRef.instance.markForCheck(); + + this.tooltipShown.emit(this); + } + }, this.delayShow); + } + + hide(): void { + if (this._showTimeout) { + clearTimeout(this._showTimeout); + this._showTimeout = null; + } else { + return; + } + + this._hideTimeout = setTimeout(() => { + this.tooltipHide.emit(this); + + if (!this._tooltipRef) { + this._overlayRef.detach(); + this._open = false; + this.tooltipHidden.emit(this); + } else { + this._tooltipRef.instance._hidden.pipe(first()).subscribe(() => { + this._overlayRef.detach(); + this._open = false; + this.tooltipHidden.emit(this); + }); + this._tooltipRef.instance.animationState = 'hidden'; + this._tooltipRef.instance.markForCheck(); + } + }, this.delayHide); + } + + toggle(): void { + if (this._open) { + this.hide(); + } else { + this.show(); + } + } +} diff --git a/projects/mdb-angular-ui-kit/tooltip/tooltip.module.ts b/projects/mdb-angular-ui-kit/tooltip/tooltip.module.ts new file mode 100644 index 00000000..845b1bfc --- /dev/null +++ b/projects/mdb-angular-ui-kit/tooltip/tooltip.module.ts @@ -0,0 +1,12 @@ +import { MdbTooltipDirective } from './tooltip.directive'; +import { NgModule } from '@angular/core'; +import { OverlayModule } from '@angular/cdk/overlay'; +import { CommonModule } from '@angular/common'; +import { MdbTooltipComponent } from './tooltip.component'; + +@NgModule({ + imports: [CommonModule, OverlayModule], + declarations: [MdbTooltipDirective, MdbTooltipComponent], + exports: [MdbTooltipDirective, MdbTooltipComponent], +}) +export class MdbTooltipModule {} diff --git a/projects/mdb-angular-ui-kit/tooltip/tooltip.types.ts b/projects/mdb-angular-ui-kit/tooltip/tooltip.types.ts new file mode 100644 index 00000000..677da187 --- /dev/null +++ b/projects/mdb-angular-ui-kit/tooltip/tooltip.types.ts @@ -0,0 +1 @@ +export type MdbTooltipPosition = 'top' | 'right' | 'bottom' | 'left'; diff --git a/projects/angular-bootstrap-md/tsconfig.lib.json b/projects/mdb-angular-ui-kit/tsconfig.lib.json similarity index 57% rename from projects/angular-bootstrap-md/tsconfig.lib.json rename to projects/mdb-angular-ui-kit/tsconfig.lib.json index ca4d2315..ea356ef5 100644 --- a/projects/angular-bootstrap-md/tsconfig.lib.json +++ b/projects/mdb-angular-ui-kit/tsconfig.lib.json @@ -1,16 +1,11 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "../../out-tsc/lib", - "target": "es2015", - "module": "es2015", - "moduleResolution": "node", "declaration": true, - "sourceMap": true, + "declarationMap": true, "inlineSources": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "importHelpers": true, "types": [], "lib": [ "dom", @@ -18,11 +13,8 @@ ] }, "angularCompilerOptions": { - "enableIvy": false, "skipTemplateCodegen": true, "strictMetadataEmit": true, - "fullTemplateTypeCheck": true, - "strictInjectionParameters": true, "enableResourceInlining": true }, "exclude": [ diff --git a/projects/mdb-angular-ui-kit/tsconfig.lib.prod.json b/projects/mdb-angular-ui-kit/tsconfig.lib.prod.json new file mode 100644 index 00000000..06de549e --- /dev/null +++ b/projects/mdb-angular-ui-kit/tsconfig.lib.prod.json @@ -0,0 +1,10 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "declarationMap": false + }, + "angularCompilerOptions": { + "compilationMode": "partial" + } +} diff --git a/projects/mdb-angular-ui-kit/tsconfig.schematics.json b/projects/mdb-angular-ui-kit/tsconfig.schematics.json new file mode 100644 index 00000000..ff47ee5f --- /dev/null +++ b/projects/mdb-angular-ui-kit/tsconfig.schematics.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "lib": ["es2018", "dom"], + "declaration": true, + "module": "commonjs", + "moduleResolution": "node", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitThis": true, + "noUnusedParameters": true, + "noUnusedLocals": true, + "rootDir": "schematics", + "outDir": "../../dist/mdb-angular-ui-kit/schematics", + "skipDefaultLibCheck": true, + "skipLibCheck": true, + "sourceMap": true, + "strictNullChecks": true, + "target": "es6", + "types": ["jasmine", "node"] + }, + "include": ["schematics/"], + "exclude": [] +} diff --git a/projects/mdb-angular-ui-kit/tsconfig.spec.json b/projects/mdb-angular-ui-kit/tsconfig.spec.json new file mode 100644 index 00000000..372750f4 --- /dev/null +++ b/projects/mdb-angular-ui-kit/tsconfig.spec.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/spec", + "types": ["jest"], + "module": "commonjs", + "emitDecoratorMetadata": true, + "allowJs": true + }, + "files": [], + "include": ["**/*.spec.ts", "**/*.d.ts"] +} diff --git a/projects/angular-bootstrap-md/tslint.json b/projects/mdb-angular-ui-kit/tslint.json similarity index 89% rename from projects/angular-bootstrap-md/tslint.json rename to projects/mdb-angular-ui-kit/tslint.json index ab956012..124133f8 100644 --- a/projects/angular-bootstrap-md/tslint.json +++ b/projects/mdb-angular-ui-kit/tslint.json @@ -4,13 +4,13 @@ "directive-selector": [ true, "attribute", - "mdb", + "lib", "camelCase" ], "component-selector": [ true, "element", - "mdb", + "lib", "kebab-case" ] } diff --git a/projects/mdb-angular-ui-kit/validation/error.directive.ts b/projects/mdb-angular-ui-kit/validation/error.directive.ts new file mode 100644 index 00000000..ad7c17ea --- /dev/null +++ b/projects/mdb-angular-ui-kit/validation/error.directive.ts @@ -0,0 +1,60 @@ +import { + Input, + HostBinding, + ElementRef, + Renderer2, + OnInit, + OnDestroy, + Component, +} from '@angular/core'; +import { fromEvent, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +let defaultIdNumber = 0; + +@Component({ + selector: 'mdb-error', + template: '', + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +export class MdbErrorDirective implements OnInit, OnDestroy { + @Input() id = `mdb-error-${defaultIdNumber++}`; + + @HostBinding('class.error-message') errorMsg = true; + @HostBinding('attr.id') messageId = this.id; + + readonly _destroy$: Subject = new Subject(); + + constructor(private _elementRef: ElementRef, private renderer: Renderer2) {} + + private _getClosestEl(el: any, selector: string): HTMLElement | null { + for (; el && el !== document; el = el.parentNode) { + if (el.matches && el.matches(selector)) { + return el; + } + } + return null; + } + + ngOnInit(): void { + const textarea = this._getClosestEl(this._elementRef.nativeElement, 'textarea'); + + if (textarea) { + let height = textarea.offsetHeight + 4 + 'px'; + this.renderer.setStyle(this._elementRef.nativeElement, 'top', height); + + fromEvent(textarea, 'keyup') + .pipe(takeUntil(this._destroy$)) + .subscribe(() => { + height = textarea.offsetHeight + 4 + 'px'; + this.renderer.setStyle(this._elementRef.nativeElement, 'top', height); + }); + } + } + + ngOnDestroy(): void { + this._destroy$.next(); + this._destroy$.complete(); + } +} diff --git a/projects/mdb-angular-ui-kit/validation/index.ts b/projects/mdb-angular-ui-kit/validation/index.ts new file mode 100644 index 00000000..4aaf8f92 --- /dev/null +++ b/projects/mdb-angular-ui-kit/validation/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/projects/mdb-angular-ui-kit/validation/ng-package.json b/projects/mdb-angular-ui-kit/validation/ng-package.json new file mode 100644 index 00000000..ecef3ed8 --- /dev/null +++ b/projects/mdb-angular-ui-kit/validation/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "index.ts" + } +} \ No newline at end of file diff --git a/projects/angular-bootstrap-md/src/lib/free/input-utilities/index.ts b/projects/mdb-angular-ui-kit/validation/public_api.ts similarity index 72% rename from projects/angular-bootstrap-md/src/lib/free/input-utilities/index.ts rename to projects/mdb-angular-ui-kit/validation/public_api.ts index b750328e..5aa0ed87 100644 --- a/projects/angular-bootstrap-md/src/lib/free/input-utilities/index.ts +++ b/projects/mdb-angular-ui-kit/validation/public_api.ts @@ -1,5 +1,4 @@ -export { InputUtilitiesModule } from './input-utilities.module'; +export { MdbValidateDirective } from './validate.directive'; export { MdbErrorDirective } from './error.directive'; export { MdbSuccessDirective } from './success.directive'; -export { MdbValidateDirective } from './validate.directive'; - +export { MdbValidationModule } from './validation.module'; diff --git a/projects/mdb-angular-ui-kit/validation/success.directive.ts b/projects/mdb-angular-ui-kit/validation/success.directive.ts new file mode 100644 index 00000000..0eeeacc5 --- /dev/null +++ b/projects/mdb-angular-ui-kit/validation/success.directive.ts @@ -0,0 +1,60 @@ +import { + Input, + HostBinding, + ElementRef, + Renderer2, + OnInit, + OnDestroy, + Component, +} from '@angular/core'; +import { fromEvent, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +let defaultIdNumber = 0; + +@Component({ + selector: 'mdb-success', + template: '', + standalone: false, +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +export class MdbSuccessDirective implements OnInit, OnDestroy { + @Input() id = `mdb-success-${defaultIdNumber++}`; + + @HostBinding('class.success-message') successMsg = true; + @HostBinding('attr.id') messageId = this.id; + + readonly _destroy$: Subject = new Subject(); + + constructor(private _elementRef: ElementRef, private renderer: Renderer2) {} + + private _getClosestEl(el: any, selector: string): HTMLElement | null { + for (; el && el !== document; el = el.parentNode) { + if (el.matches && el.matches(selector)) { + return el; + } + } + return null; + } + + ngOnInit(): void { + const textarea = this._getClosestEl(this._elementRef.nativeElement, 'textarea'); + + if (textarea) { + let height = textarea.offsetHeight + 4 + 'px'; + this.renderer.setStyle(this._elementRef.nativeElement, 'top', height); + + fromEvent(textarea, 'keyup') + .pipe(takeUntil(this._destroy$)) + .subscribe(() => { + height = textarea.offsetHeight + 4 + 'px'; + this.renderer.setStyle(this._elementRef.nativeElement, 'top', height); + }); + } + } + + ngOnDestroy(): void { + this._destroy$.next(); + this._destroy$.complete(); + } +} diff --git a/projects/mdb-angular-ui-kit/validation/validate.directive.spec.ts b/projects/mdb-angular-ui-kit/validation/validate.directive.spec.ts new file mode 100644 index 00000000..eb24d105 --- /dev/null +++ b/projects/mdb-angular-ui-kit/validation/validate.directive.spec.ts @@ -0,0 +1,63 @@ +import { Component } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MdbValidationModule } from './validation.module'; + +const template = ` + +`; + +@Component({ + selector: 'mdb-collapse-test', + template, + standalone: false, +}) +class TestValidateComponent { + success = true; + error = true; +} + +describe('MDB Collapse', () => { + let fixture: ComponentFixture; + let element: any; + let component: any; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestValidateComponent], + imports: [MdbValidationModule], + teardown: { destroyAfterEach: false }, + }); + fixture = TestBed.createComponent(TestValidateComponent); + fixture.detectChanges(); + component = fixture.componentInstance; + element = fixture.nativeElement; + }); + + it('should add validation classes', () => { + const input = fixture.nativeElement.querySelector('.input'); + expect(input.classList.contains('validate-success')).toBe(true); + expect(input.classList.contains('validate-error')).toBe(true); + }); + + it('should only add validate-success class if validateError is set to false', () => { + component.success = true; + component.error = false; + fixture.detectChanges(); + + const input = fixture.nativeElement.querySelector('.input'); + + expect(input.classList.contains('validate-success')).toBe(true); + expect(input.classList.contains('validate-error')).toBe(false); + }); + + it('should only add validate-error class if validateSuccess is set to false', () => { + component.success = false; + component.error = true; + fixture.detectChanges(); + + const input = fixture.nativeElement.querySelector('.input'); + + expect(input.classList.contains('validate-success')).toBe(false); + expect(input.classList.contains('validate-error')).toBe(true); + }); +}); diff --git a/projects/mdb-angular-ui-kit/validation/validate.directive.ts b/projects/mdb-angular-ui-kit/validation/validate.directive.ts new file mode 100644 index 00000000..41d649b8 --- /dev/null +++ b/projects/mdb-angular-ui-kit/validation/validate.directive.ts @@ -0,0 +1,79 @@ +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; +import { Directive, ElementRef, Input, OnInit, Renderer2 } from '@angular/core'; + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[mdbValidate]', + standalone: false, +}) +export class MdbValidateDirective implements OnInit { + private _validate = true; + private _validateSuccess = true; + private _validateError = true; + + @Input() + get mdbValidate(): boolean { + return this._mdbValidate; + } + set mdbValidate(value: boolean) { + this._mdbValidate = coerceBooleanProperty(value); + } + private _mdbValidate: boolean; + + @Input() + get validate(): boolean { + return this._validate; + } + set validate(value: boolean) { + this._validate = coerceBooleanProperty(value); + this.updateErrorClass(); + this.updateSuccessClass(); + } + + @Input() + get validateSuccess(): boolean { + return this._validateSuccess; + } + set validateSuccess(value: boolean) { + this._validateSuccess = coerceBooleanProperty(value); + this.updateSuccessClass(); + } + + @Input() + get validateError(): boolean { + return this._validateError; + } + set validateError(value: boolean) { + this._validateError = coerceBooleanProperty(value); + this.updateErrorClass(); + this.updateSuccessClass(); + } + + constructor(private renderer: Renderer2, private _elementRef: ElementRef) {} + + updateSuccessClass(): void { + if (this.validate && this.validateSuccess) { + this.renderer.addClass(this._elementRef.nativeElement, 'validate-success'); + } else { + this.renderer.removeClass(this._elementRef.nativeElement, 'validate-success'); + } + } + + updateErrorClass(): void { + if (this.validate && this.validateError) { + this.renderer.addClass(this._elementRef.nativeElement, 'validate-error'); + } else { + this.renderer.removeClass(this._elementRef.nativeElement, 'validate-error'); + } + } + + ngOnInit(): void { + this.updateSuccessClass(); + this.updateErrorClass(); + } + + static ngAcceptInputType_mdbValidate: BooleanInput; + static ngAcceptInputType_validate: BooleanInput; + static ngAcceptInputType_validateSuccess: BooleanInput; + static ngAcceptInputType_validateError: BooleanInput; +} diff --git a/projects/angular-bootstrap-md/src/lib/free/input-utilities/input-utilities.module.ts b/projects/mdb-angular-ui-kit/validation/validation.module.ts similarity index 92% rename from projects/angular-bootstrap-md/src/lib/free/input-utilities/input-utilities.module.ts rename to projects/mdb-angular-ui-kit/validation/validation.module.ts index 528d4dcf..8c2c5f2f 100644 --- a/projects/angular-bootstrap-md/src/lib/free/input-utilities/input-utilities.module.ts +++ b/projects/mdb-angular-ui-kit/validation/validation.module.ts @@ -9,4 +9,4 @@ import { MdbValidateDirective } from './validate.directive'; declarations: [MdbErrorDirective, MdbSuccessDirective, MdbValidateDirective], exports: [MdbErrorDirective, MdbSuccessDirective, MdbValidateDirective], }) -export class InputUtilitiesModule {} +export class MdbValidationModule {} diff --git a/setup-jest.ts b/setup-jest.ts new file mode 100644 index 00000000..eb5e4856 --- /dev/null +++ b/setup-jest.ts @@ -0,0 +1,30 @@ +import 'jest-preset-angular'; + +/* global mocks for jsdom */ +const mock = () => { + let storage: { [key: string]: string } = {}; + return { + getItem: (key: string) => (key in storage ? storage[key] : null), + setItem: (key: string, value: string) => (storage[key] = value || ''), + removeItem: (key: string) => delete storage[key], + clear: () => (storage = {}), + }; +}; + +Object.defineProperty(window, 'localStorage', { value: mock() }); +Object.defineProperty(window, 'sessionStorage', { value: mock() }); +Object.defineProperty(window, 'getComputedStyle', { + value: () => ['-webkit-appearance'], +}); + +Object.defineProperty(document.body.style, 'transform', { + value: () => { + return { + enumerable: true, + configurable: true, + }; + }, +}); + +/* output shorter and more meaningful Zone error stack traces */ +// Error.stackTraceLimit = 2; diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts new file mode 100644 index 00000000..d425c6f5 --- /dev/null +++ b/src/app/app-routing.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +const routes: Routes = []; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/src/app/app.component.html b/src/app/app.component.html index a0312825..afab9d51 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,12 +1,56 @@ - -
-
-

Material Design for Bootstrap (Angular)

+
+
+
+
+ +
+
This is MDB
-
Thank you for using our product. We're glad you're with us.
-
Start browsing documentation.
- -

MDB Team

+

+ We hope it exceeds your expectations.
+ 1. Please report all bugs on our + support forum
+ 2. Help us improve with a + 4 minute feedback survey
+ 3. Access MDB GO for + free hosting & deployment
+

+

+ Keep coding,
+ MDB Team +

+
+ Start with MDB tutorials + Start with backend template +

PS. Free users get PRO cheaper

+ UPGRADE WITH DISCOUNT +
- diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 2d5bf129..be40e20e 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,31 +1,36 @@ -import { TestBed, async } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; import { AppComponent } from './app.component'; describe('AppComponent', () => { - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + RouterTestingModule + ], + declarations: [ AppComponent - ], - }).compileComponents(); - })); + ], + teardown: { destroyAfterEach: false } +}).compileComponents(); + }); it('should create the app', () => { const fixture = TestBed.createComponent(AppComponent); - const app = fixture.debugElement.componentInstance; + const app = fixture.componentInstance; expect(app).toBeTruthy(); }); - it(`should have as title 'angular-bootstrap-md-app'`, () => { + it(`should have as title 'mdb-angular-ui-kit-free'`, () => { const fixture = TestBed.createComponent(AppComponent); - const app = fixture.debugElement.componentInstance; - expect(app.title).toEqual('angular-bootstrap-md-app'); + const app = fixture.componentInstance; + expect(app.title).toEqual('mdb-angular-ui-kit-free'); }); - it('should render title in a h1 tag', () => { + it('should render title', () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); - const compiled = fixture.debugElement.nativeElement; - expect(compiled.querySelector('h1').textContent).toContain('Welcome to angular-bootstrap-md-app!'); + const compiled = fixture.nativeElement; + expect(compiled.querySelector('.content span').textContent).toContain('mdb-angular-ui-kit-free app is running!'); }); }); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index d3d64a8d..d88dc45f 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,10 +1,11 @@ import { Component } from '@angular/core'; @Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'] + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'], + standalone: false }) export class AppComponent { - + title = 'mdb-angular-ui-kit-free'; } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 19bc8872..d3c1b1cf 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,16 +1,51 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; -import { MDBBootstrapModule } from 'angular-bootstrap-md'; +import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; +// MDB Modules +import { MdbAccordionModule } from 'mdb-angular-ui-kit/accordion'; +import { MdbCarouselModule } from 'mdb-angular-ui-kit/carousel'; +import { MdbCheckboxModule } from 'mdb-angular-ui-kit/checkbox'; +import { MdbCollapseModule } from 'mdb-angular-ui-kit/collapse'; +import { MdbDropdownModule } from 'mdb-angular-ui-kit/dropdown'; +import { MdbFormsModule } from 'mdb-angular-ui-kit/forms'; +import { MdbModalModule } from 'mdb-angular-ui-kit/modal'; +import { MdbPopoverModule } from 'mdb-angular-ui-kit/popover'; +import { MdbRadioModule } from 'mdb-angular-ui-kit/radio'; +import { MdbRangeModule } from 'mdb-angular-ui-kit/range'; +import { MdbRippleModule } from 'mdb-angular-ui-kit/ripple'; +import { MdbScrollspyModule } from 'mdb-angular-ui-kit/scrollspy'; +import { MdbTabsModule } from 'mdb-angular-ui-kit/tabs'; +import { MdbTooltipModule } from 'mdb-angular-ui-kit/tooltip'; +import { MdbValidationModule } from 'mdb-angular-ui-kit/validation'; + +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; + @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, - MDBBootstrapModule.forRoot() + AppRoutingModule, + BrowserAnimationsModule, + MdbAccordionModule, + MdbCarouselModule, + MdbCheckboxModule, + MdbCollapseModule, + MdbDropdownModule, + MdbFormsModule, + MdbModalModule, + MdbPopoverModule, + MdbRadioModule, + MdbRangeModule, + MdbRippleModule, + MdbScrollspyModule, + MdbTabsModule, + MdbTooltipModule, + MdbValidationModule, ], providers: [], bootstrap: [AppComponent] diff --git a/src/browserslist b/src/browserslist deleted file mode 100644 index 37371cb0..00000000 --- a/src/browserslist +++ /dev/null @@ -1,11 +0,0 @@ -# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers -# For additional information regarding the format and rule options, please see: -# https://github.com/browserslist/browserslist#queries -# -# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed - -> 0.5% -last 2 versions -Firefox ESR -not dead -not IE 9-11 \ No newline at end of file diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 7b4f817a..30d7bccb 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -13,4 +13,4 @@ export const environment = { * This import should be commented out in production mode because it will have a negative impact * on performance if an error is thrown. */ -// import 'zone.js/dist/zone-error'; // Included with Angular CLI. +// import 'zone.js/plugins/zone-error'; // Included with Angular CLI. diff --git a/src/favicon.ico b/src/favicon.ico index 8081c7ce..997406ad 100644 Binary files a/src/favicon.ico and b/src/favicon.ico differ diff --git a/src/index.html b/src/index.html index 85945412..9af86eb9 100644 --- a/src/index.html +++ b/src/index.html @@ -1,14 +1,17 @@ - + - - - AngularBootstrapMdApp - - - - - - - - + + + MdbAngularUiKitFree + + + + + + + + diff --git a/src/karma.conf.js b/src/karma.conf.js deleted file mode 100644 index b6e00421..00000000 --- a/src/karma.conf.js +++ /dev/null @@ -1,31 +0,0 @@ -// Karma configuration file, see link for more information -// https://karma-runner.github.io/1.0/config/configuration-file.html - -module.exports = function (config) { - config.set({ - basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage-istanbul-reporter'), - require('@angular-devkit/build-angular/plugins/karma') - ], - client: { - clearContext: false // leave Jasmine Spec Runner output visible in browser - }, - coverageIstanbulReporter: { - dir: require('path').join(__dirname, '../coverage'), - reports: ['html', 'lcovonly'], - fixWebpackSourcePaths: true - }, - reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false - }); -}; \ No newline at end of file diff --git a/src/polyfills.ts b/src/polyfills.ts index ee8b84da..dcd18eac 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -18,61 +18,34 @@ * BROWSER POLYFILLS */ -/** IE9, IE10 and IE11 requires all of the following polyfills. **/ -// import 'core-js/es6/symbol'; -// import 'core-js/es6/object'; -// import 'core-js/es6/function'; -// import 'core-js/es6/parse-int'; -// import 'core-js/es6/parse-float'; -// import 'core-js/es6/number'; -// import 'core-js/es6/math'; -// import 'core-js/es6/string'; -// import 'core-js/es6/date'; -// import 'core-js/es6/array'; -// import 'core-js/es6/regexp'; -// import 'core-js/es6/map'; -// import 'core-js/es6/weak-map'; -// import 'core-js/es6/set'; - -/** - * If the application will be indexed by Google Search, the following is required. - * Googlebot uses a renderer based on Chrome 41. - * https://developers.google.com/search/docs/guides/rendering - **/ -// import 'core-js/es6/array'; - -/** IE10 and IE11 requires the following for NgClass support on SVG elements */ -// import 'classlist.js'; // Run `npm install --save classlist.js`. - -/** IE10 and IE11 requires the following for the Reflect API. */ -// import 'core-js/es6/reflect'; - -/** - * Web Animations `@angular/platform-browser/animations` - * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. - * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). - **/ -// import 'web-animations-js'; // Run `npm install --save web-animations-js`. - /** * By default, zone.js will patch all possible macroTask and DomEvents * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * */ - // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame - // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick - // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames - - /* - * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js - * with the following flag, it will bypass `zone.js` patch for IE/Edge - */ -// (window as any).__Zone_enable_cross_context_check = true; - /*************************************************************************************************** * Zone JS is required by default for Angular itself. */ -import 'zone.js/dist/zone'; // Included with Angular CLI. +import 'zone.js'; // Included with Angular CLI. /*************************************************************************************************** diff --git a/src/styles.scss b/src/styles.scss index 90d4ee00..c690c22d 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1 +1,4 @@ /* You can add global styles to this file, and also import other style files */ +/* You can add global styles to this file, and also import other style files */ +@import '@fortawesome/fontawesome-free/css/all.css'; +@import '../projects/mdb-angular-ui-kit/assets/scss/mdb.scss'; \ No newline at end of file diff --git a/src/test.ts b/src/test.ts deleted file mode 100644 index 16317897..00000000 --- a/src/test.ts +++ /dev/null @@ -1,20 +0,0 @@ -// This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import 'zone.js/dist/zone-testing'; -import { getTestBed } from '@angular/core/testing'; -import { - BrowserDynamicTestingModule, - platformBrowserDynamicTesting -} from '@angular/platform-browser-dynamic/testing'; - -declare const require: any; - -// 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); diff --git a/src/tsconfig.app.json b/src/tsconfig.app.json deleted file mode 100644 index 190fd300..00000000 --- a/src/tsconfig.app.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/app", - "types": [] - }, - "exclude": [ - "test.ts", - "**/*.spec.ts" - ] -} diff --git a/src/tsconfig.spec.json b/src/tsconfig.spec.json deleted file mode 100644 index de773363..00000000 --- a/src/tsconfig.spec.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/spec", - "types": [ - "jasmine", - "node" - ] - }, - "files": [ - "test.ts", - "polyfills.ts" - ], - "include": [ - "**/*.spec.ts", - "**/*.d.ts" - ] -} diff --git a/src/tslint.json b/src/tslint.json deleted file mode 100644 index 52e2c1a5..00000000 --- a/src/tslint.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../tslint.json", - "rules": { - "directive-selector": [ - true, - "attribute", - "app", - "camelCase" - ], - "component-selector": [ - true, - "element", - "app", - "kebab-case" - ] - } -} diff --git a/test-config.helper.ts b/test-config.helper.ts new file mode 100644 index 00000000..4bec94ea --- /dev/null +++ b/test-config.helper.ts @@ -0,0 +1,21 @@ +import { TestBed } from '@angular/core/testing'; + +type CompilerOptions = Partial<{ + providers: any[]; + useJit: boolean; + preserveWhitespaces: boolean; +}>; +export type ConfigureFn = (testBed: typeof TestBed) => void; + +export const configureTests = (configure: ConfigureFn, compilerOptions: CompilerOptions = {}) => { + const compilerConfig: CompilerOptions = { + preserveWhitespaces: false, + ...compilerOptions, + }; + + const configuredTestBed = TestBed.configureCompiler(compilerConfig); + + configure(configuredTestBed); + + return configuredTestBed.compileComponents().then(() => configuredTestBed); +}; diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 00000000..82d91dc4 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,15 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": [ + "src/main.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/tsconfig.json b/tsconfig.json index 8c5e87a0..0c328578 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,26 +4,25 @@ "baseUrl": "./", "outDir": "./dist/out-tsc", "sourceMap": true, + "esModuleInterop": true, "declaration": false, - "module": "es2015", - "moduleResolution": "node", - "emitDecoratorMetadata": true, "experimentalDecorators": true, - "target": "es5", - "typeRoots": [ - "node_modules/@types" - ], + "moduleResolution": "bundler", + "importHelpers": true, + "target": "ES2022", + "module": "es2020", "lib": [ "es2018", "dom" ], "paths": { - "angular-bootstrap-md": [ - "dist/angular-bootstrap-md" + "mdb-angular-ui-kit": [ + "projects/mdb-angular-ui-kit" ], - "angular-bootstrap-md/*": [ - "dist/angular-bootstrap-md/*" + "mdb-angular-ui-kit/*": [ + "projects/mdb-angular-ui-kit/*" ] - } + }, + "useDefineForClassFields": false } -} +} \ No newline at end of file diff --git a/tsconfig.spec.json b/tsconfig.spec.json new file mode 100644 index 00000000..58c6bb3a --- /dev/null +++ b/tsconfig.spec.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "jest" + ], + "module": "commonjs", + "emitDecoratorMetadata": true, + "allowJs": true + }, + "files": [ + "src/polyfills.ts" + ], + "include": [ + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/tslint.json b/tslint.json index 6ddb6b29..277c8eba 100644 --- a/tslint.json +++ b/tslint.json @@ -1,37 +1,37 @@ { + "extends": "tslint:recommended", "rulesDirectory": [ - "node_modules/codelyzer" + "codelyzer" ], "rules": { + "align": { + "options": [ + "parameters", + "statements" + ] + }, + "array-type": false, "arrow-return-shorthand": true, - "callable-types": true, - "class-name": true, - "comment-format": [ - true, - "check-space" - ], "curly": true, "deprecation": { - "severity": "warn" + "severity": "warning" }, "eofline": true, - "forin": true, "import-blacklist": [ true, "rxjs/Rx" ], "import-spacing": true, - "indent": [ - true, - "spaces" - ], - "interface-over-type-literal": true, - "label-position": true, + "indent": { + "options": [ + "spaces" + ] + }, + "max-classes-per-file": false, "max-line-length": [ true, 140 ], - "member-access": false, "member-ordering": [ true, { @@ -43,8 +43,6 @@ ] } ], - "no-arg": true, - "no-bitwise": true, "no-console": [ true, "debug", @@ -53,79 +51,102 @@ "timeEnd", "trace" ], - "no-construct": true, - "no-debugger": true, - "no-duplicate-super": true, "no-empty": false, - "no-empty-interface": true, - "no-eval": true, "no-inferrable-types": [ true, "ignore-params" ], - "no-misused-new": true, "no-non-null-assertion": true, "no-redundant-jsdoc": true, - "no-shadowed-variable": true, - "no-string-literal": false, - "no-string-throw": true, "no-switch-case-fall-through": true, - "no-trailing-whitespace": true, - "no-unnecessary-initializer": true, - "no-unused-expression": true, - "no-use-before-declare": true, - "no-var-keyword": true, - "object-literal-sort-keys": false, - "one-line": [ + "no-var-requires": false, + "object-literal-key-quotes": [ true, - "check-open-brace", - "check-catch", - "check-else", - "check-whitespace" + "as-needed" ], - "prefer-const": true, "quotemark": [ true, "single" ], - "radix": true, - "semicolon": [ - true, - "always" - ], - "triple-equals": [ - true, - "allow-null-check" - ], - "typedef-whitespace": [ - true, - { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" + "semicolon": { + "options": [ + "always" + ] + }, + "space-before-function-paren": { + "options": { + "anonymous": "never", + "asyncArrow": "always", + "constructor": "never", + "method": "never", + "named": "never" } - ], - "unified-signatures": true, - "variable-name": false, - "whitespace": [ + }, + "typedef": [ true, - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type" + "call-signature" ], - "no-output-on-prefix": true, - "use-input-property-decorator": true, - "use-output-property-decorator": true, - "use-host-property-decorator": true, + "typedef-whitespace": { + "options": [ + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }, + { + "call-signature": "onespace", + "index-signature": "onespace", + "parameter": "onespace", + "property-declaration": "onespace", + "variable-declaration": "onespace" + } + ] + }, + "variable-name": { + "options": [ + "ban-keywords", + "check-format", + "allow-pascal-case" + ] + }, + "whitespace": { + "options": [ + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type", + "check-typecast" + ] + }, + "component-class-suffix": true, + "contextual-lifecycle": true, + "directive-class-suffix": true, + "no-conflicting-lifecycle": true, + "no-host-metadata-property": true, "no-input-rename": true, + "no-inputs-metadata-property": true, + "no-output-native": true, + "no-output-on-prefix": true, "no-output-rename": true, - "use-life-cycle-interface": true, + "no-outputs-metadata-property": true, + "template-banana-in-box": true, + "template-no-negated-async": true, + "use-lifecycle-interface": true, "use-pipe-transform-interface": true, - "component-class-suffix": true, - "directive-class-suffix": true + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ] } } diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index 92bde1da..00000000 --- a/webpack.config.js +++ /dev/null @@ -1,26 +0,0 @@ -const path = require('path'); -const nodeExternals = require('webpack-node-externals'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); - -module.exports = { - entry: './projects/schematics/src/ng-add/index.js', - output: { - path: path.resolve(__dirname, 'dist/angular-bootstrap-md/schematics/ng-add'), - filename: 'index.js', - libraryTarget: 'commonjs2' - }, - mode: 'production', - target: 'node', - externals: [ - nodeExternals({ - whitelist: ['schematics-utilities'] - }) - ], - plugins: [ - new CopyWebpackPlugin([{ - from: 'projects/schematics/src/collection.json', - to: '../collection.json', - toType: 'file' - }], {}) - ] -};