@sandlada/vue-mdc
Version:

247 lines (246 loc) • 8.58 kB
JavaScript
/**
* @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