@fluix-ui/vanilla 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/README.md +107 -2
- package/dist/index.cjs +246 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -3
- package/dist/index.d.ts +35 -3
- package/dist/index.global.js +959 -0
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +247 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -8,14 +8,105 @@ Vanilla JS adapter for Fluix UI components.
|
|
|
8
8
|
npm install @fluix-ui/vanilla @fluix-ui/css
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## How imports work in Vanilla JS
|
|
12
|
+
|
|
13
|
+
### Option A: npm + bundler (Vite/Webpack/Rollup)
|
|
14
|
+
|
|
15
|
+
Use standard ESM imports in your source files:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createToaster, fluix } from "@fluix-ui/vanilla";
|
|
19
|
+
import "@fluix-ui/css";
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Option B: browser without bundler (CDN + global)
|
|
23
|
+
|
|
24
|
+
The IIFE build exposes a global `window.Fluix`.
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fluix-ui/css/dist/fluix.css" />
|
|
28
|
+
<script src="https://cdn.jsdelivr.net/npm/@fluix-ui/vanilla/dist/index.global.js"></script>
|
|
29
|
+
<script>
|
|
30
|
+
const { createToaster, fluix, createNotch, createMenu } = window.Fluix;
|
|
31
|
+
createToaster({ position: "top-right" });
|
|
32
|
+
fluix.success({ title: "Loaded" });
|
|
33
|
+
</script>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick start (Toasts)
|
|
12
37
|
|
|
13
38
|
```ts
|
|
14
39
|
import { createToaster, fluix } from "@fluix-ui/vanilla";
|
|
15
40
|
import "@fluix-ui/css";
|
|
41
|
+
|
|
42
|
+
const toaster = createToaster({ position: "top-right", layout: "stack" });
|
|
43
|
+
|
|
44
|
+
document.querySelector("#save-btn")?.addEventListener("click", () => {
|
|
45
|
+
fluix.success({
|
|
46
|
+
title: "Saved",
|
|
47
|
+
description: "Your changes were stored.",
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Promise toasts
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
await fluix.promise(saveUser(), {
|
|
56
|
+
loading: { title: "Saving..." },
|
|
57
|
+
success: (data) => ({
|
|
58
|
+
title: "Saved",
|
|
59
|
+
description: `User ${data.name} updated`,
|
|
60
|
+
}),
|
|
61
|
+
error: (err) => ({
|
|
62
|
+
title: "Failed",
|
|
63
|
+
description: err instanceof Error ? err.message : "Unexpected error",
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
16
66
|
```
|
|
17
67
|
|
|
18
|
-
|
|
68
|
+
## Notch
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { createNotch } from "@fluix-ui/vanilla";
|
|
72
|
+
|
|
73
|
+
const container = document.querySelector("#notch-root");
|
|
74
|
+
if (!container) throw new Error("Missing #notch-root");
|
|
75
|
+
|
|
76
|
+
const notch = createNotch(container, {
|
|
77
|
+
position: "top-center",
|
|
78
|
+
trigger: "click",
|
|
79
|
+
theme: "dark",
|
|
80
|
+
pill: "Now",
|
|
81
|
+
content: "Now playing: Fluix Theme",
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
notch.open();
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Menu
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { createMenu } from "@fluix-ui/vanilla";
|
|
91
|
+
|
|
92
|
+
const container = document.querySelector("#menu-root");
|
|
93
|
+
if (!container) throw new Error("Missing #menu-root");
|
|
94
|
+
|
|
95
|
+
const menu = createMenu(container, {
|
|
96
|
+
activeId: "home",
|
|
97
|
+
orientation: "horizontal",
|
|
98
|
+
variant: "pill",
|
|
99
|
+
theme: "dark",
|
|
100
|
+
fill: "#6366f1", // custom indicator color
|
|
101
|
+
items: [
|
|
102
|
+
{ id: "home", label: "Home" },
|
|
103
|
+
{ id: "projects", label: "Projects" },
|
|
104
|
+
{ id: "settings", label: "Settings" },
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Theming
|
|
19
110
|
|
|
20
111
|
Pass any theme name — themes are pure CSS. See `@fluix-ui/css` for details.
|
|
21
112
|
|
|
@@ -23,6 +114,20 @@ Pass any theme name — themes are pure CSS. See `@fluix-ui/css` for details.
|
|
|
23
114
|
fluix.success({ title: "Done", theme: "midnight" });
|
|
24
115
|
```
|
|
25
116
|
|
|
117
|
+
## Exports
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { createToaster, createNotch, createMenu, fluix } from "@fluix-ui/vanilla";
|
|
121
|
+
import type {
|
|
122
|
+
NotchOptions,
|
|
123
|
+
MenuOptions,
|
|
124
|
+
MenuInstance,
|
|
125
|
+
MenuItemConfig,
|
|
126
|
+
FluixToastOptions,
|
|
127
|
+
FluixToasterConfig,
|
|
128
|
+
} from "@fluix-ui/vanilla";
|
|
129
|
+
```
|
|
130
|
+
|
|
26
131
|
## Docs
|
|
27
132
|
|
|
28
133
|
- Official docs: https://fluix.ivanlopezdev.es
|
package/dist/index.cjs
CHANGED
|
@@ -1236,11 +1236,257 @@ function createNotch(container, options) {
|
|
|
1236
1236
|
}
|
|
1237
1237
|
};
|
|
1238
1238
|
}
|
|
1239
|
+
var SVG_NS3 = "http://www.w3.org/2000/svg";
|
|
1240
|
+
var GOO_MATRIX = "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10";
|
|
1241
|
+
function applyAttrs3(el, attrs) {
|
|
1242
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
1243
|
+
el.setAttribute(key, value);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
function createMenu(container, options) {
|
|
1247
|
+
let {
|
|
1248
|
+
orientation = core.MENU_DEFAULTS.orientation,
|
|
1249
|
+
variant = "pill",
|
|
1250
|
+
theme = "dark",
|
|
1251
|
+
activeId: controlledActiveId,
|
|
1252
|
+
onActiveChange,
|
|
1253
|
+
spring,
|
|
1254
|
+
roundness = core.MENU_DEFAULTS.roundness,
|
|
1255
|
+
blur: blurProp,
|
|
1256
|
+
fill,
|
|
1257
|
+
items
|
|
1258
|
+
} = options;
|
|
1259
|
+
const springConfig = () => spring ?? core.FLUIX_SPRING;
|
|
1260
|
+
const resolvedBlur = () => blurProp ?? Math.min(10, Math.max(6, roundness * 0.45));
|
|
1261
|
+
const machine = core.createMenuMachine({
|
|
1262
|
+
orientation,
|
|
1263
|
+
variant,
|
|
1264
|
+
spring,
|
|
1265
|
+
roundness,
|
|
1266
|
+
blur: blurProp,
|
|
1267
|
+
fill,
|
|
1268
|
+
initialActiveId: controlledActiveId ?? null
|
|
1269
|
+
});
|
|
1270
|
+
let snapshot = machine.store.getSnapshot();
|
|
1271
|
+
let lastActiveNotified = snapshot.activeId;
|
|
1272
|
+
const attrs = core.getMenuAttrs({ orientation, theme, variant });
|
|
1273
|
+
const filterId = `fluix-menu-goo-${Math.random().toString(36).slice(2, 8)}`;
|
|
1274
|
+
const isTab = variant === "tab";
|
|
1275
|
+
const navEl = document.createElement("nav");
|
|
1276
|
+
applyAttrs3(navEl, attrs.root);
|
|
1277
|
+
navEl.setAttribute("aria-label", "Fluix menu");
|
|
1278
|
+
const canvasDiv = document.createElement("div");
|
|
1279
|
+
applyAttrs3(canvasDiv, attrs.canvas);
|
|
1280
|
+
const svg = document.createElementNS(SVG_NS3, "svg");
|
|
1281
|
+
svg.setAttribute("xmlns", SVG_NS3);
|
|
1282
|
+
svg.setAttribute("width", "1");
|
|
1283
|
+
svg.setAttribute("height", "1");
|
|
1284
|
+
svg.setAttribute("viewBox", "0 0 1 1");
|
|
1285
|
+
svg.setAttribute("aria-hidden", "true");
|
|
1286
|
+
let indicatorEl;
|
|
1287
|
+
if (isTab) {
|
|
1288
|
+
indicatorEl = document.createElementNS(SVG_NS3, "path");
|
|
1289
|
+
applyAttrs3(indicatorEl, attrs.indicator);
|
|
1290
|
+
indicatorEl.setAttribute("d", "");
|
|
1291
|
+
indicatorEl.setAttribute("opacity", "0");
|
|
1292
|
+
indicatorEl.style.fill = fill ?? "var(--fluix-menu-indicator)";
|
|
1293
|
+
svg.appendChild(indicatorEl);
|
|
1294
|
+
} else {
|
|
1295
|
+
const defs = document.createElementNS(SVG_NS3, "defs");
|
|
1296
|
+
const filter = document.createElementNS(SVG_NS3, "filter");
|
|
1297
|
+
filter.setAttribute("id", filterId);
|
|
1298
|
+
filter.setAttribute("x", "-20%");
|
|
1299
|
+
filter.setAttribute("y", "-20%");
|
|
1300
|
+
filter.setAttribute("width", "140%");
|
|
1301
|
+
filter.setAttribute("height", "140%");
|
|
1302
|
+
filter.setAttribute("color-interpolation-filters", "sRGB");
|
|
1303
|
+
const feBlur = document.createElementNS(SVG_NS3, "feGaussianBlur");
|
|
1304
|
+
feBlur.setAttribute("in", "SourceGraphic");
|
|
1305
|
+
feBlur.setAttribute("stdDeviation", String(resolvedBlur()));
|
|
1306
|
+
feBlur.setAttribute("result", "blur");
|
|
1307
|
+
const feCM = document.createElementNS(SVG_NS3, "feColorMatrix");
|
|
1308
|
+
feCM.setAttribute("in", "blur");
|
|
1309
|
+
feCM.setAttribute("type", "matrix");
|
|
1310
|
+
feCM.setAttribute("values", GOO_MATRIX);
|
|
1311
|
+
feCM.setAttribute("result", "goo");
|
|
1312
|
+
const feComp = document.createElementNS(SVG_NS3, "feComposite");
|
|
1313
|
+
feComp.setAttribute("in", "SourceGraphic");
|
|
1314
|
+
feComp.setAttribute("in2", "goo");
|
|
1315
|
+
feComp.setAttribute("operator", "atop");
|
|
1316
|
+
filter.appendChild(feBlur);
|
|
1317
|
+
filter.appendChild(feCM);
|
|
1318
|
+
filter.appendChild(feComp);
|
|
1319
|
+
defs.appendChild(filter);
|
|
1320
|
+
svg.appendChild(defs);
|
|
1321
|
+
const gGroup = document.createElementNS(SVG_NS3, "g");
|
|
1322
|
+
gGroup.setAttribute("filter", `url(#${filterId})`);
|
|
1323
|
+
indicatorEl = document.createElementNS(SVG_NS3, "rect");
|
|
1324
|
+
applyAttrs3(indicatorEl, attrs.indicator);
|
|
1325
|
+
indicatorEl.setAttribute("x", "0");
|
|
1326
|
+
indicatorEl.setAttribute("y", "0");
|
|
1327
|
+
indicatorEl.setAttribute("width", "0");
|
|
1328
|
+
indicatorEl.setAttribute("height", "0");
|
|
1329
|
+
indicatorEl.setAttribute("rx", "0");
|
|
1330
|
+
indicatorEl.setAttribute("ry", "0");
|
|
1331
|
+
indicatorEl.setAttribute("opacity", "0");
|
|
1332
|
+
indicatorEl.style.fill = fill ?? "var(--fluix-menu-indicator)";
|
|
1333
|
+
gGroup.appendChild(indicatorEl);
|
|
1334
|
+
svg.appendChild(gGroup);
|
|
1335
|
+
}
|
|
1336
|
+
canvasDiv.appendChild(svg);
|
|
1337
|
+
navEl.appendChild(canvasDiv);
|
|
1338
|
+
const listDiv = document.createElement("div");
|
|
1339
|
+
applyAttrs3(listDiv, attrs.list);
|
|
1340
|
+
const buttonMap = /* @__PURE__ */ new Map();
|
|
1341
|
+
function createItemButton(item) {
|
|
1342
|
+
const btn = document.createElement("button");
|
|
1343
|
+
btn.type = "button";
|
|
1344
|
+
const active = snapshot.activeId === item.id;
|
|
1345
|
+
const itemAttrs = attrs.item({ id: item.id, active, disabled: item.disabled });
|
|
1346
|
+
applyAttrs3(btn, itemAttrs);
|
|
1347
|
+
if (item.disabled) btn.disabled = true;
|
|
1348
|
+
btn.textContent = item.label;
|
|
1349
|
+
btn.addEventListener("click", () => {
|
|
1350
|
+
if (item.disabled) return;
|
|
1351
|
+
if (controlledActiveId === void 0) {
|
|
1352
|
+
machine.setActive(item.id);
|
|
1353
|
+
} else {
|
|
1354
|
+
onActiveChange?.(item.id);
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
buttonMap.set(item.id, btn);
|
|
1358
|
+
listDiv.appendChild(btn);
|
|
1359
|
+
}
|
|
1360
|
+
for (const item of items) {
|
|
1361
|
+
createItemButton(item);
|
|
1362
|
+
}
|
|
1363
|
+
navEl.appendChild(listDiv);
|
|
1364
|
+
container.appendChild(navEl);
|
|
1365
|
+
let size = { width: 0, height: 0 };
|
|
1366
|
+
let measureRaf = 0;
|
|
1367
|
+
const measure = () => {
|
|
1368
|
+
const rect = navEl.getBoundingClientRect();
|
|
1369
|
+
const w = Math.ceil(rect.width);
|
|
1370
|
+
const h = Math.ceil(rect.height);
|
|
1371
|
+
if (size.width !== w || size.height !== h) {
|
|
1372
|
+
size = { width: w, height: h };
|
|
1373
|
+
updateSvgSize();
|
|
1374
|
+
connection?.sync(false);
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
const resizeObs = new ResizeObserver(() => {
|
|
1378
|
+
cancelAnimationFrame(measureRaf);
|
|
1379
|
+
measureRaf = requestAnimationFrame(measure);
|
|
1380
|
+
});
|
|
1381
|
+
resizeObs.observe(navEl);
|
|
1382
|
+
function updateSvgSize() {
|
|
1383
|
+
const w = Math.max(1, size.width);
|
|
1384
|
+
const h = Math.max(1, size.height);
|
|
1385
|
+
svg.setAttribute("width", String(w));
|
|
1386
|
+
svg.setAttribute("height", String(h));
|
|
1387
|
+
svg.setAttribute("viewBox", `0 0 ${w} ${h}`);
|
|
1388
|
+
}
|
|
1389
|
+
let connection = core.connectMenu({
|
|
1390
|
+
root: navEl,
|
|
1391
|
+
indicator: indicatorEl,
|
|
1392
|
+
getActiveId: () => snapshot.activeId,
|
|
1393
|
+
onSelect(id) {
|
|
1394
|
+
if (controlledActiveId === void 0) {
|
|
1395
|
+
machine.setActive(id);
|
|
1396
|
+
} else {
|
|
1397
|
+
onActiveChange?.(id);
|
|
1398
|
+
}
|
|
1399
|
+
},
|
|
1400
|
+
spring: springConfig(),
|
|
1401
|
+
variant,
|
|
1402
|
+
orientation
|
|
1403
|
+
});
|
|
1404
|
+
requestAnimationFrame(() => {
|
|
1405
|
+
measure();
|
|
1406
|
+
connection.sync(false);
|
|
1407
|
+
});
|
|
1408
|
+
const unsubscribe = machine.store.subscribe(() => {
|
|
1409
|
+
const next = machine.store.getSnapshot();
|
|
1410
|
+
snapshot = next;
|
|
1411
|
+
for (const item of items) {
|
|
1412
|
+
const btn = buttonMap.get(item.id);
|
|
1413
|
+
if (btn) {
|
|
1414
|
+
const active = next.activeId === item.id;
|
|
1415
|
+
const itemAttrs = attrs.item({ id: item.id, active, disabled: item.disabled });
|
|
1416
|
+
applyAttrs3(btn, itemAttrs);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
if (next.activeId && lastActiveNotified !== next.activeId && onActiveChange) {
|
|
1420
|
+
onActiveChange(next.activeId);
|
|
1421
|
+
}
|
|
1422
|
+
lastActiveNotified = next.activeId;
|
|
1423
|
+
connection.sync(false);
|
|
1424
|
+
});
|
|
1425
|
+
return {
|
|
1426
|
+
setActive(id) {
|
|
1427
|
+
machine.setActive(id);
|
|
1428
|
+
},
|
|
1429
|
+
update(opts) {
|
|
1430
|
+
if (opts.orientation !== void 0) orientation = opts.orientation;
|
|
1431
|
+
if (opts.variant !== void 0) variant = opts.variant;
|
|
1432
|
+
if (opts.theme !== void 0) theme = opts.theme;
|
|
1433
|
+
if (opts.activeId !== void 0) controlledActiveId = opts.activeId;
|
|
1434
|
+
if (opts.onActiveChange !== void 0) onActiveChange = opts.onActiveChange;
|
|
1435
|
+
if (opts.spring !== void 0) spring = opts.spring;
|
|
1436
|
+
if (opts.roundness !== void 0) roundness = opts.roundness;
|
|
1437
|
+
if (opts.blur !== void 0) blurProp = opts.blur;
|
|
1438
|
+
if (opts.fill !== void 0) fill = opts.fill;
|
|
1439
|
+
machine.configure({ orientation, variant, spring, roundness, blur: blurProp, fill });
|
|
1440
|
+
if (controlledActiveId !== void 0) {
|
|
1441
|
+
machine.setActive(controlledActiveId ?? null);
|
|
1442
|
+
}
|
|
1443
|
+
const newAttrs = core.getMenuAttrs({ orientation, theme, variant });
|
|
1444
|
+
applyAttrs3(navEl, newAttrs.root);
|
|
1445
|
+
if (opts.items !== void 0) {
|
|
1446
|
+
items = opts.items;
|
|
1447
|
+
listDiv.innerHTML = "";
|
|
1448
|
+
buttonMap.clear();
|
|
1449
|
+
for (const item of items) {
|
|
1450
|
+
createItemButton(item);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
connection.destroy();
|
|
1454
|
+
connection = core.connectMenu({
|
|
1455
|
+
root: navEl,
|
|
1456
|
+
indicator: indicatorEl,
|
|
1457
|
+
getActiveId: () => snapshot.activeId,
|
|
1458
|
+
onSelect(id) {
|
|
1459
|
+
if (controlledActiveId === void 0) {
|
|
1460
|
+
machine.setActive(id);
|
|
1461
|
+
} else {
|
|
1462
|
+
onActiveChange?.(id);
|
|
1463
|
+
}
|
|
1464
|
+
},
|
|
1465
|
+
spring: springConfig(),
|
|
1466
|
+
variant,
|
|
1467
|
+
orientation
|
|
1468
|
+
});
|
|
1469
|
+
requestAnimationFrame(() => {
|
|
1470
|
+
measure();
|
|
1471
|
+
connection.sync(false);
|
|
1472
|
+
});
|
|
1473
|
+
},
|
|
1474
|
+
destroy() {
|
|
1475
|
+
unsubscribe();
|
|
1476
|
+
cancelAnimationFrame(measureRaf);
|
|
1477
|
+
resizeObs.disconnect();
|
|
1478
|
+
connection.destroy();
|
|
1479
|
+
machine.destroy();
|
|
1480
|
+
navEl.remove();
|
|
1481
|
+
}
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1239
1484
|
|
|
1240
1485
|
Object.defineProperty(exports, "fluix", {
|
|
1241
1486
|
enumerable: true,
|
|
1242
1487
|
get: function () { return core.fluix; }
|
|
1243
1488
|
});
|
|
1489
|
+
exports.createMenu = createMenu;
|
|
1244
1490
|
exports.createNotch = createNotch;
|
|
1245
1491
|
exports.createToaster = createToaster;
|
|
1246
1492
|
//# sourceMappingURL=index.cjs.map
|