@iyulab/components 1.6.0 → 1.7.1
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.
- package/CHANGELOG.md +12 -0
- package/dist/components/UFormControlElement.d.ts +3 -1
- package/dist/components/UFormControlElement.js +1 -1
- package/dist/components/input/UInput.d.ts +6 -2
- package/dist/components/input/UInput.js +4 -1
- package/dist/components/radio/URadio.d.ts +7 -1
- package/dist/components/radio/URadio.js +8 -0
- package/dist/components/rating/URating.d.ts +11 -3
- package/dist/components/rating/URating.js +16 -2
- package/dist/components/select/USelect.d.ts +9 -1
- package/dist/components/select/USelect.js +20 -0
- package/dist/components/slider/USlider.d.ts +7 -1
- package/dist/components/slider/USlider.js +19 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.7.0] - 2026-07-17
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **폼 컨트롤 `change` 이벤트 의미론을 네이티브 규약으로 교정** — `USelect`/`URadio`/`URating`/`USlider`가 `updated()` 경로에서 무조건 `change`를 발화해, (1) 옵션 slot 등록 시 `value===undefined` 상태의 change가 발화되어 React 등 controlled 래퍼의 state를 오염시키고(옵션 등록 전 세팅한 초기값이 유실·서버 enum 기본값으로 저장되는 무증상 데이터 결함 — yesung 실측), (2) 프로그램적 `value` 세팅이 사용자 이벤트로 위장되어 에코 루프를 만들던 문제 수정. 이제 `change`는 **사용자 상호작용**(옵션 클릭·키보드·칩 제거·지우기·드래그 확정)에서만 발화한다(ISSUE-components-20260717-uselect-value-before-options). `UInput`(blur 발화)·`UMenu`/`UTree`(핸들러 발화)는 원래 규약대로였으며 변경 없음.
|
|
7
|
+
- `USlider`: 문서("드래그 완료 후 발생")와 달리 **드래그 중 매 pointermove마다 change가 연사**되던 결함 수정 — 이제 `pointerup` 시 값이 실제로 바뀐 경우 1회 발화. 단일 select에서 동일 옵션 재선택, 선택된 라디오 재클릭도 네이티브와 동일하게 미발화.
|
|
8
|
+
- `USelect`/`URadio`/`URating`의 `onChangeValue()`가 `updated()` 내부에서 `validate()`→`requestUpdate()`를 호출해 v1.5.1 검증 아키텍처를 위반하고 "scheduled an update after an update completed" Lit 경고를 재유발하던 잔재 제거 — 검증 UI 갱신(`validate()`)은 사용자 상호작용 경로에서만 수행하고, 프로그램적 세팅은 base의 silent `setValidity()`로 internals만 갱신한다.
|
|
9
|
+
- **마크업 `value` attribute 선언이 일반 문자열에서 silently null이 되던 갭 수정** — base가 `type: Object`(JSON.parse)여서 `<u-input value="hello">`·`<u-select value="b">`가 null로 해석됐다. 기본 해석을 raw 문자열로 바꾸고, `u-rating`/`u-slider`는 숫자, `u-select`(multiple)/`u-slider`(range)는 JSON 배열(`value='["a","b"]'`)을 지원한다.
|
|
10
|
+
- `USlider` range 표시 텍스트의 구분자 인코딩 오염(`5 ??10`) → `5 ~ 10`으로 교정. `URating` min/max 주석 모지바케 정리.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- 프로그램적 `value` 세팅은 이제 `change` 발화·`invalid` 플래그 자동 갱신을 하지 않는다(`ElementInternals` validity는 계속 동기화됨). 프로그램적 변경 후 검증 UI 갱신이 필요하면 `validate()`를 명시 호출할 것. `value='"quoted"'` 형태(JSON 문자열)로 attribute를 우회 선언하던 경우 이제 따옴표 포함 raw 문자열로 해석된다.
|
|
14
|
+
|
|
3
15
|
## [1.6.0] - 2026-07-16
|
|
4
16
|
|
|
5
17
|
### Added
|
|
@@ -23,7 +23,9 @@ export declare abstract class UFormControlElement<T> extends UElement {
|
|
|
23
23
|
description?: string;
|
|
24
24
|
/** 폼 제출 시 사용되는 이름 */
|
|
25
25
|
name?: string;
|
|
26
|
-
/** 폼 제출 시 사용되는
|
|
26
|
+
/** 폼 제출 시 사용되는 값. attribute 선언 시 기본은 raw 문자열로 해석한다 —
|
|
27
|
+
* (이전의 type: Object는 JSON.parse 실패로 일반 문자열 attribute가 silently null이 되는 갭이 있었다)
|
|
28
|
+
* string이 아닌 값 타입을 갖는 컨트롤(URating/USlider/USelect multiple)은 자신의 converter로 override한다. */
|
|
27
29
|
value?: T;
|
|
28
30
|
/**
|
|
29
31
|
* ElementInternals는 폼과의 연동, 유효성 검사 상태 관리 등을 지원하는 네이티브 API입니다.
|
|
@@ -116,6 +116,6 @@ __decorate([property({ type: Boolean }), __decorateMetadata("design:type", Boole
|
|
|
116
116
|
__decorate([property({ type: String }), __decorateMetadata("design:type", String)], UFormControlElement.prototype, "label", void 0);
|
|
117
117
|
__decorate([property({ type: String }), __decorateMetadata("design:type", String)], UFormControlElement.prototype, "description", void 0);
|
|
118
118
|
__decorate([property({ type: String }), __decorateMetadata("design:type", String)], UFormControlElement.prototype, "name", void 0);
|
|
119
|
-
__decorate([property(
|
|
119
|
+
__decorate([property(), __decorateMetadata("design:type", Object)], UFormControlElement.prototype, "value", void 0);
|
|
120
120
|
//#endregion
|
|
121
121
|
export { UFormControlElement };
|
|
@@ -16,7 +16,11 @@ export type InputVariant = 'outlined' | 'filled' | 'underlined' | 'borderless';
|
|
|
16
16
|
*
|
|
17
17
|
* @csspart field - u-field 요소
|
|
18
18
|
* @csspart container - input과 prefix/suffix를 감싸는 컨테이너
|
|
19
|
-
* @csspart input - 네이티브 input
|
|
19
|
+
* @csspart input - 네이티브 input 요소.
|
|
20
|
+
* text-align 등 표현 관련 속성을 지정하지 않으므로 소비앱이 ::part(input)으로 재정의한다.
|
|
21
|
+
* 예: `u-input[type="number"]::part(input) { text-align: right; font-variant-numeric: tabular-nums; }`
|
|
22
|
+
* (숫자 우측정렬은 값이 열로 쌓여 자릿수를 비교할 때 유효하므로 라이브러리 기본값으로 두지 않는다.
|
|
23
|
+
* docs/theming.md 참고)
|
|
20
24
|
* @csspart popover - 드롭다운 팝오버 요소
|
|
21
25
|
*
|
|
22
26
|
* @cssprop --input-popover-width - 드롭다운 팝오버의 너비 (기본값: 앵커(트리거) 너비)
|
|
@@ -32,7 +36,7 @@ export declare class UInput extends UFormControlElement<string> {
|
|
|
32
36
|
variant: InputVariant;
|
|
33
37
|
/** 전체 지우기 버튼 표시 여부 */
|
|
34
38
|
clearable: boolean;
|
|
35
|
-
/** input 요소의 type
|
|
39
|
+
/** input 요소의 type 속성. 외부 `u-input[type="..."]` 스타일 훅을 위해 reflect 한다 (URadio.type 과 동일). */
|
|
36
40
|
type: InputType;
|
|
37
41
|
/** 최소 글자 수 */
|
|
38
42
|
minlength?: number;
|
|
@@ -276,7 +276,10 @@ __decorate([property({
|
|
|
276
276
|
type: Boolean,
|
|
277
277
|
reflect: true
|
|
278
278
|
}), __decorateMetadata("design:type", Boolean)], UInput.prototype, "clearable", void 0);
|
|
279
|
-
__decorate([property({
|
|
279
|
+
__decorate([property({
|
|
280
|
+
type: String,
|
|
281
|
+
reflect: true
|
|
282
|
+
}), __decorateMetadata("design:type", Object)], UInput.prototype, "type", void 0);
|
|
280
283
|
__decorate([property({ type: Number }), __decorateMetadata("design:type", Number)], UInput.prototype, "minlength", void 0);
|
|
281
284
|
__decorate([property({ type: Number }), __decorateMetadata("design:type", Number)], UInput.prototype, "maxlength", void 0);
|
|
282
285
|
__decorate([property({ type: String }), __decorateMetadata("design:type", String)], UInput.prototype, "min", void 0);
|
|
@@ -11,7 +11,8 @@ export type RadioOrientation = "vertical" | "horizontal";
|
|
|
11
11
|
* @csspart field - u-field 요소
|
|
12
12
|
* @csspart container - 옵션들을 감싸는 컨테이너
|
|
13
13
|
*
|
|
14
|
-
* @event change - 선택 값이 변경될 때
|
|
14
|
+
* @event change - 사용자 상호작용(옵션 클릭·키보드)으로 선택 값이 변경될 때 발생.
|
|
15
|
+
* 네이티브 라디오와 동일하게 프로그램적 value 세팅·옵션 등록으로는 발화하지 않는다.
|
|
15
16
|
*/
|
|
16
17
|
export declare class URadio extends UFormControlElement<string> {
|
|
17
18
|
static styles: import('lit').CSSResultGroup[];
|
|
@@ -30,6 +31,11 @@ export declare class URadio extends UFormControlElement<string> {
|
|
|
30
31
|
private setup;
|
|
31
32
|
private cleanup;
|
|
32
33
|
private onChangeValue;
|
|
34
|
+
/** 사용자 상호작용으로 값이 바뀐 경로에서만 호출한다 — 프로그램적 value 세팅은
|
|
35
|
+
* 네이티브 폼 컨트롤과 동일하게 change를 발화하지 않는다.
|
|
36
|
+
* UI 재렌더를 동반한 validate()도 이 경로에서만 수행한다(v1.5.1 검증 아키텍처 —
|
|
37
|
+
* updated() 경로는 base의 silent setValidity()만 수행해 Lit 중복 업데이트를 피한다). */
|
|
38
|
+
private emitChange;
|
|
33
39
|
private handleSlotChange;
|
|
34
40
|
private handleOptionClick;
|
|
35
41
|
private handleOptionKeyDown;
|
|
@@ -25,7 +25,9 @@ var URadio = class URadio extends UFormControlElement {
|
|
|
25
25
|
if (this.readonly || this.disabled) return;
|
|
26
26
|
const option = e.currentTarget;
|
|
27
27
|
if (option.disabled) return;
|
|
28
|
+
if (option.value === this.value) return;
|
|
28
29
|
this.value = option.value;
|
|
30
|
+
this.emitChange();
|
|
29
31
|
};
|
|
30
32
|
this.handleOptionKeyDown = (e) => {
|
|
31
33
|
if (this.readonly || this.disabled) return;
|
|
@@ -125,6 +127,12 @@ var URadio = class URadio extends UFormControlElement {
|
|
|
125
127
|
option.selected = option.value === this.value;
|
|
126
128
|
});
|
|
127
129
|
this.internals?.setFormValue(this.value || "");
|
|
130
|
+
}
|
|
131
|
+
/** 사용자 상호작용으로 값이 바뀐 경로에서만 호출한다 — 프로그램적 value 세팅은
|
|
132
|
+
* 네이티브 폼 컨트롤과 동일하게 change를 발화하지 않는다.
|
|
133
|
+
* UI 재렌더를 동반한 validate()도 이 경로에서만 수행한다(v1.5.1 검증 아키텍처 —
|
|
134
|
+
* updated() 경로는 base의 silent setValidity()만 수행해 Lit 중복 업데이트를 피한다). */
|
|
135
|
+
emitChange() {
|
|
128
136
|
if (!this.novalidate) this.validate();
|
|
129
137
|
this.dispatchEvent(new Event("change", {
|
|
130
138
|
bubbles: true,
|
|
@@ -14,16 +14,19 @@ import { UFormControlElement } from '../UFormControlElement.js';
|
|
|
14
14
|
* @cssprop --rating-symbol-color - 활성화된 심볼 색상
|
|
15
15
|
* @cssprop --rating-symbol-off-color - 비활성화된 심볼 색상
|
|
16
16
|
*
|
|
17
|
-
* @event change - 레이팅
|
|
17
|
+
* @event change - 사용자 상호작용(심볼 클릭·키보드)으로 레이팅 값이 변경될 때 발생.
|
|
18
|
+
* 프로그램적 value 세팅으로는 발화하지 않는다.
|
|
18
19
|
*/
|
|
19
20
|
export declare class URating extends UFormControlElement<number> {
|
|
20
21
|
static styles: import('lit').CSSResultGroup[];
|
|
21
|
-
/**
|
|
22
|
+
/** 최소 값 */
|
|
22
23
|
min: number;
|
|
23
|
-
/**
|
|
24
|
+
/** 최대 값 */
|
|
24
25
|
max: number;
|
|
25
26
|
/** 정밀도 (0.5 등) */
|
|
26
27
|
precision: number;
|
|
28
|
+
/** 레이팅 값. attribute 선언 시 숫자로 해석한다. */
|
|
29
|
+
value?: number;
|
|
27
30
|
private buffer;
|
|
28
31
|
private symbol;
|
|
29
32
|
private symbolOff;
|
|
@@ -35,6 +38,11 @@ export declare class URating extends UFormControlElement<number> {
|
|
|
35
38
|
private isStepMismatch;
|
|
36
39
|
reset(): void;
|
|
37
40
|
private onChangeValue;
|
|
41
|
+
/** 사용자 상호작용으로 값이 바뀐 경로에서만 호출한다 — 프로그램적 value 세팅은
|
|
42
|
+
* 네이티브 폼 컨트롤과 동일하게 change를 발화하지 않는다.
|
|
43
|
+
* UI 재렌더를 동반한 validate()도 이 경로에서만 수행한다(v1.5.1 검증 아키텍처 —
|
|
44
|
+
* updated() 경로는 base의 silent setValidity()만 수행해 Lit 중복 업데이트를 피한다). */
|
|
45
|
+
private emitChange;
|
|
38
46
|
private handleSlotChange;
|
|
39
47
|
private handleSymbolPointerMove;
|
|
40
48
|
private handleSymbolPointerLeave;
|
|
@@ -37,7 +37,10 @@ var URating = class URating extends UFormControlElement {
|
|
|
37
37
|
if (this.disabled || this.readonly) return;
|
|
38
38
|
const score = Number(e.currentTarget.dataset.score);
|
|
39
39
|
const val = this.precision < 1 ? this.calibrate(e, score) : score;
|
|
40
|
-
|
|
40
|
+
const next = this.value === val ? 0 : val;
|
|
41
|
+
if (next === this.value) return;
|
|
42
|
+
this.value = next;
|
|
43
|
+
this.emitChange();
|
|
41
44
|
};
|
|
42
45
|
this.handleSymbolKeydown = (e) => {
|
|
43
46
|
if (this.disabled || this.readonly) return;
|
|
@@ -68,7 +71,11 @@ var URating = class URating extends UFormControlElement {
|
|
|
68
71
|
case "Enter": {
|
|
69
72
|
e.preventDefault();
|
|
70
73
|
const score = Number(e.currentTarget.dataset.score);
|
|
71
|
-
|
|
74
|
+
const next = this.value === score ? 0 : score;
|
|
75
|
+
if (next !== this.value) {
|
|
76
|
+
this.value = next;
|
|
77
|
+
this.emitChange();
|
|
78
|
+
}
|
|
72
79
|
return;
|
|
73
80
|
}
|
|
74
81
|
default: return;
|
|
@@ -166,6 +173,12 @@ var URating = class URating extends UFormControlElement {
|
|
|
166
173
|
onChangeValue() {
|
|
167
174
|
this.buffer = -1;
|
|
168
175
|
this.internals?.setFormValue(this.value?.toString() || "");
|
|
176
|
+
}
|
|
177
|
+
/** 사용자 상호작용으로 값이 바뀐 경로에서만 호출한다 — 프로그램적 value 세팅은
|
|
178
|
+
* 네이티브 폼 컨트롤과 동일하게 change를 발화하지 않는다.
|
|
179
|
+
* UI 재렌더를 동반한 validate()도 이 경로에서만 수행한다(v1.5.1 검증 아키텍처 —
|
|
180
|
+
* updated() 경로는 base의 silent setValidity()만 수행해 Lit 중복 업데이트를 피한다). */
|
|
181
|
+
emitChange() {
|
|
169
182
|
if (!this.novalidate) this.validate();
|
|
170
183
|
this.dispatchEvent(new Event("change", {
|
|
171
184
|
bubbles: true,
|
|
@@ -183,6 +196,7 @@ var URating = class URating extends UFormControlElement {
|
|
|
183
196
|
__decorate([property({ type: Number }), __decorateMetadata("design:type", Object)], URating.prototype, "min", void 0);
|
|
184
197
|
__decorate([property({ type: Number }), __decorateMetadata("design:type", Object)], URating.prototype, "max", void 0);
|
|
185
198
|
__decorate([property({ type: Number }), __decorateMetadata("design:type", Object)], URating.prototype, "precision", void 0);
|
|
199
|
+
__decorate([property({ type: Number }), __decorateMetadata("design:type", Number)], URating.prototype, "value", void 0);
|
|
186
200
|
__decorate([state(), __decorateMetadata("design:type", Object)], URating.prototype, "buffer", void 0);
|
|
187
201
|
__decorate([state(), __decorateMetadata("design:type", Object)], URating.prototype, "symbol", void 0);
|
|
188
202
|
__decorate([state(), __decorateMetadata("design:type", Object)], URating.prototype, "symbolOff", void 0);
|
|
@@ -22,7 +22,8 @@ export type SelectVariant = 'outlined' | 'filled' | 'underlined' | 'borderless';
|
|
|
22
22
|
* @cssprop --select-popover-min-height - 팝오버의 최소 높이 (기본값: 0px)
|
|
23
23
|
* @cssprop --select-popover-max-height - 팝오버의 최대 높이 (기본값: 50vh)
|
|
24
24
|
*
|
|
25
|
-
* @event change - 선택 값이 변경될 때
|
|
25
|
+
* @event change - 사용자 상호작용(옵션 클릭·칩 제거·지우기)으로 선택 값이 변경될 때 발생.
|
|
26
|
+
* 네이티브 select와 동일하게 프로그램적 value 세팅·옵션 등록으로는 발화하지 않는다.
|
|
26
27
|
*/
|
|
27
28
|
export declare class USelect extends UFormControlElement<string | string[]> {
|
|
28
29
|
static styles: import('lit').CSSResultGroup[];
|
|
@@ -42,6 +43,8 @@ export declare class USelect extends UFormControlElement<string | string[]> {
|
|
|
42
43
|
maxCount?: number;
|
|
43
44
|
/** placeholder 텍스트 */
|
|
44
45
|
placeholder?: string;
|
|
46
|
+
/** 폼 제출 시 사용되는 값. attribute로는 단일 값 문자열 또는 JSON 배열(`value='["a","b"]'`)을 지원한다. */
|
|
47
|
+
value?: string | string[];
|
|
45
48
|
containerEl?: HTMLElement;
|
|
46
49
|
popoverEl?: UPopover;
|
|
47
50
|
private options;
|
|
@@ -58,6 +61,11 @@ export declare class USelect extends UFormControlElement<string | string[]> {
|
|
|
58
61
|
private setup;
|
|
59
62
|
private cleanup;
|
|
60
63
|
private onChangeValue;
|
|
64
|
+
/** 사용자 상호작용으로 값이 바뀐 경로에서만 호출한다 — 프로그램적 value 세팅은
|
|
65
|
+
* 네이티브 폼 컨트롤과 동일하게 change를 발화하지 않는다.
|
|
66
|
+
* UI 재렌더를 동반한 validate()도 이 경로에서만 수행한다(v1.5.1 검증 아키텍처 —
|
|
67
|
+
* updated() 경로는 base의 silent setValidity()만 수행해 Lit 중복 업데이트를 피한다). */
|
|
68
|
+
private emitChange;
|
|
61
69
|
private handleSlotChange;
|
|
62
70
|
private handleOptionClick;
|
|
63
71
|
private handleOptionKeydown;
|
|
@@ -36,8 +36,11 @@ var USelect = class USelect extends UFormControlElement {
|
|
|
36
36
|
if (this.maxCount != null && values.length >= this.maxCount) return;
|
|
37
37
|
this.value = [...values, option.value];
|
|
38
38
|
}
|
|
39
|
+
this.emitChange();
|
|
39
40
|
} else {
|
|
41
|
+
const changed = option.value !== this.value;
|
|
40
42
|
this.value = option.value;
|
|
43
|
+
if (changed) this.emitChange();
|
|
41
44
|
this.popoverEl?.hide();
|
|
42
45
|
}
|
|
43
46
|
};
|
|
@@ -104,12 +107,14 @@ var USelect = class USelect extends UFormControlElement {
|
|
|
104
107
|
e.preventDefault();
|
|
105
108
|
e.stopPropagation();
|
|
106
109
|
this.reset();
|
|
110
|
+
this.emitChange();
|
|
107
111
|
this.containerEl?.click();
|
|
108
112
|
};
|
|
109
113
|
this.handleChipRemove = (e) => {
|
|
110
114
|
const value = e.currentTarget.dataset.value;
|
|
111
115
|
if (!value) return;
|
|
112
116
|
this.value = this.valueAsArray.filter((v) => v !== value);
|
|
117
|
+
this.emitChange();
|
|
113
118
|
};
|
|
114
119
|
}
|
|
115
120
|
static {
|
|
@@ -263,6 +268,12 @@ var USelect = class USelect extends UFormControlElement {
|
|
|
263
268
|
for (const option of this.options) if (this.multiple) option.selected = values.includes(option.value);
|
|
264
269
|
else option.selected = option.value === this.value;
|
|
265
270
|
this.internals?.setFormValue(this.valueAsString);
|
|
271
|
+
}
|
|
272
|
+
/** 사용자 상호작용으로 값이 바뀐 경로에서만 호출한다 — 프로그램적 value 세팅은
|
|
273
|
+
* 네이티브 폼 컨트롤과 동일하게 change를 발화하지 않는다.
|
|
274
|
+
* UI 재렌더를 동반한 validate()도 이 경로에서만 수행한다(v1.5.1 검증 아키텍처 —
|
|
275
|
+
* updated() 경로는 base의 silent setValidity()만 수행해 Lit 중복 업데이트를 피한다). */
|
|
276
|
+
emitChange() {
|
|
266
277
|
if (!this.novalidate) this.validate();
|
|
267
278
|
this.dispatchEvent(new Event("change", {
|
|
268
279
|
bubbles: true,
|
|
@@ -299,6 +310,15 @@ __decorate([property({
|
|
|
299
310
|
attribute: "max-count"
|
|
300
311
|
}), __decorateMetadata("design:type", Number)], USelect.prototype, "maxCount", void 0);
|
|
301
312
|
__decorate([property({ type: String }), __decorateMetadata("design:type", String)], USelect.prototype, "placeholder", void 0);
|
|
313
|
+
__decorate([property({ converter: { fromAttribute: (value) => {
|
|
314
|
+
if (value == null) return void 0;
|
|
315
|
+
if (value.trim().startsWith("[")) try {
|
|
316
|
+
return JSON.parse(value);
|
|
317
|
+
} catch {
|
|
318
|
+
return value;
|
|
319
|
+
}
|
|
320
|
+
return value;
|
|
321
|
+
} } }), __decorateMetadata("design:type", Object)], USelect.prototype, "value", void 0);
|
|
302
322
|
__decorate([query(".container", true), __decorateMetadata("design:type", typeof HTMLElement === "undefined" ? Object : HTMLElement)], USelect.prototype, "containerEl", void 0);
|
|
303
323
|
__decorate([query("u-popover", true), __decorateMetadata("design:type", typeof UPopover === "undefined" ? Object : UPopover)], USelect.prototype, "popoverEl", void 0);
|
|
304
324
|
__decorate([state(), __decorateMetadata("design:type", Array)], USelect.prototype, "options", void 0);
|
|
@@ -32,7 +32,8 @@ export type SliderFormatter = (value: number) => string;
|
|
|
32
32
|
* @cssprop --slider-mark-color - 마크 색상
|
|
33
33
|
* @cssprop --slider-mark-border-color - 마크 테두리 색상
|
|
34
34
|
*
|
|
35
|
-
* @event change -
|
|
35
|
+
* @event change - 사용자 상호작용으로 값이 확정됐을 때 발생 — 드래그는 완료(pointerup) 시,
|
|
36
|
+
* 키보드는 조작마다. 프로그램적 value 세팅으로는 발화하지 않는다.
|
|
36
37
|
*/
|
|
37
38
|
export declare class USlider extends UFormControlElement<number | number[]> {
|
|
38
39
|
static styles: import('lit').CSSResultGroup[];
|
|
@@ -54,6 +55,8 @@ export declare class USlider extends UFormControlElement<number | number[]> {
|
|
|
54
55
|
max: number;
|
|
55
56
|
/** 값 변경 단위 */
|
|
56
57
|
step: number;
|
|
58
|
+
/** 슬라이더 값. attribute로는 숫자 또는 range용 JSON 배열(`value='[10,20]'`)을 지원한다. */
|
|
59
|
+
value?: number | number[];
|
|
57
60
|
private dragging;
|
|
58
61
|
private trackEl;
|
|
59
62
|
private containerEl?;
|
|
@@ -72,6 +75,9 @@ export declare class USlider extends UFormControlElement<number | number[]> {
|
|
|
72
75
|
protected setValidity(): void;
|
|
73
76
|
reset(): void;
|
|
74
77
|
private onChangeValue;
|
|
78
|
+
/** 사용자 상호작용으로 값이 확정된 경로에서만 호출한다 — 프로그램적 value 세팅은
|
|
79
|
+
* 네이티브 폼 컨트롤과 동일하게 change를 발화하지 않는다. */
|
|
80
|
+
private emitChange;
|
|
75
81
|
private renderThumb;
|
|
76
82
|
private formatDisplay;
|
|
77
83
|
private formatValue;
|
|
@@ -26,6 +26,7 @@ var USlider = class USlider extends UFormControlElement {
|
|
|
26
26
|
const pct = Math.max(0, Math.min(rect.width, ev.clientX - rect.left)) / rect.width * 100;
|
|
27
27
|
return this.percentToVal(pct);
|
|
28
28
|
};
|
|
29
|
+
const before = JSON.stringify(this.value);
|
|
29
30
|
const val = getPointerValue(e);
|
|
30
31
|
if (this.range) {
|
|
31
32
|
const closer = Math.abs(val - this.minVal) <= Math.abs(val - this.maxVal) ? "min" : "max";
|
|
@@ -47,6 +48,7 @@ var USlider = class USlider extends UFormControlElement {
|
|
|
47
48
|
document.removeEventListener("pointermove", handleDocumentMove);
|
|
48
49
|
document.removeEventListener("pointerup", handleDocumentUp);
|
|
49
50
|
this.dragging = null;
|
|
51
|
+
if (JSON.stringify(this.value) !== before) this.emitChange();
|
|
50
52
|
};
|
|
51
53
|
document.addEventListener("pointermove", handleDocumentMove);
|
|
52
54
|
document.addEventListener("pointerup", handleDocumentUp);
|
|
@@ -82,9 +84,11 @@ var USlider = class USlider extends UFormControlElement {
|
|
|
82
84
|
}
|
|
83
85
|
e.preventDefault();
|
|
84
86
|
const newVal = this.snap(currentVal + delta);
|
|
87
|
+
const before = JSON.stringify(this.value);
|
|
85
88
|
if (thumb === "min") if (this.range) this.minVal = Math.min(newVal, this.maxVal);
|
|
86
89
|
else this.value = newVal;
|
|
87
90
|
else this.maxVal = Math.max(newVal, this.minVal);
|
|
91
|
+
if (JSON.stringify(this.value) !== before) this.emitChange();
|
|
88
92
|
};
|
|
89
93
|
}
|
|
90
94
|
static {
|
|
@@ -197,6 +201,10 @@ var USlider = class USlider extends UFormControlElement {
|
|
|
197
201
|
if (Array.isArray(this.value)) this.value = [this.snap(this.value[0]), this.snap(this.value[1])];
|
|
198
202
|
else if (typeof this.value === "number") this.value = this.snap(this.value);
|
|
199
203
|
this.internals?.setFormValue(this.valueAsNumber.toString());
|
|
204
|
+
}
|
|
205
|
+
/** 사용자 상호작용으로 값이 확정된 경로에서만 호출한다 — 프로그램적 value 세팅은
|
|
206
|
+
* 네이티브 폼 컨트롤과 동일하게 change를 발화하지 않는다. */
|
|
207
|
+
emitChange() {
|
|
200
208
|
this.dispatchEvent(new Event("change", {
|
|
201
209
|
bubbles: true,
|
|
202
210
|
composed: true
|
|
@@ -229,7 +237,7 @@ var USlider = class USlider extends UFormControlElement {
|
|
|
229
237
|
`;
|
|
230
238
|
}
|
|
231
239
|
formatDisplay() {
|
|
232
|
-
if (this.range) return `${this.formatValue(this.minVal)}
|
|
240
|
+
if (this.range) return `${this.formatValue(this.minVal)} ~ ${this.formatValue(this.maxVal)}`;
|
|
233
241
|
else return this.formatValue(this.valueAsNumber);
|
|
234
242
|
}
|
|
235
243
|
formatValue(val) {
|
|
@@ -269,6 +277,16 @@ __decorate([property({ type: Number }), __decorateMetadata("design:type", Number
|
|
|
269
277
|
__decorate([property({ type: Number }), __decorateMetadata("design:type", Number)], USlider.prototype, "min", void 0);
|
|
270
278
|
__decorate([property({ type: Number }), __decorateMetadata("design:type", Number)], USlider.prototype, "max", void 0);
|
|
271
279
|
__decorate([property({ type: Number }), __decorateMetadata("design:type", Number)], USlider.prototype, "step", void 0);
|
|
280
|
+
__decorate([property({ converter: { fromAttribute: (value) => {
|
|
281
|
+
if (value == null) return void 0;
|
|
282
|
+
if (value.trim().startsWith("[")) try {
|
|
283
|
+
return JSON.parse(value);
|
|
284
|
+
} catch {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const n = Number(value);
|
|
288
|
+
return Number.isNaN(n) ? void 0 : n;
|
|
289
|
+
} } }), __decorateMetadata("design:type", Object)], USlider.prototype, "value", void 0);
|
|
272
290
|
__decorate([state(), __decorateMetadata("design:type", Object)], USlider.prototype, "dragging", void 0);
|
|
273
291
|
__decorate([query(".track"), __decorateMetadata("design:type", typeof HTMLElement === "undefined" ? Object : HTMLElement)], USlider.prototype, "trackEl", void 0);
|
|
274
292
|
__decorate([query(".container"), __decorateMetadata("design:type", typeof HTMLElement === "undefined" ? Object : HTMLElement)], USlider.prototype, "containerEl", void 0);
|