@enigmax/primitives 0.17.0 → 0.19.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/dist/button-CaXaqG_K.d.ts +63 -0
- package/dist/chunk-2QFTRNAZ.js +88 -0
- package/dist/chunk-6BGBYUSZ.js +114 -0
- package/dist/chunk-AU3H5WIY.js +107 -0
- package/dist/{chunk-UZFEEFMF.js → chunk-D5A2ZMAG.js} +34 -1
- package/dist/chunk-DTWZDONY.js +99 -0
- package/dist/chunk-F25CGNQC.js +19 -0
- package/dist/chunk-FWVWX67R.js +462 -0
- package/dist/chunk-HC2ME5PU.js +168 -0
- package/dist/chunk-HS3X3XCW.js +43 -0
- package/dist/chunk-IXVMRVD4.js +96 -0
- package/dist/chunk-KJINGUQN.js +188 -0
- package/dist/chunk-MMQPZGSU.js +161 -0
- package/dist/chunk-OCMI7R6H.js +79 -0
- package/dist/chunk-QQFNAKMY.js +47 -0
- package/dist/chunk-QYMUIW5I.js +28 -0
- package/dist/chunk-R4ZAEE7V.js +249 -0
- package/dist/chunk-S653GLSF.js +17 -0
- package/dist/chunk-SNYUBXWQ.js +149 -0
- package/dist/chunk-U3V4EHOB.js +41 -0
- package/dist/chunk-UOSSNUSC.js +309 -0
- package/dist/chunk-XNNQRA35.js +31 -0
- package/dist/chunk-XQHCZAPJ.js +102 -0
- package/dist/chunk-ZCUFYBPB.js +154 -0
- package/dist/chunk-ZWR2EXHQ.js +55 -0
- package/dist/flags-BBJc9unY.d.ts +133 -0
- package/dist/index-dTdAbOWl.d.ts +144 -0
- package/dist/index.d.ts +18 -603
- package/dist/index.js +11 -2
- package/dist/input-BwXjFenq.d.ts +77 -0
- package/dist/marquee-CJ3Uwy3E.d.ts +81 -0
- package/dist/network-D2LsBG_k.d.ts +39 -0
- package/dist/next/index.d.ts +22 -4
- package/dist/next/index.js +25 -4
- package/dist/notifications-BpVV6sel.d.ts +70 -0
- package/dist/palette-D7iuQh_T.d.ts +86 -0
- package/dist/password-3DRQYAYQ.js +2 -0
- package/dist/password-C8lG4Zm9.d.ts +71 -0
- package/dist/password-FB2CUEKJ.js +1 -0
- package/dist/react/button.d.ts +75 -0
- package/dist/react/button.js +4 -0
- package/dist/react/flag.d.ts +37 -0
- package/dist/react/flag.js +3 -0
- package/dist/react/index.d.ts +29 -312
- package/dist/react/index.js +24 -3
- package/dist/react/input.d.ts +4 -0
- package/dist/react/input.js +3 -0
- package/dist/react/marquee.d.ts +44 -0
- package/dist/react/marquee.js +3 -0
- package/dist/react/network.d.ts +20 -0
- package/dist/react/network.js +3 -0
- package/dist/react/notifications.d.ts +17 -0
- package/dist/react/notifications.js +3 -0
- package/dist/react/palette.d.ts +231 -0
- package/dist/react/palette.js +5 -0
- package/dist/react/relative-time.d.ts +21 -0
- package/dist/react/relative-time.js +3 -0
- package/dist/react/search.d.ts +30 -0
- package/dist/react/search.js +3 -0
- package/dist/react/slot.d.ts +47 -0
- package/dist/react/slot.js +2 -0
- package/dist/react/toast.d.ts +40 -0
- package/dist/react/toast.js +4 -0
- package/dist/react-router/index.d.ts +22 -4
- package/dist/react-router/index.js +25 -4
- package/dist/relative-time-YpRTG7YH.d.ts +106 -0
- package/dist/search/index.d.ts +2 -2
- package/dist/search/index.js +1 -1
- package/dist/{search-CsO3L1Lw.d.ts → search-DXxY8SEH.d.ts} +15 -1
- package/dist/search-UQEXAPQB.js +50 -0
- package/package.json +54 -3
- package/recipes/input/styles.css +60 -20
- package/recipes/palette/styles.css +243 -0
- package/registry.json +186 -18
- package/src/core/flags.ts +88 -42
- package/src/core/input-icons.ts +34 -0
- package/src/core/input.ts +5 -21
- package/src/core/palette.ts +0 -0
- package/src/core/search.ts +60 -0
- package/src/index.ts +19 -3
- package/src/react/button.tsx +70 -4
- package/src/react/flag.tsx +18 -6
- package/src/react/index.ts +50 -5
- package/src/react/input/icon.tsx +17 -0
- package/src/react/input/index.tsx +307 -0
- package/src/react/input/password.tsx +174 -0
- package/src/react/input/search.tsx +82 -0
- package/src/react/input/types.ts +146 -0
- package/src/react/input/write-value.ts +18 -0
- package/src/react/palette/context.ts +60 -0
- package/src/react/palette/index.tsx +66 -0
- package/src/react/palette/root.tsx +661 -0
- package/src/react/slot.tsx +91 -0
- package/src/react/use-button.ts +3 -1
- package/dist/chunk-4GLYRF4B.js +0 -1259
- package/dist/chunk-JFNND6P4.js +0 -749
- package/src/react/input.tsx +0 -429
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// src/core/relative-time.ts
|
|
2
|
+
var ZONED = /([zZ]|[+-]\d{2}:?\d{2})$/;
|
|
3
|
+
var DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
4
|
+
var HAS_TIME = /\d{2}:\d{2}/;
|
|
5
|
+
var DURATION = /^([+-])?P(?:(\d+(?:\.\d+)?)Y)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)W)?(?:(\d+(?:\.\d+)?)D)?(?:T(?:(\d+(?:\.\d+)?)H)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)S)?)?$/;
|
|
6
|
+
var SECOND = 1e3;
|
|
7
|
+
var MINUTE = 60 * SECOND;
|
|
8
|
+
var HOUR = 60 * MINUTE;
|
|
9
|
+
var DAY = 24 * HOUR;
|
|
10
|
+
var WEEK = 7 * DAY;
|
|
11
|
+
var MONTH = 30 * DAY;
|
|
12
|
+
var YEAR = 365 * DAY;
|
|
13
|
+
function ensureZone(text) {
|
|
14
|
+
const trimmed = text.trim();
|
|
15
|
+
if (!trimmed || DATE_ONLY.test(trimmed) || ZONED.test(trimmed)) return trimmed;
|
|
16
|
+
const iso = trimmed.replace(" ", "T");
|
|
17
|
+
return HAS_TIME.test(iso) ? `${iso}Z` : iso;
|
|
18
|
+
}
|
|
19
|
+
function normalizeDate(value) {
|
|
20
|
+
if (value == null) return null;
|
|
21
|
+
if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
|
|
22
|
+
if (typeof value === "number") return Number.isFinite(value) ? new Date(value) : null;
|
|
23
|
+
const date = new Date(ensureZone(value));
|
|
24
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
25
|
+
}
|
|
26
|
+
function parseDuration(value) {
|
|
27
|
+
const match = DURATION.exec(value.trim());
|
|
28
|
+
if (!match) return 0;
|
|
29
|
+
const [, sign, years, months, weeks, days, hours, minutes, seconds] = match;
|
|
30
|
+
const n = (part) => part ? Number(part) : 0;
|
|
31
|
+
const total = n(years) * YEAR + n(months) * MONTH + n(weeks) * WEEK + n(days) * DAY + n(hours) * HOUR + n(minutes) * MINUTE + n(seconds) * SECOND;
|
|
32
|
+
return sign === "-" ? -total : total;
|
|
33
|
+
}
|
|
34
|
+
function dateTimeOptions(options) {
|
|
35
|
+
const { second, minute, hour, weekday, day, month, year, timeZone, timeZoneName } = options;
|
|
36
|
+
const parts = { timeZone, timeZoneName, second, minute, hour, weekday, day, month, year };
|
|
37
|
+
for (const key of Object.keys(parts)) {
|
|
38
|
+
if (parts[key] === void 0) delete parts[key];
|
|
39
|
+
}
|
|
40
|
+
return parts;
|
|
41
|
+
}
|
|
42
|
+
function format(date, locale, options) {
|
|
43
|
+
try {
|
|
44
|
+
return new Intl.DateTimeFormat(locale, options).format(date);
|
|
45
|
+
} catch {
|
|
46
|
+
return date.toISOString();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function relativeTimeView(value, options = {}, now = /* @__PURE__ */ new Date()) {
|
|
50
|
+
const date = normalizeDate(value);
|
|
51
|
+
if (!date) return { date: null, iso: "", label: "", exact: "", beyondThreshold: false, absoluteOnly: false };
|
|
52
|
+
const { locale, threshold = "P30D", prefix = "on", numericBeyondThreshold = false } = options;
|
|
53
|
+
const age = Math.abs(date.getTime() - now.getTime());
|
|
54
|
+
const beyondThreshold = age > parseDuration(threshold);
|
|
55
|
+
const absoluteOnly = numericBeyondThreshold && beyondThreshold;
|
|
56
|
+
const parts = absoluteOnly ? { day: "numeric", month: "numeric", year: "numeric", timeZone: options.timeZone } : { day: "numeric", month: "short", year: "numeric", ...dateTimeOptions(options) };
|
|
57
|
+
const label = format(date, locale, parts);
|
|
58
|
+
return {
|
|
59
|
+
date,
|
|
60
|
+
iso: date.toISOString(),
|
|
61
|
+
label: !absoluteOnly && prefix ? `${prefix} ${label}` : label,
|
|
62
|
+
exact: format(date, locale, { dateStyle: "full", timeStyle: "long", timeZone: options.timeZone }),
|
|
63
|
+
beyondThreshold,
|
|
64
|
+
absoluteOnly
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function relativeTimeAttributes(view, options = {}) {
|
|
68
|
+
const attributes = {
|
|
69
|
+
datetime: view.iso,
|
|
70
|
+
format: options.format,
|
|
71
|
+
tense: options.tense,
|
|
72
|
+
precision: options.precision,
|
|
73
|
+
threshold: options.threshold,
|
|
74
|
+
prefix: options.prefix,
|
|
75
|
+
"format-style": options.formatStyle,
|
|
76
|
+
second: options.second,
|
|
77
|
+
minute: options.minute,
|
|
78
|
+
hour: options.hour,
|
|
79
|
+
weekday: options.weekday,
|
|
80
|
+
day: options.day,
|
|
81
|
+
month: options.month,
|
|
82
|
+
year: options.year,
|
|
83
|
+
lang: options.locale,
|
|
84
|
+
"time-zone": options.timeZone,
|
|
85
|
+
"time-zone-name": options.timeZoneName,
|
|
86
|
+
// Boolean attributes are read by presence: "false" would still be true.
|
|
87
|
+
"no-title": options.noTitle ? "" : void 0
|
|
88
|
+
};
|
|
89
|
+
const out = {};
|
|
90
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
91
|
+
if (value !== void 0) out[key] = value;
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { ensureZone, normalizeDate, parseDuration, relativeTimeAttributes, relativeTimeView };
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { iconMarkup, INPUT_ICON_PATHS } from './chunk-F25CGNQC.js';
|
|
2
|
+
|
|
3
|
+
// src/core/input.ts
|
|
4
|
+
var EYE = iconMarkup(INPUT_ICON_PATHS.eye);
|
|
5
|
+
var EYE_OFF = iconMarkup(INPUT_ICON_PATHS.eyeOff);
|
|
6
|
+
function resolve(value, state) {
|
|
7
|
+
return typeof value === "function" ? value(state) : value;
|
|
8
|
+
}
|
|
9
|
+
function paint(target, icon) {
|
|
10
|
+
if (typeof icon === "string") target.innerHTML = icon;
|
|
11
|
+
else {
|
|
12
|
+
target.replaceChildren(icon);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function createInput(input, options = {}) {
|
|
16
|
+
let opts = { ...options };
|
|
17
|
+
let revealed = false;
|
|
18
|
+
let destroyed = false;
|
|
19
|
+
let restoreTimer = null;
|
|
20
|
+
const owned = !opts.container;
|
|
21
|
+
const container = opts.container ?? document.createElement("span");
|
|
22
|
+
const buttons = /* @__PURE__ */ new Map();
|
|
23
|
+
function state() {
|
|
24
|
+
return { input, revealed, locked: input.disabled || input.readOnly };
|
|
25
|
+
}
|
|
26
|
+
function revealConfig() {
|
|
27
|
+
return typeof opts.reveal === "object" ? opts.reveal : {};
|
|
28
|
+
}
|
|
29
|
+
function revealAction() {
|
|
30
|
+
const config = revealConfig();
|
|
31
|
+
return {
|
|
32
|
+
name: "reveal",
|
|
33
|
+
label: (current) => current.revealed ? config.hideLabel ?? "Hide password" : config.showLabel ?? "Show password",
|
|
34
|
+
icon: (current) => current.revealed ? config.hideIcon ?? EYE_OFF : config.showIcon ?? EYE,
|
|
35
|
+
pressed: (current) => current.revealed,
|
|
36
|
+
onSelect: () => setRevealed(!revealed)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function isPasswordField() {
|
|
40
|
+
return input.type === "password" || revealed && input.dataset.enigmaInputReveal === "on";
|
|
41
|
+
}
|
|
42
|
+
function activeActions() {
|
|
43
|
+
const custom = opts.actions ?? [];
|
|
44
|
+
const wantsReveal = opts.reveal !== false && isPasswordField();
|
|
45
|
+
const builtIn = wantsReveal && !custom.some((action) => action.name === "reveal") ? [revealAction()] : [];
|
|
46
|
+
return [...builtIn, ...custom];
|
|
47
|
+
}
|
|
48
|
+
function setRevealed(next) {
|
|
49
|
+
if (!isPasswordField() && next) return;
|
|
50
|
+
const selection = readSelection();
|
|
51
|
+
revealed = next;
|
|
52
|
+
input.type = next ? "text" : "password";
|
|
53
|
+
input.dataset.enigmaInputReveal = next ? "on" : "off";
|
|
54
|
+
render();
|
|
55
|
+
if (selection) {
|
|
56
|
+
applySelection(selection);
|
|
57
|
+
if (restoreTimer !== null) clearTimeout(restoreTimer);
|
|
58
|
+
restoreTimer = setTimeout(() => {
|
|
59
|
+
restoreTimer = null;
|
|
60
|
+
if (!destroyed && document.activeElement === input) applySelection(selection);
|
|
61
|
+
}, 0);
|
|
62
|
+
}
|
|
63
|
+
opts.onRevealChange?.(next);
|
|
64
|
+
}
|
|
65
|
+
function readSelection() {
|
|
66
|
+
if (document.activeElement !== input) return null;
|
|
67
|
+
try {
|
|
68
|
+
const { selectionStart, selectionEnd } = input;
|
|
69
|
+
return selectionStart === null || selectionEnd === null ? null : [selectionStart, selectionEnd];
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function applySelection(selection) {
|
|
75
|
+
try {
|
|
76
|
+
input.setSelectionRange(selection[0], selection[1]);
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function render() {
|
|
81
|
+
if (destroyed) return;
|
|
82
|
+
const selection = readSelection();
|
|
83
|
+
const current = state();
|
|
84
|
+
const actions = activeActions();
|
|
85
|
+
const seen = /* @__PURE__ */ new Set();
|
|
86
|
+
for (const action of actions) {
|
|
87
|
+
seen.add(action.name);
|
|
88
|
+
const visible = action.visible ? action.visible(current) : true;
|
|
89
|
+
let button = buttons.get(action.name);
|
|
90
|
+
if (!button) {
|
|
91
|
+
button = document.createElement("button");
|
|
92
|
+
button.type = "button";
|
|
93
|
+
button.dataset.enigmaInputAction = action.name;
|
|
94
|
+
buttons.set(action.name, button);
|
|
95
|
+
container.append(button);
|
|
96
|
+
}
|
|
97
|
+
if (!visible) {
|
|
98
|
+
button.remove();
|
|
99
|
+
buttons.delete(action.name);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (!button.isConnected) container.append(button);
|
|
103
|
+
const label = resolve(action.label, current);
|
|
104
|
+
button.setAttribute("aria-label", label);
|
|
105
|
+
button.title = label;
|
|
106
|
+
button.disabled = current.locked;
|
|
107
|
+
if (action.pressed) button.setAttribute("aria-pressed", String(action.pressed(current)));
|
|
108
|
+
else button.removeAttribute("aria-pressed");
|
|
109
|
+
paint(button, resolve(action.icon, current));
|
|
110
|
+
}
|
|
111
|
+
for (const [name, button] of [...buttons]) {
|
|
112
|
+
if (seen.has(name)) continue;
|
|
113
|
+
button.remove();
|
|
114
|
+
buttons.delete(name);
|
|
115
|
+
}
|
|
116
|
+
container.hidden = buttons.size === 0;
|
|
117
|
+
if (selection) {
|
|
118
|
+
try {
|
|
119
|
+
input.setSelectionRange(selection[0], selection[1]);
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function onMouseDown(event) {
|
|
125
|
+
if (!event.target?.closest("[data-enigma-input-action]")) return;
|
|
126
|
+
event.preventDefault();
|
|
127
|
+
}
|
|
128
|
+
function onClick(event) {
|
|
129
|
+
const target = event.target?.closest("[data-enigma-input-action]");
|
|
130
|
+
if (!target) return;
|
|
131
|
+
const action = activeActions().find((candidate) => candidate.name === target.dataset.enigmaInputAction);
|
|
132
|
+
if (!action) return;
|
|
133
|
+
event.preventDefault();
|
|
134
|
+
action.onSelect(state());
|
|
135
|
+
}
|
|
136
|
+
const observer = typeof MutationObserver === "function" ? new MutationObserver(() => {
|
|
137
|
+
if (!isPasswordField() && revealed) revealed = false;
|
|
138
|
+
render();
|
|
139
|
+
}) : null;
|
|
140
|
+
function mount() {
|
|
141
|
+
input.dataset.enigmaInput = "";
|
|
142
|
+
container.dataset.enigmaInputActions = "";
|
|
143
|
+
container.dataset.position = opts.position ?? "end";
|
|
144
|
+
if (owned && !container.isConnected) {
|
|
145
|
+
if ((opts.position ?? "end") === "start") input.before(container);
|
|
146
|
+
else input.after(container);
|
|
147
|
+
}
|
|
148
|
+
container.addEventListener("mousedown", onMouseDown);
|
|
149
|
+
container.addEventListener("click", onClick);
|
|
150
|
+
observer?.observe(input, { attributes: true, attributeFilter: ["type", "disabled", "readonly"] });
|
|
151
|
+
render();
|
|
152
|
+
}
|
|
153
|
+
mount();
|
|
154
|
+
return {
|
|
155
|
+
get revealed() {
|
|
156
|
+
return revealed;
|
|
157
|
+
},
|
|
158
|
+
reveal(next) {
|
|
159
|
+
setRevealed(next ?? !revealed);
|
|
160
|
+
},
|
|
161
|
+
refresh: render,
|
|
162
|
+
update(next) {
|
|
163
|
+
const moved = next.position !== void 0 && next.position !== opts.position;
|
|
164
|
+
opts = { ...opts, ...next };
|
|
165
|
+
if (moved && owned) {
|
|
166
|
+
container.dataset.position = opts.position ?? "end";
|
|
167
|
+
if (opts.position === "start") input.before(container);
|
|
168
|
+
else input.after(container);
|
|
169
|
+
}
|
|
170
|
+
render();
|
|
171
|
+
},
|
|
172
|
+
destroy() {
|
|
173
|
+
destroyed = true;
|
|
174
|
+
if (restoreTimer !== null) clearTimeout(restoreTimer);
|
|
175
|
+
observer?.disconnect();
|
|
176
|
+
container.removeEventListener("mousedown", onMouseDown);
|
|
177
|
+
container.removeEventListener("click", onClick);
|
|
178
|
+
for (const button of buttons.values()) button.remove();
|
|
179
|
+
buttons.clear();
|
|
180
|
+
if (owned) container.remove();
|
|
181
|
+
delete input.dataset.enigmaInput;
|
|
182
|
+
delete input.dataset.enigmaInputReveal;
|
|
183
|
+
if (revealed) input.type = "password";
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export { createInput };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// src/core/button.ts
|
|
2
|
+
var TICK_MS = 100;
|
|
3
|
+
function store(cooldown) {
|
|
4
|
+
if (!cooldown.key || typeof window === "undefined") return null;
|
|
5
|
+
try {
|
|
6
|
+
const target = cooldown.storage === "local" ? window.localStorage : window.sessionStorage;
|
|
7
|
+
const probe = "__enigma_probe__";
|
|
8
|
+
target.setItem(probe, "1");
|
|
9
|
+
target.removeItem(probe);
|
|
10
|
+
return target;
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function normalize(cooldown) {
|
|
16
|
+
if (cooldown == null) return null;
|
|
17
|
+
return typeof cooldown === "number" ? { ms: cooldown } : cooldown;
|
|
18
|
+
}
|
|
19
|
+
function isTyping(target) {
|
|
20
|
+
const element = target;
|
|
21
|
+
if (!element) return false;
|
|
22
|
+
const tag = element.tagName;
|
|
23
|
+
return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || element.isContentEditable === true;
|
|
24
|
+
}
|
|
25
|
+
function createButton(options = {}) {
|
|
26
|
+
let opts = { ...options };
|
|
27
|
+
let loading = Boolean(opts.loading);
|
|
28
|
+
let readyAt = 0;
|
|
29
|
+
let timer = null;
|
|
30
|
+
let destroyed = false;
|
|
31
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
32
|
+
const storageKey = () => {
|
|
33
|
+
const cooldown = normalize(opts.cooldown);
|
|
34
|
+
return cooldown?.key ? `enigma:cooldown:${cooldown.key}` : null;
|
|
35
|
+
};
|
|
36
|
+
function restore() {
|
|
37
|
+
const cooldown = normalize(opts.cooldown);
|
|
38
|
+
if (!cooldown) return;
|
|
39
|
+
const target = store(cooldown);
|
|
40
|
+
const key = storageKey();
|
|
41
|
+
if (!target || !key) return;
|
|
42
|
+
const saved = Number(target.getItem(key));
|
|
43
|
+
if (Number.isFinite(saved) && saved > Date.now()) readyAt = saved;
|
|
44
|
+
}
|
|
45
|
+
function remaining() {
|
|
46
|
+
return Math.max(0, readyAt - Date.now());
|
|
47
|
+
}
|
|
48
|
+
function snapshot() {
|
|
49
|
+
const disabled = Boolean(opts.disabled);
|
|
50
|
+
const cooldown = remaining();
|
|
51
|
+
return {
|
|
52
|
+
element: opts.href ? "a" : "button",
|
|
53
|
+
available: !disabled && !loading && cooldown === 0,
|
|
54
|
+
loading,
|
|
55
|
+
disabled,
|
|
56
|
+
cooldown,
|
|
57
|
+
shortcut: opts.shortcut ?? null
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function emit() {
|
|
61
|
+
const state = snapshot();
|
|
62
|
+
opts.onChange?.(state);
|
|
63
|
+
for (const listener of listeners) listener(state);
|
|
64
|
+
}
|
|
65
|
+
function stopTicking() {
|
|
66
|
+
if (timer === null) return;
|
|
67
|
+
clearInterval(timer);
|
|
68
|
+
timer = null;
|
|
69
|
+
}
|
|
70
|
+
function startTicking() {
|
|
71
|
+
if (timer !== null || remaining() === 0) return;
|
|
72
|
+
timer = setInterval(() => {
|
|
73
|
+
if (remaining() > 0) {
|
|
74
|
+
emit();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
stopTicking();
|
|
78
|
+
emit();
|
|
79
|
+
}, TICK_MS);
|
|
80
|
+
}
|
|
81
|
+
function beginCooldown() {
|
|
82
|
+
const cooldown = normalize(opts.cooldown);
|
|
83
|
+
if (!cooldown || cooldown.ms <= 0) return;
|
|
84
|
+
readyAt = Date.now() + cooldown.ms;
|
|
85
|
+
const target = store(cooldown);
|
|
86
|
+
const key = storageKey();
|
|
87
|
+
if (target && key) {
|
|
88
|
+
try {
|
|
89
|
+
target.setItem(key, String(readyAt));
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
startTicking();
|
|
94
|
+
}
|
|
95
|
+
async function press(event) {
|
|
96
|
+
if (destroyed || !snapshot().available) return;
|
|
97
|
+
const result = opts.onPress?.(event);
|
|
98
|
+
if (result instanceof Promise) {
|
|
99
|
+
loading = true;
|
|
100
|
+
emit();
|
|
101
|
+
try {
|
|
102
|
+
await result;
|
|
103
|
+
} finally {
|
|
104
|
+
loading = false;
|
|
105
|
+
beginCooldown();
|
|
106
|
+
emit();
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
beginCooldown();
|
|
111
|
+
emit();
|
|
112
|
+
}
|
|
113
|
+
function onKeyDown(event) {
|
|
114
|
+
if (!opts.shortcut || isTyping(event.target)) return;
|
|
115
|
+
if (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey) return;
|
|
116
|
+
if (event.key.toLowerCase() !== opts.shortcut.toLowerCase()) return;
|
|
117
|
+
if (!snapshot().available) return;
|
|
118
|
+
event.preventDefault();
|
|
119
|
+
void press(event);
|
|
120
|
+
}
|
|
121
|
+
if (typeof window !== "undefined") {
|
|
122
|
+
restore();
|
|
123
|
+
startTicking();
|
|
124
|
+
window.addEventListener("keydown", onKeyDown);
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
get state() {
|
|
128
|
+
return snapshot();
|
|
129
|
+
},
|
|
130
|
+
press,
|
|
131
|
+
update(next) {
|
|
132
|
+
const hadCooldown = JSON.stringify(normalize(opts.cooldown));
|
|
133
|
+
opts = { ...opts, ...next };
|
|
134
|
+
if (next.loading !== void 0) loading = Boolean(next.loading);
|
|
135
|
+
if (JSON.stringify(normalize(opts.cooldown)) !== hadCooldown) restore();
|
|
136
|
+
emit();
|
|
137
|
+
},
|
|
138
|
+
reset() {
|
|
139
|
+
readyAt = 0;
|
|
140
|
+
stopTicking();
|
|
141
|
+
const cooldown = normalize(opts.cooldown);
|
|
142
|
+
const key = storageKey();
|
|
143
|
+
if (cooldown && key) store(cooldown)?.removeItem(key);
|
|
144
|
+
emit();
|
|
145
|
+
},
|
|
146
|
+
subscribe(listener) {
|
|
147
|
+
listeners.add(listener);
|
|
148
|
+
return () => {
|
|
149
|
+
listeners.delete(listener);
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
destroy() {
|
|
153
|
+
destroyed = true;
|
|
154
|
+
stopTicking();
|
|
155
|
+
listeners.clear();
|
|
156
|
+
if (typeof window !== "undefined") window.removeEventListener("keydown", onKeyDown);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export { createButton };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { relativeTimeView, relativeTimeAttributes } from './chunk-IXVMRVD4.js';
|
|
2
|
+
import { useEffect, useCallback, createElement } from 'react';
|
|
3
|
+
|
|
4
|
+
var loading = null;
|
|
5
|
+
function loadElement() {
|
|
6
|
+
if (loading || typeof window === "undefined") return;
|
|
7
|
+
loading = import('@github/relative-time-element').catch(() => {
|
|
8
|
+
return null;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function RelativeTime({
|
|
12
|
+
date,
|
|
13
|
+
fallback = null,
|
|
14
|
+
capitalize = false,
|
|
15
|
+
capitalizeFirst = true,
|
|
16
|
+
now,
|
|
17
|
+
format,
|
|
18
|
+
tense,
|
|
19
|
+
precision,
|
|
20
|
+
threshold,
|
|
21
|
+
prefix,
|
|
22
|
+
formatStyle,
|
|
23
|
+
locale,
|
|
24
|
+
timeZone,
|
|
25
|
+
second,
|
|
26
|
+
minute,
|
|
27
|
+
hour,
|
|
28
|
+
weekday,
|
|
29
|
+
day,
|
|
30
|
+
month,
|
|
31
|
+
year,
|
|
32
|
+
timeZoneName,
|
|
33
|
+
noTitle,
|
|
34
|
+
numericBeyondThreshold,
|
|
35
|
+
...rest
|
|
36
|
+
}) {
|
|
37
|
+
useEffect(loadElement, []);
|
|
38
|
+
const applyPrefix = useCallback((node) => {
|
|
39
|
+
if (!node) return;
|
|
40
|
+
if (prefix === void 0 || prefix === "") node.removeAttribute("prefix");
|
|
41
|
+
else node.setAttribute("prefix", prefix);
|
|
42
|
+
}, [prefix]);
|
|
43
|
+
const options = {
|
|
44
|
+
format,
|
|
45
|
+
tense,
|
|
46
|
+
precision,
|
|
47
|
+
threshold,
|
|
48
|
+
prefix,
|
|
49
|
+
formatStyle,
|
|
50
|
+
locale,
|
|
51
|
+
timeZone,
|
|
52
|
+
second,
|
|
53
|
+
minute,
|
|
54
|
+
hour,
|
|
55
|
+
weekday,
|
|
56
|
+
day,
|
|
57
|
+
month,
|
|
58
|
+
year,
|
|
59
|
+
timeZoneName,
|
|
60
|
+
noTitle,
|
|
61
|
+
numericBeyondThreshold
|
|
62
|
+
};
|
|
63
|
+
const view = relativeTimeView(date, options, now);
|
|
64
|
+
if (!view.date) return fallback;
|
|
65
|
+
const marks = {
|
|
66
|
+
"data-relative-time-capitalize": capitalize ? "true" : void 0,
|
|
67
|
+
"data-relative-time-capitalize-first-letter": capitalizeFirst ? "true" : void 0,
|
|
68
|
+
// The text differs between the server's instant and the browser's, which is the
|
|
69
|
+
// one hydration difference React has an escape hatch for rather than a bug.
|
|
70
|
+
suppressHydrationWarning: true
|
|
71
|
+
};
|
|
72
|
+
if (view.absoluteOnly) {
|
|
73
|
+
return createElement("time", { dateTime: view.iso, title: noTitle ? void 0 : view.exact, ...marks, ...rest }, view.label);
|
|
74
|
+
}
|
|
75
|
+
const { prefix: prefixAttribute, ...attributes } = relativeTimeAttributes(view, options);
|
|
76
|
+
return createElement("relative-time", { ...attributes, ...marks, ...rest, ref: applyPrefix }, view.label);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { RelativeTime };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createSearch } from './chunk-D5A2ZMAG.js';
|
|
2
|
+
import { useRef, useState, useMemo, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
function useSearch(options = {}) {
|
|
5
|
+
const inputRef = useRef(null);
|
|
6
|
+
const instanceRef = useRef(null);
|
|
7
|
+
const optionsRef = useRef(options);
|
|
8
|
+
optionsRef.current = options;
|
|
9
|
+
const [query, setQuery] = useState("");
|
|
10
|
+
const [results, setResults] = useState([]);
|
|
11
|
+
const instance = useMemo(() => {
|
|
12
|
+
const created = createSearch({
|
|
13
|
+
...optionsRef.current,
|
|
14
|
+
onResults: (next, nextQuery) => {
|
|
15
|
+
setResults(next);
|
|
16
|
+
setQuery(nextQuery);
|
|
17
|
+
optionsRef.current.onResults?.(next, nextQuery);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
instanceRef.current = created;
|
|
21
|
+
return created;
|
|
22
|
+
}, []);
|
|
23
|
+
useEffect(() => () => instance.destroy(), [instance]);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
instance.setItems(options.items ?? []);
|
|
26
|
+
}, [instance, options.items]);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const input = inputRef.current;
|
|
29
|
+
if (!input) return;
|
|
30
|
+
return instance.attach(input);
|
|
31
|
+
}, [instance]);
|
|
32
|
+
return {
|
|
33
|
+
inputRef,
|
|
34
|
+
query,
|
|
35
|
+
results,
|
|
36
|
+
search: (next) => instance.search(next),
|
|
37
|
+
searchNow: (next) => {
|
|
38
|
+
instance.searchNow(next);
|
|
39
|
+
},
|
|
40
|
+
clear: () => {
|
|
41
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
42
|
+
instance.searchNow("");
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { useSearch };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createNotifications } from './chunk-SNYUBXWQ.js';
|
|
2
|
+
import { useRef, useSyncExternalStore, useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
var defaultQueue = createNotifications();
|
|
5
|
+
function useNotifications(queue = defaultQueue) {
|
|
6
|
+
const snapshot = useRef(queue.items);
|
|
7
|
+
const items = useSyncExternalStore(
|
|
8
|
+
(listener) => queue.subscribe((next) => {
|
|
9
|
+
snapshot.current = next;
|
|
10
|
+
listener();
|
|
11
|
+
}),
|
|
12
|
+
() => snapshot.current,
|
|
13
|
+
() => snapshot.current
|
|
14
|
+
);
|
|
15
|
+
return useMemo(() => ({
|
|
16
|
+
items,
|
|
17
|
+
notify: queue.notify,
|
|
18
|
+
dismiss: queue.dismiss,
|
|
19
|
+
dismissAll: queue.dismissAll,
|
|
20
|
+
pause: queue.pause,
|
|
21
|
+
resume: queue.resume
|
|
22
|
+
}), [items, queue]);
|
|
23
|
+
}
|
|
24
|
+
function createNotificationQueue(options) {
|
|
25
|
+
return createNotifications(options);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { createNotificationQueue, defaultQueue, useNotifications };
|