@material/web 2.4.2-nightly.7619df0.0 → 2.4.2-nightly.c72e5fe.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -218,6 +218,10 @@ export class Field extends LitElement {
218
218
  if (wasFloating === shouldBeFloating) {
219
219
  return;
220
220
  }
221
+ const keyframes = this.getLabelKeyframes();
222
+ if (!keyframes.length) {
223
+ return;
224
+ }
221
225
  this.isAnimating = true;
222
226
  this.labelAnimation?.cancel();
223
227
  // Only one label is visible at a time for clearer text rendering.
@@ -232,7 +236,10 @@ export class Field extends LitElement {
232
236
  // Re-calculating the animation each time will prevent any visual glitches
233
237
  // from appearing.
234
238
  // TODO(b/241113345): use animation tokens
235
- this.labelAnimation = this.floatingLabelEl?.animate(this.getLabelKeyframes(), { duration: 150, easing: EASING.STANDARD });
239
+ this.labelAnimation = this.floatingLabelEl?.animate(keyframes, {
240
+ duration: 150,
241
+ easing: EASING.STANDARD,
242
+ });
236
243
  this.labelAnimation?.addEventListener('finish', () => {
237
244
  // At the end of the animation, update the visible label.
238
245
  this.isAnimating = false;
@@ -247,6 +254,10 @@ export class Field extends LitElement {
247
254
  const { x: restingX, y: restingY, height: restingHeight, } = restingLabelEl.getBoundingClientRect();
248
255
  const floatingScrollWidth = floatingLabelEl.scrollWidth;
249
256
  const restingScrollWidth = restingLabelEl.scrollWidth;
257
+ // If either label has no dimensions (e.g., display: none), skip animation
258
+ if (floatingScrollWidth === 0 || restingScrollWidth === 0) {
259
+ return [];
260
+ }
250
261
  // Scale by width ratio instead of font size since letter-spacing will scale
251
262
  // incorrectly. Using the width we can better approximate the adjusted
252
263
  // scale and compensate for tracking and overflow.
@@ -1 +1 @@
1
- {"version":3,"file":"field.js","sourceRoot":"","sources":["field.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license\n * Copyright 2021 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport {\n html,\n LitElement,\n nothing,\n PropertyValues,\n render,\n TemplateResult,\n} from 'lit';\nimport {property, query, queryAssignedElements, state} from 'lit/decorators.js';\nimport {classMap} from 'lit/directives/class-map.js';\n\nimport {EASING} from '../../internal/motion/animation.js';\n\n/**\n * A field component.\n */\nexport class Field extends LitElement {\n @property({type: Boolean}) disabled = false;\n @property({type: Boolean}) error = false;\n @property({type: Boolean}) focused = false;\n @property() label = '';\n @property({type: Boolean, attribute: 'no-asterisk'}) noAsterisk = false;\n @property({type: Boolean}) populated = false;\n @property({type: Boolean}) required = false;\n @property({type: Boolean}) resizable = false;\n @property({attribute: 'supporting-text'}) supportingText = '';\n @property({attribute: 'error-text'}) errorText = '';\n @property({type: Number}) count = -1;\n @property({type: Number}) max = -1;\n\n /**\n * Whether or not the field has leading content.\n */\n @property({type: Boolean, attribute: 'has-start'}) hasStart = false;\n\n /**\n * Whether or not the field has trailing content.\n */\n @property({type: Boolean, attribute: 'has-end'}) hasEnd = false;\n\n @queryAssignedElements({slot: 'aria-describedby'})\n private readonly slottedAriaDescribedBy!: HTMLElement[];\n\n private get counterText() {\n // Count and max are typed as number, but can be set to null when Lit removes\n // their attributes. These getters coerce back to a number for calculations.\n const countAsNumber = this.count ?? -1;\n const maxAsNumber = this.max ?? -1;\n // Counter does not show if count is negative, or max is negative or 0.\n if (countAsNumber < 0 || maxAsNumber <= 0) {\n return '';\n }\n\n return `${countAsNumber} / ${maxAsNumber}`;\n }\n\n private get supportingOrErrorText() {\n return this.error && this.errorText ? this.errorText : this.supportingText;\n }\n\n @state() private isAnimating = false;\n private labelAnimation?: Animation;\n /**\n * When set to true, the error text's `role=\"alert\"` will be removed, then\n * re-added after an animation frame. This will re-announce an error message\n * to screen readers.\n */\n @state() private refreshErrorAlert = false;\n @state() private disableTransitions = false;\n @query('.label.floating')\n private readonly floatingLabelEl!: HTMLElement | null;\n @query('.label.resting') private readonly restingLabelEl!: HTMLElement | null;\n @query('.container') private readonly containerEl!: HTMLElement | null;\n\n /**\n * Re-announces the field's error supporting text to screen readers.\n *\n * Error text announces to screen readers anytime it is visible and changes.\n * Use the method to re-announce the message when the text has not changed,\n * but announcement is still needed (such as for `reportValidity()`).\n */\n reannounceError() {\n this.refreshErrorAlert = true;\n }\n\n protected override update(props: PropertyValues<Field>) {\n // Client-side property updates\n const isDisabledChanging =\n props.has('disabled') && props.get('disabled') !== undefined;\n if (isDisabledChanging) {\n this.disableTransitions = true;\n }\n\n // When disabling, remove focus styles if focused.\n if (this.disabled && this.focused) {\n props.set('focused', true);\n this.focused = false;\n }\n\n // Animate if focused or populated change.\n this.animateLabelIfNeeded({\n wasFocused: props.get('focused'),\n wasPopulated: props.get('populated'),\n });\n\n super.update(props);\n }\n\n protected override render() {\n const floatingLabel = this.renderLabel(/*isFloating*/ true);\n const restingLabel = this.renderLabel(/*isFloating*/ false);\n const outline = this.renderOutline?.(floatingLabel);\n const classes = {\n 'disabled': this.disabled,\n 'disable-transitions': this.disableTransitions,\n 'error': this.error && !this.disabled,\n 'focused': this.focused,\n 'with-start': this.hasStart,\n 'with-end': this.hasEnd,\n 'populated': this.populated,\n 'resizable': this.resizable,\n 'required': this.required,\n 'no-label': !this.label,\n };\n\n return html`\n <div class=\"field ${classMap(classes)}\">\n <div class=\"container-overflow\">\n ${this.renderBackground?.()}\n <slot name=\"container\"></slot>\n ${this.renderStateLayer?.()} ${this.renderIndicator?.()} ${outline}\n <div class=\"container\">\n <div class=\"start\">\n <slot name=\"start\"></slot>\n </div>\n <div class=\"middle\">\n <div class=\"label-wrapper\">\n ${restingLabel} ${outline ? nothing : floatingLabel}\n </div>\n <div class=\"content\">\n <slot></slot>\n </div>\n </div>\n <div class=\"end\">\n <slot name=\"end\"></slot>\n </div>\n </div>\n </div>\n ${this.renderSupportingText()}\n </div>\n `;\n }\n\n protected override updated(changed: PropertyValues<Field>) {\n if (\n changed.has('supportingText') ||\n changed.has('errorText') ||\n changed.has('count') ||\n changed.has('max')\n ) {\n this.updateSlottedAriaDescribedBy();\n }\n\n if (this.refreshErrorAlert) {\n // The past render cycle removed the role=\"alert\" from the error message.\n // Re-add it after an animation frame to re-announce the error.\n requestAnimationFrame(() => {\n this.refreshErrorAlert = false;\n });\n }\n\n if (this.disableTransitions) {\n requestAnimationFrame(() => {\n this.disableTransitions = false;\n });\n }\n }\n\n protected renderBackground?(): TemplateResult;\n protected renderStateLayer?(): TemplateResult;\n protected renderIndicator?(): TemplateResult;\n protected renderOutline?(floatingLabel: unknown): TemplateResult;\n\n private renderSupportingText() {\n const {supportingOrErrorText, counterText} = this;\n if (!supportingOrErrorText && !counterText) {\n return nothing;\n }\n\n // Always render the supporting text span so that our `space-around`\n // container puts the counter at the end.\n const start = html`<span>${supportingOrErrorText}</span>`;\n // Conditionally render counter so we don't render the extra `gap`.\n // TODO(b/244473435): add aria-label and announcements\n const end = counterText\n ? html`<span class=\"counter\">${counterText}</span>`\n : nothing;\n\n // Announce if there is an error and error text visible.\n // If refreshErrorAlert is true, do not announce. This will remove the\n // role=\"alert\" attribute. Another render cycle will happen after an\n // animation frame to re-add the role.\n const shouldErrorAnnounce =\n this.error && this.errorText && !this.refreshErrorAlert;\n const role = shouldErrorAnnounce ? 'alert' : nothing;\n return html`\n <div class=\"supporting-text\" role=${role}>${start}${end}</div>\n <slot\n name=\"aria-describedby\"\n @slotchange=${this.updateSlottedAriaDescribedBy}></slot>\n `;\n }\n\n private updateSlottedAriaDescribedBy() {\n for (const element of this.slottedAriaDescribedBy) {\n render(html`${this.supportingOrErrorText} ${this.counterText}`, element);\n element.setAttribute('hidden', '');\n }\n }\n\n private renderLabel(isFloating: boolean) {\n if (!this.label) {\n return nothing;\n }\n\n let visible: boolean;\n if (isFloating) {\n // Floating label is visible when focused/populated or when animating.\n visible = this.focused || this.populated || this.isAnimating;\n } else {\n // Resting label is visible when unfocused. It is never visible while\n // animating.\n visible = !this.focused && !this.populated && !this.isAnimating;\n }\n\n const classes = {\n 'hidden': !visible,\n 'floating': isFloating,\n 'resting': !isFloating,\n };\n\n // Add '*' if a label is present and the field is required\n const labelText = `${this.label}${\n this.required && !this.noAsterisk ? '*' : ''\n }`;\n\n return html`\n <span class=\"label ${classMap(classes)}\" aria-hidden=${!visible}\n >${labelText}</span\n >\n `;\n }\n\n private animateLabelIfNeeded({\n wasFocused,\n wasPopulated,\n }: {\n wasFocused?: boolean;\n wasPopulated?: boolean;\n }) {\n if (!this.label) {\n return;\n }\n\n wasFocused ??= this.focused;\n wasPopulated ??= this.populated;\n const wasFloating = wasFocused || wasPopulated;\n const shouldBeFloating = this.focused || this.populated;\n if (wasFloating === shouldBeFloating) {\n return;\n }\n\n this.isAnimating = true;\n this.labelAnimation?.cancel();\n\n // Only one label is visible at a time for clearer text rendering.\n // The floating label is visible and used during animation. At the end of\n // the animation, it will either remain visible (if floating) or hide and\n // the resting label will be shown.\n //\n // We don't use forward filling because if the dimensions of the text field\n // change (leading icon removed, density changes, etc), then the animation\n // will be inaccurate.\n //\n // Re-calculating the animation each time will prevent any visual glitches\n // from appearing.\n // TODO(b/241113345): use animation tokens\n this.labelAnimation = this.floatingLabelEl?.animate(\n this.getLabelKeyframes(),\n {duration: 150, easing: EASING.STANDARD},\n );\n\n this.labelAnimation?.addEventListener('finish', () => {\n // At the end of the animation, update the visible label.\n this.isAnimating = false;\n });\n }\n\n private getLabelKeyframes() {\n const {floatingLabelEl, restingLabelEl} = this;\n if (!floatingLabelEl || !restingLabelEl) {\n return [];\n }\n\n const {\n x: floatingX,\n y: floatingY,\n height: floatingHeight,\n } = floatingLabelEl.getBoundingClientRect();\n const {\n x: restingX,\n y: restingY,\n height: restingHeight,\n } = restingLabelEl.getBoundingClientRect();\n const floatingScrollWidth = floatingLabelEl.scrollWidth;\n const restingScrollWidth = restingLabelEl.scrollWidth;\n // Scale by width ratio instead of font size since letter-spacing will scale\n // incorrectly. Using the width we can better approximate the adjusted\n // scale and compensate for tracking and overflow.\n // (use scrollWidth instead of width to account for clipped labels)\n const scale = restingScrollWidth / floatingScrollWidth;\n const xDelta = restingX - floatingX;\n // The line-height of the resting and floating label are different. When\n // we move the floating label down to the resting label's position, it won't\n // exactly match because of this. We need to adjust by half of what the\n // final scaled floating label's height will be.\n const yDelta =\n restingY -\n floatingY +\n Math.round((restingHeight - floatingHeight * scale) / 2);\n\n // Create the two transforms: floating to resting (using the calculations\n // above), and resting to floating (re-setting the transform to initial\n // values).\n const restTransform = `translateX(${xDelta}px) translateY(${yDelta}px) scale(${scale})`;\n const floatTransform = `translateX(0) translateY(0) scale(1)`;\n\n // Constrain the floating labels width to a scaled percentage of the\n // resting label's width. This will prevent long clipped labels from\n // overflowing the container.\n const restingClientWidth = restingLabelEl.clientWidth;\n const isRestingClipped = restingScrollWidth > restingClientWidth;\n const width = isRestingClipped ? `${restingClientWidth / scale}px` : '';\n if (this.focused || this.populated) {\n return [\n {transform: restTransform, width},\n {transform: floatTransform, width},\n ];\n }\n\n return [\n {transform: floatTransform, width},\n {transform: restTransform, width},\n ];\n }\n\n getSurfacePositionClientRect() {\n return this.containerEl!.getBoundingClientRect();\n }\n}\n"]}
1
+ {"version":3,"file":"field.js","sourceRoot":"","sources":["field.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license\n * Copyright 2021 Google LLC\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport {\n html,\n LitElement,\n nothing,\n PropertyValues,\n render,\n TemplateResult,\n} from 'lit';\nimport {property, query, queryAssignedElements, state} from 'lit/decorators.js';\nimport {classMap} from 'lit/directives/class-map.js';\n\nimport {EASING} from '../../internal/motion/animation.js';\n\n/**\n * A field component.\n */\nexport class Field extends LitElement {\n @property({type: Boolean}) disabled = false;\n @property({type: Boolean}) error = false;\n @property({type: Boolean}) focused = false;\n @property() label = '';\n @property({type: Boolean, attribute: 'no-asterisk'}) noAsterisk = false;\n @property({type: Boolean}) populated = false;\n @property({type: Boolean}) required = false;\n @property({type: Boolean}) resizable = false;\n @property({attribute: 'supporting-text'}) supportingText = '';\n @property({attribute: 'error-text'}) errorText = '';\n @property({type: Number}) count = -1;\n @property({type: Number}) max = -1;\n\n /**\n * Whether or not the field has leading content.\n */\n @property({type: Boolean, attribute: 'has-start'}) hasStart = false;\n\n /**\n * Whether or not the field has trailing content.\n */\n @property({type: Boolean, attribute: 'has-end'}) hasEnd = false;\n\n @queryAssignedElements({slot: 'aria-describedby'})\n private readonly slottedAriaDescribedBy!: HTMLElement[];\n\n private get counterText() {\n // Count and max are typed as number, but can be set to null when Lit removes\n // their attributes. These getters coerce back to a number for calculations.\n const countAsNumber = this.count ?? -1;\n const maxAsNumber = this.max ?? -1;\n // Counter does not show if count is negative, or max is negative or 0.\n if (countAsNumber < 0 || maxAsNumber <= 0) {\n return '';\n }\n\n return `${countAsNumber} / ${maxAsNumber}`;\n }\n\n private get supportingOrErrorText() {\n return this.error && this.errorText ? this.errorText : this.supportingText;\n }\n\n @state() private isAnimating = false;\n private labelAnimation?: Animation;\n /**\n * When set to true, the error text's `role=\"alert\"` will be removed, then\n * re-added after an animation frame. This will re-announce an error message\n * to screen readers.\n */\n @state() private refreshErrorAlert = false;\n @state() private disableTransitions = false;\n @query('.label.floating')\n private readonly floatingLabelEl!: HTMLElement | null;\n @query('.label.resting') private readonly restingLabelEl!: HTMLElement | null;\n @query('.container') private readonly containerEl!: HTMLElement | null;\n\n /**\n * Re-announces the field's error supporting text to screen readers.\n *\n * Error text announces to screen readers anytime it is visible and changes.\n * Use the method to re-announce the message when the text has not changed,\n * but announcement is still needed (such as for `reportValidity()`).\n */\n reannounceError() {\n this.refreshErrorAlert = true;\n }\n\n protected override update(props: PropertyValues<Field>) {\n // Client-side property updates\n const isDisabledChanging =\n props.has('disabled') && props.get('disabled') !== undefined;\n if (isDisabledChanging) {\n this.disableTransitions = true;\n }\n\n // When disabling, remove focus styles if focused.\n if (this.disabled && this.focused) {\n props.set('focused', true);\n this.focused = false;\n }\n\n // Animate if focused or populated change.\n this.animateLabelIfNeeded({\n wasFocused: props.get('focused'),\n wasPopulated: props.get('populated'),\n });\n\n super.update(props);\n }\n\n protected override render() {\n const floatingLabel = this.renderLabel(/*isFloating*/ true);\n const restingLabel = this.renderLabel(/*isFloating*/ false);\n const outline = this.renderOutline?.(floatingLabel);\n const classes = {\n 'disabled': this.disabled,\n 'disable-transitions': this.disableTransitions,\n 'error': this.error && !this.disabled,\n 'focused': this.focused,\n 'with-start': this.hasStart,\n 'with-end': this.hasEnd,\n 'populated': this.populated,\n 'resizable': this.resizable,\n 'required': this.required,\n 'no-label': !this.label,\n };\n\n return html`\n <div class=\"field ${classMap(classes)}\">\n <div class=\"container-overflow\">\n ${this.renderBackground?.()}\n <slot name=\"container\"></slot>\n ${this.renderStateLayer?.()} ${this.renderIndicator?.()} ${outline}\n <div class=\"container\">\n <div class=\"start\">\n <slot name=\"start\"></slot>\n </div>\n <div class=\"middle\">\n <div class=\"label-wrapper\">\n ${restingLabel} ${outline ? nothing : floatingLabel}\n </div>\n <div class=\"content\">\n <slot></slot>\n </div>\n </div>\n <div class=\"end\">\n <slot name=\"end\"></slot>\n </div>\n </div>\n </div>\n ${this.renderSupportingText()}\n </div>\n `;\n }\n\n protected override updated(changed: PropertyValues<Field>) {\n if (\n changed.has('supportingText') ||\n changed.has('errorText') ||\n changed.has('count') ||\n changed.has('max')\n ) {\n this.updateSlottedAriaDescribedBy();\n }\n\n if (this.refreshErrorAlert) {\n // The past render cycle removed the role=\"alert\" from the error message.\n // Re-add it after an animation frame to re-announce the error.\n requestAnimationFrame(() => {\n this.refreshErrorAlert = false;\n });\n }\n\n if (this.disableTransitions) {\n requestAnimationFrame(() => {\n this.disableTransitions = false;\n });\n }\n }\n\n protected renderBackground?(): TemplateResult;\n protected renderStateLayer?(): TemplateResult;\n protected renderIndicator?(): TemplateResult;\n protected renderOutline?(floatingLabel: unknown): TemplateResult;\n\n private renderSupportingText() {\n const {supportingOrErrorText, counterText} = this;\n if (!supportingOrErrorText && !counterText) {\n return nothing;\n }\n\n // Always render the supporting text span so that our `space-around`\n // container puts the counter at the end.\n const start = html`<span>${supportingOrErrorText}</span>`;\n // Conditionally render counter so we don't render the extra `gap`.\n // TODO(b/244473435): add aria-label and announcements\n const end = counterText\n ? html`<span class=\"counter\">${counterText}</span>`\n : nothing;\n\n // Announce if there is an error and error text visible.\n // If refreshErrorAlert is true, do not announce. This will remove the\n // role=\"alert\" attribute. Another render cycle will happen after an\n // animation frame to re-add the role.\n const shouldErrorAnnounce =\n this.error && this.errorText && !this.refreshErrorAlert;\n const role = shouldErrorAnnounce ? 'alert' : nothing;\n return html`\n <div class=\"supporting-text\" role=${role}>${start}${end}</div>\n <slot\n name=\"aria-describedby\"\n @slotchange=${this.updateSlottedAriaDescribedBy}></slot>\n `;\n }\n\n private updateSlottedAriaDescribedBy() {\n for (const element of this.slottedAriaDescribedBy) {\n render(html`${this.supportingOrErrorText} ${this.counterText}`, element);\n element.setAttribute('hidden', '');\n }\n }\n\n private renderLabel(isFloating: boolean) {\n if (!this.label) {\n return nothing;\n }\n\n let visible: boolean;\n if (isFloating) {\n // Floating label is visible when focused/populated or when animating.\n visible = this.focused || this.populated || this.isAnimating;\n } else {\n // Resting label is visible when unfocused. It is never visible while\n // animating.\n visible = !this.focused && !this.populated && !this.isAnimating;\n }\n\n const classes = {\n 'hidden': !visible,\n 'floating': isFloating,\n 'resting': !isFloating,\n };\n\n // Add '*' if a label is present and the field is required\n const labelText = `${this.label}${\n this.required && !this.noAsterisk ? '*' : ''\n }`;\n\n return html`\n <span class=\"label ${classMap(classes)}\" aria-hidden=${!visible}\n >${labelText}</span\n >\n `;\n }\n\n private animateLabelIfNeeded({\n wasFocused,\n wasPopulated,\n }: {\n wasFocused?: boolean;\n wasPopulated?: boolean;\n }) {\n if (!this.label) {\n return;\n }\n\n wasFocused ??= this.focused;\n wasPopulated ??= this.populated;\n const wasFloating = wasFocused || wasPopulated;\n const shouldBeFloating = this.focused || this.populated;\n if (wasFloating === shouldBeFloating) {\n return;\n }\n\n const keyframes = this.getLabelKeyframes();\n if (!keyframes.length) {\n return;\n }\n\n this.isAnimating = true;\n this.labelAnimation?.cancel();\n\n // Only one label is visible at a time for clearer text rendering.\n // The floating label is visible and used during animation. At the end of\n // the animation, it will either remain visible (if floating) or hide and\n // the resting label will be shown.\n //\n // We don't use forward filling because if the dimensions of the text field\n // change (leading icon removed, density changes, etc), then the animation\n // will be inaccurate.\n //\n // Re-calculating the animation each time will prevent any visual glitches\n // from appearing.\n // TODO(b/241113345): use animation tokens\n this.labelAnimation = this.floatingLabelEl?.animate(keyframes, {\n duration: 150,\n easing: EASING.STANDARD,\n });\n\n this.labelAnimation?.addEventListener('finish', () => {\n // At the end of the animation, update the visible label.\n this.isAnimating = false;\n });\n }\n\n private getLabelKeyframes() {\n const {floatingLabelEl, restingLabelEl} = this;\n if (!floatingLabelEl || !restingLabelEl) {\n return [];\n }\n\n const {\n x: floatingX,\n y: floatingY,\n height: floatingHeight,\n } = floatingLabelEl.getBoundingClientRect();\n const {\n x: restingX,\n y: restingY,\n height: restingHeight,\n } = restingLabelEl.getBoundingClientRect();\n const floatingScrollWidth = floatingLabelEl.scrollWidth;\n const restingScrollWidth = restingLabelEl.scrollWidth;\n // If either label has no dimensions (e.g., display: none), skip animation\n if (floatingScrollWidth === 0 || restingScrollWidth === 0) {\n return [];\n }\n // Scale by width ratio instead of font size since letter-spacing will scale\n // incorrectly. Using the width we can better approximate the adjusted\n // scale and compensate for tracking and overflow.\n // (use scrollWidth instead of width to account for clipped labels)\n const scale = restingScrollWidth / floatingScrollWidth;\n const xDelta = restingX - floatingX;\n // The line-height of the resting and floating label are different. When\n // we move the floating label down to the resting label's position, it won't\n // exactly match because of this. We need to adjust by half of what the\n // final scaled floating label's height will be.\n const yDelta =\n restingY -\n floatingY +\n Math.round((restingHeight - floatingHeight * scale) / 2);\n\n // Create the two transforms: floating to resting (using the calculations\n // above), and resting to floating (re-setting the transform to initial\n // values).\n const restTransform = `translateX(${xDelta}px) translateY(${yDelta}px) scale(${scale})`;\n const floatTransform = `translateX(0) translateY(0) scale(1)`;\n\n // Constrain the floating labels width to a scaled percentage of the\n // resting label's width. This will prevent long clipped labels from\n // overflowing the container.\n const restingClientWidth = restingLabelEl.clientWidth;\n const isRestingClipped = restingScrollWidth > restingClientWidth;\n const width = isRestingClipped ? `${restingClientWidth / scale}px` : '';\n if (this.focused || this.populated) {\n return [\n {transform: restTransform, width},\n {transform: floatTransform, width},\n ];\n }\n\n return [\n {transform: floatTransform, width},\n {transform: restTransform, width},\n ];\n }\n\n getSurfacePositionClientRect() {\n return this.containerEl!.getBoundingClientRect();\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@material/web",
3
- "version": "2.4.2-nightly.7619df0.0",
3
+ "version": "2.4.2-nightly.c72e5fe.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -0,0 +1,82 @@
1
+ //
2
+ // Copyright 2025 Google LLC
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ //
5
+
6
+ // Utility assert functions that throw errors.
7
+
8
+ // go/keep-sorted start by_regex='(.+) prefix_order=sass:
9
+ @use 'sass:meta';
10
+ @use 'throw';
11
+ @use 'type';
12
+ // go/keep-sorted end
13
+
14
+ /// Asserts that the argument is a specific type. If it is, the argument is
15
+ /// returned, otherwise an error is thrown.
16
+ ///
17
+ /// @example scss
18
+ /// @mixin multiply($a, $b) {
19
+ /// $a: assert.is-type($a, 'number');
20
+ /// $b: assert.is-type($b, 'number');
21
+ /// @return $a * $b;
22
+ /// }
23
+ ///
24
+ /// @function is-empty($value) {
25
+ /// $value: assert.is-type(
26
+ /// $value,
27
+ /// 'list|map|null',
28
+ /// $message: '$value must be a list, map, or null',
29
+ /// $source: 'is-empty'
30
+ /// );
31
+ /// @return $value and list.length($value) == 0;
32
+ /// }
33
+ ///
34
+ /// @param {*} $arg - The argument to check.
35
+ /// @param {string} $type - The string type to assert the argument matches.
36
+ /// Multiple types may be separated by '|'.
37
+ /// @param {string} $message - Optional custom error message.
38
+ /// @param {string} $source - Optional source of the error message.
39
+ /// @return {*} The argument if it matches the type string.
40
+ /// @throw Error if the argument does not match the type string.
41
+ @function is-type(
42
+ $arg,
43
+ $type,
44
+ $message: 'Argument must be type #{meta.inspect($type)}. $arg: #{meta.inspect($arg)}',
45
+ $source: 'assert.is-type'
46
+ ) {
47
+ @if type.matches($arg, $type) {
48
+ @return $arg;
49
+ }
50
+ @return throw.error($message, $source);
51
+ }
52
+
53
+ /// Asserts that the argument is a specific type. If it is, the argument is
54
+ /// returned, otherwise an error is thrown.
55
+ ///
56
+ /// @example scss
57
+ /// @function get-strict($map, $key) {
58
+ /// @return assert.not-type(
59
+ /// map.get($map, $key),
60
+ /// 'null',
61
+ /// $message: 'Key must be in the map'
62
+ /// );
63
+ /// }
64
+ ///
65
+ /// @param {*} $arg - The argument to check.
66
+ /// @param {string} $type - The string type to assert the argument does not
67
+ /// match. Multiple types may be separated by '|'.
68
+ /// @param {string} $message - Optional custom error message.
69
+ /// @param {string} $source - Optional source of the error message.
70
+ /// @return {*} The argument if it does not match the type string.
71
+ /// @throw Error if the argument matches the type string.
72
+ @function not-type(
73
+ $arg,
74
+ $type,
75
+ $message: 'Argument may not be type #{meta.inspect($type)}. $arg: #{meta.inspect($arg)}',
76
+ $source: 'assert.not-type'
77
+ ) {
78
+ @if type.matches($arg, $type) {
79
+ @return throw.error($message, $source);
80
+ }
81
+ @return $arg;
82
+ }
@@ -0,0 +1,114 @@
1
+ //
2
+ // Copyright 2025 Google LLC
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ //
5
+
6
+ // Extensions to the go/sass:list built-in module.
7
+
8
+ // go/keep-sorted start by_regex='(.+) prefix_order=sass:
9
+ @use 'sass:list';
10
+ @use 'assert';
11
+ @use 'throw';
12
+ // go/keep-sorted end
13
+
14
+ /// Returns the difference between two lists.
15
+ ///
16
+ /// @example scss
17
+ /// $listA: ('apple', 'banana', 'cherry', 'date');
18
+ /// $listB: ('banana', 'cherry', 'apple');
19
+ /// $listC: ('apple', 'banana', 'date');
20
+ ///
21
+ /// @debug list_ext.difference($listA, $listB); // ('date')
22
+ /// @debug list_ext.difference($listA, $listC); // ('cherry')
23
+ ///
24
+ /// @param {list} $listA - The first list to compare.
25
+ /// @param {list} $listB - The second list to compare.
26
+ /// @return {list} All items in $listA that are not in $listB.
27
+ @function difference($listA, $listB) {
28
+ $listA: assert.is-type($listA, 'list', $source: 'list_ext.difference');
29
+ $listB: assert.is-type($listB, 'list', $source: 'list_ext.difference');
30
+ $error: throw.get-error($listA, $listB);
31
+ @if $error {
32
+ @return $error;
33
+ }
34
+
35
+ $result: ();
36
+ @each $item in $listA {
37
+ @if not list.index($listB, $item) {
38
+ $result: list.append($result, $item, list.separator($listA));
39
+ }
40
+ }
41
+ @return $result;
42
+ }
43
+
44
+ /// Checks if two lists contain the same elements, regardless of their order.
45
+ ///
46
+ /// The function iterates through each list and verifies that every element in
47
+ /// one list is present in the other. The order of elements does not affect the
48
+ /// result.
49
+ ///
50
+ /// @example scss
51
+ /// $listA: ('apple', 'banana', 'cherry');
52
+ /// $listB: ('banana', 'cherry', 'apple');
53
+ /// $listC: ('apple', 'banana', 'date');
54
+ ///
55
+ /// @debug list_ext.are-equal($listA, $listB); // true
56
+ /// @debug list_ext.are-equal($listA, $listC); // false
57
+ ///
58
+ /// @param {list} $listA - The first list to compare.
59
+ /// @param {list} $listB - The second list to compare.
60
+ /// @return {boolean} `true` if the lists contain the same elements, otherwise
61
+ /// `false`.
62
+ @function are-equal($listA, $listB) {
63
+ $listA: assert.is-type($listA, 'list', $source: 'list_ext.are-equal');
64
+ $listB: assert.is-type($listB, 'list', $source: 'list_ext.are-equal');
65
+ $error: throw.get-error($listA, $listB);
66
+ @if $error {
67
+ @return $error;
68
+ }
69
+ @if list.length($listA) != list.length($listB) {
70
+ @return false;
71
+ }
72
+ $result: true;
73
+ @each $key in $listA {
74
+ @if not list.index($listB, $key) {
75
+ $result: false;
76
+ }
77
+ }
78
+
79
+ @each $key in $listB {
80
+ @if not list.index($listA, $key) {
81
+ $result: false;
82
+ }
83
+ }
84
+ @return $result;
85
+ }
86
+
87
+ /// Returns the intersection of two lists.
88
+ ///
89
+ /// @example scss
90
+ /// $listA: ('apple', 'banana', 'cherry', 'date');
91
+ /// $listB: ('banana', 'cherry', 'apple');
92
+ /// $listC: ('apple', 'banana', 'date');
93
+ ///
94
+ /// @debug list_ext.intersection($listA, $listB); // ('apple', 'banana', 'cherry')
95
+ /// @debug list_ext.intersection($listA, $listC); // ('apple', 'banana')
96
+ ///
97
+ /// @param {list} $listA - The first list to compare.
98
+ /// @param {list} $listB - The second list to compare.
99
+ /// @return {list} All items in $listA that are also in $listB.
100
+ @function intersection($listA, $listB) {
101
+ $listA: assert.is-type($listA, 'list', $source: 'list_ext.intersection');
102
+ $listB: assert.is-type($listB, 'list', $source: 'list_ext.intersection');
103
+ $error: throw.get-error($listA, $listB);
104
+ @if $error {
105
+ @return $error;
106
+ }
107
+ $result: ();
108
+ @each $item in $listA {
109
+ @if list.index($listB, $item) {
110
+ $result: list.append($result, $item, list.separator($listA));
111
+ }
112
+ }
113
+ @return $result;
114
+ }
@@ -0,0 +1,222 @@
1
+ //
2
+ // Copyright 2025 Google LLC
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ //
5
+
6
+ // Extensions to the go/sass:map built-in module.
7
+
8
+ // go/keep-sorted start by_regex='(.+) prefix_order=sass:
9
+ @use 'sass:list';
10
+ @use 'sass:map';
11
+ @use 'sass:meta';
12
+ @use 'assert';
13
+ @use 'throw';
14
+ @use 'type';
15
+ // go/keep-sorted end
16
+
17
+ /// The same as `map.get()` but throws an error if the key is not found.
18
+ ///
19
+ /// This is useful over `map.get()` when using Sass maps like records, where
20
+ /// the key is expected to exist.
21
+ ///
22
+ /// @example scss
23
+ /// $map: (
24
+ /// 'name': 'foo',
25
+ /// 'value': blue,
26
+ /// );
27
+ ///
28
+ /// @debug map_ext.get-strict($map, 'name'); // 'foo'
29
+ /// @debug map_ext.get-strict($map, 'bar'); // ERROR: Key "bar" expected but not found in $map: ('name': 'foo', 'value': blue)
30
+ ///
31
+ /// @param {map} $map - The map to retrieve the value from.
32
+ /// @param {string} $key - The key of the value to retrieve.
33
+ /// @param {list} $keys - Additional keys to retrieve deeply nested values.
34
+ /// @return {*} The value at the given key.
35
+ /// @throw Error if the key does not exist in the map.
36
+ @function get-strict($map, $key, $keys...) {
37
+ $map: assert.is-type($map, 'map', $source: 'map_ext.get-strict');
38
+ @if throw.get-error($map) {
39
+ @return $map;
40
+ }
41
+ @if not map.has-key($map, $key, $keys...) {
42
+ @return throw.error(
43
+ 'Key #{$key} expected but not found in $map: #{meta.inspect($map)}',
44
+ $source: 'map.get-strict'
45
+ );
46
+ }
47
+
48
+ @return map.get($map, $key, $keys...);
49
+ }
50
+
51
+ /// Splits a Map and returns a List pair with two new Maps: the first with the
52
+ /// provided keys and the second without.
53
+ ///
54
+ /// @example scss
55
+ /// $map: (
56
+ /// 'focus': blue,
57
+ /// 'focus-within': blue,
58
+ /// 'hover': teal,
59
+ /// 'active': green,
60
+ /// );
61
+ ///
62
+ /// $pair: map_ext.split($map, ('focus', 'focus-within'));
63
+ ///
64
+ /// $map-with-focus-keys: list.nth($pair, 1);
65
+ /// @debug $map-with-focus-keys; // ('focus': blue, 'focus-within': blue)
66
+ ///
67
+ /// $map-with-remaining-keys: list.nth($pair, 2);
68
+ /// @debug $map-with-remaining-keys; // ('hover': teal, 'active': green)
69
+ ///
70
+ /// @param {map} $map - The Map to split.
71
+ /// @param {list} $keys - List of keys to split the Map by.
72
+ /// @return {list} A List pair with two new Maps: the first with the keys
73
+ /// provided, and the second with the remaining keys.
74
+ @function split($map, $keys) {
75
+ @if type.matches($keys, 'string') {
76
+ // A list with a single string `('key')` collapses to a single string type
77
+ // at build time. We can force it to be a list using a list API method.
78
+ // Ex:
79
+ // map_ext.split($map, ('key')) is the same as
80
+ // map_ext.split($map, 'key')
81
+ $keys: list.append((), $keys);
82
+ }
83
+
84
+ // In Sass, `()` counts as both a map and a list, and always reports its type
85
+ // as a list. If we're given an empty map-list, we don't need to throw a type
86
+ // error.
87
+ $map: assert.is-type($map, 'map|list', $source: 'map_ext.split');
88
+ $keys: assert.is-type($keys, 'list', $source: 'map_ext.split');
89
+ $error: throw.get-error($map, $keys);
90
+ @if not $error and list.length($keys) == 0 {
91
+ $error: throw.error(
92
+ 'List of keys to split by are empty',
93
+ $source: 'map_ext.split'
94
+ );
95
+ }
96
+ @if $error {
97
+ @return $error;
98
+ }
99
+
100
+ // In Sass, `()` is an empty list. We can force Sass to use map types by
101
+ // creating the map with map APIs. This ensures that even if a map is empty,
102
+ // `meta.type-of()` will report it as a map.
103
+ $map-with-keys: map.merge((), ());
104
+ $map-without-keys: map.merge((), ());
105
+ @each $key, $value in $map {
106
+ $has-key: list.index($keys, $key) != null;
107
+ @if $has-key {
108
+ $map-with-keys: map.set($map-with-keys, $key, $value);
109
+ } @else {
110
+ $map-without-keys: map.set($map-without-keys, $key, $value);
111
+ }
112
+ }
113
+
114
+ @return ($map-with-keys, $map-without-keys);
115
+ }
116
+
117
+ /// Splits a Map and returns a new Map that only includes the provided keys.
118
+ ///
119
+ /// @example scss
120
+ /// $map: (
121
+ /// 'focus': blue,
122
+ /// 'focus-within': blue,
123
+ /// 'hover': teal,
124
+ /// 'active': green,
125
+ /// );
126
+ ///
127
+ /// $map-with-focus-keys: map_ext.pick($map, ('focus', 'focus-within'));
128
+ /// @debug $map-with-focus-keys; // ('focus': blue, 'focus-within': blue)
129
+ ///
130
+ /// @param {map} $map - The Map to split.
131
+ /// @param {list} $keys - List of keys to include in the new Map.
132
+ /// @return {map} Map with only the keys provided.
133
+ @function pick($map, $keys) {
134
+ $result: split($map, $keys);
135
+ @if throw.get-error($result) {
136
+ @return $result;
137
+ }
138
+ @return list.nth($result, 1);
139
+ }
140
+
141
+ /// Splits a Map and returns a new Map that excludes the provided keys.
142
+ ///
143
+ /// @example scss
144
+ /// $map: (
145
+ /// 'focus': blue,
146
+ /// 'focus-within': blue,
147
+ /// 'hover': teal,
148
+ /// 'active': green,
149
+ /// );
150
+ ///
151
+ /// $map-without-focus-keys: map_ext.omit($map, ('focus', 'focus-within'));
152
+ /// @debug $map-without-focus-keys; // ('hover': teal, 'active': green)
153
+ ///
154
+ /// @param {map} $map - The Map to split.
155
+ /// @param {list} $keys - List of keys to exclude from the new Map.
156
+ /// @return {map} Map without the keys provided.
157
+ @function omit($map, $keys) {
158
+ $result: split($map, $keys);
159
+ @if throw.get-error($result) {
160
+ @return $result;
161
+ }
162
+ @return list.nth($result, 2);
163
+ }
164
+
165
+ /// Returns the given map with any matching keys renamed according to the
166
+ /// provided Map of keys to rename.
167
+ ///
168
+ /// @example scss
169
+ /// $map: ('foo': red);
170
+ ///
171
+ /// $new-map: map_ext.rename-keys($map, ('foo': 'bar'));
172
+ /// @debug $new-map; // ('bar': red)
173
+ ///
174
+ /// @param {map} $map - The map to rename keys within.
175
+ /// @param {map} $keys-to-rename - A map of keys and their new names.
176
+ /// @return {map} The map with any matching keys renamed.
177
+ @function rename-keys($map, $keys-to-rename) {
178
+ $new-map: omit($map, map.keys($keys-to-rename));
179
+ @if throw.get-error($new-map) {
180
+ @return $new-map;
181
+ }
182
+
183
+ @each $old-key-name, $new-key-name in $keys-to-rename {
184
+ @if map.has-key($map, $old-key-name) {
185
+ $new-map: map.set($new-map, $new-key-name, map.get($map, $old-key-name));
186
+ }
187
+ }
188
+
189
+ @return $new-map;
190
+ }
191
+
192
+ /// Returns a list of keys where $mapB diverges from $mapA.
193
+ /// Divergence occurs when:
194
+ /// 1. A key exists in $mapB but not in $mapA.
195
+ /// 2. A key exists in both maps but with different values.
196
+ ///
197
+ /// @example scss
198
+ /// $mapA: ('foo': red, 'bar': yellow, 'baz': 10);
199
+ /// $mapB: ('foo': red, 'bar': green, 'baz': 10, 'fooBar': blue);
200
+ ///
201
+ /// $differences: map_ext.difference($mapA, $mapB);
202
+ /// @debug $differences; // ('bar', 'fooBar')
203
+ ///
204
+ /// @param {map} $mapA - The reference map.
205
+ /// @param {map} $mapB - The map to compare against the reference.
206
+ /// @return {list} A list of keys where $mapB diverges from $mapA.
207
+ @function difference($mapA, $mapB) {
208
+ $mapA: assert.is-type($mapA, 'map', $source: 'map_ext.difference');
209
+ $mapB: assert.is-type($mapB, 'map', $source: 'map_ext.difference');
210
+ $error: throw.get-error($mapA, $mapB);
211
+ @if $error {
212
+ @return $error;
213
+ }
214
+ $differences: ();
215
+ @each $key, $value in $mapB {
216
+ @if not map.has-key($mapA, $key) or map.get($mapA, $key) != $value {
217
+ $differences: list.append($differences, $key);
218
+ }
219
+ }
220
+
221
+ @return $differences;
222
+ }