legacy-select.mjs 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. import * as i2 from '@angular/cdk/overlay';
  2. import { OverlayModule } from '@angular/cdk/overlay';
  3. import * as i1 from '@angular/common';
  4. import { CommonModule } from '@angular/common';
  5. import * as i0 from '@angular/core';
  6. import { Directive, Component, ViewEncapsulation, ChangeDetectionStrategy, ContentChildren, ContentChild, NgModule } from '@angular/core';
  7. import { MatCommonModule } from '@angular/material/core';
  8. import { _countGroupLabelsBeforeLegacyOption, _getLegacyOptionScrollPosition, MAT_LEGACY_OPTION_PARENT_COMPONENT, MatLegacyOption, MAT_LEGACY_OPTGROUP, MatLegacyOptionModule } from '@angular/material/legacy-core';
  9. import { MatLegacyFormFieldControl, MatLegacyFormFieldModule } from '@angular/material/legacy-form-field';
  10. import { CdkScrollableModule } from '@angular/cdk/scrolling';
  11. import { MAT_SELECT_TRIGGER, _MatSelectBase, MAT_SELECT_SCROLL_STRATEGY_PROVIDER } from '@angular/material/select';
  12. export { MAT_SELECT_CONFIG as MAT_LEGACY_SELECT_CONFIG, MAT_SELECT_SCROLL_STRATEGY as MAT_LEGACY_SELECT_SCROLL_STRATEGY, MAT_SELECT_SCROLL_STRATEGY_PROVIDER as MAT_LEGACY_SELECT_SCROLL_STRATEGY_PROVIDER, MAT_SELECT_SCROLL_STRATEGY_PROVIDER_FACTORY as MAT_LEGACY_SELECT_SCROLL_STRATEGY_PROVIDER_FACTORY, MAT_SELECT_TRIGGER as MAT_LEGACY_SELECT_TRIGGER } from '@angular/material/select';
  13. import { takeUntil, take } from 'rxjs/operators';
  14. import { trigger, transition, query, animateChild, state, style, animate } from '@angular/animations';
  15. /**
  16. * The following are all the animations for the mat-select component, with each
  17. * const containing the metadata for one animation.
  18. *
  19. * The values below match the implementation of the AngularJS Material mat-select animation.
  20. * @docs-private
  21. * @deprecated Use `matSelectAnimations` from `@angular/material/select` instead. See https://material.angular.io/guide/mdc-migration for information about migrating.
  22. * @breaking-change 17.0.0
  23. */
  24. const matLegacySelectAnimations = {
  25. /**
  26. * This animation ensures the select's overlay panel animation (transformPanel) is called when
  27. * closing the select.
  28. * This is needed due to https://github.com/angular/angular/issues/23302
  29. */
  30. transformPanelWrap: trigger('transformPanelWrap', [
  31. transition('* => void', query('@transformPanel', [animateChild()], { optional: true })),
  32. ]),
  33. /**
  34. * This animation transforms the select's overlay panel on and off the page.
  35. *
  36. * When the panel is attached to the DOM, it expands its width by the amount of padding, scales it
  37. * up to 100% on the Y axis, fades in its border, and translates slightly up and to the
  38. * side to ensure the option text correctly overlaps the trigger text.
  39. *
  40. * When the panel is removed from the DOM, it simply fades out linearly.
  41. */
  42. transformPanel: trigger('transformPanel', [
  43. state('void', style({
  44. transform: 'scaleY(0.8)',
  45. minWidth: '100%',
  46. opacity: 0,
  47. })),
  48. state('showing', style({
  49. opacity: 1,
  50. minWidth: 'calc(100% + 32px)',
  51. transform: 'scaleY(1)',
  52. })),
  53. state('showing-multiple', style({
  54. opacity: 1,
  55. minWidth: 'calc(100% + 64px)',
  56. transform: 'scaleY(1)',
  57. })),
  58. transition('void => *', animate('120ms cubic-bezier(0, 0, 0.2, 1)')),
  59. transition('* => void', animate('100ms 25ms linear', style({ opacity: 0 }))),
  60. ]),
  61. };
  62. /**
  63. * The following style constants are necessary to save here in order
  64. * to properly calculate the alignment of the selected option over
  65. * the trigger element.
  66. */
  67. /**
  68. * The max height of the select's overlay panel.
  69. * @deprecated Use `SELECT_PANEL_MAX_HEIGHT` from `@angular/material/select` instead. See https://material.angular.io/guide/mdc-migration for information about migrating.
  70. * @breaking-change 17.0.0
  71. */
  72. const SELECT_PANEL_MAX_HEIGHT = 256;
  73. /**
  74. * The panel's padding on the x-axis.
  75. * @deprecated Use `SELECT_PANEL_PADDING_X` from `@angular/material/select` instead. See https://material.angular.io/guide/mdc-migration for information about migrating.
  76. * @breaking-change 17.0.0
  77. */
  78. const SELECT_PANEL_PADDING_X = 16;
  79. /**
  80. * The panel's x axis padding if it is indented (e.g. there is an option group).
  81. * @deprecated Use `SELECT_PANEL_INDENT_PADDING_X` from `@angular/material/select` instead. See https://material.angular.io/guide/mdc-migration for information about migrating.
  82. * @breaking-change 17.0.0
  83. */
  84. const SELECT_PANEL_INDENT_PADDING_X = SELECT_PANEL_PADDING_X * 2;
  85. /**
  86. * The height of the select items in `em` units.
  87. * @deprecated Use `SELECT_ITEM_HEIGHT_EM` from `@angular/material/select` instead. See https://material.angular.io/guide/mdc-migration for information about migrating.
  88. * @breaking-change 17.0.0
  89. */
  90. const SELECT_ITEM_HEIGHT_EM = 3;
  91. // TODO(josephperrott): Revert to a constant after 2018 spec updates are fully merged.
  92. /**
  93. * Distance between the panel edge and the option text in
  94. * multi-selection mode.
  95. *
  96. * Calculated as:
  97. * (SELECT_PANEL_PADDING_X * 1.5) + 16 = 40
  98. * The padding is multiplied by 1.5 because the checkbox's margin is half the padding.
  99. * The checkbox width is 16px.
  100. *
  101. * @deprecated Use `SELECT_MULTIPLE_PANEL_PADDING_X` from `@angular/material/select` instead. See https://material.angular.io/guide/mdc-migration for information about migrating.
  102. * @breaking-change 17.0.0
  103. */
  104. const SELECT_MULTIPLE_PANEL_PADDING_X = SELECT_PANEL_PADDING_X * 1.5 + 16;
  105. /**
  106. * The select panel will only "fit" inside the viewport if it is positioned at
  107. * this value or more away from the viewport boundary.
  108. * @deprecated Use `SELECT_PANEL_VIEWPORT_PADDING` from `@angular/material/select` instead. See https://material.angular.io/guide/mdc-migration for information about migrating.
  109. * @breaking-change 17.0.0
  110. */
  111. const SELECT_PANEL_VIEWPORT_PADDING = 8;
  112. /**
  113. * Change event object that is emitted when the select value has changed.
  114. * @deprecated Use `MatSelectChange` from `@angular/material/select` instead. See https://material.angular.io/guide/mdc-migration for information about migrating.
  115. * @breaking-change 17.0.0
  116. */
  117. class MatLegacySelectChange {
  118. constructor(
  119. /** Reference to the select that emitted the change event. */
  120. source,
  121. /** Current value of the select that emitted the event. */
  122. value) {
  123. this.source = source;
  124. this.value = value;
  125. }
  126. }
  127. /**
  128. * Allows the user to customize the trigger that is displayed when the select has a value.
  129. * @deprecated Use `MatSelectTrigger` from `@angular/material/select` instead. See https://material.angular.io/guide/mdc-migration for information about migrating.
  130. * @breaking-change 17.0.0
  131. */
  132. class MatLegacySelectTrigger {
  133. static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: MatLegacySelectTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
  134. static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.0", type: MatLegacySelectTrigger, selector: "mat-select-trigger", providers: [{ provide: MAT_SELECT_TRIGGER, useExisting: MatLegacySelectTrigger }], ngImport: i0 }); }
  135. }
  136. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: MatLegacySelectTrigger, decorators: [{
  137. type: Directive,
  138. args: [{
  139. selector: 'mat-select-trigger',
  140. providers: [{ provide: MAT_SELECT_TRIGGER, useExisting: MatLegacySelectTrigger }],
  141. }]
  142. }] });
  143. /**
  144. * @deprecated Use `MatSelect` from `@angular/material/select` instead. See https://material.angular.io/guide/mdc-migration for information about migrating.
  145. * @breaking-change 17.0.0
  146. */
  147. class MatLegacySelect extends _MatSelectBase {
  148. constructor() {
  149. super(...arguments);
  150. /** The scroll position of the overlay panel, calculated to center the selected option. */
  151. this._scrollTop = 0;
  152. /** The cached font-size of the trigger element. */
  153. this._triggerFontSize = 0;
  154. /** The value of the select panel's transform-origin property. */
  155. this._transformOrigin = 'top';
  156. /**
  157. * The y-offset of the overlay panel in relation to the trigger's top start corner.
  158. * This must be adjusted to align the selected option text over the trigger text.
  159. * when the panel opens. Will change based on the y-position of the selected option.
  160. */
  161. this._offsetY = 0;
  162. this._positions = [
  163. {
  164. originX: 'start',
  165. originY: 'top',
  166. overlayX: 'start',
  167. overlayY: 'top',
  168. },
  169. {
  170. originX: 'start',
  171. originY: 'bottom',
  172. overlayX: 'start',
  173. overlayY: 'bottom',
  174. },
  175. ];
  176. }
  177. /**
  178. * Calculates the scroll position of the select's overlay panel.
  179. *
  180. * Attempts to center the selected option in the panel. If the option is
  181. * too high or too low in the panel to be scrolled to the center, it clamps the
  182. * scroll position to the min or max scroll positions respectively.
  183. */
  184. _calculateOverlayScroll(selectedIndex, scrollBuffer, maxScroll) {
  185. const itemHeight = this._getItemHeight();
  186. const optionOffsetFromScrollTop = itemHeight * selectedIndex;
  187. const halfOptionHeight = itemHeight / 2;
  188. // Starts at the optionOffsetFromScrollTop, which scrolls the option to the top of the
  189. // scroll container, then subtracts the scroll buffer to scroll the option down to
  190. // the center of the overlay panel. Half the option height must be re-added to the
  191. // scrollTop so the option is centered based on its middle, not its top edge.
  192. const optimalScrollPosition = optionOffsetFromScrollTop - scrollBuffer + halfOptionHeight;
  193. return Math.min(Math.max(0, optimalScrollPosition), maxScroll);
  194. }
  195. ngOnInit() {
  196. super.ngOnInit();
  197. this._viewportRuler
  198. .change()
  199. .pipe(takeUntil(this._destroy))
  200. .subscribe(() => {
  201. if (this.panelOpen) {
  202. this._triggerRect = this.trigger.nativeElement.getBoundingClientRect();
  203. this._changeDetectorRef.markForCheck();
  204. }
  205. });
  206. }
  207. open() {
  208. if (super._canOpen()) {
  209. super.open();
  210. this._triggerRect = this.trigger.nativeElement.getBoundingClientRect();
  211. // Note: The computed font-size will be a string pixel value (e.g. "16px").
  212. // `parseInt` ignores the trailing 'px' and converts this to a number.
  213. this._triggerFontSize = parseInt(getComputedStyle(this.trigger.nativeElement).fontSize || '0');
  214. this._calculateOverlayPosition();
  215. // Set the font size on the panel element once it exists.
  216. this._ngZone.onStable.pipe(take(1)).subscribe(() => {
  217. if (this._triggerFontSize &&
  218. this._overlayDir.overlayRef &&
  219. this._overlayDir.overlayRef.overlayElement) {
  220. this._overlayDir.overlayRef.overlayElement.style.fontSize = `${this._triggerFontSize}px`;
  221. }
  222. });
  223. }
  224. }
  225. /** Scrolls the active option into view. */
  226. _scrollOptionIntoView(index) {
  227. const labelCount = _countGroupLabelsBeforeLegacyOption(index, this.options, this.optionGroups);
  228. const itemHeight = this._getItemHeight();
  229. if (index === 0 && labelCount === 1) {
  230. // If we've got one group label before the option and we're at the top option,
  231. // scroll the list to the top. This is better UX than scrolling the list to the
  232. // top of the option, because it allows the user to read the top group's label.
  233. this.panel.nativeElement.scrollTop = 0;
  234. }
  235. else {
  236. this.panel.nativeElement.scrollTop = _getLegacyOptionScrollPosition((index + labelCount) * itemHeight, itemHeight, this.panel.nativeElement.scrollTop, SELECT_PANEL_MAX_HEIGHT);
  237. }
  238. }
  239. _positioningSettled() {
  240. this._calculateOverlayOffsetX();
  241. this.panel.nativeElement.scrollTop = this._scrollTop;
  242. }
  243. _panelDoneAnimating(isOpen) {
  244. if (this.panelOpen) {
  245. this._scrollTop = 0;
  246. }
  247. else {
  248. this._overlayDir.offsetX = 0;
  249. this._changeDetectorRef.markForCheck();
  250. }
  251. super._panelDoneAnimating(isOpen);
  252. }
  253. _getChangeEvent(value) {
  254. return new MatLegacySelectChange(this, value);
  255. }
  256. _getOverlayMinWidth() {
  257. return this._triggerRect?.width;
  258. }
  259. /**
  260. * Sets the x-offset of the overlay panel in relation to the trigger's top start corner.
  261. * This must be adjusted to align the selected option text over the trigger text when
  262. * the panel opens. Will change based on LTR or RTL text direction. Note that the offset
  263. * can't be calculated until the panel has been attached, because we need to know the
  264. * content width in order to constrain the panel within the viewport.
  265. */
  266. _calculateOverlayOffsetX() {
  267. const overlayRect = this._overlayDir.overlayRef.overlayElement.getBoundingClientRect();
  268. const viewportSize = this._viewportRuler.getViewportSize();
  269. const isRtl = this._isRtl();
  270. const paddingWidth = this.multiple
  271. ? SELECT_MULTIPLE_PANEL_PADDING_X + SELECT_PANEL_PADDING_X
  272. : SELECT_PANEL_PADDING_X * 2;
  273. let offsetX;
  274. // Adjust the offset, depending on the option padding.
  275. if (this.multiple) {
  276. offsetX = SELECT_MULTIPLE_PANEL_PADDING_X;
  277. }
  278. else if (this.disableOptionCentering) {
  279. offsetX = SELECT_PANEL_PADDING_X;
  280. }
  281. else {
  282. let selected = this._selectionModel.selected[0] || this.options.first;
  283. offsetX = selected && selected.group ? SELECT_PANEL_INDENT_PADDING_X : SELECT_PANEL_PADDING_X;
  284. }
  285. // Invert the offset in LTR.
  286. if (!isRtl) {
  287. offsetX *= -1;
  288. }
  289. // Determine how much the select overflows on each side.
  290. const leftOverflow = 0 - (overlayRect.left + offsetX - (isRtl ? paddingWidth : 0));
  291. const rightOverflow = overlayRect.right + offsetX - viewportSize.width + (isRtl ? 0 : paddingWidth);
  292. // If the element overflows on either side, reduce the offset to allow it to fit.
  293. if (leftOverflow > 0) {
  294. offsetX += leftOverflow + SELECT_PANEL_VIEWPORT_PADDING;
  295. }
  296. else if (rightOverflow > 0) {
  297. offsetX -= rightOverflow + SELECT_PANEL_VIEWPORT_PADDING;
  298. }
  299. // Set the offset directly in order to avoid having to go through change detection and
  300. // potentially triggering "changed after it was checked" errors. Round the value to avoid
  301. // blurry content in some browsers.
  302. this._overlayDir.offsetX = Math.round(offsetX);
  303. this._overlayDir.overlayRef.updatePosition();
  304. }
  305. /**
  306. * Calculates the y-offset of the select's overlay panel in relation to the
  307. * top start corner of the trigger. It has to be adjusted in order for the
  308. * selected option to be aligned over the trigger when the panel opens.
  309. */
  310. _calculateOverlayOffsetY(selectedIndex, scrollBuffer, maxScroll) {
  311. const itemHeight = this._getItemHeight();
  312. const optionHeightAdjustment = (itemHeight - this._triggerRect.height) / 2;
  313. const maxOptionsDisplayed = Math.floor(SELECT_PANEL_MAX_HEIGHT / itemHeight);
  314. let optionOffsetFromPanelTop;
  315. // Disable offset if requested by user by returning 0 as value to offset
  316. if (this.disableOptionCentering) {
  317. return 0;
  318. }
  319. if (this._scrollTop === 0) {
  320. optionOffsetFromPanelTop = selectedIndex * itemHeight;
  321. }
  322. else if (this._scrollTop === maxScroll) {
  323. const firstDisplayedIndex = this._getItemCount() - maxOptionsDisplayed;
  324. const selectedDisplayIndex = selectedIndex - firstDisplayedIndex;
  325. // The first item is partially out of the viewport. Therefore we need to calculate what
  326. // portion of it is shown in the viewport and account for it in our offset.
  327. let partialItemHeight = itemHeight - ((this._getItemCount() * itemHeight - SELECT_PANEL_MAX_HEIGHT) % itemHeight);
  328. // Because the panel height is longer than the height of the options alone,
  329. // there is always extra padding at the top or bottom of the panel. When
  330. // scrolled to the very bottom, this padding is at the top of the panel and
  331. // must be added to the offset.
  332. optionOffsetFromPanelTop = selectedDisplayIndex * itemHeight + partialItemHeight;
  333. }
  334. else {
  335. // If the option was scrolled to the middle of the panel using a scroll buffer,
  336. // its offset will be the scroll buffer minus the half height that was added to
  337. // center it.
  338. optionOffsetFromPanelTop = scrollBuffer - itemHeight / 2;
  339. }
  340. // The final offset is the option's offset from the top, adjusted for the height difference,
  341. // multiplied by -1 to ensure that the overlay moves in the correct direction up the page.
  342. // The value is rounded to prevent some browsers from blurring the content.
  343. return Math.round(optionOffsetFromPanelTop * -1 - optionHeightAdjustment);
  344. }
  345. /**
  346. * Checks that the attempted overlay position will fit within the viewport.
  347. * If it will not fit, tries to adjust the scroll position and the associated
  348. * y-offset so the panel can open fully on-screen. If it still won't fit,
  349. * sets the offset back to 0 to allow the fallback position to take over.
  350. */
  351. _checkOverlayWithinViewport(maxScroll) {
  352. const itemHeight = this._getItemHeight();
  353. const viewportSize = this._viewportRuler.getViewportSize();
  354. const topSpaceAvailable = this._triggerRect.top - SELECT_PANEL_VIEWPORT_PADDING;
  355. const bottomSpaceAvailable = viewportSize.height - this._triggerRect.bottom - SELECT_PANEL_VIEWPORT_PADDING;
  356. const panelHeightTop = Math.abs(this._offsetY);
  357. const totalPanelHeight = Math.min(this._getItemCount() * itemHeight, SELECT_PANEL_MAX_HEIGHT);
  358. const panelHeightBottom = totalPanelHeight - panelHeightTop - this._triggerRect.height;
  359. if (panelHeightBottom > bottomSpaceAvailable) {
  360. this._adjustPanelUp(panelHeightBottom, bottomSpaceAvailable);
  361. }
  362. else if (panelHeightTop > topSpaceAvailable) {
  363. this._adjustPanelDown(panelHeightTop, topSpaceAvailable, maxScroll);
  364. }
  365. else {
  366. this._transformOrigin = this._getOriginBasedOnOption();
  367. }
  368. }
  369. /** Adjusts the overlay panel up to fit in the viewport. */
  370. _adjustPanelUp(panelHeightBottom, bottomSpaceAvailable) {
  371. // Browsers ignore fractional scroll offsets, so we need to round.
  372. const distanceBelowViewport = Math.round(panelHeightBottom - bottomSpaceAvailable);
  373. // Scrolls the panel up by the distance it was extending past the boundary, then
  374. // adjusts the offset by that amount to move the panel up into the viewport.
  375. this._scrollTop -= distanceBelowViewport;
  376. this._offsetY -= distanceBelowViewport;
  377. this._transformOrigin = this._getOriginBasedOnOption();
  378. // If the panel is scrolled to the very top, it won't be able to fit the panel
  379. // by scrolling, so set the offset to 0 to allow the fallback position to take
  380. // effect.
  381. if (this._scrollTop <= 0) {
  382. this._scrollTop = 0;
  383. this._offsetY = 0;
  384. this._transformOrigin = `50% bottom 0px`;
  385. }
  386. }
  387. /** Adjusts the overlay panel down to fit in the viewport. */
  388. _adjustPanelDown(panelHeightTop, topSpaceAvailable, maxScroll) {
  389. // Browsers ignore fractional scroll offsets, so we need to round.
  390. const distanceAboveViewport = Math.round(panelHeightTop - topSpaceAvailable);
  391. // Scrolls the panel down by the distance it was extending past the boundary, then
  392. // adjusts the offset by that amount to move the panel down into the viewport.
  393. this._scrollTop += distanceAboveViewport;
  394. this._offsetY += distanceAboveViewport;
  395. this._transformOrigin = this._getOriginBasedOnOption();
  396. // If the panel is scrolled to the very bottom, it won't be able to fit the
  397. // panel by scrolling, so set the offset to 0 to allow the fallback position
  398. // to take effect.
  399. if (this._scrollTop >= maxScroll) {
  400. this._scrollTop = maxScroll;
  401. this._offsetY = 0;
  402. this._transformOrigin = `50% top 0px`;
  403. return;
  404. }
  405. }
  406. /** Calculates the scroll position and x- and y-offsets of the overlay panel. */
  407. _calculateOverlayPosition() {
  408. const itemHeight = this._getItemHeight();
  409. const items = this._getItemCount();
  410. const panelHeight = Math.min(items * itemHeight, SELECT_PANEL_MAX_HEIGHT);
  411. const scrollContainerHeight = items * itemHeight;
  412. // The farthest the panel can be scrolled before it hits the bottom
  413. const maxScroll = scrollContainerHeight - panelHeight;
  414. // If no value is selected we open the popup to the first item.
  415. let selectedOptionOffset;
  416. if (this.empty) {
  417. selectedOptionOffset = 0;
  418. }
  419. else {
  420. selectedOptionOffset = Math.max(this.options.toArray().indexOf(this._selectionModel.selected[0]), 0);
  421. }
  422. selectedOptionOffset += _countGroupLabelsBeforeLegacyOption(selectedOptionOffset, this.options, this.optionGroups);
  423. // We must maintain a scroll buffer so the selected option will be scrolled to the
  424. // center of the overlay panel rather than the top.
  425. const scrollBuffer = panelHeight / 2;
  426. this._scrollTop = this._calculateOverlayScroll(selectedOptionOffset, scrollBuffer, maxScroll);
  427. this._offsetY = this._calculateOverlayOffsetY(selectedOptionOffset, scrollBuffer, maxScroll);
  428. this._checkOverlayWithinViewport(maxScroll);
  429. }
  430. /** Sets the transform origin point based on the selected option. */
  431. _getOriginBasedOnOption() {
  432. const itemHeight = this._getItemHeight();
  433. const optionHeightAdjustment = (itemHeight - this._triggerRect.height) / 2;
  434. const originY = Math.abs(this._offsetY) - optionHeightAdjustment + itemHeight / 2;
  435. return `50% ${originY}px 0px`;
  436. }
  437. /** Calculates the height of the select's options. */
  438. _getItemHeight() {
  439. return this._triggerFontSize * SELECT_ITEM_HEIGHT_EM;
  440. }
  441. /** Calculates the amount of items in the select. This includes options and group labels. */
  442. _getItemCount() {
  443. return this.options.length + this.optionGroups.length;
  444. }
  445. static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: MatLegacySelect, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
  446. static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.0", type: MatLegacySelect, selector: "mat-select", inputs: { disabled: "disabled", disableRipple: "disableRipple", tabIndex: "tabIndex" }, host: { attributes: { "role": "combobox", "aria-autocomplete": "none", "aria-haspopup": "true", "ngSkipHydration": "" }, listeners: { "keydown": "_handleKeydown($event)", "focus": "_onFocus()", "blur": "_onBlur()" }, properties: { "attr.id": "id", "attr.tabindex": "tabIndex", "attr.aria-controls": "panelOpen ? id + \"-panel\" : null", "attr.aria-expanded": "panelOpen", "attr.aria-label": "ariaLabel || null", "attr.aria-required": "required.toString()", "attr.aria-disabled": "disabled.toString()", "attr.aria-invalid": "errorState", "attr.aria-activedescendant": "_getAriaActiveDescendant()", "class.mat-select-disabled": "disabled", "class.mat-select-invalid": "errorState", "class.mat-select-required": "required", "class.mat-select-empty": "empty", "class.mat-select-multiple": "multiple" }, classAttribute: "mat-select" }, providers: [
  447. { provide: MatLegacyFormFieldControl, useExisting: MatLegacySelect },
  448. { provide: MAT_LEGACY_OPTION_PARENT_COMPONENT, useExisting: MatLegacySelect },
  449. ], queries: [{ propertyName: "customTrigger", first: true, predicate: MAT_SELECT_TRIGGER, descendants: true }, { propertyName: "options", predicate: MatLegacyOption, descendants: true }, { propertyName: "optionGroups", predicate: MAT_LEGACY_OPTGROUP, descendants: true }], exportAs: ["matSelect"], usesInheritance: true, ngImport: i0, template: "<!--\n Note that the select trigger element specifies `aria-owns` pointing to the listbox overlay.\n While aria-owns is not required for the ARIA 1.2 `role=\"combobox\"` interaction pattern,\n it fixes an issue with VoiceOver when the select appears inside of an `aria-model=\"true\"`\n element (e.g. a dialog). Without this `aria-owns`, the `aria-modal` on a dialog prevents\n VoiceOver from \"seeing\" the select's listbox overlay for aria-activedescendant.\n Using `aria-owns` re-parents the select overlay so that it works again.\n See https://github.com/angular/components/issues/20694\n-->\n<div cdk-overlay-origin\n [attr.aria-owns]=\"panelOpen ? id + '-panel' : null\"\n class=\"mat-select-trigger\"\n (click)=\"toggle()\"\n #origin=\"cdkOverlayOrigin\"\n #trigger>\n <div class=\"mat-select-value\" [ngSwitch]=\"empty\" [attr.id]=\"_valueId\">\n <span class=\"mat-select-placeholder mat-select-min-line\" *ngSwitchCase=\"true\">{{placeholder}}</span>\n <span class=\"mat-select-value-text\" *ngSwitchCase=\"false\" [ngSwitch]=\"!!customTrigger\">\n <span class=\"mat-select-min-line\" *ngSwitchDefault>{{triggerValue}}</span>\n <ng-content select=\"mat-select-trigger\" *ngSwitchCase=\"true\"></ng-content>\n </span>\n </div>\n\n <div class=\"mat-select-arrow-wrapper\"><div class=\"mat-select-arrow\"></div></div>\n</div>\n\n<ng-template\n cdk-connected-overlay\n cdkConnectedOverlayLockPosition\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayPanelClass]=\"_overlayPanelClass\"\n [cdkConnectedOverlayScrollStrategy]=\"_scrollStrategy\"\n [cdkConnectedOverlayOrigin]=\"origin\"\n [cdkConnectedOverlayOpen]=\"panelOpen\"\n [cdkConnectedOverlayPositions]=\"_positions\"\n [cdkConnectedOverlayMinWidth]=\"_getOverlayMinWidth()\"\n [cdkConnectedOverlayOffsetY]=\"_offsetY\"\n (backdropClick)=\"close()\"\n (attach)=\"_onAttached()\"\n (detach)=\"close()\">\n <div class=\"mat-select-panel-wrap\" [@transformPanelWrap]>\n <div\n #panel\n role=\"listbox\"\n tabindex=\"-1\"\n class=\"mat-select-panel {{ _getPanelTheme() }}\"\n [attr.id]=\"id + '-panel'\"\n [attr.aria-multiselectable]=\"multiple\"\n [attr.aria-label]=\"ariaLabel || null\"\n [attr.aria-labelledby]=\"_getPanelAriaLabelledby()\"\n [ngClass]=\"panelClass\"\n [@transformPanel]=\"multiple ? 'showing-multiple' : 'showing'\"\n (@transformPanel.done)=\"_panelDoneAnimatingStream.next($event.toState)\"\n [style.transformOrigin]=\"_transformOrigin\"\n [style.font-size.px]=\"_triggerFontSize\"\n (keydown)=\"_handleKeydown($event)\">\n <ng-content></ng-content>\n </div>\n </div>\n</ng-template>\n", styles: [".mat-select{display:inline-block;width:100%;outline:none}.mat-select-trigger{display:inline-flex;align-items:center;cursor:pointer;position:relative;box-sizing:border-box;width:100%}.mat-select-disabled .mat-select-trigger{-webkit-user-select:none;user-select:none;cursor:default}.mat-select-value{width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mat-select-value-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mat-select-arrow-wrapper{height:16px;flex-shrink:0;display:inline-flex;align-items:center}.mat-form-field-appearance-fill .mat-select-arrow-wrapper{transform:translateY(-50%)}.mat-form-field-appearance-outline .mat-select-arrow-wrapper{transform:translateY(-25%)}.mat-form-field-appearance-standard.mat-form-field-has-label .mat-select:not(.mat-select-empty) .mat-select-arrow-wrapper{transform:translateY(-50%)}.mat-form-field-appearance-standard .mat-select.mat-select-empty .mat-select-arrow-wrapper{transition:transform 400ms cubic-bezier(0.25, 0.8, 0.25, 1)}._mat-animation-noopable.mat-form-field-appearance-standard .mat-select.mat-select-empty .mat-select-arrow-wrapper{transition:none}.mat-select-arrow{width:0;height:0;border-left:5px solid rgba(0,0,0,0);border-right:5px solid rgba(0,0,0,0);border-top:5px solid;margin:0 4px}.mat-form-field.mat-focused .mat-select-arrow{transform:translateX(0)}.mat-select-panel-wrap{flex-basis:100%}.mat-select-panel{min-width:112px;max-width:280px;overflow:auto;-webkit-overflow-scrolling:touch;padding-top:0;padding-bottom:0;max-height:256px;min-width:100%;border-radius:4px;outline:0}.cdk-high-contrast-active .mat-select-panel{outline:solid 1px}.mat-select-panel .mat-optgroup-label,.mat-select-panel .mat-option{font-size:inherit;line-height:3em;height:3em}.mat-form-field-type-mat-select:not(.mat-form-field-disabled) .mat-form-field-flex{cursor:pointer}.mat-form-field-type-mat-select .mat-form-field-label{width:calc(100% - 18px)}.mat-select-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}._mat-animation-noopable .mat-select-placeholder{transition:none}.mat-form-field-hide-placeholder .mat-select-placeholder{color:rgba(0,0,0,0);-webkit-text-fill-color:rgba(0,0,0,0);transition:none;display:block}.mat-select-min-line:empty::before{content:\" \";white-space:pre;width:1px;display:inline-block;visibility:hidden}"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "directive", type: i2.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i2.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }], animations: [
  450. matLegacySelectAnimations.transformPanelWrap,
  451. matLegacySelectAnimations.transformPanel,
  452. ], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
  453. }
  454. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: MatLegacySelect, decorators: [{
  455. type: Component,
  456. args: [{ selector: 'mat-select', exportAs: 'matSelect', inputs: ['disabled', 'disableRipple', 'tabIndex'], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: {
  457. 'role': 'combobox',
  458. 'aria-autocomplete': 'none',
  459. // TODO(crisbeto): the value for aria-haspopup should be `listbox`, but currently it's difficult
  460. // to sync into Google, because of an outdated automated a11y check which flags it as an invalid
  461. // value. At some point we should try to switch it back to being `listbox`.
  462. 'aria-haspopup': 'true',
  463. 'class': 'mat-select',
  464. '[attr.id]': 'id',
  465. '[attr.tabindex]': 'tabIndex',
  466. '[attr.aria-controls]': 'panelOpen ? id + "-panel" : null',
  467. '[attr.aria-expanded]': 'panelOpen',
  468. '[attr.aria-label]': 'ariaLabel || null',
  469. '[attr.aria-required]': 'required.toString()',
  470. '[attr.aria-disabled]': 'disabled.toString()',
  471. '[attr.aria-invalid]': 'errorState',
  472. '[attr.aria-activedescendant]': '_getAriaActiveDescendant()',
  473. '[class.mat-select-disabled]': 'disabled',
  474. '[class.mat-select-invalid]': 'errorState',
  475. '[class.mat-select-required]': 'required',
  476. '[class.mat-select-empty]': 'empty',
  477. '[class.mat-select-multiple]': 'multiple',
  478. '(keydown)': '_handleKeydown($event)',
  479. '(focus)': '_onFocus()',
  480. '(blur)': '_onBlur()',
  481. 'ngSkipHydration': '',
  482. }, animations: [
  483. matLegacySelectAnimations.transformPanelWrap,
  484. matLegacySelectAnimations.transformPanel,
  485. ], providers: [
  486. { provide: MatLegacyFormFieldControl, useExisting: MatLegacySelect },
  487. { provide: MAT_LEGACY_OPTION_PARENT_COMPONENT, useExisting: MatLegacySelect },
  488. ], template: "<!--\n Note that the select trigger element specifies `aria-owns` pointing to the listbox overlay.\n While aria-owns is not required for the ARIA 1.2 `role=\"combobox\"` interaction pattern,\n it fixes an issue with VoiceOver when the select appears inside of an `aria-model=\"true\"`\n element (e.g. a dialog). Without this `aria-owns`, the `aria-modal` on a dialog prevents\n VoiceOver from \"seeing\" the select's listbox overlay for aria-activedescendant.\n Using `aria-owns` re-parents the select overlay so that it works again.\n See https://github.com/angular/components/issues/20694\n-->\n<div cdk-overlay-origin\n [attr.aria-owns]=\"panelOpen ? id + '-panel' : null\"\n class=\"mat-select-trigger\"\n (click)=\"toggle()\"\n #origin=\"cdkOverlayOrigin\"\n #trigger>\n <div class=\"mat-select-value\" [ngSwitch]=\"empty\" [attr.id]=\"_valueId\">\n <span class=\"mat-select-placeholder mat-select-min-line\" *ngSwitchCase=\"true\">{{placeholder}}</span>\n <span class=\"mat-select-value-text\" *ngSwitchCase=\"false\" [ngSwitch]=\"!!customTrigger\">\n <span class=\"mat-select-min-line\" *ngSwitchDefault>{{triggerValue}}</span>\n <ng-content select=\"mat-select-trigger\" *ngSwitchCase=\"true\"></ng-content>\n </span>\n </div>\n\n <div class=\"mat-select-arrow-wrapper\"><div class=\"mat-select-arrow\"></div></div>\n</div>\n\n<ng-template\n cdk-connected-overlay\n cdkConnectedOverlayLockPosition\n cdkConnectedOverlayHasBackdrop\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayPanelClass]=\"_overlayPanelClass\"\n [cdkConnectedOverlayScrollStrategy]=\"_scrollStrategy\"\n [cdkConnectedOverlayOrigin]=\"origin\"\n [cdkConnectedOverlayOpen]=\"panelOpen\"\n [cdkConnectedOverlayPositions]=\"_positions\"\n [cdkConnectedOverlayMinWidth]=\"_getOverlayMinWidth()\"\n [cdkConnectedOverlayOffsetY]=\"_offsetY\"\n (backdropClick)=\"close()\"\n (attach)=\"_onAttached()\"\n (detach)=\"close()\">\n <div class=\"mat-select-panel-wrap\" [@transformPanelWrap]>\n <div\n #panel\n role=\"listbox\"\n tabindex=\"-1\"\n class=\"mat-select-panel {{ _getPanelTheme() }}\"\n [attr.id]=\"id + '-panel'\"\n [attr.aria-multiselectable]=\"multiple\"\n [attr.aria-label]=\"ariaLabel || null\"\n [attr.aria-labelledby]=\"_getPanelAriaLabelledby()\"\n [ngClass]=\"panelClass\"\n [@transformPanel]=\"multiple ? 'showing-multiple' : 'showing'\"\n (@transformPanel.done)=\"_panelDoneAnimatingStream.next($event.toState)\"\n [style.transformOrigin]=\"_transformOrigin\"\n [style.font-size.px]=\"_triggerFontSize\"\n (keydown)=\"_handleKeydown($event)\">\n <ng-content></ng-content>\n </div>\n </div>\n</ng-template>\n", styles: [".mat-select{display:inline-block;width:100%;outline:none}.mat-select-trigger{display:inline-flex;align-items:center;cursor:pointer;position:relative;box-sizing:border-box;width:100%}.mat-select-disabled .mat-select-trigger{-webkit-user-select:none;user-select:none;cursor:default}.mat-select-value{width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mat-select-value-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mat-select-arrow-wrapper{height:16px;flex-shrink:0;display:inline-flex;align-items:center}.mat-form-field-appearance-fill .mat-select-arrow-wrapper{transform:translateY(-50%)}.mat-form-field-appearance-outline .mat-select-arrow-wrapper{transform:translateY(-25%)}.mat-form-field-appearance-standard.mat-form-field-has-label .mat-select:not(.mat-select-empty) .mat-select-arrow-wrapper{transform:translateY(-50%)}.mat-form-field-appearance-standard .mat-select.mat-select-empty .mat-select-arrow-wrapper{transition:transform 400ms cubic-bezier(0.25, 0.8, 0.25, 1)}._mat-animation-noopable.mat-form-field-appearance-standard .mat-select.mat-select-empty .mat-select-arrow-wrapper{transition:none}.mat-select-arrow{width:0;height:0;border-left:5px solid rgba(0,0,0,0);border-right:5px solid rgba(0,0,0,0);border-top:5px solid;margin:0 4px}.mat-form-field.mat-focused .mat-select-arrow{transform:translateX(0)}.mat-select-panel-wrap{flex-basis:100%}.mat-select-panel{min-width:112px;max-width:280px;overflow:auto;-webkit-overflow-scrolling:touch;padding-top:0;padding-bottom:0;max-height:256px;min-width:100%;border-radius:4px;outline:0}.cdk-high-contrast-active .mat-select-panel{outline:solid 1px}.mat-select-panel .mat-optgroup-label,.mat-select-panel .mat-option{font-size:inherit;line-height:3em;height:3em}.mat-form-field-type-mat-select:not(.mat-form-field-disabled) .mat-form-field-flex{cursor:pointer}.mat-form-field-type-mat-select .mat-form-field-label{width:calc(100% - 18px)}.mat-select-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}._mat-animation-noopable .mat-select-placeholder{transition:none}.mat-form-field-hide-placeholder .mat-select-placeholder{color:rgba(0,0,0,0);-webkit-text-fill-color:rgba(0,0,0,0);transition:none;display:block}.mat-select-min-line:empty::before{content:\" \";white-space:pre;width:1px;display:inline-block;visibility:hidden}"] }]
  489. }], propDecorators: { options: [{
  490. type: ContentChildren,
  491. args: [MatLegacyOption, { descendants: true }]
  492. }], optionGroups: [{
  493. type: ContentChildren,
  494. args: [MAT_LEGACY_OPTGROUP, { descendants: true }]
  495. }], customTrigger: [{
  496. type: ContentChild,
  497. args: [MAT_SELECT_TRIGGER]
  498. }] } });
  499. /**
  500. * @deprecated Use `MatSelectModule` from `@angular/material/select` instead. See https://material.angular.io/guide/mdc-migration for information about migrating.
  501. * @breaking-change 17.0.0
  502. */
  503. class MatLegacySelectModule {
  504. static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: MatLegacySelectModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
  505. static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.0.0", ngImport: i0, type: MatLegacySelectModule, declarations: [MatLegacySelect, MatLegacySelectTrigger], imports: [CommonModule, OverlayModule, MatLegacyOptionModule, MatCommonModule], exports: [CdkScrollableModule,
  506. MatLegacyFormFieldModule,
  507. MatLegacySelect,
  508. MatLegacySelectTrigger,
  509. MatLegacyOptionModule,
  510. MatCommonModule] }); }
  511. static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: MatLegacySelectModule, providers: [MAT_SELECT_SCROLL_STRATEGY_PROVIDER], imports: [CommonModule, OverlayModule, MatLegacyOptionModule, MatCommonModule, CdkScrollableModule,
  512. MatLegacyFormFieldModule,
  513. MatLegacyOptionModule,
  514. MatCommonModule] }); }
  515. }
  516. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: MatLegacySelectModule, decorators: [{
  517. type: NgModule,
  518. args: [{
  519. imports: [CommonModule, OverlayModule, MatLegacyOptionModule, MatCommonModule],
  520. exports: [
  521. CdkScrollableModule,
  522. MatLegacyFormFieldModule,
  523. MatLegacySelect,
  524. MatLegacySelectTrigger,
  525. MatLegacyOptionModule,
  526. MatCommonModule,
  527. ],
  528. declarations: [MatLegacySelect, MatLegacySelectTrigger],
  529. providers: [MAT_SELECT_SCROLL_STRATEGY_PROVIDER],
  530. }]
  531. }] });
  532. /**
  533. * Generated bundle index. Do not edit.
  534. */
  535. export { MatLegacySelect, MatLegacySelectChange, MatLegacySelectModule, MatLegacySelectTrigger, matLegacySelectAnimations };
  536. //# sourceMappingURL=legacy-select.mjs.map