@mono-lit/helper 0.0.6 → 0.0.8
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/dist/{control-tooltip-C3rA_nSH.js → control-tooltip-CoPD37_h.js} +20 -11
- package/dist/{data-source-options-Cz9fVLK8.js → data-source-options-BM4r4PRw.js} +26 -9
- package/dist/{data-source-options-QjuI73g8.js → data-source-options-fxT5QIX4.js} +26 -9
- package/dist/{data-source-read-FxqzQkk2.js → data-source-read-BL266IW0.js} +25 -3
- package/dist/{data-source-read-i6CYSUEl.js → data-source-read-HyBCRg1t.js} +25 -3
- package/dist/{excel-BPdDt06G.js → excel-DtRpxmxs.js} +1 -1
- package/dist/{export-DgomqI_3.js → export-ixRdCS2y.js} +1 -1
- package/dist/export.js +3 -3
- package/dist/{field-tooltip-FyMi9vNM.js → field-tooltip-Csk4s2ve.js} +2 -3
- package/dist/field-tooltip-addon-B32dizW_.js +22 -0
- package/dist/{control-tooltip-7vegYmq3.js → field-tooltip-addon-CWCp19mJ.js} +36 -12
- package/dist/{field-tooltip.css_raw-DEctHXKm.js → field-tooltip.css_raw-U5UeTa2d.js} +3 -4
- package/dist/{field-validation-CHHQi1zl.js → field-validation-CFmUmuDx.js} +1 -1
- package/dist/field-validation.css_raw-Cx6SGrfP.js +178 -0
- package/dist/{form-control-core-rW2WvOcb.js → form-control-core-BbiACMtL.js} +1 -1
- package/dist/{hydration-flush-9AJE8FRl.js → hydration-flush-BaE9gN3C.js} +67 -17
- package/dist/index.js +10 -10
- package/dist/{index.node-tTkISFn2.js → index.node-CLqE8F8P.js} +5 -5
- package/dist/index.node.js +10 -10
- package/dist/input.css_raw-Br-T8RWv.js +5 -0
- package/dist/{light-slots-BnBR29E7.js → light-slots-B5Ck0t_E.js} +1 -1
- package/dist/{light-slots-Cb6e-LU3.js → light-slots-_no0r2Z-.js} +1 -1
- package/dist/mono-button-CLkHg3WW.js +871 -0
- package/dist/{mono-button.shadow-CIBGisyo.js → mono-button.shadow-DqZMpcqY.js} +1 -1
- package/dist/{mono-data-chart-ChkelNT7.js → mono-data-chart-NKPdAgVq.js} +2 -2
- package/dist/{mono-data-dropdown-DxpUUk2y.js → mono-data-dropdown-DUXo_jxd.js} +72 -6
- package/dist/{mono-data-grid-8jmeIXUU.js → mono-data-grid-Bb00QM72.js} +129 -18
- package/dist/{mono-data-grid-KDpu4d4_.js → mono-data-grid-Bkr-e1-i.js} +130 -19
- package/dist/{mono-element-Bc9wElPy.js → mono-element-CKcT0rwv.js} +1 -1
- package/dist/{mono-form-controller-BkMR8g14.js → mono-form-controller-BrA10erm.js} +12 -1
- package/dist/{mono-ui-KOBJcnXk.js → mono-ui-kvLVIf1V.js} +43 -15
- package/dist/{popup-portal-C8ttHDRX.js → popup-portal-CINuVjNY.js} +53 -6
- package/dist/src/components/accordion/mono-accordion.d.ts +0 -1
- package/dist/src/components/alert/mono-alert.d.ts +0 -1
- package/dist/src/components/breadcrumb/mono-breadcrumb-list.d.ts +0 -1
- package/dist/src/components/breadcrumb/mono-breadcrumb.d.ts +0 -1
- package/dist/src/components/button/mono-button-dropdown.d.ts +0 -1
- package/dist/src/components/button/mono-button.d.ts +0 -1
- package/dist/src/components/card/mono-card.d.ts +0 -1
- package/dist/src/components/chart/mono-chart-presets.d.ts +0 -4
- package/dist/src/components/chart/mono-chart.d.ts +0 -1
- package/dist/src/components/checkbox/mono-checkbox.d.ts +0 -1
- package/dist/src/components/chip/mono-chip.d.ts +0 -1
- package/dist/src/components/date/mono-date.d.ts +0 -1
- package/dist/src/components/drawer/mono-drawer.d.ts +0 -1
- package/dist/src/components/dropdown/mono-dropdown.d.ts +0 -1
- package/dist/src/components/dropdown-table/mono-dropdown-table.d.ts +0 -1
- package/dist/src/components/file-upload/mono-file-upload.d.ts +0 -1
- package/dist/src/components/filter/mono-filter-builder.d.ts +0 -1
- package/dist/src/components/input/mono-input.d.ts +0 -1
- package/dist/src/components/menu/mono-menu.d.ts +0 -1
- package/dist/src/components/modal/mono-modal.d.ts +0 -1
- package/dist/src/components/nav/mono-nav.d.ts +0 -1
- package/dist/src/components/otp/mono-otp.d.ts +0 -1
- package/dist/src/components/radio/mono-radio.d.ts +0 -1
- package/dist/src/components/rich-text-editor/mono-rich-text-editor.d.ts +0 -1
- package/dist/src/components/select/mono-select.d.ts +0 -1
- package/dist/src/components/sidebar/mono-sidebar.d.ts +0 -1
- package/dist/src/components/slider/mono-slider.d.ts +0 -1
- package/dist/src/components/switch/mono-switch.d.ts +0 -1
- package/dist/src/components/table/column-autosize.d.ts +23 -1
- package/dist/src/components/table/mono-data-grid.d.ts +6 -0
- package/dist/src/components/table/mono-table-checkbox.d.ts +0 -1
- package/dist/src/components/table/mono-table-detail.d.ts +0 -1
- package/dist/src/components/table/mono-table-empty.d.ts +0 -1
- package/dist/src/components/table/mono-table-error.d.ts +0 -1
- package/dist/src/components/table/mono-table-info.d.ts +0 -1
- package/dist/src/components/table/mono-table-loading.d.ts +0 -1
- package/dist/src/components/table/mono-table-page-size.d.ts +0 -1
- package/dist/src/components/table/mono-table-paging-group.d.ts +0 -1
- package/dist/src/components/table/mono-table-paging.d.ts +0 -1
- package/dist/src/components/table/mono-table-search.d.ts +0 -1
- package/dist/src/components/table/mono-table-sort.d.ts +0 -1
- package/dist/src/components/table/mono-table-summary.d.ts +0 -1
- package/dist/src/components/table/mono-table-th.d.ts +0 -1
- package/dist/src/components/table/table-controller-core.d.ts +2 -0
- package/dist/src/components/tabs/mono-tabs.d.ts +0 -1
- package/dist/src/components/tag-input/mono-tag-input.d.ts +0 -1
- package/dist/src/components/textarea/mono-textarea.d.ts +0 -1
- package/dist/src/components/tooltip/field-tooltip-addon.d.ts +5 -0
- package/dist/src/composables/popup-portal.d.ts +24 -1
- package/dist/src/utils/data-source-read.d.ts +10 -1
- package/dist/{styles-BODNpZV1.js → styles-D-ergrfR.js} +1 -1
- package/dist/{tooltip-Drekihf5.js → tooltip-Cu5IrDS9.js} +1 -2
- package/dist/tooltip.js +2 -3
- package/dist/ui/accordion.js +4 -11
- package/dist/ui/alert.js +3 -10
- package/dist/ui/breadcrumb.js +4 -14
- package/dist/ui/button-dropdown.js +22 -15
- package/dist/ui/button.js +1 -1
- package/dist/ui/card.js +4 -11
- package/dist/ui/chart.js +4 -23
- package/dist/ui/checkbox.js +4 -11
- package/dist/ui/chip.js +6 -9
- package/dist/ui/date.js +5 -12
- package/dist/ui/drawer.js +5 -12
- package/dist/ui/dropdown-table.js +89 -37
- package/dist/ui/dropdown.js +34 -21
- package/dist/ui/file-upload.js +4 -11
- package/dist/ui/filter.js +3 -10
- package/dist/ui/form.js +1 -1
- package/dist/ui/index.css +3840 -3840
- package/dist/ui/input.js +6 -13
- package/dist/ui/menu.js +4 -11
- package/dist/ui/modal.js +6 -13
- package/dist/ui/nav.js +4 -11
- package/dist/ui/otp.js +6 -13
- package/dist/ui/radio.js +4 -11
- package/dist/ui/rich-text-editor.js +4 -11
- package/dist/ui/select.js +12 -14
- package/dist/ui/shadow/accordion.js +1 -1
- package/dist/ui/shadow/alert.js +1 -1
- package/dist/ui/shadow/breadcrumb.js +1 -1
- package/dist/ui/shadow/button-dropdown.js +21 -7
- package/dist/ui/shadow/button.js +1 -1
- package/dist/ui/shadow/card.js +1 -1
- package/dist/ui/shadow/chart.js +3 -3
- package/dist/ui/shadow/checkbox.js +2 -2
- package/dist/ui/shadow/chip.js +1 -1
- package/dist/ui/shadow/date.js +4 -4
- package/dist/ui/shadow/drawer.js +2 -2
- package/dist/ui/shadow/dropdown-table.js +159 -34
- package/dist/ui/shadow/dropdown.js +31 -11
- package/dist/ui/shadow/file-upload.js +2 -2
- package/dist/ui/shadow/filter.js +1 -1
- package/dist/ui/shadow/input.js +5 -5
- package/dist/ui/shadow/menu.js +1 -1
- package/dist/ui/shadow/modal.js +3 -3
- package/dist/ui/shadow/nav.js +1 -1
- package/dist/ui/shadow/otp.js +5 -5
- package/dist/ui/shadow/radio.js +2 -2
- package/dist/ui/shadow/rich-text-editor.js +2 -2
- package/dist/ui/shadow/select.js +11 -6
- package/dist/ui/shadow/sidebar.js +2 -2
- package/dist/ui/shadow/slider.js +4 -4
- package/dist/ui/shadow/switch.js +2 -2
- package/dist/ui/shadow/table.js +84 -20
- package/dist/ui/shadow/tabs.js +1 -1
- package/dist/ui/shadow/tag-input.js +117 -21
- package/dist/ui/shadow/textarea.js +4 -4
- package/dist/ui/sidebar.js +4 -11
- package/dist/ui/slider.js +5 -12
- package/dist/ui/switch.js +4 -11
- package/dist/ui/table.js +85 -64
- package/dist/ui/tabs.js +4 -11
- package/dist/ui/tag-input.js +118 -29
- package/dist/ui/textarea.js +6 -13
- package/dist/ui/theme/luma.css +495 -495
- package/dist/ui/theme/lyra.css +495 -495
- package/dist/ui/theme/maia.css +495 -495
- package/dist/ui/theme/mira.css +495 -495
- package/dist/ui/theme/nova.css +495 -495
- package/dist/ui/theme/one.css +742 -742
- package/dist/ui/theme/rhea.css +495 -495
- package/dist/ui/theme/sera.css +495 -495
- package/dist/ui/theme/vega.css +495 -495
- package/package.json +2 -2
- package/dist/field-validation.css_raw-B3Iu33rS.js +0 -178
- package/dist/input.css_raw-CQu2fAsU.js +0 -5
- package/dist/mono-button-Cy5-Y0At.js +0 -878
- package/dist/tooltip-loader-3U9FTOXj.js +0 -20
- package/dist/tooltip-loader-Dcp9AUZn.js +0 -23
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
import { d as buildSizeStyle } from "./mono-ui-kvLVIf1V.js";
|
|
2
|
+
import { t as customElement } from "./mono-element-CKcT0rwv.js";
|
|
3
|
+
import { a as defineHybridPropAliases, r as booleanStringConverter, t as __decorate } from "./decorate-DbYFpi8R.js";
|
|
4
|
+
import { t as dispatchMonoEvent } from "./mono-event-BPku9HpS.js";
|
|
5
|
+
import { n as bucketHasContent, r as captureLightSlots, s as placeSlotNode, t as LateSlotWatcher } from "./light-slots-B5Ck0t_E.js";
|
|
6
|
+
import { a as rateLimitHasChanged, i as rateLimitConverter, n as normalizeDebounce, r as normalizeThrottle, t as createRateLimiter } from "./rate-limit-e9Jlpl5l.js";
|
|
7
|
+
import { LitElement, html, nothing } from "lit";
|
|
8
|
+
import { property, query, state } from "lit/decorators.js";
|
|
9
|
+
import { ifDefined } from "lit/directives/if-defined.js";
|
|
10
|
+
import { when } from "lit/directives/when.js";
|
|
11
|
+
|
|
12
|
+
//#region src/components/button/button-core.ts
|
|
13
|
+
/**
|
|
14
|
+
* `MonoButtonCore` — all render-mode-agnostic logic for `mono-button`: reactive
|
|
15
|
+
* props, hybrid aliases, the camelCase attribute fallbacks, the slot-presence
|
|
16
|
+
* `@state` (`_hasIcon`), class/getter computation, sizing, click/keyboard
|
|
17
|
+
* interactivity, and the imperative `focus/blur/click`. No `render()` — the light
|
|
18
|
+
* build keeps its `[data-mono-slot]` capture strategy and the shadow build uses
|
|
19
|
+
* native `<slot>` (each ships its own `render()`, mirroring `mono-card`).
|
|
20
|
+
*
|
|
21
|
+
* The `rounded="full"` auto-"icon-only when empty" refinement depends on light-DOM slot
|
|
22
|
+
* introspection, so it lives behind the `_defaultIsEmpty()` hook (defaults to
|
|
23
|
+
* `false` here; the light build overrides it). SSR-safe: no `document`/`window`
|
|
24
|
+
* access; `_buttonElement` (`@query`) is lazy and `focus/blur/click` only run
|
|
25
|
+
* client-side.
|
|
26
|
+
*/
|
|
27
|
+
const MonoButtonCore = (superClass) => {
|
|
28
|
+
class MonoButtonCoreClass extends superClass {
|
|
29
|
+
constructor(...args) {
|
|
30
|
+
super(...args);
|
|
31
|
+
this.cssClass = {};
|
|
32
|
+
this._hasIcon = false;
|
|
33
|
+
this._hasPrepend = false;
|
|
34
|
+
this._hasAppend = false;
|
|
35
|
+
this._pending = 0;
|
|
36
|
+
this._running = 0;
|
|
37
|
+
this._throttler = null;
|
|
38
|
+
this._debouncer = null;
|
|
39
|
+
this._directRunning = 0;
|
|
40
|
+
this._lastPhase = "idle";
|
|
41
|
+
defineHybridPropAliases(this, [
|
|
42
|
+
"iconPosition",
|
|
43
|
+
"badgeColor",
|
|
44
|
+
"iconOnly",
|
|
45
|
+
"ariaLabelText",
|
|
46
|
+
"cssClass",
|
|
47
|
+
"noAutoLoading",
|
|
48
|
+
"minWidth",
|
|
49
|
+
"maxWidth",
|
|
50
|
+
"minHeight",
|
|
51
|
+
"maxHeight"
|
|
52
|
+
]);
|
|
53
|
+
this.size = "md";
|
|
54
|
+
this.color = "primary";
|
|
55
|
+
this.variant = "solid";
|
|
56
|
+
this.disabled = false;
|
|
57
|
+
this.loading = false;
|
|
58
|
+
this.href = void 0;
|
|
59
|
+
this.target = void 0;
|
|
60
|
+
this.type = "button";
|
|
61
|
+
this.iconPosition = "left";
|
|
62
|
+
this.prepend = {};
|
|
63
|
+
this.append = {};
|
|
64
|
+
this.badge = void 0;
|
|
65
|
+
this.badgeColor = "red";
|
|
66
|
+
this.iconOnly = false;
|
|
67
|
+
this.glass = false;
|
|
68
|
+
this.tooltip = void 0;
|
|
69
|
+
this.ariaLabelText = void 0;
|
|
70
|
+
this.throttle = void 0;
|
|
71
|
+
this.debounce = void 0;
|
|
72
|
+
this.handler = void 0;
|
|
73
|
+
this.noAutoLoading = false;
|
|
74
|
+
this._isActive = false;
|
|
75
|
+
}
|
|
76
|
+
static get observedAttributes() {
|
|
77
|
+
return [
|
|
78
|
+
...super.observedAttributes ?? [],
|
|
79
|
+
"iconposition",
|
|
80
|
+
"badgecolor",
|
|
81
|
+
"icononly",
|
|
82
|
+
"arialabeltext",
|
|
83
|
+
"noautoloading",
|
|
84
|
+
"prepend",
|
|
85
|
+
"append"
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
89
|
+
super.attributeChangedCallback(name, oldValue, newValue);
|
|
90
|
+
if (oldValue === newValue) return;
|
|
91
|
+
if (name === "icononly") {
|
|
92
|
+
this.iconOnly = this._toBoolean(newValue);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (name === "prepend" || name === "append") {
|
|
96
|
+
this._setAffix(name, newValue);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (name === "iconposition") {
|
|
100
|
+
this.iconPosition = newValue ?? "left";
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (name === "badgecolor") {
|
|
104
|
+
this.badgeColor = newValue ?? "red";
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (name === "noautoloading") {
|
|
108
|
+
this.noAutoLoading = this._toBoolean(newValue);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (name === "arialabeltext") this.ariaLabelText = newValue ?? void 0;
|
|
112
|
+
}
|
|
113
|
+
willUpdate(changed) {
|
|
114
|
+
for (const key of [
|
|
115
|
+
"disabled",
|
|
116
|
+
"loading",
|
|
117
|
+
"iconOnly",
|
|
118
|
+
"glass",
|
|
119
|
+
"noAutoLoading"
|
|
120
|
+
]) if (typeof this[key] === "string") this[key] = this._toBoolean(this[key]);
|
|
121
|
+
if (changed.has("throttle")) this._invalidateLimiter("throttle");
|
|
122
|
+
if (changed.has("debounce")) this._invalidateLimiter("debounce");
|
|
123
|
+
super.willUpdate?.(changed);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Limiters are built on demand rather than up front: nothing schedules a
|
|
127
|
+
* timer server-side, and a disposed one (config change, disconnect) simply
|
|
128
|
+
* rebuilds on the next click — which is also what makes the element
|
|
129
|
+
* survive being moved or re-activated under `<KeepAlive>`.
|
|
130
|
+
*/
|
|
131
|
+
_ensureThrottler() {
|
|
132
|
+
if (this._throttler) return this._throttler;
|
|
133
|
+
const config = normalizeThrottle(this.throttle);
|
|
134
|
+
if (!config) return null;
|
|
135
|
+
this._throttler = createRateLimiter({
|
|
136
|
+
kind: "throttle",
|
|
137
|
+
config,
|
|
138
|
+
run: (...args) => this._runLimited("throttle", args[0]),
|
|
139
|
+
onChange: () => this._syncLoading()
|
|
140
|
+
});
|
|
141
|
+
return this._throttler;
|
|
142
|
+
}
|
|
143
|
+
_ensureDebouncer() {
|
|
144
|
+
if (this._debouncer) return this._debouncer;
|
|
145
|
+
const config = normalizeDebounce(this.debounce);
|
|
146
|
+
if (!config) return null;
|
|
147
|
+
this._debouncer = createRateLimiter({
|
|
148
|
+
kind: "debounce",
|
|
149
|
+
config,
|
|
150
|
+
run: (...args) => this._runLimited("debounce", args[0]),
|
|
151
|
+
onChange: () => this._syncLoading()
|
|
152
|
+
});
|
|
153
|
+
return this._debouncer;
|
|
154
|
+
}
|
|
155
|
+
/** Drop a limiter so the next click rebuilds it with the current config. */
|
|
156
|
+
_invalidateLimiter(kind) {
|
|
157
|
+
if (kind === "throttle") {
|
|
158
|
+
this._throttler?.dispose();
|
|
159
|
+
this._throttler = null;
|
|
160
|
+
} else {
|
|
161
|
+
this._debouncer?.dispose();
|
|
162
|
+
this._debouncer = null;
|
|
163
|
+
}
|
|
164
|
+
this._syncLoading();
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Mirror the limiters' counters into reactive state and report transitions.
|
|
168
|
+
*
|
|
169
|
+
* The two pipelines are independent, so the button is busy while *either*
|
|
170
|
+
* is — hence the sum. Direct (unlimited) work counts as running only: it
|
|
171
|
+
* never waits.
|
|
172
|
+
*/
|
|
173
|
+
_syncLoading() {
|
|
174
|
+
const pending = this.noAutoLoading ? 0 : (this._throttler?.pending ?? 0) + (this._debouncer?.pending ?? 0);
|
|
175
|
+
const running = this.noAutoLoading ? 0 : (this._throttler?.running ?? 0) + (this._debouncer?.running ?? 0) + this._directRunning;
|
|
176
|
+
if (this._pending !== pending) this._pending = pending;
|
|
177
|
+
if (this._running !== running) this._running = running;
|
|
178
|
+
const phase = running > 0 ? "running" : pending > 0 ? "pending" : "idle";
|
|
179
|
+
if (phase === this._lastPhase) return;
|
|
180
|
+
this._lastPhase = phase;
|
|
181
|
+
dispatchMonoEvent(this, "loading-change", {
|
|
182
|
+
loading: phase !== "idle",
|
|
183
|
+
phase
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Drop anything waiting — the debounce timer and the throttle queue — without
|
|
188
|
+
* touching work already running. Each limiter rebuilds itself on the next click.
|
|
189
|
+
*/
|
|
190
|
+
cancelPending() {
|
|
191
|
+
this._throttler?.cancel();
|
|
192
|
+
this._debouncer?.cancel();
|
|
193
|
+
this._syncLoading();
|
|
194
|
+
}
|
|
195
|
+
disconnectedCallback() {
|
|
196
|
+
this._throttler?.dispose();
|
|
197
|
+
this._debouncer?.dispose();
|
|
198
|
+
this._throttler = null;
|
|
199
|
+
this._debouncer = null;
|
|
200
|
+
super.disconnectedCallback();
|
|
201
|
+
}
|
|
202
|
+
_cls(base, key) {
|
|
203
|
+
const extra = this.cssClass?.[key];
|
|
204
|
+
return extra ? `${base} ${extra}` : base;
|
|
205
|
+
}
|
|
206
|
+
_toBoolean(value) {
|
|
207
|
+
if (typeof value === "boolean") return value;
|
|
208
|
+
if (typeof value === "string") {
|
|
209
|
+
const normalized = value.toLowerCase().trim();
|
|
210
|
+
return normalized === "" || normalized === "true";
|
|
211
|
+
}
|
|
212
|
+
return Boolean(value);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Whether the default (text) slot is empty — drives the `rounded="full"`
|
|
216
|
+
* auto-icon-only refinement. Requires light-DOM introspection, so the core
|
|
217
|
+
* returns `false` (no auto-iconize); the light build overrides it.
|
|
218
|
+
*/
|
|
219
|
+
_defaultIsEmpty() {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Icon-only, explicitly or by inference.
|
|
224
|
+
*
|
|
225
|
+
* The inference: a fully-rounded button with no text in it is a circle
|
|
226
|
+
* holding one glyph, which is the shape people reach for as a FAB. It used to
|
|
227
|
+
* hang off `fab && (circle || shape === 'circle')`; `rounded="full"` is the
|
|
228
|
+
* one prop those three collapsed into.
|
|
229
|
+
*/
|
|
230
|
+
get _isIconOnlyLike() {
|
|
231
|
+
return this.iconOnly || this.rounded === "full" && this._defaultIsEmpty();
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* What the spinner follows: the `loading` prop (a manual override that
|
|
235
|
+
* always wins) OR either self-driven phase.
|
|
236
|
+
*/
|
|
237
|
+
get _effectiveLoading() {
|
|
238
|
+
return this.loading || this._pending > 0 || this._running > 0;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Whether a click produces anything the user can observe. A spinning button
|
|
242
|
+
* is a disabled button in every phase, the wait included — no event, no
|
|
243
|
+
* handler, no form submit.
|
|
244
|
+
*
|
|
245
|
+
* This is the *behavioural* answer; `_isInert` is the structural one. They
|
|
246
|
+
* differ during the wait, and that gap is deliberate — see `_isInert`.
|
|
247
|
+
*/
|
|
248
|
+
get _isBlocked() {
|
|
249
|
+
return this.disabled || this._effectiveLoading;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Whether the native control carries `disabled` — everything `_isBlocked`
|
|
253
|
+
* covers EXCEPT the self-driven wait.
|
|
254
|
+
*
|
|
255
|
+
* A natively disabled button dispatches no click event at all. If the wait
|
|
256
|
+
* were inert, repeat clicks could never reach the debouncer to reset its
|
|
257
|
+
* timer, and `debounce` would quietly degrade from "run once the user
|
|
258
|
+
* stops" into "wait, then run, ignoring the rest". So while waiting the
|
|
259
|
+
* control stays live and merely *looks* and *behaves* disabled: clicks land,
|
|
260
|
+
* are swallowed by `_handleClick`, and only extend the wait.
|
|
261
|
+
*/
|
|
262
|
+
get _isInert() {
|
|
263
|
+
return this.disabled || this.loading || this._running > 0;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Where the spinner is drawn.
|
|
267
|
+
*
|
|
268
|
+
* It replaces the ICON, wherever the icon sits — so `icon-position="right"` spins on the
|
|
269
|
+
* right, and a button with no icon grows a leading spinner in the slot an icon would have
|
|
270
|
+
* used. The caption always stays readable.
|
|
271
|
+
*
|
|
272
|
+
* This used to be two looks. Self-driven loading kept the caption and TRAILED the spinner
|
|
273
|
+
* after it, while the `loading` prop blanked the caption and centred the spinner — so a
|
|
274
|
+
* button with an icon rendered icon + label + spinner all at once, and which of the two
|
|
275
|
+
* looks you got depended on how loading had been switched on. `MonoConfirm` sets BOTH, and
|
|
276
|
+
* so blanked the very caption it had just changed to "Menyimpan...".
|
|
277
|
+
*
|
|
278
|
+
* One rule now, whatever triggered it.
|
|
279
|
+
*/
|
|
280
|
+
get _showsSpinner() {
|
|
281
|
+
return this._effectiveLoading;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Spinning because a call is waiting rather than executing. Drives the
|
|
285
|
+
* `pending` class, which restores `pointer-events` so those clicks can be
|
|
286
|
+
* absorbed — hence the hard-block exclusions, which must never be made
|
|
287
|
+
* clickable again.
|
|
288
|
+
*/
|
|
289
|
+
get _isPendingOnly() {
|
|
290
|
+
return this._pending > 0 && this._running === 0 && !this.loading && !this.disabled;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* `prepend` / `append` are object props, so the real binding is
|
|
294
|
+
* `:append.prop="{…}"`. This covers the two ways a STRING can arrive: a
|
|
295
|
+
* hand-written JSON attribute (`append='{"border":true}'`, incl. DSD/SSR) and
|
|
296
|
+
* Vue's `String(value)` mirror of a plain `:append="{…}"` binding — which
|
|
297
|
+
* yields `"[object Object]"` and must be ignored, or it would wipe the
|
|
298
|
+
* property set moments later.
|
|
299
|
+
*/
|
|
300
|
+
_setAffix(name, value) {
|
|
301
|
+
const assign = (v) => {
|
|
302
|
+
if (name === "prepend") this.prepend = v;
|
|
303
|
+
else this.append = v;
|
|
304
|
+
};
|
|
305
|
+
if (value == null) return assign({});
|
|
306
|
+
if (typeof value === "object") return assign(value);
|
|
307
|
+
if (typeof value !== "string") return;
|
|
308
|
+
const trimmed = value.trim();
|
|
309
|
+
if (!trimmed) return assign({});
|
|
310
|
+
if (!trimmed.startsWith("{")) return;
|
|
311
|
+
if (!trimmed.endsWith("}")) return;
|
|
312
|
+
try {
|
|
313
|
+
assign(JSON.parse(trimmed));
|
|
314
|
+
} catch {}
|
|
315
|
+
}
|
|
316
|
+
/** The affix config, always an object (either can be assigned null). */
|
|
317
|
+
_affix(name) {
|
|
318
|
+
const value = name === "prepend" ? this.prepend : this.append;
|
|
319
|
+
return value && typeof value === "object" ? value : {};
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Whether an affix zone is inert. Follows the button's `disabled` by default
|
|
323
|
+
* so the control dims as one, but NOT its `loading` — a busy main action with
|
|
324
|
+
* a live trailing menu is the case this feature exists for.
|
|
325
|
+
*/
|
|
326
|
+
_affixDisabled(name) {
|
|
327
|
+
const own = this._affix(name).disabled;
|
|
328
|
+
return own === void 0 ? this.disabled : own;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* The wrapper the whole stylesheet is keyed on.
|
|
332
|
+
*
|
|
333
|
+
* Its ATTRIBUTES mirror the props one for one — `<div mono-button
|
|
334
|
+
* mono-size="sm" mono-color="danger" mono-variant="outline">` — so a
|
|
335
|
+
* hand-written twin reads like the tag, and `button.css` selects on them
|
|
336
|
+
* exactly as Basecoat selects on `data-size` / `data-variant`. A prop at its
|
|
337
|
+
* default (md / primary / solid / left) emits NO attribute — absence IS the
|
|
338
|
+
* default in the sheet — so the rendered wrapper carries exactly what the
|
|
339
|
+
* author wrote, and a hand-written twin can be compared to it 1:1. The classes
|
|
340
|
+
* (`mono-button sm outline-danger`) are still emitted as inert hooks for
|
|
341
|
+
* consumer CSS until 2.0; no rule reads them. Bound in the template (not set
|
|
342
|
+
* in `updated()`) so the SSR build carries them.
|
|
343
|
+
*/
|
|
344
|
+
_renderWrapper(inner, style) {
|
|
345
|
+
return html`<div
|
|
346
|
+
class=${this._buttonClasses}
|
|
347
|
+
style=${style ?? nothing}
|
|
348
|
+
mono-button
|
|
349
|
+
mono-size=${this.size === "md" ? nothing : this.size}
|
|
350
|
+
mono-color=${this.color === "primary" ? nothing : this.color}
|
|
351
|
+
mono-variant=${this.variant === "solid" ? nothing : this.variant}
|
|
352
|
+
mono-rounded=${ifDefined(this.rounded)}
|
|
353
|
+
mono-icon-position=${this.iconPosition === "left" ? nothing : this.iconPosition}
|
|
354
|
+
?mono-icon-only=${this._isIconOnlyLike}
|
|
355
|
+
?mono-glass=${Boolean(this.glass)}
|
|
356
|
+
?mono-loading=${this._effectiveLoading}
|
|
357
|
+
?mono-waiting=${this._isPendingOnly}
|
|
358
|
+
?mono-sized=${this._hasSize}
|
|
359
|
+
>${inner}</div>`;
|
|
360
|
+
}
|
|
361
|
+
/** @deprecated inert since the Basecoat port — see `_renderWrapper`. */
|
|
362
|
+
get _buttonClasses() {
|
|
363
|
+
const classes = [this._isIconOnlyLike ? "mono-button-icon" : "mono-button"];
|
|
364
|
+
classes.push(this.size);
|
|
365
|
+
if (this.variant === "solid") classes.push(this.color);
|
|
366
|
+
else if (this.variant === "outline") classes.push(`outline-${this.color}`);
|
|
367
|
+
else if (this.variant === "tonal") classes.push(`tonal-${this.color}`);
|
|
368
|
+
else if (this.variant === "text") classes.push(`plain-${this.color}`);
|
|
369
|
+
if (this.rounded) classes.push(`rounded-${this.rounded}`);
|
|
370
|
+
if (this.glass) classes.push("glass");
|
|
371
|
+
if (this._effectiveLoading) classes.push("loading");
|
|
372
|
+
if (this._isPendingOnly) classes.push("pending");
|
|
373
|
+
if (this._hasSize) classes.push("sized");
|
|
374
|
+
if (this.disabled) classes.push("disabled");
|
|
375
|
+
if (this._isActive) classes.push("active");
|
|
376
|
+
if (this.cssClass?.root) classes.push(this.cssClass.root);
|
|
377
|
+
return classes.join(" ");
|
|
378
|
+
}
|
|
379
|
+
/** The `badge-color` value the badge's `mono-badge` attribute carries (red by default). */
|
|
380
|
+
get _badgeColor() {
|
|
381
|
+
return this.badgeColor === "green" || this.badgeColor === "orange" || this.badgeColor === "cobalt" ? this.badgeColor : "red";
|
|
382
|
+
}
|
|
383
|
+
/** @deprecated inert since the Basecoat port — the badge is styled by `mono-badge`. */
|
|
384
|
+
get _badgeClass() {
|
|
385
|
+
let base = "btn-badge bb-red";
|
|
386
|
+
if (this.badgeColor === "green") base = "btn-badge bb-green";
|
|
387
|
+
else if (this.badgeColor === "orange") base = "btn-badge bb-orange";
|
|
388
|
+
else if (this.badgeColor === "cobalt") base = "btn-badge bb-cobalt";
|
|
389
|
+
return this.cssClass?.badge ? `${base} ${this.cssClass.badge}` : base;
|
|
390
|
+
}
|
|
391
|
+
get _ariaLabel() {
|
|
392
|
+
return this.ariaLabelText || this.tooltip;
|
|
393
|
+
}
|
|
394
|
+
/** True when any sizing prop is set — drives the `sized` class + host styles. */
|
|
395
|
+
get _hasSize() {
|
|
396
|
+
return Object.keys(buildSizeStyle(this)).length > 0;
|
|
397
|
+
}
|
|
398
|
+
/** Inline sizing object (used by the shadow build via `styleMap`). */
|
|
399
|
+
_sizeStyle() {
|
|
400
|
+
return buildSizeStyle(this);
|
|
401
|
+
}
|
|
402
|
+
_handleClick(event) {
|
|
403
|
+
if (this._isBlocked) {
|
|
404
|
+
event.preventDefault();
|
|
405
|
+
event.stopPropagation();
|
|
406
|
+
this._absorbWhileWaiting(event);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const awaited = [];
|
|
410
|
+
dispatchMonoEvent(this, "click", {
|
|
411
|
+
originalEvent: event,
|
|
412
|
+
result: void 0,
|
|
413
|
+
waitUntil: (promise) => {
|
|
414
|
+
awaited.push(Promise.resolve(promise));
|
|
415
|
+
}
|
|
416
|
+
}, { sourceEvent: event });
|
|
417
|
+
const throttler = this._ensureThrottler();
|
|
418
|
+
const debouncer = this._ensureDebouncer();
|
|
419
|
+
const rateLimited = Boolean(throttler || debouncer);
|
|
420
|
+
if (throttler) throttler.call(event);
|
|
421
|
+
if (debouncer) debouncer.call(event);
|
|
422
|
+
const runsHandlerHere = !rateLimited && Boolean(this.handler);
|
|
423
|
+
if (runsHandlerHere || awaited.length > 0) this._runDirect(event, runsHandlerHere, awaited);
|
|
424
|
+
this._maybeSubmitForm(event);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* A click that arrived while the button was already busy.
|
|
428
|
+
*
|
|
429
|
+
* It stays silent — no `mno-click`, no handler, no form submit — but a
|
|
430
|
+
* debounce still needs to *hear* it: the contract is "run once the user
|
|
431
|
+
* stops", so every repeat has to push the deadline out. Feeding the
|
|
432
|
+
* debouncer does exactly that (`p-debounce` clears and re-arms its timer on
|
|
433
|
+
* each call), which is why the waiting phase is deliberately not inert.
|
|
434
|
+
*
|
|
435
|
+
* Nothing is absorbed once work is actually running — a click then would
|
|
436
|
+
* queue a second execution, which is the opposite of what a busy button
|
|
437
|
+
* should do — nor while `disabled`/`loading` hard-block the control. A
|
|
438
|
+
* throttle is deliberately not fed either: its contract is a rate, not a
|
|
439
|
+
* quiet period, and every absorbed call would become another execution.
|
|
440
|
+
*/
|
|
441
|
+
_absorbWhileWaiting(event) {
|
|
442
|
+
if (this.disabled || this.loading) return;
|
|
443
|
+
if (this._running > 0 || this._pending === 0) return;
|
|
444
|
+
this._ensureDebouncer()?.call(event);
|
|
445
|
+
}
|
|
446
|
+
/** Immediate (unlimited) work: the handler and/or `waitUntil` promises. */
|
|
447
|
+
async _runDirect(event, runHandler, awaited) {
|
|
448
|
+
this._directRunning++;
|
|
449
|
+
this._syncLoading();
|
|
450
|
+
try {
|
|
451
|
+
if (runHandler) await this.handler?.(event);
|
|
452
|
+
if (awaited.length) await Promise.allSettled(awaited);
|
|
453
|
+
} finally {
|
|
454
|
+
this._directRunning--;
|
|
455
|
+
this._syncLoading();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* The work a limiter performs: run the handler, then emit the matching
|
|
460
|
+
* rate-limited event carrying its resolved result, then wait for anything
|
|
461
|
+
* listeners registered. The enclosing limiter is what tracks this as the
|
|
462
|
+
* `running` phase.
|
|
463
|
+
*/
|
|
464
|
+
async _runLimited(kind, event) {
|
|
465
|
+
const result = this.handler ? await this.handler(event) : void 0;
|
|
466
|
+
const awaited = [];
|
|
467
|
+
dispatchMonoEvent(this, kind, {
|
|
468
|
+
originalEvent: event,
|
|
469
|
+
result,
|
|
470
|
+
waitUntil: (promise) => {
|
|
471
|
+
awaited.push(Promise.resolve(promise));
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
if (awaited.length) await Promise.allSettled(awaited);
|
|
475
|
+
return result;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Native form submission doesn't cross a shadow boundary: in the shadow build the
|
|
479
|
+
* real `<button type="submit">` lives in the shadow root, so clicking it never
|
|
480
|
+
* submits the light-DOM `<form>` the host sits in (the inner button's form owner
|
|
481
|
+
* is `null`). When there's no native association, submit/reset the host's closest
|
|
482
|
+
* form manually via `requestSubmit()`/`reset()` (which still fires the form's
|
|
483
|
+
* `submit` event + constraint validation). The light build's inner `<button>` IS
|
|
484
|
+
* form-associated, so `innerForm` is truthy there and this is skipped — no double
|
|
485
|
+
* submit.
|
|
486
|
+
*/
|
|
487
|
+
_maybeSubmitForm(event) {
|
|
488
|
+
if (event.defaultPrevented || this.href || this.type !== "submit" && this.type !== "reset") return;
|
|
489
|
+
if (this._buttonElement?.form) return;
|
|
490
|
+
const form = this.closest("form");
|
|
491
|
+
if (!form) return;
|
|
492
|
+
if (this.type === "submit") form.requestSubmit();
|
|
493
|
+
else form.reset();
|
|
494
|
+
}
|
|
495
|
+
_handleKeyDown(event) {
|
|
496
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
497
|
+
event.preventDefault();
|
|
498
|
+
this._buttonElement?.click();
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
_handleFocus() {
|
|
502
|
+
this._isActive = true;
|
|
503
|
+
}
|
|
504
|
+
_handleBlur() {
|
|
505
|
+
this._isActive = false;
|
|
506
|
+
}
|
|
507
|
+
focus() {
|
|
508
|
+
this._buttonElement?.focus();
|
|
509
|
+
}
|
|
510
|
+
blur() {
|
|
511
|
+
this._buttonElement?.blur();
|
|
512
|
+
}
|
|
513
|
+
click() {
|
|
514
|
+
this._buttonElement?.click();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
__decorate([property({ type: String })], MonoButtonCoreClass.prototype, "size", void 0);
|
|
518
|
+
__decorate([property({ type: String })], MonoButtonCoreClass.prototype, "color", void 0);
|
|
519
|
+
__decorate([property({ type: String })], MonoButtonCoreClass.prototype, "variant", void 0);
|
|
520
|
+
__decorate([property({ type: String })], MonoButtonCoreClass.prototype, "rounded", void 0);
|
|
521
|
+
__decorate([property({
|
|
522
|
+
reflect: true,
|
|
523
|
+
converter: booleanStringConverter
|
|
524
|
+
})], MonoButtonCoreClass.prototype, "disabled", void 0);
|
|
525
|
+
__decorate([property({
|
|
526
|
+
reflect: true,
|
|
527
|
+
converter: booleanStringConverter
|
|
528
|
+
})], MonoButtonCoreClass.prototype, "loading", void 0);
|
|
529
|
+
__decorate([property({ type: String })], MonoButtonCoreClass.prototype, "width", void 0);
|
|
530
|
+
__decorate([property({ type: String })], MonoButtonCoreClass.prototype, "height", void 0);
|
|
531
|
+
__decorate([property({
|
|
532
|
+
type: String,
|
|
533
|
+
attribute: "min-width"
|
|
534
|
+
})], MonoButtonCoreClass.prototype, "minWidth", void 0);
|
|
535
|
+
__decorate([property({
|
|
536
|
+
type: String,
|
|
537
|
+
attribute: "max-width"
|
|
538
|
+
})], MonoButtonCoreClass.prototype, "maxWidth", void 0);
|
|
539
|
+
__decorate([property({
|
|
540
|
+
type: String,
|
|
541
|
+
attribute: "min-height"
|
|
542
|
+
})], MonoButtonCoreClass.prototype, "minHeight", void 0);
|
|
543
|
+
__decorate([property({
|
|
544
|
+
type: String,
|
|
545
|
+
attribute: "max-height"
|
|
546
|
+
})], MonoButtonCoreClass.prototype, "maxHeight", void 0);
|
|
547
|
+
__decorate([property({ type: String })], MonoButtonCoreClass.prototype, "href", void 0);
|
|
548
|
+
__decorate([property({ type: String })], MonoButtonCoreClass.prototype, "target", void 0);
|
|
549
|
+
__decorate([property({ type: String })], MonoButtonCoreClass.prototype, "type", void 0);
|
|
550
|
+
__decorate([property({
|
|
551
|
+
type: String,
|
|
552
|
+
attribute: "icon-position"
|
|
553
|
+
})], MonoButtonCoreClass.prototype, "iconPosition", void 0);
|
|
554
|
+
__decorate([property({ attribute: false })], MonoButtonCoreClass.prototype, "prepend", void 0);
|
|
555
|
+
__decorate([property({ attribute: false })], MonoButtonCoreClass.prototype, "append", void 0);
|
|
556
|
+
__decorate([property({ type: String })], MonoButtonCoreClass.prototype, "badge", void 0);
|
|
557
|
+
__decorate([property({
|
|
558
|
+
type: String,
|
|
559
|
+
attribute: "badge-color"
|
|
560
|
+
})], MonoButtonCoreClass.prototype, "badgeColor", void 0);
|
|
561
|
+
__decorate([property({
|
|
562
|
+
attribute: "icon-only",
|
|
563
|
+
reflect: true,
|
|
564
|
+
converter: booleanStringConverter
|
|
565
|
+
})], MonoButtonCoreClass.prototype, "iconOnly", void 0);
|
|
566
|
+
__decorate([property({
|
|
567
|
+
reflect: true,
|
|
568
|
+
converter: booleanStringConverter
|
|
569
|
+
})], MonoButtonCoreClass.prototype, "glass", void 0);
|
|
570
|
+
__decorate([property({ type: String })], MonoButtonCoreClass.prototype, "tooltip", void 0);
|
|
571
|
+
__decorate([property({
|
|
572
|
+
type: String,
|
|
573
|
+
attribute: "aria-label-text"
|
|
574
|
+
})], MonoButtonCoreClass.prototype, "ariaLabelText", void 0);
|
|
575
|
+
__decorate([property({ attribute: false })], MonoButtonCoreClass.prototype, "cssClass", void 0);
|
|
576
|
+
__decorate([property({
|
|
577
|
+
converter: rateLimitConverter,
|
|
578
|
+
hasChanged: rateLimitHasChanged
|
|
579
|
+
})], MonoButtonCoreClass.prototype, "throttle", void 0);
|
|
580
|
+
__decorate([property({
|
|
581
|
+
converter: rateLimitConverter,
|
|
582
|
+
hasChanged: rateLimitHasChanged
|
|
583
|
+
})], MonoButtonCoreClass.prototype, "debounce", void 0);
|
|
584
|
+
__decorate([property({ attribute: false })], MonoButtonCoreClass.prototype, "handler", void 0);
|
|
585
|
+
__decorate([property({
|
|
586
|
+
attribute: "no-auto-loading",
|
|
587
|
+
reflect: true,
|
|
588
|
+
converter: booleanStringConverter
|
|
589
|
+
})], MonoButtonCoreClass.prototype, "noAutoLoading", void 0);
|
|
590
|
+
__decorate([state()], MonoButtonCoreClass.prototype, "_isActive", void 0);
|
|
591
|
+
__decorate([state()], MonoButtonCoreClass.prototype, "_hasIcon", void 0);
|
|
592
|
+
__decorate([state()], MonoButtonCoreClass.prototype, "_hasPrepend", void 0);
|
|
593
|
+
__decorate([state()], MonoButtonCoreClass.prototype, "_hasAppend", void 0);
|
|
594
|
+
__decorate([state()], MonoButtonCoreClass.prototype, "_pending", void 0);
|
|
595
|
+
__decorate([state()], MonoButtonCoreClass.prototype, "_running", void 0);
|
|
596
|
+
__decorate([query("div > button, div > a")], MonoButtonCoreClass.prototype, "_buttonElement", void 0);
|
|
597
|
+
return MonoButtonCoreClass;
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
//#endregion
|
|
601
|
+
//#region src/components/button/mono-button.ts
|
|
602
|
+
let MonoButton = class MonoButton extends MonoButtonCore(LitElement) {
|
|
603
|
+
constructor(..._args) {
|
|
604
|
+
super(..._args);
|
|
605
|
+
this._buckets = /* @__PURE__ */ new Map();
|
|
606
|
+
this._slotsCaptured = false;
|
|
607
|
+
this._guardedAffixes = /* @__PURE__ */ new WeakSet();
|
|
608
|
+
this._lateAffix = [];
|
|
609
|
+
}
|
|
610
|
+
createRenderRoot() {
|
|
611
|
+
return this;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Light-DOM refinement: `rounded="full"` auto-icon-only only when the default slot is
|
|
615
|
+
* empty. `bucketHasContent` (not a length check) so a bucket holding nothing but
|
|
616
|
+
* framework anchors still counts as empty.
|
|
617
|
+
*/
|
|
618
|
+
_defaultIsEmpty() {
|
|
619
|
+
return !bucketHasContent(this._buckets.get("default"));
|
|
620
|
+
}
|
|
621
|
+
_renderBadge() {
|
|
622
|
+
return when(Boolean(this.badge), () => html`
|
|
623
|
+
<span class=${this._badgeClass} mono-badge=${this._badgeColor}>
|
|
624
|
+
${this.badge}
|
|
625
|
+
</span>
|
|
626
|
+
`, () => nothing);
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* The `[data-mono-slot]` targets must be **structurally stable** across every
|
|
630
|
+
* re-render.
|
|
631
|
+
*
|
|
632
|
+
* These are light-DOM elements holding nodes the *consumer's* framework
|
|
633
|
+
* created and still tracks. Toggling `loading` used to swap between two
|
|
634
|
+
* template branches, so Lit destroyed the target `<span>` — taking the
|
|
635
|
+
* consumer's nodes with it — and `updated()` re-appended them into a brand-new
|
|
636
|
+
* one. Vue's vnodes then referenced detached nodes, and the next patch died
|
|
637
|
+
* with `TypeError: can't access property "__vnode", el is null`.
|
|
638
|
+
*
|
|
639
|
+
* So visibility is expressed with the existing `.button-icon.hidden` class
|
|
640
|
+
* (`display: none !important`), never by adding or removing the target. With
|
|
641
|
+
* the span stable, `_placeSlot` finds its nodes already in place and mutates
|
|
642
|
+
* nothing at all on a loading toggle.
|
|
643
|
+
*/
|
|
644
|
+
_renderIconOnlyContent() {
|
|
645
|
+
return html`
|
|
646
|
+
<span class=${this._iconClass()} mono-icon ?mono-spinning=${this._showsSpinner} ?mono-empty=${!this._hasIcon && !this._showsSpinner} data-mono-slot="icon"></span>
|
|
647
|
+
|
|
648
|
+
${this._renderBadge()}
|
|
649
|
+
`;
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Icon slot class, hidden in place while loading rather than unmounted — but
|
|
653
|
+
* only in overlay mode. Inline loading keeps the icon, so the button doesn't
|
|
654
|
+
* reshuffle its contents just to show progress.
|
|
655
|
+
*/
|
|
656
|
+
/**
|
|
657
|
+
* The icon box, which is also where the spinner is drawn.
|
|
658
|
+
*
|
|
659
|
+
* `spinning` paints the ring and hides whatever is slotted inside; `empty` collapses the box
|
|
660
|
+
* when there is neither. The span itself is ALWAYS rendered — see `_renderNormalContent`.
|
|
661
|
+
*/
|
|
662
|
+
_iconClass() {
|
|
663
|
+
const base = this._cls("button-icon", "icon");
|
|
664
|
+
const state = [this._showsSpinner ? "spinning" : "", !this._hasIcon && !this._showsSpinner ? "empty" : ""].filter(Boolean).join(" ");
|
|
665
|
+
return state ? `${base} ${state}` : base;
|
|
666
|
+
}
|
|
667
|
+
_renderNormalContent() {
|
|
668
|
+
const contentBase = this._hasIcon ? this.iconPosition === "right" ? "button-content icon-right" : "button-content icon-left" : "button-content";
|
|
669
|
+
const contentClass = this._cls(contentBase, "content");
|
|
670
|
+
const iconClass = this._iconClass();
|
|
671
|
+
const textClass = this._cls("button-text", "text");
|
|
672
|
+
const iconSpan = (position) => this.iconPosition === position ? html`<span class=${iconClass} mono-icon ?mono-spinning=${this._showsSpinner} ?mono-empty=${!this._hasIcon && !this._showsSpinner} data-mono-slot="icon"></span>` : nothing;
|
|
673
|
+
return html`
|
|
674
|
+
<div class=${contentClass} mono-content>
|
|
675
|
+
${iconSpan("left")}
|
|
676
|
+
|
|
677
|
+
<span class=${textClass} mono-text data-mono-slot="default"></span>
|
|
678
|
+
|
|
679
|
+
${iconSpan("right")}
|
|
680
|
+
</div>
|
|
681
|
+
|
|
682
|
+
${this._renderBadge()}
|
|
683
|
+
`;
|
|
684
|
+
}
|
|
685
|
+
_renderContent() {
|
|
686
|
+
if (this._isIconOnlyLike) return this._renderIconOnlyContent();
|
|
687
|
+
return this._renderNormalContent();
|
|
688
|
+
}
|
|
689
|
+
render() {
|
|
690
|
+
const isDisabled = this._isBlocked;
|
|
691
|
+
const isInert = this._isInert;
|
|
692
|
+
if (this.href) return this._renderWrapper(html`
|
|
693
|
+
${this._renderAffix("prepend")}
|
|
694
|
+
<a
|
|
695
|
+
class=${"mono-button-native" + (this.cssClass?.main ? " " + this.cssClass.main : "")}
|
|
696
|
+
mono-native
|
|
697
|
+
href=${ifDefined(this.href)}
|
|
698
|
+
target=${ifDefined(this.target)}
|
|
699
|
+
title=${ifDefined(this.tooltip)}
|
|
700
|
+
aria-label=${ifDefined(this._ariaLabel)}
|
|
701
|
+
role="button"
|
|
702
|
+
aria-disabled=${isDisabled ? "true" : "false"}
|
|
703
|
+
aria-busy=${this._effectiveLoading ? "true" : "false"}
|
|
704
|
+
@click=${this._handleClick}
|
|
705
|
+
@keydown=${this._handleKeyDown}
|
|
706
|
+
@focus=${this._handleFocus}
|
|
707
|
+
@blur=${this._handleBlur}
|
|
708
|
+
>
|
|
709
|
+
${this._renderContent()}
|
|
710
|
+
</a>
|
|
711
|
+
${this._renderAffix("append")}
|
|
712
|
+
`);
|
|
713
|
+
return this._renderWrapper(html`
|
|
714
|
+
${this._renderAffix("prepend")}
|
|
715
|
+
<button
|
|
716
|
+
class=${ifDefined(this.cssClass?.main)}
|
|
717
|
+
mono-native
|
|
718
|
+
type=${this.type}
|
|
719
|
+
title=${ifDefined(this.tooltip)}
|
|
720
|
+
aria-label=${ifDefined(this._ariaLabel)}
|
|
721
|
+
?disabled=${isInert}
|
|
722
|
+
aria-disabled=${isDisabled ? "true" : "false"}
|
|
723
|
+
aria-busy=${this._effectiveLoading ? "true" : "false"}
|
|
724
|
+
@click=${this._handleClick}
|
|
725
|
+
@keydown=${this._handleKeyDown}
|
|
726
|
+
@focus=${this._handleFocus}
|
|
727
|
+
@blur=${this._handleBlur}
|
|
728
|
+
>
|
|
729
|
+
${this._renderContent()}
|
|
730
|
+
</button>
|
|
731
|
+
${this._renderAffix("append")}
|
|
732
|
+
`);
|
|
733
|
+
}
|
|
734
|
+
connectedCallback() {
|
|
735
|
+
super.connectedCallback();
|
|
736
|
+
this.setAttribute("role", "button");
|
|
737
|
+
this._captureSlots();
|
|
738
|
+
if (this.isConnected) this.performUpdate();
|
|
739
|
+
if (!this._lateAffix.length) for (const name of ["prepend", "append"]) this._lateAffix.push({
|
|
740
|
+
name,
|
|
741
|
+
watcher: new LateSlotWatcher(this, name, () => this._recaptureAffix())
|
|
742
|
+
});
|
|
743
|
+
for (const { watcher } of this._lateAffix) watcher.start();
|
|
744
|
+
}
|
|
745
|
+
disconnectedCallback() {
|
|
746
|
+
for (const { watcher } of this._lateAffix) watcher.stop();
|
|
747
|
+
super.disconnectedCallback();
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Claim a `slot="prepend"` / `slot="append"` child that arrived after connect.
|
|
751
|
+
*
|
|
752
|
+
* Deliberately NOT `captureLightSlots` a second time. This build renders into
|
|
753
|
+
* the host (`createRenderRoot` → `this`), so by now the host's children include
|
|
754
|
+
* Lit's own `<div class="mono-button">` — a re-capture would bucket that as
|
|
755
|
+
* default content and detach it, wiping the rendered button. `LateSlotWatcher`
|
|
756
|
+
* hands back only the direct child carrying the slot name, which is all that is
|
|
757
|
+
* wanted here.
|
|
758
|
+
*/
|
|
759
|
+
_recaptureAffix() {
|
|
760
|
+
let gained = false;
|
|
761
|
+
for (const { name, watcher } of this._lateAffix) {
|
|
762
|
+
const el = watcher.find();
|
|
763
|
+
if (!el) continue;
|
|
764
|
+
el.removeAttribute("slot");
|
|
765
|
+
if (el.parentNode === this) this.removeChild(el);
|
|
766
|
+
this._buckets.set(name, [...this._buckets.get(name) ?? [], el]);
|
|
767
|
+
if (name === "prepend") this._hasPrepend = true;
|
|
768
|
+
else this._hasAppend = true;
|
|
769
|
+
gained = true;
|
|
770
|
+
}
|
|
771
|
+
if (gained) this.requestUpdate();
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Stop a nested control's click at the affix boundary — the plain `click`
|
|
775
|
+
* and the `mno-click` / `mnoClick` aliases alike.
|
|
776
|
+
*
|
|
777
|
+
* An affix can hold a whole `<mono-button>` or a dropdown caret. Sibling
|
|
778
|
+
* placement keeps its clicks out of `_handleClick`, but they would still
|
|
779
|
+
* bubble to the HOST, where a consumer's `@click` is this button's action —
|
|
780
|
+
* a split button's caret would run the button's handler. So an affix's click
|
|
781
|
+
* ends here: its own handlers (deeper) and any document-level CAPTURE
|
|
782
|
+
* listener have already run. Same guard `mono-button-dropdown` puts on its
|
|
783
|
+
* embedded buttons.
|
|
784
|
+
*/
|
|
785
|
+
_guardAffixEvents(el) {
|
|
786
|
+
if (this._guardedAffixes.has(el)) return;
|
|
787
|
+
this._guardedAffixes.add(el);
|
|
788
|
+
const stop = (event) => event.stopPropagation();
|
|
789
|
+
el.addEventListener("click", stop);
|
|
790
|
+
el.addEventListener("mno-click", stop);
|
|
791
|
+
el.addEventListener("mnoClick", stop);
|
|
792
|
+
}
|
|
793
|
+
_captureSlots() {
|
|
794
|
+
if (this._slotsCaptured) return;
|
|
795
|
+
this._slotsCaptured = true;
|
|
796
|
+
this._buckets = captureLightSlots(this, { names: [
|
|
797
|
+
"icon",
|
|
798
|
+
"prepend",
|
|
799
|
+
"append"
|
|
800
|
+
] });
|
|
801
|
+
this._hasIcon = bucketHasContent(this._buckets.get("icon"));
|
|
802
|
+
this._hasPrepend = bucketHasContent(this._buckets.get("prepend"));
|
|
803
|
+
this._hasAppend = bucketHasContent(this._buckets.get("append"));
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* An affix zone renders only when its slot has content, so an affix-free button
|
|
807
|
+
* keeps byte-identical markup. That conditional is safe here — unlike the icon
|
|
808
|
+
* span, whose target must never unmount — because capture runs BEFORE the
|
|
809
|
+
* synchronous first render in `connectedCallback`, and because the flags only
|
|
810
|
+
* ever go false→true: captured nodes stay captured, so a target that exists
|
|
811
|
+
* once is never taken away from under the consumer's nodes.
|
|
812
|
+
*/
|
|
813
|
+
_renderAffix(name) {
|
|
814
|
+
if (!(name === "prepend" ? this._hasPrepend : this._hasAppend)) return nothing;
|
|
815
|
+
const cls = [
|
|
816
|
+
`button-${name}`,
|
|
817
|
+
this._affix(name).divider ? "has-divider" : "",
|
|
818
|
+
this._affixDisabled(name) ? "disabled" : "",
|
|
819
|
+
this.cssClass?.[name] ?? ""
|
|
820
|
+
].filter(Boolean).join(" ");
|
|
821
|
+
return html`<span
|
|
822
|
+
class=${cls}
|
|
823
|
+
mono-affix=${name}
|
|
824
|
+
?mono-divider=${Boolean(this._affix(name).divider)}
|
|
825
|
+
?mono-disabled=${this._affixDisabled(name)}
|
|
826
|
+
data-mono-slot=${name}
|
|
827
|
+
></span>`;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Apply sizing to the HOST (`mono-button` is inline-block). The `.sized` class
|
|
831
|
+
* makes the wrapper + inner control fill the host, so width/height set here
|
|
832
|
+
* cascade down. Props that aren't set are removed so they don't linger.
|
|
833
|
+
*/
|
|
834
|
+
_applyHostSize() {
|
|
835
|
+
const style = buildSizeStyle(this);
|
|
836
|
+
for (const prop of [
|
|
837
|
+
"width",
|
|
838
|
+
"height",
|
|
839
|
+
"min-width",
|
|
840
|
+
"max-width",
|
|
841
|
+
"min-height",
|
|
842
|
+
"max-height"
|
|
843
|
+
]) {
|
|
844
|
+
const value = style[prop];
|
|
845
|
+
if (value) this.style.setProperty(prop, value);
|
|
846
|
+
else this.style.removeProperty(prop);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
updated(changed) {
|
|
850
|
+
super.updated(changed);
|
|
851
|
+
this._applyHostSize();
|
|
852
|
+
this._placeSlot("icon", this._buckets.get("icon") ?? []);
|
|
853
|
+
this._placeSlot("prepend", this._buckets.get("prepend") ?? []);
|
|
854
|
+
this._placeSlot("append", this._buckets.get("append") ?? []);
|
|
855
|
+
for (const name of ["prepend", "append"]) {
|
|
856
|
+
const zone = this.querySelector(`[data-mono-slot="${name}"]`);
|
|
857
|
+
if (zone) this._guardAffixEvents(zone);
|
|
858
|
+
}
|
|
859
|
+
if (!this._isIconOnlyLike) this._placeSlot("default", this._buckets.get("default") ?? []);
|
|
860
|
+
}
|
|
861
|
+
_placeSlot(name, nodes) {
|
|
862
|
+
if (!nodes.length) return;
|
|
863
|
+
const target = this.querySelector(`[data-mono-slot="${name}"]`);
|
|
864
|
+
if (!target) return;
|
|
865
|
+
for (const node of nodes) placeSlotNode(target, node);
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
MonoButton = __decorate([customElement("mono-button")], MonoButton);
|
|
869
|
+
|
|
870
|
+
//#endregion
|
|
871
|
+
export { MonoButton as t };
|