UNPKG

@sandlada/vue-mdc

Version:

![Vue MDC Logo](https://raw.githubusercontent.com/sandlada/vue-mdc/refs/heads/main/docs/vue-mdc-cover.png)

247 lines (246 loc) 8.58 kB
/** * @license * Copyright 2025 Sandlada & Kai Orion * SPDX-License-Identifier: MIT */ import { defineComponent, createVNode, Fragment } from "vue"; import { componentNamePrefix } from "../../internals/component-name-prefix/component-name-prefix.js"; import { isServer } from "../../utils/is-server.js"; import { MaterialDesignSystem } from "../../utils/material-design-system.js"; import { props, FieldAppearance } from "./field.definition.js"; import css from "./styles/field.module.scss.js"; const Field = /* @__PURE__ */ defineComponent({ name: `${componentNamePrefix}-field`, props, slots: {}, emits: [], mounted() { if (isServer()) { return; } this.populated = this.defaultPopulated; this.focused = this.defaultFocused; this.animateLabelIfNeeded(); }, beforeUpdate() { if (this.disabled && this.focused) { this.focused = false; } }, updated() { if (this.refreshErrorAlert) { requestAnimationFrame(() => { this.refreshErrorAlert = false; }); } if (this.disableTransitions) { requestAnimationFrame(() => { this.refreshErrorAlert = false; }); } this.animateLabelIfNeeded(); }, data: () => ({ labelAnimation: null, isAnimating: false, refreshErrorAlert: false, disableTransitions: false, _populated: false, _focused: false, _wasPopulated: false, _wasFocused: false }), computed: { populated: { get() { return this._populated; }, set(value) { this._wasPopulated = this._populated; this._populated = value; } }, focused: { get() { return this._focused; }, set(value) { this._wasFocused = this._focused; this._focused = value; } } }, methods: { getFloatingLabelElement() { return this.$el.querySelector(`.${css.floating}.${css.label}`); }, getRestingLabelElement() { return this.$el.querySelector(`.${css.resting}.${css.label}`); }, getContainerElement() { return this.$el.querySelector(`.${css.container}`); }, supportingTextOrErrorText() { return this.error && this.errorText ? this.errorText : this.supportingText; }, counterText() { const countNumber = this.count ?? -1; const maxNumber = this.max ?? -1; if (countNumber < 0 || maxNumber <= 0) { return ""; } return `${countNumber} / ${maxNumber}`; }, renderSupportingText() { const text = this.supportingTextOrErrorText(); if (!text && !this.counterText()) { return createVNode(Fragment, null, null); } const start = createVNode("span", null, [text]); const end = this.counterText() ? createVNode("span", { "class": css.counter }, [this.counterText()]) : createVNode(Fragment, null, null); const shouldErrorAnnounce = this.error && this.errorText && !this.refreshErrorAlert; const role = shouldErrorAnnounce ? "alert" : ""; return createVNode("div", { "class": css["supporting-text"], "role": role }, [start, end]); }, renderLabel(isFloating) { let visible; if (isFloating) { visible = this.focused || this.populated || this.isAnimating; } else { visible = !this.focused && !this.populated && !this.isAnimating; } const classes = [!visible && css.hidden, isFloating && css.floating, !isFloating && css.resting, css.label]; const labelText = `${this.label}${this.required && !this.noAsterisk ? "*" : ""}`; return createVNode("span", { "class": classes }, [labelText]); }, renderOutline(floatingLabel) { return createVNode("div", { "class": css.outline }, [createVNode("div", { "class": css["outline-start"] }, null), createVNode("div", { "class": css["outline-notch"] }, [createVNode("div", { "class": css["outline-panel-inactive"] }, null), createVNode("div", { "class": css["outline-panel-active"] }, null), createVNode("div", { "class": css["outline-label"] }, [floatingLabel])]), createVNode("div", { "class": css["outline-end"] }, null)]); }, renderBackground() { return createVNode("div", { "class": css.background }, null); }, renderStateLayer() { return createVNode("div", { "class": css["state-layer"] }, null); }, renderIndicator() { return createVNode("div", { "class": css["active-indicator"] }, null); }, animateLabelIfNeeded() { if (!this.label) { return; } const wasFloating = this._wasFocused || this._wasPopulated; const shouldBeFloating = this.focused || this.populated; if (wasFloating === shouldBeFloating) { return; } this.isAnimating = true; this.labelAnimation?.cancel(); this.labelAnimation = this.getFloatingLabelElement().animate(this.getLabelKeyframes(), { duration: 150, easing: MaterialDesignSystem.Motion.Easing.Standard }); this.labelAnimation?.addEventListener("finish", () => { this.isAnimating = false; }); }, getLabelKeyframes() { if (!this.getFloatingLabelElement() || !this.getRestingLabelElement()) { return []; } const { x: floatingX, y: floatingY, height: floatingHeight } = this.getFloatingLabelElement().getBoundingClientRect(); const { x: restingX, y: restingY, height: restingHeight } = this.getRestingLabelElement().getBoundingClientRect(); const floatingScrollWidth = this.getFloatingLabelElement().scrollWidth; const restingScrollWidth = this.getRestingLabelElement().scrollWidth; const scale = restingScrollWidth / floatingScrollWidth; const xDelta = restingX - floatingX; const yDelta = restingY - floatingY + Math.round((restingHeight - floatingHeight * scale) / 2); const restTransform = `translateX(${xDelta}px) translateY(${yDelta}px) scale(${scale})`; const floatTransform = `translateX(0) translateY(0) scale(1)`; const restingClientWidth = this.getRestingLabelElement().clientWidth; const isRestingClipped = restingScrollWidth > restingClientWidth; const width = isRestingClipped ? `${restingClientWidth / scale}px` : ""; if (this.focused || this.populated) { return [{ transform: restTransform, width }, { transform: floatTransform, width }]; } return [{ transform: floatTransform, width }, { transform: restTransform, width }]; } }, render() { const floatingLabel = this.renderLabel(true); const restingLabel = this.renderLabel(false); const isOutlined = this.appearance === FieldAppearance.Outlined; const classes = [this.disabled && css["disabled"], this.disableTransitions && css["disable-transitions"], this.error && !this.disabled && css["error"], this.focused && css["focused"], this.hasStart && css["with-start"], this.hasEnd && css["with-end"], this.populated && css["populated"], this.resizable && css["resizable"], this.required && css["required"], !this.label && css["no-label"], this.appearance === FieldAppearance.Filled && css["filled-field"], this.appearance === FieldAppearance.Outlined && css["outlined-field"]]; return createVNode("div", { "class": classes }, [createVNode("div", { "class": css["container-overflow"] }, [this.$slots.container && createVNode("span", { "class": css["container-slot"] }, [this.$slots.container()]), !isOutlined && this.renderBackground(), !isOutlined && this.renderStateLayer(), !isOutlined && this.renderIndicator(), isOutlined && this.renderOutline(floatingLabel), createVNode("div", { "class": css.container }, [createVNode("div", { "class": css.start }, [this.$slots.start && this.$slots.start()]), createVNode("div", { "class": css.middle }, [createVNode("div", { "class": css["label-wrapper"] }, [restingLabel, !isOutlined && floatingLabel]), createVNode("div", { "class": css.content }, [this.$slots.default && this.$slots.default()])]), createVNode("div", { "class": css.end }, [this.$slots.end && this.$slots.end()])])]), this.renderSupportingText()]); }, inheritAttrs: true }); export { Field }; //# sourceMappingURL=field.js.map