@enigmax/primitives 0.16.0 → 0.18.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-F25CGNQC.js +19 -0
- package/dist/chunk-HC2ME5PU.js +168 -0
- package/dist/chunk-HS3X3XCW.js +43 -0
- package/dist/chunk-IIT6U7LM.js +249 -0
- package/dist/chunk-IXVMRVD4.js +96 -0
- package/dist/chunk-KJINGUQN.js +188 -0
- package/dist/chunk-LKA2UG4P.js +542 -0
- package/dist/chunk-MMQPZGSU.js +161 -0
- package/dist/chunk-OCMI7R6H.js +79 -0
- package/dist/chunk-QYMUIW5I.js +28 -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-WSB655JB.js +47 -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-D-ARvWpQ.d.ts +306 -0
- package/dist/index-DNXPtY9X.d.ts +144 -0
- package/dist/index.d.ts +16 -496
- package/dist/index.js +9 -1
- 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 +20 -3
- package/dist/next/index.js +23 -3
- package/dist/notifications-BpVV6sel.d.ts +70 -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 +28 -286
- package/dist/react/index.js +22 -2
- 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 +3 -0
- package/dist/react/palette.js +4 -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 +20 -3
- package/dist/react-router/index.js +23 -3
- package/dist/relative-time-YpRTG7YH.d.ts +106 -0
- package/dist/search-P4OHCRXD.js +50 -0
- package/package.json +53 -1
- package/registry.json +235 -16
- package/src/core/flags.ts +266 -0
- package/src/core/input-icons.ts +34 -0
- package/src/core/input.ts +5 -21
- package/src/core/palette.ts +0 -0
- package/src/index.ts +18 -0
- package/src/react/button.tsx +70 -4
- package/src/react/flag.tsx +70 -0
- package/src/react/index.ts +52 -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 +621 -0
- package/src/react/slot.tsx +91 -0
- package/src/react/use-button.ts +3 -1
- package/dist/chunk-53DQY6LP.js +0 -1164
- package/dist/chunk-U2KYYMBJ.js +0 -724
- package/src/react/input.tsx +0 -429
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { INPUT_ICON_PATHS } from './chunk-F25CGNQC.js';
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { lazy, forwardRef, useRef, useState, useId, useCallback, useEffect, Suspense } from 'react';
|
|
4
|
+
|
|
5
|
+
var ICON_PROPS = {
|
|
6
|
+
viewBox: "0 0 24 24",
|
|
7
|
+
width: "1em",
|
|
8
|
+
height: "1em",
|
|
9
|
+
fill: "none",
|
|
10
|
+
stroke: "currentColor",
|
|
11
|
+
strokeWidth: 2,
|
|
12
|
+
strokeLinecap: "round",
|
|
13
|
+
strokeLinejoin: "round",
|
|
14
|
+
"aria-hidden": true
|
|
15
|
+
};
|
|
16
|
+
function Icon({ paths }) {
|
|
17
|
+
return /* @__PURE__ */ jsx("svg", { ...ICON_PROPS, children: paths.map((path) => /* @__PURE__ */ jsx("path", { d: path }, path)) });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/react/input/write-value.ts
|
|
21
|
+
function writeValue(input, next) {
|
|
22
|
+
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
|
|
23
|
+
if (setter) setter.call(input, next);
|
|
24
|
+
else input.value = next;
|
|
25
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
26
|
+
}
|
|
27
|
+
var PasswordExtras = lazy(() => import('./password-3DRQYAYQ.js').then((module) => ({ default: module.PasswordExtras })));
|
|
28
|
+
var SearchExtras = lazy(() => import('./search-P4OHCRXD.js').then((module) => ({ default: module.SearchExtras })));
|
|
29
|
+
var Input = forwardRef(function Input2(props, forwardedRef) {
|
|
30
|
+
const {
|
|
31
|
+
reveal,
|
|
32
|
+
revealLabels,
|
|
33
|
+
generate = false,
|
|
34
|
+
generateLabel = "Generate a password",
|
|
35
|
+
revealOnGenerate = true,
|
|
36
|
+
copyOnGenerate = false,
|
|
37
|
+
onGenerate,
|
|
38
|
+
actions = [],
|
|
39
|
+
position = "end",
|
|
40
|
+
strength = false,
|
|
41
|
+
onStrengthChange,
|
|
42
|
+
breach,
|
|
43
|
+
breachDelay = 500,
|
|
44
|
+
onBreachChange,
|
|
45
|
+
items,
|
|
46
|
+
keys,
|
|
47
|
+
delay,
|
|
48
|
+
limit,
|
|
49
|
+
fuse,
|
|
50
|
+
fuseOptions,
|
|
51
|
+
matcher,
|
|
52
|
+
onResults,
|
|
53
|
+
renderResults,
|
|
54
|
+
clearable,
|
|
55
|
+
clearLabel = "Clear",
|
|
56
|
+
wrapperProps,
|
|
57
|
+
fieldProps,
|
|
58
|
+
classNames,
|
|
59
|
+
children,
|
|
60
|
+
type = "text",
|
|
61
|
+
onChange,
|
|
62
|
+
...rest
|
|
63
|
+
} = props;
|
|
64
|
+
const innerRef = useRef(null);
|
|
65
|
+
const [element, setElement] = useState(null);
|
|
66
|
+
const [revealed, setRevealed] = useState(false);
|
|
67
|
+
const describedBy = useId();
|
|
68
|
+
const isPassword = type === "password";
|
|
69
|
+
const isSearch = type === "search";
|
|
70
|
+
const showReveal = reveal ?? isPassword;
|
|
71
|
+
const showGenerate = generate !== false && isPassword;
|
|
72
|
+
const showClear = (clearable ?? isSearch) && isSearch;
|
|
73
|
+
const controlled = rest.value !== void 0;
|
|
74
|
+
const [ownValue, setOwnValue] = useState(String(rest.defaultValue ?? ""));
|
|
75
|
+
const value = controlled ? String(rest.value ?? "") : ownValue;
|
|
76
|
+
const handleChange = useCallback((event) => {
|
|
77
|
+
if (!controlled) setOwnValue(event.target.value);
|
|
78
|
+
onChange?.(event);
|
|
79
|
+
}, [controlled, onChange]);
|
|
80
|
+
const caret = useRef(null);
|
|
81
|
+
const toggleReveal = useCallback(() => {
|
|
82
|
+
const input = innerRef.current;
|
|
83
|
+
if (input && typeof document !== "undefined" && document.activeElement === input) {
|
|
84
|
+
try {
|
|
85
|
+
const { selectionStart, selectionEnd } = input;
|
|
86
|
+
caret.current = selectionStart === null || selectionEnd === null ? null : [selectionStart, selectionEnd];
|
|
87
|
+
} catch {
|
|
88
|
+
caret.current = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
setRevealed((current) => !current);
|
|
92
|
+
}, []);
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
const selection = caret.current;
|
|
95
|
+
if (!selection) return;
|
|
96
|
+
caret.current = null;
|
|
97
|
+
const restore = () => {
|
|
98
|
+
const input = innerRef.current;
|
|
99
|
+
if (!input || document.activeElement !== input) return;
|
|
100
|
+
try {
|
|
101
|
+
input.setSelectionRange(selection[0], selection[1]);
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
restore();
|
|
106
|
+
const timer = setTimeout(restore, 0);
|
|
107
|
+
return () => clearTimeout(timer);
|
|
108
|
+
}, [revealed]);
|
|
109
|
+
const handleGenerate = useCallback(async () => {
|
|
110
|
+
const input = innerRef.current;
|
|
111
|
+
if (!input) return;
|
|
112
|
+
const { generatePassword } = await import('./password-FB2CUEKJ.js');
|
|
113
|
+
const password = generatePassword(typeof generate === "object" ? generate : {});
|
|
114
|
+
writeValue(input, password);
|
|
115
|
+
if (revealOnGenerate) setRevealed(true);
|
|
116
|
+
if (copyOnGenerate) void navigator.clipboard?.writeText(password).catch(() => {
|
|
117
|
+
});
|
|
118
|
+
input.focus();
|
|
119
|
+
onGenerate?.(password);
|
|
120
|
+
}, [generate, revealOnGenerate, copyOnGenerate, onGenerate]);
|
|
121
|
+
const handleClear = useCallback(() => {
|
|
122
|
+
const input = innerRef.current;
|
|
123
|
+
if (!input) return;
|
|
124
|
+
writeValue(input, "");
|
|
125
|
+
input.focus();
|
|
126
|
+
}, []);
|
|
127
|
+
const [score, setScore] = useState(null);
|
|
128
|
+
const [breached, setBreached] = useState(false);
|
|
129
|
+
const handleBreach = useCallback((state) => {
|
|
130
|
+
setBreached(state.status === "breached");
|
|
131
|
+
onBreachChange?.(state);
|
|
132
|
+
}, [onBreachChange]);
|
|
133
|
+
const builtIn = [];
|
|
134
|
+
if (showGenerate) {
|
|
135
|
+
builtIn.push({
|
|
136
|
+
name: "generate",
|
|
137
|
+
label: generateLabel,
|
|
138
|
+
icon: /* @__PURE__ */ jsx(Icon, { paths: INPUT_ICON_PATHS.generate }),
|
|
139
|
+
onSelect: () => {
|
|
140
|
+
void handleGenerate();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
if (showReveal) {
|
|
145
|
+
builtIn.push({
|
|
146
|
+
name: "reveal",
|
|
147
|
+
label: revealed ? revealLabels?.hide ?? "Hide password" : revealLabels?.show ?? "Show password",
|
|
148
|
+
icon: /* @__PURE__ */ jsx(Icon, { paths: revealed ? INPUT_ICON_PATHS.eyeOff : INPUT_ICON_PATHS.eye }),
|
|
149
|
+
pressed: revealed,
|
|
150
|
+
onSelect: toggleReveal
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (showClear) {
|
|
154
|
+
builtIn.push({
|
|
155
|
+
name: "clear",
|
|
156
|
+
label: clearLabel,
|
|
157
|
+
icon: /* @__PURE__ */ jsx(Icon, { paths: INPUT_ICON_PATHS.clear }),
|
|
158
|
+
// Nothing to clear is nothing to press: the button would be a target that does
|
|
159
|
+
// not respond, which reads as broken rather than as unavailable.
|
|
160
|
+
visible: value.length > 0,
|
|
161
|
+
onSelect: handleClear
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const overridden = new Set(actions.map((action) => action.name));
|
|
165
|
+
const rendered = [...builtIn.filter((action) => !overridden.has(action.name)), ...actions].filter((action) => action.visible !== false);
|
|
166
|
+
const locked = rest.disabled === true || rest.readOnly === true;
|
|
167
|
+
const buttons = rendered.length === 0 ? null : /* @__PURE__ */ jsx("span", { "data-enigma-input-actions": "", "data-position": position, className: classNames?.actions, children: rendered.map((action) => /* @__PURE__ */ jsx(
|
|
168
|
+
"button",
|
|
169
|
+
{
|
|
170
|
+
type: "button",
|
|
171
|
+
"data-enigma-input-action": action.name,
|
|
172
|
+
className: classNames?.action,
|
|
173
|
+
"aria-label": action.label,
|
|
174
|
+
title: action.label,
|
|
175
|
+
"aria-pressed": action.pressed,
|
|
176
|
+
disabled: locked,
|
|
177
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
178
|
+
onClick: action.onSelect,
|
|
179
|
+
children: action.icon
|
|
180
|
+
},
|
|
181
|
+
action.name
|
|
182
|
+
)) });
|
|
183
|
+
const wantsPasswordChunk = isPassword && (strength !== false || Boolean(breach));
|
|
184
|
+
const wantsSearchChunk = isSearch && Boolean(items || matcher);
|
|
185
|
+
return /* @__PURE__ */ jsxs(
|
|
186
|
+
"div",
|
|
187
|
+
{
|
|
188
|
+
...wrapperProps,
|
|
189
|
+
"data-enigma-input-root": "",
|
|
190
|
+
"data-revealed": revealed ? "" : void 0,
|
|
191
|
+
"data-breached": breached ? "" : void 0,
|
|
192
|
+
"data-score": score ?? void 0,
|
|
193
|
+
children: [
|
|
194
|
+
/* @__PURE__ */ jsxs("div", { ...fieldProps, "data-enigma-input-field": "", children: [
|
|
195
|
+
position === "start" && buttons,
|
|
196
|
+
/* @__PURE__ */ jsx(
|
|
197
|
+
"input",
|
|
198
|
+
{
|
|
199
|
+
...rest,
|
|
200
|
+
ref: (node) => {
|
|
201
|
+
innerRef.current = node;
|
|
202
|
+
setElement(node);
|
|
203
|
+
if (typeof forwardedRef === "function") forwardedRef(node);
|
|
204
|
+
else if (forwardedRef) forwardedRef.current = node;
|
|
205
|
+
},
|
|
206
|
+
type: revealed && isPassword ? "text" : type,
|
|
207
|
+
onChange: handleChange,
|
|
208
|
+
"data-enigma-input": "",
|
|
209
|
+
"aria-describedby": score !== null ? describedBy : rest["aria-describedby"]
|
|
210
|
+
}
|
|
211
|
+
),
|
|
212
|
+
position === "end" && buttons
|
|
213
|
+
] }),
|
|
214
|
+
wantsPasswordChunk && /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
|
|
215
|
+
PasswordExtras,
|
|
216
|
+
{
|
|
217
|
+
id: describedBy,
|
|
218
|
+
value,
|
|
219
|
+
strength,
|
|
220
|
+
onStrengthChange,
|
|
221
|
+
onScore: setScore,
|
|
222
|
+
breach,
|
|
223
|
+
breachDelay,
|
|
224
|
+
onBreachChange: handleBreach,
|
|
225
|
+
classNames: classNames?.strength
|
|
226
|
+
}
|
|
227
|
+
) }),
|
|
228
|
+
wantsSearchChunk && /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
|
|
229
|
+
SearchExtras,
|
|
230
|
+
{
|
|
231
|
+
input: element,
|
|
232
|
+
items,
|
|
233
|
+
keys,
|
|
234
|
+
delay,
|
|
235
|
+
limit,
|
|
236
|
+
fuse,
|
|
237
|
+
fuseOptions,
|
|
238
|
+
matcher,
|
|
239
|
+
onResults,
|
|
240
|
+
renderResults
|
|
241
|
+
}
|
|
242
|
+
) }),
|
|
243
|
+
children
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
export { Input };
|
|
@@ -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 };
|