jsswift 2.0.1 → 2.1.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.
- package/AI.md +5 -0
- package/README.md +2 -0
- package/dist/build/build-configured-runtime.d.mts +29 -0
- package/dist/build/build-configured-runtime.mjs +92 -0
- package/dist/build/runtime-layout.mjs +13 -0
- package/dist/build/select-ui.mjs +167 -0
- package/dist/build/vite-jss.d.mts +2 -0
- package/dist/build/vite-jss.mjs +73 -0
- package/dist/cms.js +3522 -0
- package/dist/documentions/AI.md +5 -0
- package/dist/documentions/README.md +1 -0
- package/dist/documentions/configured-runtime.md +82 -0
- package/dist/documentions/knowledge-map.json +20 -1
- package/dist/ui-src/00-bootstrap.js +330 -0
- package/dist/ui-src/01-foundation-helpers.js +809 -0
- package/dist/ui-src/10-primitives-layout.js +5091 -0
- package/dist/ui-src/20-display-content.js +780 -0
- package/dist/ui-src/21-list-content.js +246 -0
- package/dist/ui-src/22-banner-content.js +376 -0
- package/dist/ui-src/23-tooltip.js +276 -0
- package/dist/ui-src/31-form-advanced.js +2784 -0
- package/dist/ui-src/40-navigation.js +1262 -0
- package/dist/ui-src/41-tab-panel.js +896 -0
- package/dist/ui-src/50-feedback.js +999 -0
- package/dist/ui-src/51-feedback-service.js +166 -0
- package/dist/ui-src/60-shell-app.js +1223 -0
- package/dist/ui-src/61-form-service.js +368 -0
- package/dist/ui-src/70-dialog.js +333 -0
- package/dist/ui-src/71-menu-overlays.js +1217 -0
- package/dist/ui-src/80-data.js +437 -0
- package/dist/ui-src/modules.json +212 -0
- package/dist/ui.js +12565 -0
- package/package.json +26 -2
|
@@ -0,0 +1,1217 @@
|
|
|
1
|
+
UI.Menu = (...args) => {
|
|
2
|
+
const { props, children } = JSswift.uiNormalizeArgs(args);
|
|
3
|
+
const slots = props.slots || {};
|
|
4
|
+
const stateList = ["primary", "secondary", "warning", "danger", "success", "info", "light", "dark"];
|
|
5
|
+
const sizeList = ["xs", "sm", "md", "lg", "xl"];
|
|
6
|
+
let currentProps = { ...props };
|
|
7
|
+
let entry = null;
|
|
8
|
+
let boundEl = null;
|
|
9
|
+
let lastActive = null;
|
|
10
|
+
let openTimer = null;
|
|
11
|
+
let closeTimer = null;
|
|
12
|
+
|
|
13
|
+
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj || {}, key);
|
|
14
|
+
const resolveRender = (value, ctx) => typeof value === "function" ? value(ctx) : value;
|
|
15
|
+
const splitClasses = (value) => String(value || "").split(/\s+/).filter(Boolean);
|
|
16
|
+
const isPlainObject = (value) => value && typeof value === "object" && !value.nodeType && !Array.isArray(value) && !(value instanceof Function);
|
|
17
|
+
const isAnchorLike = (value) => !!value && typeof value === "object" && (value.nodeType === 1 || typeof value.getBoundingClientRect === "function");
|
|
18
|
+
const clearTimers = () => {
|
|
19
|
+
clearTimeout(openTimer);
|
|
20
|
+
clearTimeout(closeTimer);
|
|
21
|
+
openTimer = null;
|
|
22
|
+
closeTimer = null;
|
|
23
|
+
};
|
|
24
|
+
const setTrackedClasses = (target, key, classes) => {
|
|
25
|
+
if (!target) return;
|
|
26
|
+
const prev = target[key] || [];
|
|
27
|
+
if (prev.length) target.classList.remove(...prev);
|
|
28
|
+
const next = (classes || []).filter(Boolean);
|
|
29
|
+
if (next.length) target.classList.add(...next);
|
|
30
|
+
target[key] = next;
|
|
31
|
+
};
|
|
32
|
+
const setStyleValue = (target, name, value, formatter = (v) => v) => {
|
|
33
|
+
if (!target) return;
|
|
34
|
+
if (value == null || value === "") {
|
|
35
|
+
target.style.removeProperty(name);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
target.style.setProperty(name, formatter(value));
|
|
39
|
+
};
|
|
40
|
+
const getOptions = () => currentProps || {};
|
|
41
|
+
const getStateClass = (opts) => {
|
|
42
|
+
const value = String(uiUnwrap(opts.state ?? opts.color) || "").toLowerCase();
|
|
43
|
+
return stateList.includes(value) ? value : "";
|
|
44
|
+
};
|
|
45
|
+
const getSizeClass = (opts) => {
|
|
46
|
+
const value = String(uiUnwrap(opts.size) || "").toLowerCase();
|
|
47
|
+
return sizeList.includes(value) ? value : "";
|
|
48
|
+
};
|
|
49
|
+
const getPlacement = (opts) => String(uiUnwrap(opts.placement ?? opts.position ?? "bottom-start"));
|
|
50
|
+
const getDelay = () => {
|
|
51
|
+
const raw = uiUnwrap(getOptions().delay ?? getOptions().showDelay);
|
|
52
|
+
const value = Number(raw);
|
|
53
|
+
return Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
54
|
+
};
|
|
55
|
+
const getHideDelay = () => {
|
|
56
|
+
const raw = uiUnwrap(getOptions().hideDelay);
|
|
57
|
+
const value = Number(raw);
|
|
58
|
+
return Number.isFinite(value) ? Math.max(0, value) : 80;
|
|
59
|
+
};
|
|
60
|
+
const getAnchor = (fallback = null) => {
|
|
61
|
+
if (fallback) return fallback;
|
|
62
|
+
const opts = getOptions();
|
|
63
|
+
return boundEl || uiUnwrap(opts.anchorEl ?? opts.triggerEl ?? opts.target ?? opts.anchor) || null;
|
|
64
|
+
};
|
|
65
|
+
const parseTriggers = (value) => {
|
|
66
|
+
if (value == null || value === true) return new Set(["click"]);
|
|
67
|
+
if (value === false) return new Set(["manual"]);
|
|
68
|
+
const raw = Array.isArray(value) ? value : String(value).split(/[\s,|/]+/);
|
|
69
|
+
const out = new Set();
|
|
70
|
+
raw.forEach((item) => {
|
|
71
|
+
const key = String(item || "").trim().toLowerCase();
|
|
72
|
+
if (!key) return;
|
|
73
|
+
if (key === "mouseenter" || key === "mouseover") out.add("hover");
|
|
74
|
+
else if (key === "blur" || key === "focusin") out.add("focus");
|
|
75
|
+
else if (key === "tap") out.add("click");
|
|
76
|
+
else out.add(key);
|
|
77
|
+
});
|
|
78
|
+
return out.size ? out : new Set(["manual"]);
|
|
79
|
+
};
|
|
80
|
+
const parseOpenArgs = (arg1, arg2) => {
|
|
81
|
+
let anchorEl = null;
|
|
82
|
+
let nextProps = null;
|
|
83
|
+
if (isAnchorLike(arg1)) {
|
|
84
|
+
anchorEl = arg1;
|
|
85
|
+
if (isPlainObject(arg2)) nextProps = arg2;
|
|
86
|
+
} else if (isPlainObject(arg1)) {
|
|
87
|
+
nextProps = arg1;
|
|
88
|
+
const maybeAnchor = arg1.anchorEl ?? arg1.triggerEl ?? arg1.target ?? arg1.anchor;
|
|
89
|
+
if (isAnchorLike(maybeAnchor)) anchorEl = maybeAnchor;
|
|
90
|
+
} else if (arg1 != null) {
|
|
91
|
+
anchorEl = arg1;
|
|
92
|
+
}
|
|
93
|
+
return { anchorEl, nextProps };
|
|
94
|
+
};
|
|
95
|
+
const getFocusableItems = (root = entry?.panel) => Array.from(root?.querySelectorAll?.(
|
|
96
|
+
"[data-menu-item]:not([disabled]):not([tabindex='-1']), .jss-menu-item:not([disabled]):not([tabindex='-1']), [role='menuitem']:not([disabled]):not([tabindex='-1']), a[href]:not([tabindex='-1']), button:not([disabled]):not([tabindex='-1'])"
|
|
97
|
+
) || []).filter((el) => el.offsetParent !== null && el.getAttribute("aria-hidden") !== "true");
|
|
98
|
+
const focusInitialItem = () => {
|
|
99
|
+
if (uiUnwrap(getOptions().autoFocus) === false) return;
|
|
100
|
+
setTimeout(() => {
|
|
101
|
+
const items = getFocusableItems();
|
|
102
|
+
if (!items.length) return;
|
|
103
|
+
const active = items.find((el) => el.classList.contains("is-active") || el.classList.contains("is-selected") || el.classList.contains("is-checked"));
|
|
104
|
+
(active || items[0]).focus();
|
|
105
|
+
}, 0);
|
|
106
|
+
};
|
|
107
|
+
const buildIconNode = (value, ctx, size = "sm") => {
|
|
108
|
+
const raw = resolveRender(value, ctx);
|
|
109
|
+
if (raw == null) return [];
|
|
110
|
+
if (typeof raw === "string") return [UI.Icon({ name: raw, size })];
|
|
111
|
+
return slotToArray(raw, { ...ctx, as: "icon" });
|
|
112
|
+
};
|
|
113
|
+
const renderItemEntries = (source, ctx) => {
|
|
114
|
+
const raw = resolveRender(source, ctx);
|
|
115
|
+
if (raw == null || raw === false) return [];
|
|
116
|
+
if (Array.isArray(raw)) return raw.flatMap((entryValue, index) => renderItemEntries(entryValue, { ...ctx, index }));
|
|
117
|
+
if (typeof raw === "function") return renderItemEntries(raw(ctx), ctx);
|
|
118
|
+
if (raw?.nodeType) return [raw];
|
|
119
|
+
if (typeof raw === "string" || typeof raw === "number") {
|
|
120
|
+
return renderItemEntries({ label: raw }, ctx);
|
|
121
|
+
}
|
|
122
|
+
if (!isPlainObject(raw)) {
|
|
123
|
+
return slotToArray(raw, { ...ctx, as: "item" });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const kind = String(raw.type ?? raw.kind ?? "").toLowerCase();
|
|
127
|
+
if (kind === "separator" || kind === "divider" || raw.separator === true || raw.divider === true) {
|
|
128
|
+
return [_.div({ class: "jss-menu-divider", role: "separator" })];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const groupItems = raw.items ?? raw.children;
|
|
132
|
+
if ((kind === "group" || kind === "section" || Array.isArray(groupItems)) && groupItems != null) {
|
|
133
|
+
const groupCtx = { ...ctx, item: raw };
|
|
134
|
+
const labelNodes = renderSlotToArray(slots, "groupLabel", groupCtx, resolveRender(raw.label ?? raw.title, groupCtx));
|
|
135
|
+
const contentNodes = renderItemEntries(groupItems, groupCtx);
|
|
136
|
+
if (!contentNodes.length) return [];
|
|
137
|
+
return [_.div(
|
|
138
|
+
{ class: uiClass(["jss-menu-group", raw.class]) },
|
|
139
|
+
labelNodes.length ? _.div({ class: "jss-menu-group-label" }, ...labelNodes) : null,
|
|
140
|
+
_.div({ class: "jss-menu-group-items" }, ...contentNodes)
|
|
141
|
+
)];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const itemCtx = {
|
|
145
|
+
...ctx,
|
|
146
|
+
item: raw,
|
|
147
|
+
close,
|
|
148
|
+
dismiss: close,
|
|
149
|
+
open,
|
|
150
|
+
toggle,
|
|
151
|
+
update,
|
|
152
|
+
isOpen,
|
|
153
|
+
entry: () => entry,
|
|
154
|
+
anchorEl: getAnchor(),
|
|
155
|
+
props: getOptions()
|
|
156
|
+
};
|
|
157
|
+
const disabled = !!uiUnwrap(raw.disabled);
|
|
158
|
+
const checked = raw.checked == null ? null : !!uiUnwrap(raw.checked);
|
|
159
|
+
const active = !!uiUnwrap(raw.active ?? raw.selected);
|
|
160
|
+
const itemState = getStateClass(raw);
|
|
161
|
+
const href = uiUnwrap(raw.href);
|
|
162
|
+
const to = uiUnwrap(raw.to);
|
|
163
|
+
const closeOnSelect = hasOwn(raw, "closeOnSelect")
|
|
164
|
+
? !!uiUnwrap(raw.closeOnSelect)
|
|
165
|
+
: (raw.keepOpen === true ? false : (getOptions().closeOnSelect !== false));
|
|
166
|
+
const ariaRole = raw.role || (checked != null ? "menuitemcheckbox" : "menuitem");
|
|
167
|
+
const iconNodes = buildIconNode(raw.icon, itemCtx);
|
|
168
|
+
const iconRightNodes = buildIconNode(raw.iconRight ?? raw.endIcon, itemCtx);
|
|
169
|
+
const titleNodes = renderSlotToArray(slots, "itemTitle", itemCtx, resolveRender(raw.title ?? raw.label, itemCtx));
|
|
170
|
+
const subtitleNodes = renderSlotToArray(slots, "itemSubtitle", itemCtx, resolveRender(raw.subtitle ?? raw.description, itemCtx));
|
|
171
|
+
const shortcutNodes = renderSlotToArray(slots, "itemShortcut", itemCtx, resolveRender(raw.shortcut, itemCtx));
|
|
172
|
+
const badgeNodes = renderSlotToArray(slots, "itemBadge", itemCtx, resolveRender(raw.badge, itemCtx));
|
|
173
|
+
const customBodyNodes = renderSlotToArray(slots, "item", itemCtx, resolveRender(raw.content ?? raw.body ?? raw.render, itemCtx));
|
|
174
|
+
const asideNodes = [
|
|
175
|
+
...badgeNodes,
|
|
176
|
+
...shortcutNodes,
|
|
177
|
+
...(checked === true && !iconRightNodes.length && !badgeNodes.length
|
|
178
|
+
? [_.span({ class: "jss-menu-entry-check" }, UI.Icon({ name: raw.checkedIcon || "check", size: "sm" }))]
|
|
179
|
+
: []),
|
|
180
|
+
...iconRightNodes
|
|
181
|
+
];
|
|
182
|
+
const entryClass = uiClass([
|
|
183
|
+
"jss-menu-entry",
|
|
184
|
+
"jss-menu-item",
|
|
185
|
+
itemState ? `jss-state-${itemState}` : "",
|
|
186
|
+
uiWhen(active, "is-active"),
|
|
187
|
+
uiWhen(checked === true, "is-checked"),
|
|
188
|
+
uiWhen(disabled, "is-disabled"),
|
|
189
|
+
raw.class
|
|
190
|
+
]);
|
|
191
|
+
const entryProps = {
|
|
192
|
+
class: entryClass,
|
|
193
|
+
role: ariaRole,
|
|
194
|
+
tabIndex: disabled ? -1 : (raw.tabIndex ?? -1),
|
|
195
|
+
"data-menu-item": true,
|
|
196
|
+
"data-menu-select": closeOnSelect ? "true" : "false",
|
|
197
|
+
"data-menu-close": raw.close === true ? "true" : null,
|
|
198
|
+
"data-menu-keep-open": closeOnSelect ? null : "true",
|
|
199
|
+
"aria-disabled": disabled ? "true" : null,
|
|
200
|
+
"aria-checked": checked == null ? null : String(checked),
|
|
201
|
+
title: raw.titleAttr ?? raw.tooltip ?? null,
|
|
202
|
+
type: href ? null : "button",
|
|
203
|
+
href: href || null,
|
|
204
|
+
target: raw.target || null,
|
|
205
|
+
rel: raw.rel || (raw.target === "_blank" ? "noopener noreferrer" : null),
|
|
206
|
+
onClick: (e) => {
|
|
207
|
+
if (disabled) {
|
|
208
|
+
e.preventDefault();
|
|
209
|
+
e.stopPropagation();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
getOptions().onItemClick?.(raw, itemCtx, e);
|
|
213
|
+
const result = raw.onClick?.(itemCtx, e);
|
|
214
|
+
if (e.defaultPrevented) return;
|
|
215
|
+
if (to && JSswift.router?.navigate) {
|
|
216
|
+
e.preventDefault();
|
|
217
|
+
JSswift.router.navigate(to);
|
|
218
|
+
}
|
|
219
|
+
if (result === false) return;
|
|
220
|
+
if (closeOnSelect) close();
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
if (raw.attrs && typeof raw.attrs === "object") Object.assign(entryProps, raw.attrs);
|
|
224
|
+
|
|
225
|
+
const Tag = href ? _.a : _.button;
|
|
226
|
+
const bodyNodes = customBodyNodes.length
|
|
227
|
+
? customBodyNodes
|
|
228
|
+
: [
|
|
229
|
+
titleNodes.length ? _.div({ class: "jss-menu-entry-title" }, ...titleNodes) : null,
|
|
230
|
+
subtitleNodes.length ? _.div({ class: "jss-menu-entry-subtitle" }, ...subtitleNodes) : null
|
|
231
|
+
].filter(Boolean);
|
|
232
|
+
|
|
233
|
+
return [Tag(
|
|
234
|
+
entryProps,
|
|
235
|
+
iconNodes.length ? _.span({ class: "jss-menu-entry-icon" }, ...iconNodes) : null,
|
|
236
|
+
_.span(
|
|
237
|
+
{ class: "jss-menu-entry-main" },
|
|
238
|
+
...bodyNodes
|
|
239
|
+
),
|
|
240
|
+
asideNodes.length ? _.span({ class: "jss-menu-entry-aside" }, ...asideNodes) : null
|
|
241
|
+
)];
|
|
242
|
+
};
|
|
243
|
+
const buildContent = () => {
|
|
244
|
+
const opts = getOptions();
|
|
245
|
+
const ctx = {
|
|
246
|
+
close,
|
|
247
|
+
dismiss: close,
|
|
248
|
+
open,
|
|
249
|
+
toggle,
|
|
250
|
+
update,
|
|
251
|
+
isOpen,
|
|
252
|
+
entry: () => entry,
|
|
253
|
+
anchorEl: getAnchor(),
|
|
254
|
+
props: opts
|
|
255
|
+
};
|
|
256
|
+
const iconNodes = buildIconNode(opts.icon, ctx, opts.iconSize || "md");
|
|
257
|
+
const eyebrowNodes = renderSlotToArray(slots, "eyebrow", ctx, resolveRender(opts.eyebrow, ctx));
|
|
258
|
+
const titleNodes = renderSlotToArray(slots, "title", ctx, resolveRender(opts.title ?? opts.heading ?? opts.label, ctx));
|
|
259
|
+
const subtitleNodes = renderSlotToArray(slots, "subtitle", ctx, resolveRender(opts.subtitle ?? opts.description, ctx));
|
|
260
|
+
const beforeNodes = renderSlotToArray(slots, "before", ctx, resolveRender(opts.before, ctx));
|
|
261
|
+
const headerNodes = renderSlotToArray(slots, "header", ctx, resolveRender(opts.header, ctx));
|
|
262
|
+
const afterNodes = renderSlotToArray(slots, "after", ctx, resolveRender(opts.after, ctx));
|
|
263
|
+
const footerNodes = renderSlotToArray(slots, "footer", ctx, resolveRender(opts.footer, ctx));
|
|
264
|
+
const statusNodes = renderSlotToArray(slots, "status", ctx, resolveRender(opts.status, ctx));
|
|
265
|
+
|
|
266
|
+
let contentRaw = opts.content ?? opts.body;
|
|
267
|
+
if (contentRaw == null && children && children.length) contentRaw = children;
|
|
268
|
+
let contentNodes = renderSlotToArray(slots, "content", ctx, resolveRender(contentRaw, ctx));
|
|
269
|
+
if (!contentNodes.length) contentNodes = renderSlotToArray(slots, "body", ctx, resolveRender(contentRaw, ctx));
|
|
270
|
+
if (!contentNodes.length) contentNodes = renderSlotToArray(slots, "default", ctx, resolveRender(contentRaw, ctx));
|
|
271
|
+
|
|
272
|
+
const itemNodes = renderItemEntries(opts.items ?? opts.actionsList ?? opts.entries, ctx);
|
|
273
|
+
const emptyNodes = !itemNodes.length
|
|
274
|
+
? renderSlotToArray(slots, "empty", ctx, resolveRender(opts.empty ?? opts.emptyText, ctx))
|
|
275
|
+
: [];
|
|
276
|
+
|
|
277
|
+
let headerEl = null;
|
|
278
|
+
if (headerNodes.length) {
|
|
279
|
+
headerEl = _.div({ class: uiClass(["jss-menu-head", "jss-menu-head-custom", opts.headerClass]) }, ...headerNodes);
|
|
280
|
+
} else if (iconNodes.length || eyebrowNodes.length || titleNodes.length || subtitleNodes.length) {
|
|
281
|
+
headerEl = _.div(
|
|
282
|
+
{ class: uiClass(["jss-menu-head", opts.headerClass]) },
|
|
283
|
+
iconNodes.length ? _.div({ class: "jss-menu-head-icon" }, ...iconNodes) : null,
|
|
284
|
+
_.div(
|
|
285
|
+
{ class: "jss-menu-head-main" },
|
|
286
|
+
eyebrowNodes.length ? _.div({ class: "jss-menu-eyebrow" }, ...eyebrowNodes) : null,
|
|
287
|
+
titleNodes.length ? _.div({ class: "jss-menu-title" }, ...titleNodes) : null,
|
|
288
|
+
subtitleNodes.length ? _.div({ class: "jss-menu-subtitle" }, ...subtitleNodes) : null
|
|
289
|
+
)
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return _.div(
|
|
294
|
+
{
|
|
295
|
+
class: uiClass([
|
|
296
|
+
"jss-menu",
|
|
297
|
+
"jss-menu-shell",
|
|
298
|
+
uiWhen(opts.dense, "dense"),
|
|
299
|
+
uiWhen(itemNodes.length > 0, "has-items"),
|
|
300
|
+
uiWhen(footerNodes.length > 0 || statusNodes.length > 0, "has-footer"),
|
|
301
|
+
opts.class
|
|
302
|
+
]),
|
|
303
|
+
style: opts.style || {},
|
|
304
|
+
role: opts.role || "menu",
|
|
305
|
+
"aria-label": opts.ariaLabel || null
|
|
306
|
+
},
|
|
307
|
+
beforeNodes.length ? _.div({ class: "jss-menu-before" }, ...beforeNodes) : null,
|
|
308
|
+
headerEl,
|
|
309
|
+
contentNodes.length ? _.div({ class: uiClass(["jss-menu-content", opts.contentClass]) }, ...contentNodes) : null,
|
|
310
|
+
itemNodes.length ? _.div({ class: uiClass(["jss-menu-items", opts.itemsClass]) }, ...itemNodes) : null,
|
|
311
|
+
(!itemNodes.length && emptyNodes.length) ? _.div({ class: "jss-menu-empty" }, ...emptyNodes) : null,
|
|
312
|
+
afterNodes.length ? _.div({ class: "jss-menu-after" }, ...afterNodes) : null,
|
|
313
|
+
(statusNodes.length || footerNodes.length)
|
|
314
|
+
? _.div(
|
|
315
|
+
{ class: uiClass(["jss-menu-footer", opts.footerClass]) },
|
|
316
|
+
statusNodes.length ? _.div({ class: "jss-menu-status" }, ...statusNodes) : null,
|
|
317
|
+
footerNodes.length ? _.div({ class: "jss-menu-footer-actions" }, ...footerNodes) : null
|
|
318
|
+
)
|
|
319
|
+
: null
|
|
320
|
+
);
|
|
321
|
+
};
|
|
322
|
+
const applyEntryOptions = (currentEntry) => {
|
|
323
|
+
if (!currentEntry?.panel) return;
|
|
324
|
+
const opts = getOptions();
|
|
325
|
+
const stateClass = getStateClass(opts);
|
|
326
|
+
const sizeClass = getSizeClass(opts);
|
|
327
|
+
setTrackedClasses(currentEntry.panel, "_menuClassTokens", [
|
|
328
|
+
"jss-menu-panel",
|
|
329
|
+
"jss-clear-set",
|
|
330
|
+
"jss-singularity",
|
|
331
|
+
sizeClass ? `jss-menu-${sizeClass}` : "",
|
|
332
|
+
stateClass ? `jss-state-${stateClass}` : "",
|
|
333
|
+
uiUnwrap(opts.outline) ? "outline" : "",
|
|
334
|
+
...splitClasses(opts.panelClass)
|
|
335
|
+
]);
|
|
336
|
+
setTrackedClasses(currentEntry.overlay, "_menuOverlayClassTokens", [
|
|
337
|
+
...splitClasses(opts.overlayClass)
|
|
338
|
+
]);
|
|
339
|
+
currentEntry.panel.dataset.placement = getPlacement(opts);
|
|
340
|
+
setStyleValue(currentEntry.panel, "--jss-menu-width", uiUnwrap(opts.width), toCssSize);
|
|
341
|
+
setStyleValue(currentEntry.panel, "--jss-menu-min-width", uiUnwrap(opts.minWidth), toCssSize);
|
|
342
|
+
setStyleValue(currentEntry.panel, "--jss-menu-max-width", uiUnwrap(opts.maxWidth), toCssSize);
|
|
343
|
+
setStyleValue(currentEntry.panel, "--jss-menu-max-height", uiUnwrap(opts.maxHeight), toCssSize);
|
|
344
|
+
setStyleValue(currentEntry.panel, "--jss-menu-body-max-height", uiUnwrap(opts.bodyMaxHeight ?? opts.contentMaxHeight), toCssSize);
|
|
345
|
+
if (opts.panelStyle) Object.assign(currentEntry.panel.style, opts.panelStyle);
|
|
346
|
+
setPropertyProps(currentEntry.panel, opts);
|
|
347
|
+
};
|
|
348
|
+
const renderOpenContent = () => {
|
|
349
|
+
if (!entry?.panel) return;
|
|
350
|
+
entry.panel.replaceChildren(buildContent());
|
|
351
|
+
};
|
|
352
|
+
const close = () => {
|
|
353
|
+
clearTimers();
|
|
354
|
+
if (!entry) return;
|
|
355
|
+
const toClose = entry;
|
|
356
|
+
entry = null;
|
|
357
|
+
overlayLeave(toClose, () => JSswift.overlay.close(toClose.id));
|
|
358
|
+
};
|
|
359
|
+
const update = (nextProps = {}) => {
|
|
360
|
+
if (nextProps && typeof nextProps === "object") currentProps = { ...currentProps, ...nextProps };
|
|
361
|
+
if (entry) {
|
|
362
|
+
const opts = getOptions();
|
|
363
|
+
applyEntryOptions(entry);
|
|
364
|
+
renderOpenContent();
|
|
365
|
+
entry.reposition?.({
|
|
366
|
+
anchorEl: entry._anchorEl,
|
|
367
|
+
placement: getPlacement(opts),
|
|
368
|
+
offsetX: opts.offsetX ?? 0,
|
|
369
|
+
offsetY: opts.offsetY ?? opts.offset ?? 8
|
|
370
|
+
});
|
|
371
|
+
focusInitialItem();
|
|
372
|
+
}
|
|
373
|
+
return api;
|
|
374
|
+
};
|
|
375
|
+
const open = (arg1 = null, arg2 = null) => {
|
|
376
|
+
clearTimers();
|
|
377
|
+
const { anchorEl, nextProps } = parseOpenArgs(arg1, arg2);
|
|
378
|
+
if (nextProps) currentProps = { ...currentProps, ...nextProps };
|
|
379
|
+
const opts = getOptions();
|
|
380
|
+
const anchor = getAnchor(anchorEl);
|
|
381
|
+
if (!anchor || uiUnwrap(opts.disabled)) return null;
|
|
382
|
+
if (entry && entry._anchorEl === anchor) {
|
|
383
|
+
const nextOpts = getOptions();
|
|
384
|
+
applyEntryOptions(entry);
|
|
385
|
+
renderOpenContent();
|
|
386
|
+
entry.reposition?.({
|
|
387
|
+
anchorEl: entry._anchorEl,
|
|
388
|
+
placement: getPlacement(nextOpts),
|
|
389
|
+
offsetX: nextOpts.offsetX ?? 0,
|
|
390
|
+
offsetY: nextOpts.offsetY ?? nextOpts.offset ?? 8
|
|
391
|
+
});
|
|
392
|
+
focusInitialItem();
|
|
393
|
+
return entry;
|
|
394
|
+
}
|
|
395
|
+
if (entry) close();
|
|
396
|
+
lastActive = document.activeElement;
|
|
397
|
+
let currentRef = null;
|
|
398
|
+
entry = JSswift.overlay.open(() => buildContent(), {
|
|
399
|
+
type: "menu",
|
|
400
|
+
anchorEl: anchor,
|
|
401
|
+
placement: getPlacement(opts),
|
|
402
|
+
offsetX: opts.offsetX ?? 0,
|
|
403
|
+
offsetY: opts.offsetY ?? opts.offset ?? 8,
|
|
404
|
+
backdrop: opts.backdrop === true,
|
|
405
|
+
lockScroll: opts.lockScroll === true,
|
|
406
|
+
trapFocus: opts.trapFocus === true,
|
|
407
|
+
autoFocus: false,
|
|
408
|
+
closeOnOutside: opts.closeOnOutside !== false,
|
|
409
|
+
closeOnBackdrop: opts.closeOnBackdrop ?? opts.backdrop === true,
|
|
410
|
+
closeOnEsc: opts.closeOnEsc !== false,
|
|
411
|
+
className: uiClassStatic(["jss-menu-panel"]),
|
|
412
|
+
onClose: () => {
|
|
413
|
+
currentRef?._menuCleanup?.();
|
|
414
|
+
getOptions().onClose?.(currentRef);
|
|
415
|
+
if (getOptions().returnFocus !== false && lastActive && typeof lastActive.focus === "function") {
|
|
416
|
+
setTimeout(() => lastActive.focus(), 0);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
if (!entry?.panel) return entry;
|
|
421
|
+
const current = entry;
|
|
422
|
+
currentRef = current;
|
|
423
|
+
current._anchorEl = anchor;
|
|
424
|
+
const cancelHide = () => {
|
|
425
|
+
clearTimeout(closeTimer);
|
|
426
|
+
closeTimer = null;
|
|
427
|
+
};
|
|
428
|
+
const scheduleHide = () => {
|
|
429
|
+
hide();
|
|
430
|
+
};
|
|
431
|
+
const onPanelClick = (e) => {
|
|
432
|
+
const target = e.target;
|
|
433
|
+
if (!target || !target.closest) return;
|
|
434
|
+
if (target.closest("[data-menu-close]")) {
|
|
435
|
+
close();
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (getOptions().closeOnSelect === false) return;
|
|
439
|
+
const selected = target.closest("[data-menu-item], [data-menu-select], .jss-menu-item, [role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio'], a[href], button:not([disabled])");
|
|
440
|
+
if (!selected) return;
|
|
441
|
+
if (selected.closest("[data-menu-keep-open]")) return;
|
|
442
|
+
if (selected.getAttribute("data-menu-select") === "false") return;
|
|
443
|
+
close();
|
|
444
|
+
};
|
|
445
|
+
const onPanelKeydown = (e) => {
|
|
446
|
+
if (e.key === "Escape") {
|
|
447
|
+
e.preventDefault();
|
|
448
|
+
close();
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(e.key)) return;
|
|
452
|
+
const items = getFocusableItems(current.panel);
|
|
453
|
+
if (!items.length) return;
|
|
454
|
+
e.preventDefault();
|
|
455
|
+
const activeIndex = items.indexOf(document.activeElement);
|
|
456
|
+
if (e.key === "Home") {
|
|
457
|
+
items[0].focus();
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (e.key === "End") {
|
|
461
|
+
items[items.length - 1].focus();
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const dir = e.key === "ArrowDown" ? 1 : -1;
|
|
465
|
+
const next = activeIndex < 0
|
|
466
|
+
? (dir > 0 ? 0 : items.length - 1)
|
|
467
|
+
: (activeIndex + dir + items.length) % items.length;
|
|
468
|
+
items[next].focus();
|
|
469
|
+
};
|
|
470
|
+
current.panel.addEventListener("click", onPanelClick);
|
|
471
|
+
current.panel.addEventListener("keydown", onPanelKeydown);
|
|
472
|
+
const activeTriggers = parseTriggers(opts.trigger ?? opts.triggers ?? (hasOwn(opts, "open") ? "manual" : null));
|
|
473
|
+
const allowHover = activeTriggers.has("hover");
|
|
474
|
+
const allowFocus = activeTriggers.has("focus");
|
|
475
|
+
if (allowHover || allowFocus) {
|
|
476
|
+
current.panel.addEventListener("mouseenter", cancelHide);
|
|
477
|
+
current.panel.addEventListener("mouseleave", scheduleHide);
|
|
478
|
+
current.panel.addEventListener("focusin", cancelHide);
|
|
479
|
+
current.panel.addEventListener("focusout", scheduleHide);
|
|
480
|
+
}
|
|
481
|
+
current._menuCleanup = () => {
|
|
482
|
+
current.panel?.removeEventListener("click", onPanelClick);
|
|
483
|
+
current.panel?.removeEventListener("keydown", onPanelKeydown);
|
|
484
|
+
current.panel?.removeEventListener("mouseenter", cancelHide);
|
|
485
|
+
current.panel?.removeEventListener("mouseleave", scheduleHide);
|
|
486
|
+
current.panel?.removeEventListener("focusin", cancelHide);
|
|
487
|
+
current.panel?.removeEventListener("focusout", scheduleHide);
|
|
488
|
+
};
|
|
489
|
+
applyEntryOptions(current);
|
|
490
|
+
current.reposition?.({
|
|
491
|
+
anchorEl: anchor,
|
|
492
|
+
placement: getPlacement(opts),
|
|
493
|
+
offsetX: opts.offsetX ?? 0,
|
|
494
|
+
offsetY: opts.offsetY ?? opts.offset ?? 8
|
|
495
|
+
});
|
|
496
|
+
overlayEnter(current);
|
|
497
|
+
getOptions().onOpen?.(current);
|
|
498
|
+
focusInitialItem();
|
|
499
|
+
return current;
|
|
500
|
+
};
|
|
501
|
+
const show = (anchorEl, nextProps = null) => {
|
|
502
|
+
clearTimeout(closeTimer);
|
|
503
|
+
closeTimer = null;
|
|
504
|
+
clearTimeout(openTimer);
|
|
505
|
+
openTimer = setTimeout(() => open(anchorEl, nextProps), getDelay());
|
|
506
|
+
};
|
|
507
|
+
const hide = (immediate = false) => {
|
|
508
|
+
clearTimeout(openTimer);
|
|
509
|
+
openTimer = null;
|
|
510
|
+
if (!entry) return;
|
|
511
|
+
clearTimeout(closeTimer);
|
|
512
|
+
if (immediate) {
|
|
513
|
+
close();
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
closeTimer = setTimeout(() => close(), getHideDelay());
|
|
517
|
+
};
|
|
518
|
+
const toggle = (arg1 = null, arg2 = null) => isOpen() ? (close(), null) : open(arg1, arg2);
|
|
519
|
+
const isOpen = () => !!entry;
|
|
520
|
+
const bind = (el) => {
|
|
521
|
+
if (!el) return () => { };
|
|
522
|
+
boundEl = el;
|
|
523
|
+
const opts = getOptions();
|
|
524
|
+
const triggers = parseTriggers(opts.trigger ?? opts.triggers ?? (hasOwn(opts, "open") ? "manual" : null));
|
|
525
|
+
const allowHover = triggers.has("hover");
|
|
526
|
+
const allowFocus = triggers.has("focus");
|
|
527
|
+
const allowClick = triggers.has("click");
|
|
528
|
+
const isManual = triggers.has("manual") || (!allowHover && !allowFocus && !allowClick);
|
|
529
|
+
const cleanups = [];
|
|
530
|
+
const cleanup = () => {
|
|
531
|
+
clearTimers();
|
|
532
|
+
cleanups.forEach((fn) => fn());
|
|
533
|
+
if (boundEl === el) boundEl = null;
|
|
534
|
+
};
|
|
535
|
+
if (hasOwn(opts, "open")) {
|
|
536
|
+
if (uiIsReactive(opts.open)) {
|
|
537
|
+
JSswift.reactive.effect(() => {
|
|
538
|
+
if (!boundEl) return;
|
|
539
|
+
if (uiUnwrap(getOptions().open)) open(boundEl);
|
|
540
|
+
else hide(true);
|
|
541
|
+
}, "UI.Menu:open");
|
|
542
|
+
} else if (opts.open === true) {
|
|
543
|
+
open(el);
|
|
544
|
+
} else if (opts.open === false) {
|
|
545
|
+
hide(true);
|
|
546
|
+
}
|
|
547
|
+
return cleanup;
|
|
548
|
+
}
|
|
549
|
+
if (isManual || uiUnwrap(opts.disabled)) return cleanup;
|
|
550
|
+
if (allowHover) {
|
|
551
|
+
const onEnter = () => show(el);
|
|
552
|
+
const onLeave = () => hide();
|
|
553
|
+
el.addEventListener("mouseenter", onEnter);
|
|
554
|
+
el.addEventListener("mouseleave", onLeave);
|
|
555
|
+
cleanups.push(() => {
|
|
556
|
+
el.removeEventListener("mouseenter", onEnter);
|
|
557
|
+
el.removeEventListener("mouseleave", onLeave);
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
if (allowFocus) {
|
|
561
|
+
const onFocus = () => show(el);
|
|
562
|
+
const onBlur = () => hide();
|
|
563
|
+
el.addEventListener("focus", onFocus);
|
|
564
|
+
el.addEventListener("blur", onBlur);
|
|
565
|
+
cleanups.push(() => {
|
|
566
|
+
el.removeEventListener("focus", onFocus);
|
|
567
|
+
el.removeEventListener("blur", onBlur);
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
if (allowClick) {
|
|
571
|
+
const onClick = (e) => {
|
|
572
|
+
getOptions().onTriggerClick?.(e);
|
|
573
|
+
if (e.defaultPrevented) return;
|
|
574
|
+
toggle(el);
|
|
575
|
+
};
|
|
576
|
+
el.addEventListener("click", onClick);
|
|
577
|
+
cleanups.push(() => el.removeEventListener("click", onClick));
|
|
578
|
+
}
|
|
579
|
+
return cleanup;
|
|
580
|
+
};
|
|
581
|
+
const api = {
|
|
582
|
+
open,
|
|
583
|
+
close,
|
|
584
|
+
show,
|
|
585
|
+
hide,
|
|
586
|
+
toggle,
|
|
587
|
+
update,
|
|
588
|
+
bind,
|
|
589
|
+
isOpen,
|
|
590
|
+
entry: () => entry,
|
|
591
|
+
props: () => ({ ...currentProps })
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
return api;
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
UI.Popover = (...args) => {
|
|
599
|
+
const { props, children } = JSswift.uiNormalizeArgs(args);
|
|
600
|
+
const slots = props.slots || {};
|
|
601
|
+
const stateList = ["primary", "secondary", "warning", "danger", "success", "info", "light", "dark"];
|
|
602
|
+
const sizeList = ["xs", "sm", "md", "lg", "xl"];
|
|
603
|
+
let currentProps = { ...props };
|
|
604
|
+
let entry = null;
|
|
605
|
+
let boundEl = null;
|
|
606
|
+
let lastActive = null;
|
|
607
|
+
let openTimer = null;
|
|
608
|
+
let closeTimer = null;
|
|
609
|
+
|
|
610
|
+
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj || {}, key);
|
|
611
|
+
const resolveRender = (value, ctx) => typeof value === "function" ? value(ctx) : value;
|
|
612
|
+
const splitClasses = (value) => String(value || "").split(/\s+/).filter(Boolean);
|
|
613
|
+
const isPlainObject = (value) => value && typeof value === "object" && !value.nodeType && !Array.isArray(value) && !(value instanceof Function);
|
|
614
|
+
const isAnchorLike = (value) => !!value && typeof value === "object" && (value.nodeType === 1 || typeof value.getBoundingClientRect === "function");
|
|
615
|
+
const clearTimers = () => {
|
|
616
|
+
clearTimeout(openTimer);
|
|
617
|
+
clearTimeout(closeTimer);
|
|
618
|
+
openTimer = null;
|
|
619
|
+
closeTimer = null;
|
|
620
|
+
};
|
|
621
|
+
const setTrackedClasses = (target, key, classes) => {
|
|
622
|
+
if (!target) return;
|
|
623
|
+
const prev = target[key] || [];
|
|
624
|
+
if (prev.length) target.classList.remove(...prev);
|
|
625
|
+
const next = (classes || []).filter(Boolean);
|
|
626
|
+
if (next.length) target.classList.add(...next);
|
|
627
|
+
target[key] = next;
|
|
628
|
+
};
|
|
629
|
+
const setStyleValue = (target, name, value, formatter = (v) => v) => {
|
|
630
|
+
if (!target) return;
|
|
631
|
+
if (value == null || value === "") {
|
|
632
|
+
target.style.removeProperty(name);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
target.style.setProperty(name, formatter(value));
|
|
636
|
+
};
|
|
637
|
+
const getOptions = () => currentProps;
|
|
638
|
+
const getStateClass = (opts) => {
|
|
639
|
+
const value = uiUnwrap(opts.state ?? opts.color);
|
|
640
|
+
if (!value) return "";
|
|
641
|
+
return stateList.includes(value) ? value : String(value);
|
|
642
|
+
};
|
|
643
|
+
const getSizeClass = (opts) => {
|
|
644
|
+
const value = uiUnwrap(opts.size);
|
|
645
|
+
return (typeof value === "string" && sizeList.includes(value)) ? value : "";
|
|
646
|
+
};
|
|
647
|
+
const getAlignClass = (opts) => {
|
|
648
|
+
const raw = String(uiUnwrap(opts.actionsAlign ?? opts.footerAlign ?? opts.alignActions ?? "end")).toLowerCase();
|
|
649
|
+
if (["start", "left"].includes(raw)) return "start";
|
|
650
|
+
if (["center", "middle"].includes(raw)) return "center";
|
|
651
|
+
if (["between", "space-between"].includes(raw)) return "between";
|
|
652
|
+
if (["stretch", "full"].includes(raw)) return "stretch";
|
|
653
|
+
return "end";
|
|
654
|
+
};
|
|
655
|
+
const getPlacement = (opts) => String(uiUnwrap(opts.placement ?? opts.position ?? "bottom-start"));
|
|
656
|
+
const getNumber = (value, fallback) => {
|
|
657
|
+
const raw = uiUnwrap(value);
|
|
658
|
+
if (raw == null || raw === "") return fallback;
|
|
659
|
+
const n = Number(raw);
|
|
660
|
+
return Number.isFinite(n) ? n : fallback;
|
|
661
|
+
};
|
|
662
|
+
const getDelay = () => getNumber(getOptions().delay, 0);
|
|
663
|
+
const getHideDelay = () => getNumber(getOptions().hideDelay, 120);
|
|
664
|
+
const isClosable = (opts) => {
|
|
665
|
+
const value = opts.closable ?? opts.dismissible ?? opts.closeButton;
|
|
666
|
+
return value === true;
|
|
667
|
+
};
|
|
668
|
+
const getAnchor = (anchorOverride = null) => {
|
|
669
|
+
if (anchorOverride) return anchorOverride;
|
|
670
|
+
const opts = getOptions();
|
|
671
|
+
return boundEl || uiUnwrap(opts.anchorEl ?? opts.triggerEl ?? opts.target ?? opts.anchor) || null;
|
|
672
|
+
};
|
|
673
|
+
const parseTriggers = (value) => {
|
|
674
|
+
if (value == null || value === true) return new Set(["click"]);
|
|
675
|
+
if (value === false) return new Set(["manual"]);
|
|
676
|
+
const raw = Array.isArray(value) ? value : String(value).split(/[\s,|/]+/);
|
|
677
|
+
const out = new Set();
|
|
678
|
+
raw.forEach((item) => {
|
|
679
|
+
const key = String(item || "").trim().toLowerCase();
|
|
680
|
+
if (!key) return;
|
|
681
|
+
if (key === "mouseenter" || key === "mouseover") out.add("hover");
|
|
682
|
+
else if (key === "blur" || key === "focusin") out.add("focus");
|
|
683
|
+
else if (key === "tap") out.add("click");
|
|
684
|
+
else out.add(key);
|
|
685
|
+
});
|
|
686
|
+
return out.size ? out : new Set(["manual"]);
|
|
687
|
+
};
|
|
688
|
+
const parseOpenArgs = (arg1, arg2) => {
|
|
689
|
+
let anchorEl = null;
|
|
690
|
+
let nextProps = null;
|
|
691
|
+
if (isAnchorLike(arg1)) {
|
|
692
|
+
anchorEl = arg1;
|
|
693
|
+
if (isPlainObject(arg2)) nextProps = arg2;
|
|
694
|
+
} else if (isPlainObject(arg1)) {
|
|
695
|
+
nextProps = arg1;
|
|
696
|
+
const maybeAnchor = arg1.anchorEl ?? arg1.triggerEl ?? arg1.target ?? arg1.anchor;
|
|
697
|
+
if (isAnchorLike(maybeAnchor)) anchorEl = maybeAnchor;
|
|
698
|
+
} else if (arg1 != null) {
|
|
699
|
+
anchorEl = arg1;
|
|
700
|
+
}
|
|
701
|
+
return { anchorEl, nextProps };
|
|
702
|
+
};
|
|
703
|
+
const buildContent = () => {
|
|
704
|
+
const opts = getOptions();
|
|
705
|
+
const ctx = {
|
|
706
|
+
close,
|
|
707
|
+
dismiss: close,
|
|
708
|
+
open,
|
|
709
|
+
toggle,
|
|
710
|
+
update,
|
|
711
|
+
isOpen,
|
|
712
|
+
entry: () => entry,
|
|
713
|
+
anchorEl: getAnchor(),
|
|
714
|
+
props: opts
|
|
715
|
+
};
|
|
716
|
+
const iconFallback = opts.icon != null
|
|
717
|
+
? (typeof opts.icon === "string"
|
|
718
|
+
? UI.Icon({ name: opts.icon, size: opts.iconSize || "md" })
|
|
719
|
+
: JSswift.ui.slot(opts.icon, { as: "icon" }))
|
|
720
|
+
: null;
|
|
721
|
+
const eyebrowNodes = renderSlotToArray(slots, "eyebrow", ctx, resolveRender(opts.eyebrow, ctx));
|
|
722
|
+
const titleNodes = renderSlotToArray(slots, "title", ctx, resolveRender(opts.title ?? opts.heading ?? opts.label, ctx));
|
|
723
|
+
const subtitleNodes = renderSlotToArray(slots, "subtitle", ctx, resolveRender(opts.subtitle ?? opts.description, ctx));
|
|
724
|
+
const iconNodes = renderSlotToArray(slots, "icon", ctx, iconFallback);
|
|
725
|
+
const headerNodes = renderSlotToArray(slots, "header", ctx, resolveRender(opts.headerContent ?? opts.head ?? opts.header, ctx));
|
|
726
|
+
const closeSlotNodes = isClosable(opts)
|
|
727
|
+
? renderSlotToArray(slots, "close", ctx, UI.Btn({
|
|
728
|
+
class: "jss-dialog-close",
|
|
729
|
+
size: "sm",
|
|
730
|
+
outline: true,
|
|
731
|
+
"aria-label": opts.closeLabel || "Chiudi popover",
|
|
732
|
+
"data-popover-close": true
|
|
733
|
+
}, UI.Icon({ name: opts.closeIcon || "close", size: "sm" })))
|
|
734
|
+
: [];
|
|
735
|
+
|
|
736
|
+
let bodyRaw = opts.content ?? opts.body ?? opts.message ?? opts.text;
|
|
737
|
+
if (bodyRaw == null && children && children.length) bodyRaw = children;
|
|
738
|
+
let contentNodes = renderSlotToArray(slots, "content", ctx, resolveRender(bodyRaw, ctx));
|
|
739
|
+
if (!contentNodes.length) contentNodes = renderSlotToArray(slots, "body", ctx, resolveRender(bodyRaw, ctx));
|
|
740
|
+
if (!contentNodes.length) contentNodes = renderSlotToArray(slots, "default", ctx, resolveRender(bodyRaw, ctx));
|
|
741
|
+
|
|
742
|
+
const footerRaw = resolveRender(opts.footer ?? opts.actions, ctx);
|
|
743
|
+
let footerNodes = renderSlotToArray(slots, "footer", ctx, footerRaw);
|
|
744
|
+
if (!footerNodes.length) footerNodes = renderSlotToArray(slots, "actions", ctx, footerRaw);
|
|
745
|
+
|
|
746
|
+
let headerEl = null;
|
|
747
|
+
if (headerNodes.length) {
|
|
748
|
+
headerEl = _.div({ class: "jss-dialog-head jss-dialog-head-custom" }, ...headerNodes);
|
|
749
|
+
} else if (eyebrowNodes.length || titleNodes.length || subtitleNodes.length || iconNodes.length || closeSlotNodes.length) {
|
|
750
|
+
headerEl = _.div(
|
|
751
|
+
{ class: "jss-dialog-head" },
|
|
752
|
+
iconNodes.length ? _.div({ class: "jss-dialog-icon" }, ...iconNodes) : null,
|
|
753
|
+
_.div(
|
|
754
|
+
{ class: "jss-dialog-head-main" },
|
|
755
|
+
eyebrowNodes.length ? _.div({ class: "jss-dialog-eyebrow" }, ...eyebrowNodes) : null,
|
|
756
|
+
titleNodes.length ? _.div({ class: "jss-dialog-title" }, ...titleNodes) : null,
|
|
757
|
+
subtitleNodes.length ? _.div({ class: "jss-dialog-subtitle" }, ...subtitleNodes) : null
|
|
758
|
+
),
|
|
759
|
+
closeSlotNodes.length ? _.div({ class: "jss-dialog-close-wrap" }, ...closeSlotNodes) : null
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const sections = [];
|
|
764
|
+
if (headerEl) sections.push(headerEl);
|
|
765
|
+
if (contentNodes.length) {
|
|
766
|
+
sections.push(_.div({ class: uiClass(["jss-dialog-body", opts.bodyClass]) }, ...contentNodes));
|
|
767
|
+
}
|
|
768
|
+
if (footerNodes.length) {
|
|
769
|
+
sections.push(_.div({
|
|
770
|
+
class: uiClass([
|
|
771
|
+
"jss-dialog-actions",
|
|
772
|
+
`is-${getAlignClass(opts)}`,
|
|
773
|
+
uiWhen(opts.stackActions, "is-stacked"),
|
|
774
|
+
opts.actionsClass,
|
|
775
|
+
opts.footerClass
|
|
776
|
+
])
|
|
777
|
+
}, ...footerNodes));
|
|
778
|
+
}
|
|
779
|
+
if (!sections.length) {
|
|
780
|
+
sections.push(_.div({ class: uiClass(["jss-dialog-body", opts.bodyClass]) }));
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return _.div({
|
|
784
|
+
class: uiClass([
|
|
785
|
+
"jss-dialog-shell",
|
|
786
|
+
"jss-popover-shell",
|
|
787
|
+
uiWhen(!!headerEl, "has-head"),
|
|
788
|
+
uiWhen(footerNodes.length > 0, "has-footer"),
|
|
789
|
+
uiWhen(opts.divider !== false, "with-divider")
|
|
790
|
+
])
|
|
791
|
+
}, ...sections);
|
|
792
|
+
};
|
|
793
|
+
const applyEntryOptions = (currentEntry) => {
|
|
794
|
+
if (!currentEntry?.panel) return;
|
|
795
|
+
const opts = getOptions();
|
|
796
|
+
const stateClass = getStateClass(opts);
|
|
797
|
+
const sizeClass = getSizeClass(opts);
|
|
798
|
+
setTrackedClasses(currentEntry.panel, "_popoverClassTokens", [
|
|
799
|
+
"jss-dialog",
|
|
800
|
+
"jss-popover",
|
|
801
|
+
"jss-singularity",
|
|
802
|
+
"jss-clear-set",
|
|
803
|
+
sizeClass ? `jss-dialog-${sizeClass}` : "",
|
|
804
|
+
stateClass,
|
|
805
|
+
stateClass ? `jss-state-${stateClass}` : "",
|
|
806
|
+
uiUnwrap(opts.scrollable) ? "scrollable" : "",
|
|
807
|
+
uiUnwrap(opts.stickyHeader) ? "sticky-head" : "",
|
|
808
|
+
uiUnwrap(opts.stickyActions) ? "sticky-actions" : "",
|
|
809
|
+
uiUnwrap(opts.borderless) ? "borderless" : "",
|
|
810
|
+
...splitClasses(opts.class),
|
|
811
|
+
...splitClasses(opts.panelClass)
|
|
812
|
+
]);
|
|
813
|
+
setTrackedClasses(currentEntry.overlay, "_popoverOverlayClassTokens", [
|
|
814
|
+
...splitClasses(opts.overlayClass)
|
|
815
|
+
]);
|
|
816
|
+
currentEntry.panel.dataset.placement = getPlacement(opts);
|
|
817
|
+
setStyleValue(currentEntry.panel, "--jss-dialog-width", uiUnwrap(opts.width), toCssSize);
|
|
818
|
+
setStyleValue(currentEntry.panel, "--jss-dialog-min-width", uiUnwrap(opts.minWidth), toCssSize);
|
|
819
|
+
setStyleValue(currentEntry.panel, "--jss-dialog-max-width", uiUnwrap(opts.maxWidth), toCssSize);
|
|
820
|
+
setStyleValue(currentEntry.panel, "--jss-dialog-max-height", uiUnwrap(opts.maxHeight), toCssSize);
|
|
821
|
+
setStyleValue(currentEntry.panel, "--jss-dialog-body-max-height", uiUnwrap(opts.bodyMaxHeight ?? opts.contentMaxHeight), toCssSize);
|
|
822
|
+
if (opts.style) Object.assign(currentEntry.panel.style, opts.style);
|
|
823
|
+
setPropertyProps(currentEntry.panel, opts);
|
|
824
|
+
currentEntry.panel.setAttribute("role", opts.role || "dialog");
|
|
825
|
+
currentEntry.panel.setAttribute("aria-modal", opts.modal === true ? "true" : "false");
|
|
826
|
+
if (opts.ariaLabel) currentEntry.panel.setAttribute("aria-label", opts.ariaLabel);
|
|
827
|
+
else currentEntry.panel.removeAttribute("aria-label");
|
|
828
|
+
};
|
|
829
|
+
const renderOpenContent = () => {
|
|
830
|
+
if (!entry?.panel) return;
|
|
831
|
+
entry.panel.replaceChildren(buildContent());
|
|
832
|
+
};
|
|
833
|
+
const close = () => {
|
|
834
|
+
clearTimers();
|
|
835
|
+
if (!entry) return;
|
|
836
|
+
const toClose = entry;
|
|
837
|
+
entry = null;
|
|
838
|
+
overlayLeave(toClose, () => JSswift.overlay.close(toClose.id));
|
|
839
|
+
};
|
|
840
|
+
const update = (nextProps = {}) => {
|
|
841
|
+
if (nextProps && typeof nextProps === "object") currentProps = { ...currentProps, ...nextProps };
|
|
842
|
+
if (entry) {
|
|
843
|
+
applyEntryOptions(entry);
|
|
844
|
+
renderOpenContent();
|
|
845
|
+
}
|
|
846
|
+
return api;
|
|
847
|
+
};
|
|
848
|
+
const open = (arg1 = null, arg2 = null) => {
|
|
849
|
+
clearTimers();
|
|
850
|
+
const { anchorEl, nextProps } = parseOpenArgs(arg1, arg2);
|
|
851
|
+
if (nextProps) currentProps = { ...currentProps, ...nextProps };
|
|
852
|
+
const opts = getOptions();
|
|
853
|
+
const anchor = getAnchor(anchorEl);
|
|
854
|
+
if (!anchor || uiUnwrap(opts.disabled)) return null;
|
|
855
|
+
if (entry && entry._anchorEl === anchor) {
|
|
856
|
+
applyEntryOptions(entry);
|
|
857
|
+
renderOpenContent();
|
|
858
|
+
return entry;
|
|
859
|
+
}
|
|
860
|
+
if (entry) close();
|
|
861
|
+
lastActive = document.activeElement;
|
|
862
|
+
const activeTriggers = parseTriggers(opts.trigger ?? opts.triggers ?? (hasOwn(opts, "open") ? "manual" : null));
|
|
863
|
+
const allowHover = activeTriggers.has("hover");
|
|
864
|
+
const allowFocus = activeTriggers.has("focus");
|
|
865
|
+
let currentRef = null;
|
|
866
|
+
entry = JSswift.overlay.open(() => buildContent(), {
|
|
867
|
+
type: "popover",
|
|
868
|
+
anchorEl: anchor,
|
|
869
|
+
placement: getPlacement(opts),
|
|
870
|
+
offsetX: opts.offsetX ?? 0,
|
|
871
|
+
offsetY: opts.offsetY ?? opts.offset ?? 10,
|
|
872
|
+
backdrop: opts.backdrop === true,
|
|
873
|
+
lockScroll: opts.lockScroll === true,
|
|
874
|
+
trapFocus: opts.trapFocus === true,
|
|
875
|
+
autoFocus: opts.autoFocus === true,
|
|
876
|
+
closeOnOutside: opts.closeOnOutside !== false,
|
|
877
|
+
closeOnBackdrop: opts.closeOnBackdrop ?? opts.backdrop === true,
|
|
878
|
+
closeOnEsc: opts.closeOnEsc !== false,
|
|
879
|
+
className: uiClassStatic(["jss-dialog", "jss-popover"]),
|
|
880
|
+
onClose: () => {
|
|
881
|
+
currentRef?._popoverCleanup?.();
|
|
882
|
+
getOptions().onClose?.(currentRef);
|
|
883
|
+
if (getOptions().returnFocus !== false && lastActive && typeof lastActive.focus === "function") {
|
|
884
|
+
setTimeout(() => lastActive.focus(), 0);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
if (!entry?.panel) return entry;
|
|
889
|
+
const current = entry;
|
|
890
|
+
currentRef = current;
|
|
891
|
+
current._anchorEl = anchor;
|
|
892
|
+
const cancelHide = () => {
|
|
893
|
+
clearTimeout(closeTimer);
|
|
894
|
+
closeTimer = null;
|
|
895
|
+
};
|
|
896
|
+
const scheduleHide = () => {
|
|
897
|
+
if (!allowHover && !allowFocus) return;
|
|
898
|
+
hide();
|
|
899
|
+
};
|
|
900
|
+
const onPanelClick = (e) => {
|
|
901
|
+
const target = e.target;
|
|
902
|
+
if (!target || !target.closest) return;
|
|
903
|
+
if (target.closest("[data-popover-close]") || target.closest("[data-dialog-close]")) {
|
|
904
|
+
close();
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
if (getOptions().closeOnSelect === true && target.closest("[data-popover-select]")) {
|
|
908
|
+
close();
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
current.panel.addEventListener("click", onPanelClick);
|
|
912
|
+
if (allowHover || allowFocus) {
|
|
913
|
+
current.panel.addEventListener("mouseenter", cancelHide);
|
|
914
|
+
current.panel.addEventListener("mouseleave", scheduleHide);
|
|
915
|
+
current.panel.addEventListener("focusin", cancelHide);
|
|
916
|
+
current.panel.addEventListener("focusout", scheduleHide);
|
|
917
|
+
}
|
|
918
|
+
current._popoverCleanup = () => {
|
|
919
|
+
current.panel?.removeEventListener("click", onPanelClick);
|
|
920
|
+
current.panel?.removeEventListener("mouseenter", cancelHide);
|
|
921
|
+
current.panel?.removeEventListener("mouseleave", scheduleHide);
|
|
922
|
+
current.panel?.removeEventListener("focusin", cancelHide);
|
|
923
|
+
current.panel?.removeEventListener("focusout", scheduleHide);
|
|
924
|
+
};
|
|
925
|
+
applyEntryOptions(current);
|
|
926
|
+
overlayEnter(current);
|
|
927
|
+
getOptions().onOpen?.(current);
|
|
928
|
+
return current;
|
|
929
|
+
};
|
|
930
|
+
const show = (anchorEl, nextProps = null) => {
|
|
931
|
+
clearTimeout(closeTimer);
|
|
932
|
+
closeTimer = null;
|
|
933
|
+
clearTimeout(openTimer);
|
|
934
|
+
openTimer = setTimeout(() => open(anchorEl, nextProps), getDelay());
|
|
935
|
+
};
|
|
936
|
+
const hide = (immediate = false) => {
|
|
937
|
+
clearTimeout(openTimer);
|
|
938
|
+
openTimer = null;
|
|
939
|
+
if (!entry) return;
|
|
940
|
+
clearTimeout(closeTimer);
|
|
941
|
+
if (immediate) {
|
|
942
|
+
close();
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
closeTimer = setTimeout(() => close(), getHideDelay());
|
|
946
|
+
};
|
|
947
|
+
const toggle = (arg1 = null, arg2 = null) => isOpen() ? (close(), null) : open(arg1, arg2);
|
|
948
|
+
const isOpen = () => !!entry;
|
|
949
|
+
const bind = (el) => {
|
|
950
|
+
if (!el) return () => { };
|
|
951
|
+
boundEl = el;
|
|
952
|
+
const opts = getOptions();
|
|
953
|
+
const triggers = parseTriggers(opts.trigger ?? opts.triggers ?? (hasOwn(opts, "open") ? "manual" : null));
|
|
954
|
+
const allowHover = triggers.has("hover");
|
|
955
|
+
const allowFocus = triggers.has("focus");
|
|
956
|
+
const allowClick = triggers.has("click");
|
|
957
|
+
const isManual = triggers.has("manual") || (!allowHover && !allowFocus && !allowClick);
|
|
958
|
+
const cleanups = [];
|
|
959
|
+
const cleanup = () => {
|
|
960
|
+
clearTimers();
|
|
961
|
+
cleanups.forEach((fn) => fn());
|
|
962
|
+
if (boundEl === el) boundEl = null;
|
|
963
|
+
};
|
|
964
|
+
if (hasOwn(opts, "open")) {
|
|
965
|
+
if (uiIsReactive(opts.open)) {
|
|
966
|
+
JSswift.reactive.effect(() => {
|
|
967
|
+
if (!boundEl) return;
|
|
968
|
+
if (uiUnwrap(getOptions().open)) open(boundEl);
|
|
969
|
+
else hide(true);
|
|
970
|
+
}, "UI.Popover:open");
|
|
971
|
+
} else if (opts.open === true) {
|
|
972
|
+
open(el);
|
|
973
|
+
} else if (opts.open === false) {
|
|
974
|
+
hide(true);
|
|
975
|
+
}
|
|
976
|
+
return cleanup;
|
|
977
|
+
}
|
|
978
|
+
if (isManual || uiUnwrap(opts.disabled)) return cleanup;
|
|
979
|
+
if (allowHover) {
|
|
980
|
+
const onEnter = () => show(el);
|
|
981
|
+
const onLeave = () => hide();
|
|
982
|
+
el.addEventListener("mouseenter", onEnter);
|
|
983
|
+
el.addEventListener("mouseleave", onLeave);
|
|
984
|
+
cleanups.push(() => {
|
|
985
|
+
el.removeEventListener("mouseenter", onEnter);
|
|
986
|
+
el.removeEventListener("mouseleave", onLeave);
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
if (allowFocus) {
|
|
990
|
+
const onFocus = () => show(el);
|
|
991
|
+
const onBlur = () => hide();
|
|
992
|
+
el.addEventListener("focus", onFocus);
|
|
993
|
+
el.addEventListener("blur", onBlur);
|
|
994
|
+
cleanups.push(() => {
|
|
995
|
+
el.removeEventListener("focus", onFocus);
|
|
996
|
+
el.removeEventListener("blur", onBlur);
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
if (allowClick) {
|
|
1000
|
+
const onClick = (e) => {
|
|
1001
|
+
getOptions().onTriggerClick?.(e);
|
|
1002
|
+
if (e.defaultPrevented) return;
|
|
1003
|
+
toggle(el);
|
|
1004
|
+
};
|
|
1005
|
+
el.addEventListener("click", onClick);
|
|
1006
|
+
cleanups.push(() => el.removeEventListener("click", onClick));
|
|
1007
|
+
}
|
|
1008
|
+
return cleanup;
|
|
1009
|
+
};
|
|
1010
|
+
const api = {
|
|
1011
|
+
open,
|
|
1012
|
+
close,
|
|
1013
|
+
show,
|
|
1014
|
+
hide,
|
|
1015
|
+
toggle,
|
|
1016
|
+
update,
|
|
1017
|
+
bind,
|
|
1018
|
+
isOpen,
|
|
1019
|
+
entry: () => entry,
|
|
1020
|
+
props: () => ({ ...currentProps })
|
|
1021
|
+
};
|
|
1022
|
+
|
|
1023
|
+
return api;
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
|
|
1027
|
+
UI.ContextMenu = (...args) => {
|
|
1028
|
+
const { props, children } = JSswift.uiNormalizeArgs(args);
|
|
1029
|
+
let currentProps = { ...props };
|
|
1030
|
+
let lastPoint = null;
|
|
1031
|
+
let lastAnchor = null;
|
|
1032
|
+
let lastTriggerEl = null;
|
|
1033
|
+
|
|
1034
|
+
const isPlainObject = (value) => value && typeof value === "object" && !value.nodeType && !Array.isArray(value) && !(value instanceof Function);
|
|
1035
|
+
const isAnchorLike = (value) => !!value && typeof value === "object" && (value.nodeType === 1 || typeof value.getBoundingClientRect === "function");
|
|
1036
|
+
const isEventLike = (value) => !!value && typeof value === "object" && ("clientX" in value || "type" in value || "target" in value || "currentTarget" in value);
|
|
1037
|
+
const isPointLike = (value) => !!value && Number.isFinite(Number(value.x)) && Number.isFinite(Number(value.y));
|
|
1038
|
+
const getOptions = () => currentProps;
|
|
1039
|
+
const normalizePoint = (x, y) => {
|
|
1040
|
+
const px = Number(x);
|
|
1041
|
+
const py = Number(y);
|
|
1042
|
+
if (!Number.isFinite(px) || !Number.isFinite(py)) return null;
|
|
1043
|
+
return { x: px, y: py };
|
|
1044
|
+
};
|
|
1045
|
+
const createVirtualAnchor = (point) => point ? {
|
|
1046
|
+
_jssContextPoint: point,
|
|
1047
|
+
getBoundingClientRect: () => ({
|
|
1048
|
+
top: point.y,
|
|
1049
|
+
bottom: point.y,
|
|
1050
|
+
left: point.x,
|
|
1051
|
+
right: point.x,
|
|
1052
|
+
width: 0,
|
|
1053
|
+
height: 0
|
|
1054
|
+
})
|
|
1055
|
+
} : null;
|
|
1056
|
+
const getElementPoint = (el) => {
|
|
1057
|
+
if (!isAnchorLike(el)) return null;
|
|
1058
|
+
const rect = el.getBoundingClientRect();
|
|
1059
|
+
return normalizePoint(rect.left + Math.min(rect.width / 2, 24), rect.top + Math.min(rect.height / 2, 24));
|
|
1060
|
+
};
|
|
1061
|
+
const extractAnchor = (value) => uiUnwrap(value?.anchorEl ?? value?.triggerEl ?? value?.target ?? value?.anchor ?? null);
|
|
1062
|
+
const getEventPoint = (event, fallbackEl = null) => {
|
|
1063
|
+
if (!event) return getElementPoint(fallbackEl || lastTriggerEl);
|
|
1064
|
+
const point = normalizePoint(event.clientX, event.clientY);
|
|
1065
|
+
if (point) return point;
|
|
1066
|
+
const anchor = extractAnchor({ anchorEl: fallbackEl || event.currentTarget || event.target || lastTriggerEl });
|
|
1067
|
+
return getElementPoint(anchor);
|
|
1068
|
+
};
|
|
1069
|
+
const stripContextKeys = (value = {}) => {
|
|
1070
|
+
if (!isPlainObject(value)) return {};
|
|
1071
|
+
const next = { ...value };
|
|
1072
|
+
delete next.event;
|
|
1073
|
+
delete next.point;
|
|
1074
|
+
delete next.x;
|
|
1075
|
+
delete next.y;
|
|
1076
|
+
return next;
|
|
1077
|
+
};
|
|
1078
|
+
const buildMenuProps = () => {
|
|
1079
|
+
const opts = getOptions();
|
|
1080
|
+
return {
|
|
1081
|
+
...opts,
|
|
1082
|
+
trigger: "manual",
|
|
1083
|
+
placement: opts.placement || "bottom-start",
|
|
1084
|
+
offsetX: opts.offsetX ?? 0,
|
|
1085
|
+
offsetY: opts.offsetY ?? opts.offset ?? 4,
|
|
1086
|
+
closeOnOutside: opts.closeOnOutside !== false,
|
|
1087
|
+
closeOnEsc: opts.closeOnEsc !== false,
|
|
1088
|
+
role: opts.role || "menu",
|
|
1089
|
+
ariaLabel: opts.ariaLabel || opts.title || "Context menu",
|
|
1090
|
+
class: uiClass(["jss-context-menu-shell", opts.class]),
|
|
1091
|
+
panelClass: uiClass(["jss-context-menu-panel", opts.panelClass])
|
|
1092
|
+
};
|
|
1093
|
+
};
|
|
1094
|
+
const menu = UI.Menu(buildMenuProps(), ...children);
|
|
1095
|
+
|
|
1096
|
+
const resolveOpenInput = (arg1 = null, arg2 = null, arg3 = null) => {
|
|
1097
|
+
let point = null;
|
|
1098
|
+
let anchor = null;
|
|
1099
|
+
let nextProps = null;
|
|
1100
|
+
|
|
1101
|
+
if (isEventLike(arg1)) {
|
|
1102
|
+
point = getEventPoint(arg1, lastTriggerEl);
|
|
1103
|
+
nextProps = isPlainObject(arg2) ? stripContextKeys(arg2) : null;
|
|
1104
|
+
} else if (typeof arg1 === "number" || typeof arg2 === "number") {
|
|
1105
|
+
point = normalizePoint(arg1, arg2);
|
|
1106
|
+
nextProps = isPlainObject(arg3) ? stripContextKeys(arg3) : null;
|
|
1107
|
+
} else if (isPointLike(arg1)) {
|
|
1108
|
+
point = normalizePoint(arg1.x, arg1.y);
|
|
1109
|
+
nextProps = isPlainObject(arg2) ? stripContextKeys(arg2) : null;
|
|
1110
|
+
} else if (isAnchorLike(arg1)) {
|
|
1111
|
+
anchor = arg1;
|
|
1112
|
+
nextProps = isPlainObject(arg2) ? stripContextKeys(arg2) : null;
|
|
1113
|
+
} else if (isPlainObject(arg1)) {
|
|
1114
|
+
const rawPoint = isPointLike(arg1.point) ? arg1.point : (isPointLike(arg1) ? arg1 : null);
|
|
1115
|
+
const rawAnchor = extractAnchor(arg1);
|
|
1116
|
+
const rawEvent = arg1.event;
|
|
1117
|
+
point = rawPoint ? normalizePoint(rawPoint.x, rawPoint.y) : getEventPoint(rawEvent, rawAnchor || lastTriggerEl);
|
|
1118
|
+
anchor = rawAnchor && isAnchorLike(rawAnchor) ? rawAnchor : null;
|
|
1119
|
+
nextProps = {
|
|
1120
|
+
...stripContextKeys(arg1),
|
|
1121
|
+
...(isPlainObject(arg2) ? stripContextKeys(arg2) : {})
|
|
1122
|
+
};
|
|
1123
|
+
} else if (lastPoint) {
|
|
1124
|
+
point = { ...lastPoint };
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
return { point, anchor, nextProps };
|
|
1128
|
+
};
|
|
1129
|
+
const rememberAnchor = (anchor, point = null) => {
|
|
1130
|
+
lastAnchor = anchor || lastAnchor;
|
|
1131
|
+
lastPoint = point || (anchor && anchor._jssContextPoint) || lastPoint;
|
|
1132
|
+
};
|
|
1133
|
+
const mergeProps = (nextProps = null) => {
|
|
1134
|
+
if (isPlainObject(nextProps) && Object.keys(nextProps).length) {
|
|
1135
|
+
currentProps = { ...currentProps, ...nextProps };
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
const getOpenAnchor = (inputAnchor = null, inputPoint = null) => {
|
|
1139
|
+
if (inputPoint) return createVirtualAnchor(inputPoint);
|
|
1140
|
+
if (inputAnchor && isAnchorLike(inputAnchor)) return inputAnchor;
|
|
1141
|
+
const configAnchor = extractAnchor(getOptions());
|
|
1142
|
+
if (isAnchorLike(configAnchor)) return configAnchor;
|
|
1143
|
+
if (lastPoint) return createVirtualAnchor(lastPoint);
|
|
1144
|
+
if (isAnchorLike(lastAnchor)) return lastAnchor;
|
|
1145
|
+
if (isAnchorLike(lastTriggerEl)) return lastTriggerEl;
|
|
1146
|
+
return null;
|
|
1147
|
+
};
|
|
1148
|
+
const open = (arg1 = null, arg2 = null, arg3 = null) => {
|
|
1149
|
+
const { point, anchor, nextProps } = resolveOpenInput(arg1, arg2, arg3);
|
|
1150
|
+
mergeProps(nextProps);
|
|
1151
|
+
const openAnchor = getOpenAnchor(anchor, point);
|
|
1152
|
+
if (!openAnchor) return null;
|
|
1153
|
+
rememberAnchor(openAnchor, point);
|
|
1154
|
+
return menu.open(openAnchor, buildMenuProps());
|
|
1155
|
+
};
|
|
1156
|
+
const openAt = (x, y, nextProps = null) => open(x, y, nextProps);
|
|
1157
|
+
const openFromEvent = (event, nextProps = null) => open(event, nextProps);
|
|
1158
|
+
const close = () => {
|
|
1159
|
+
menu.close();
|
|
1160
|
+
return api;
|
|
1161
|
+
};
|
|
1162
|
+
const hide = (immediate = true) => {
|
|
1163
|
+
if (immediate === false) menu.hide(false);
|
|
1164
|
+
else menu.close();
|
|
1165
|
+
return api;
|
|
1166
|
+
};
|
|
1167
|
+
const show = (arg1 = null, arg2 = null, arg3 = null) => open(arg1, arg2, arg3);
|
|
1168
|
+
const toggle = (arg1 = null, arg2 = null, arg3 = null) => menu.isOpen() ? (close(), null) : open(arg1, arg2, arg3);
|
|
1169
|
+
const update = (nextProps = {}) => {
|
|
1170
|
+
mergeProps(stripContextKeys(nextProps));
|
|
1171
|
+
menu.update(buildMenuProps());
|
|
1172
|
+
return api;
|
|
1173
|
+
};
|
|
1174
|
+
const bind = (el, bindProps = null) => {
|
|
1175
|
+
if (!el) return () => { };
|
|
1176
|
+
const scopedProps = isPlainObject(bindProps) ? stripContextKeys(bindProps) : null;
|
|
1177
|
+
const onContextMenu = (e) => {
|
|
1178
|
+
lastTriggerEl = el;
|
|
1179
|
+
e.preventDefault();
|
|
1180
|
+
const result = getOptions().onTrigger?.(e, { element: el, open, close, update });
|
|
1181
|
+
if (result === false || uiUnwrap(getOptions().disabled)) return;
|
|
1182
|
+
open({ event: e, anchorEl: el }, scopedProps);
|
|
1183
|
+
};
|
|
1184
|
+
const onKeyboardOpen = (e) => {
|
|
1185
|
+
if (e.key !== "ContextMenu" && !(e.shiftKey && e.key === "F10")) return;
|
|
1186
|
+
lastTriggerEl = el;
|
|
1187
|
+
e.preventDefault();
|
|
1188
|
+
const result = getOptions().onTrigger?.(e, { element: el, open, close, update });
|
|
1189
|
+
if (result === false || uiUnwrap(getOptions().disabled)) return;
|
|
1190
|
+
open({ event: e, anchorEl: el }, scopedProps);
|
|
1191
|
+
};
|
|
1192
|
+
el.addEventListener("contextmenu", onContextMenu);
|
|
1193
|
+
el.addEventListener("keydown", onKeyboardOpen);
|
|
1194
|
+
return () => {
|
|
1195
|
+
el.removeEventListener("contextmenu", onContextMenu);
|
|
1196
|
+
el.removeEventListener("keydown", onKeyboardOpen);
|
|
1197
|
+
};
|
|
1198
|
+
};
|
|
1199
|
+
const api = {
|
|
1200
|
+
open,
|
|
1201
|
+
openAt,
|
|
1202
|
+
openFromEvent,
|
|
1203
|
+
show,
|
|
1204
|
+
hide,
|
|
1205
|
+
close,
|
|
1206
|
+
toggle,
|
|
1207
|
+
update,
|
|
1208
|
+
bind,
|
|
1209
|
+
isOpen: menu.isOpen,
|
|
1210
|
+
entry: menu.entry,
|
|
1211
|
+
props: () => ({ ...currentProps })
|
|
1212
|
+
};
|
|
1213
|
+
|
|
1214
|
+
return api;
|
|
1215
|
+
};
|
|
1216
|
+
|
|
1217
|
+
})(JSswift);
|