@enigmax/primitives 0.11.0 → 0.12.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.
@@ -817,4 +817,334 @@ function estimatePasswordStrength(password, options = {}) {
817
817
  return { score, bits, warnings, empty: false };
818
818
  }
819
819
 
820
- export { INPUT_ICON_PATHS, createButton, createInput, createMarquee, estimatePasswordStrength, generatePassword, iconMarkup };
820
+ // src/core/notifications.ts
821
+ var DEFAULT_MAX = 4;
822
+ var DEFAULT_DURATION = 5e3;
823
+ function createNotifications(options = {}) {
824
+ const view = typeof document === "undefined" ? null : document;
825
+ const max = options.max ?? DEFAULT_MAX;
826
+ const defaultDuration = options.duration ?? DEFAULT_DURATION;
827
+ const sticky = /* @__PURE__ */ new Set([...options.stickyTones ?? ["error"], "loading"]);
828
+ let items = [];
829
+ let paused = false;
830
+ let sequence = 0;
831
+ const listeners = /* @__PURE__ */ new Set();
832
+ const timers = /* @__PURE__ */ new Map();
833
+ function emit() {
834
+ const snapshot = Object.freeze([...items]);
835
+ for (const listener of listeners) listener(snapshot);
836
+ }
837
+ function clearTimer(id) {
838
+ const timer = timers.get(id);
839
+ if (timer?.handle) clearTimeout(timer.handle);
840
+ timers.delete(id);
841
+ }
842
+ function startTimer(id, remaining) {
843
+ if (!Number.isFinite(remaining) || remaining <= 0) return;
844
+ const handle = paused ? null : setTimeout(() => dismiss(id), remaining);
845
+ timers.set(id, { handle, remaining, startedAt: Date.now() });
846
+ }
847
+ function dismiss(id) {
848
+ const next = items.filter((item) => item.id !== id);
849
+ if (next.length === items.length) return;
850
+ items = next;
851
+ clearTimer(id);
852
+ emit();
853
+ }
854
+ function notify(input) {
855
+ const tone = input.tone ?? "info";
856
+ const duration = input.duration ?? (sticky.has(tone) ? Infinity : defaultDuration);
857
+ const existing = input.key ? items.find((item) => item.key === input.key) : void 0;
858
+ const id = existing?.id ?? `n${++sequence}`;
859
+ const notification = {
860
+ id,
861
+ key: input.key,
862
+ title: input.title,
863
+ body: input.body,
864
+ tone,
865
+ duration,
866
+ action: input.action,
867
+ data: input.data,
868
+ createdAt: Date.now()
869
+ };
870
+ items = existing ? items.map((item) => item.id === id ? notification : item) : [...items, notification];
871
+ if (items.length > max) {
872
+ const evictable = items.find((item) => Number.isFinite(item.duration) && item.id !== id);
873
+ if (evictable) {
874
+ items = items.filter((item) => item.id !== evictable.id);
875
+ clearTimer(evictable.id);
876
+ } else {
877
+ items = items.slice(items.length - max);
878
+ }
879
+ }
880
+ clearTimer(id);
881
+ startTimer(id, duration);
882
+ emit();
883
+ return id;
884
+ }
885
+ const onVisibility = () => {
886
+ if (!view) return;
887
+ if (view.hidden) instance.pause();
888
+ else instance.resume();
889
+ };
890
+ function update(id, patch) {
891
+ const current = items.find((item) => item.id === id);
892
+ if (!current) return;
893
+ const tone = patch.tone ?? current.tone;
894
+ const duration = patch.duration ?? (patch.tone ? sticky.has(tone) ? Infinity : defaultDuration : current.duration);
895
+ items = items.map((item) => item.id === id ? { ...current, ...patch, tone, duration } : item);
896
+ clearTimer(id);
897
+ startTimer(id, duration);
898
+ emit();
899
+ }
900
+ function resolveMessage(message, value) {
901
+ const resolved = typeof message === "function" ? message(value) : message;
902
+ return typeof resolved === "string" ? { title: resolved } : resolved;
903
+ }
904
+ async function promise(work, messages) {
905
+ const start = typeof messages.loading === "string" ? { title: messages.loading } : messages.loading;
906
+ const key = start.key ?? `p${++sequence}`;
907
+ notify({ ...start, key, tone: "loading" });
908
+ try {
909
+ const value = await work;
910
+ notify({ ...resolveMessage(messages.success, value), key, tone: "success" });
911
+ return value;
912
+ } catch (error) {
913
+ notify({ ...resolveMessage(messages.error, error), key, tone: "error" });
914
+ throw error;
915
+ }
916
+ }
917
+ const instance = {
918
+ get items() {
919
+ return Object.freeze([...items]);
920
+ },
921
+ notify,
922
+ update,
923
+ promise,
924
+ dismiss,
925
+ dismissAll() {
926
+ for (const item of items) clearTimer(item.id);
927
+ items = [];
928
+ emit();
929
+ },
930
+ pause() {
931
+ if (paused) return;
932
+ paused = true;
933
+ for (const [id, timer] of timers) {
934
+ if (!timer.handle) continue;
935
+ clearTimeout(timer.handle);
936
+ timers.set(id, {
937
+ handle: null,
938
+ remaining: Math.max(timer.remaining - (Date.now() - timer.startedAt), 0),
939
+ startedAt: Date.now()
940
+ });
941
+ }
942
+ },
943
+ resume() {
944
+ if (!paused) return;
945
+ paused = false;
946
+ for (const [id, timer] of [...timers]) {
947
+ clearTimer(id);
948
+ startTimer(id, timer.remaining);
949
+ }
950
+ },
951
+ subscribe(listener) {
952
+ listeners.add(listener);
953
+ return () => {
954
+ listeners.delete(listener);
955
+ };
956
+ },
957
+ destroy() {
958
+ for (const item of items) clearTimer(item.id);
959
+ items = [];
960
+ listeners.clear();
961
+ view?.removeEventListener("visibilitychange", onVisibility);
962
+ }
963
+ };
964
+ view?.addEventListener("visibilitychange", onVisibility);
965
+ return instance;
966
+ }
967
+
968
+ // src/core/network.ts
969
+ var SERVER_NETWORK_STATE = {
970
+ online: true,
971
+ downlink: null,
972
+ downlinkMax: null,
973
+ effectiveType: null,
974
+ rtt: null,
975
+ saveData: null,
976
+ type: null,
977
+ recovered: false,
978
+ slow: false
979
+ };
980
+ function connection() {
981
+ if (typeof navigator === "undefined") return null;
982
+ const nav = navigator;
983
+ return nav.connection ?? nav.mozConnection ?? nav.webkitConnection ?? null;
984
+ }
985
+ function shallowEqual(left, right) {
986
+ for (const key of Object.keys(left)) {
987
+ if (left[key] !== right[key]) return false;
988
+ }
989
+ return true;
990
+ }
991
+ function createNetworkMonitor() {
992
+ if (typeof window === "undefined") {
993
+ return {
994
+ get state() {
995
+ return SERVER_NETWORK_STATE;
996
+ },
997
+ subscribe() {
998
+ return () => {
999
+ };
1000
+ },
1001
+ destroy() {
1002
+ }
1003
+ };
1004
+ }
1005
+ const listeners = /* @__PURE__ */ new Set();
1006
+ let hasBeenOffline = false;
1007
+ let state = read();
1008
+ function read() {
1009
+ const link2 = connection();
1010
+ const online = navigator.onLine;
1011
+ const effectiveType = link2?.effectiveType ?? null;
1012
+ const saveData = link2?.saveData ?? null;
1013
+ return {
1014
+ online,
1015
+ downlink: link2?.downlink ?? null,
1016
+ downlinkMax: link2?.downlinkMax ?? null,
1017
+ effectiveType,
1018
+ rtt: link2?.rtt ?? null,
1019
+ saveData,
1020
+ type: link2?.type ?? null,
1021
+ recovered: online && hasBeenOffline,
1022
+ slow: saveData === true || effectiveType === "2g" || effectiveType === "slow-2g"
1023
+ };
1024
+ }
1025
+ function update() {
1026
+ if (!navigator.onLine) hasBeenOffline = true;
1027
+ const next = read();
1028
+ if (shallowEqual(state, next)) return;
1029
+ state = next;
1030
+ for (const listener of listeners) listener(state);
1031
+ }
1032
+ const link = connection();
1033
+ window.addEventListener("online", update, { passive: true });
1034
+ window.addEventListener("offline", update, { passive: true });
1035
+ link?.addEventListener("change", update, { passive: true });
1036
+ return {
1037
+ get state() {
1038
+ return state;
1039
+ },
1040
+ subscribe(listener) {
1041
+ listeners.add(listener);
1042
+ return () => {
1043
+ listeners.delete(listener);
1044
+ };
1045
+ },
1046
+ destroy() {
1047
+ listeners.clear();
1048
+ window.removeEventListener("online", update);
1049
+ window.removeEventListener("offline", update);
1050
+ link?.removeEventListener("change", update);
1051
+ }
1052
+ };
1053
+ }
1054
+
1055
+ // src/core/relative-time.ts
1056
+ var ZONED = /([zZ]|[+-]\d{2}:?\d{2})$/;
1057
+ var DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
1058
+ var HAS_TIME = /\d{2}:\d{2}/;
1059
+ 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)?)?$/;
1060
+ var SECOND = 1e3;
1061
+ var MINUTE = 60 * SECOND;
1062
+ var HOUR = 60 * MINUTE;
1063
+ var DAY = 24 * HOUR;
1064
+ var WEEK = 7 * DAY;
1065
+ var MONTH = 30 * DAY;
1066
+ var YEAR = 365 * DAY;
1067
+ function ensureZone(text) {
1068
+ const trimmed = text.trim();
1069
+ if (!trimmed || DATE_ONLY.test(trimmed) || ZONED.test(trimmed)) return trimmed;
1070
+ const iso = trimmed.replace(" ", "T");
1071
+ return HAS_TIME.test(iso) ? `${iso}Z` : iso;
1072
+ }
1073
+ function normalizeDate(value) {
1074
+ if (value == null) return null;
1075
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
1076
+ if (typeof value === "number") return Number.isFinite(value) ? new Date(value) : null;
1077
+ const date = new Date(ensureZone(value));
1078
+ return Number.isNaN(date.getTime()) ? null : date;
1079
+ }
1080
+ function parseDuration(value) {
1081
+ const match = DURATION.exec(value.trim());
1082
+ if (!match) return 0;
1083
+ const [, sign, years, months, weeks, days, hours, minutes, seconds] = match;
1084
+ const n = (part) => part ? Number(part) : 0;
1085
+ const total = n(years) * YEAR + n(months) * MONTH + n(weeks) * WEEK + n(days) * DAY + n(hours) * HOUR + n(minutes) * MINUTE + n(seconds) * SECOND;
1086
+ return sign === "-" ? -total : total;
1087
+ }
1088
+ function dateTimeOptions(options) {
1089
+ const { second, minute, hour, weekday, day, month, year, timeZone, timeZoneName } = options;
1090
+ const parts = { timeZone, timeZoneName, second, minute, hour, weekday, day, month, year };
1091
+ for (const key of Object.keys(parts)) {
1092
+ if (parts[key] === void 0) delete parts[key];
1093
+ }
1094
+ return parts;
1095
+ }
1096
+ function format(date, locale, options) {
1097
+ try {
1098
+ return new Intl.DateTimeFormat(locale, options).format(date);
1099
+ } catch {
1100
+ return date.toISOString();
1101
+ }
1102
+ }
1103
+ function relativeTimeView(value, options = {}, now = /* @__PURE__ */ new Date()) {
1104
+ const date = normalizeDate(value);
1105
+ if (!date) return { date: null, iso: "", label: "", exact: "", beyondThreshold: false, absoluteOnly: false };
1106
+ const { locale, threshold = "P30D", prefix = "on", numericBeyondThreshold = false } = options;
1107
+ const age = Math.abs(date.getTime() - now.getTime());
1108
+ const beyondThreshold = age > parseDuration(threshold);
1109
+ const absoluteOnly = numericBeyondThreshold && beyondThreshold;
1110
+ const parts = absoluteOnly ? { day: "numeric", month: "numeric", year: "numeric", timeZone: options.timeZone } : { day: "numeric", month: "short", year: "numeric", ...dateTimeOptions(options) };
1111
+ const label = format(date, locale, parts);
1112
+ return {
1113
+ date,
1114
+ iso: date.toISOString(),
1115
+ label: !absoluteOnly && prefix ? `${prefix} ${label}` : label,
1116
+ exact: format(date, locale, { dateStyle: "full", timeStyle: "long", timeZone: options.timeZone }),
1117
+ beyondThreshold,
1118
+ absoluteOnly
1119
+ };
1120
+ }
1121
+ function relativeTimeAttributes(view, options = {}) {
1122
+ const attributes = {
1123
+ datetime: view.iso,
1124
+ format: options.format,
1125
+ tense: options.tense,
1126
+ precision: options.precision,
1127
+ threshold: options.threshold,
1128
+ prefix: options.prefix,
1129
+ "format-style": options.formatStyle,
1130
+ second: options.second,
1131
+ minute: options.minute,
1132
+ hour: options.hour,
1133
+ weekday: options.weekday,
1134
+ day: options.day,
1135
+ month: options.month,
1136
+ year: options.year,
1137
+ lang: options.locale,
1138
+ "time-zone": options.timeZone,
1139
+ "time-zone-name": options.timeZoneName,
1140
+ // Boolean attributes are read by presence: "false" would still be true.
1141
+ "no-title": options.noTitle ? "" : void 0
1142
+ };
1143
+ const out = {};
1144
+ for (const [key, value] of Object.entries(attributes)) {
1145
+ if (value !== void 0) out[key] = value;
1146
+ }
1147
+ return out;
1148
+ }
1149
+
1150
+ export { INPUT_ICON_PATHS, SERVER_NETWORK_STATE, createButton, createInput, createMarquee, createNetworkMonitor, createNotifications, ensureZone, estimatePasswordStrength, generatePassword, iconMarkup, normalizeDate, parseDuration, relativeTimeAttributes, relativeTimeView };
@@ -1,7 +1,7 @@
1
- import { generatePassword, estimatePasswordStrength, INPUT_ICON_PATHS, createButton, createMarquee, createInput } from './chunk-42Y7OOJM.js';
1
+ import { generatePassword, estimatePasswordStrength, INPUT_ICON_PATHS, createNotifications, createButton, createMarquee, createInput, createNetworkMonitor, SERVER_NETWORK_STATE, relativeTimeView, relativeTimeAttributes } from './chunk-DD2X5B2J.js';
2
2
  import { createSearch } from './chunk-UZFEEFMF.js';
3
- import { forwardRef, createElement, useRef, useState, useId, useCallback, useEffect, useMemo, useLayoutEffect } from 'react';
4
- import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { forwardRef, createElement, useRef, useState, useId, useCallback, useEffect, useMemo, useSyncExternalStore, useLayoutEffect } from 'react';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
 
6
6
  var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
7
7
  function useMarquee(options = {}) {
@@ -172,7 +172,7 @@ function useButton(options = {}) {
172
172
  event.preventDefault();
173
173
  return;
174
174
  }
175
- void instance.press();
175
+ void instance.press(event);
176
176
  },
177
177
  "aria-disabled": !state.available,
178
178
  "aria-busy": state.loading,
@@ -198,15 +198,16 @@ var Button = forwardRef(function Button2({
198
198
  pending,
199
199
  href,
200
200
  disabled,
201
- loading,
201
+ loading: loading2,
202
202
  cooldown,
203
203
  shortcut,
204
204
  onPress,
205
+ onClick,
205
206
  onChange,
206
207
  type = "button",
207
208
  ...rest
208
209
  }, ref) {
209
- const button = useButton({ href, disabled, loading, cooldown, shortcut, onPress, onChange });
210
+ const button = useButton({ href, disabled, loading: loading2, cooldown, shortcut, onPress: onPress ?? onClick, onChange });
210
211
  const state = button;
211
212
  const Tag = as ?? (state.element === "a" ? getLinkComponent() : "button");
212
213
  const native = Tag === "button" ? { type } : {};
@@ -462,5 +463,256 @@ function PasswordStrength({
462
463
  }
463
464
  );
464
465
  }
466
+ var defaultQueue = createNotifications();
467
+ function useNotifications(queue = defaultQueue) {
468
+ const snapshot = useRef(queue.items);
469
+ const items = useSyncExternalStore(
470
+ (listener) => queue.subscribe((next) => {
471
+ snapshot.current = next;
472
+ listener();
473
+ }),
474
+ () => snapshot.current,
475
+ () => snapshot.current
476
+ );
477
+ return useMemo(() => ({
478
+ items,
479
+ notify: queue.notify,
480
+ dismiss: queue.dismiss,
481
+ dismissAll: queue.dismissAll,
482
+ pause: queue.pause,
483
+ resume: queue.resume
484
+ }), [items, queue]);
485
+ }
486
+ function createNotificationQueue(options) {
487
+ return createNotifications(options);
488
+ }
489
+ function swipeAxis(position) {
490
+ if (position.endsWith("-center")) return { axis: "y", sign: position.startsWith("top") ? -1 : 1 };
491
+ return { axis: "x", sign: position.endsWith("-left") ? -1 : 1 };
492
+ }
493
+ function Toaster({
494
+ queue = defaultQueue,
495
+ position = "bottom-right",
496
+ exitDuration = 200,
497
+ swipeThreshold = 60,
498
+ children,
499
+ label = "Notifications",
500
+ className,
501
+ style
502
+ }) {
503
+ const [rendered, setRendered] = useState(
504
+ () => queue.items.map((notification) => ({ notification, leaving: false }))
505
+ );
506
+ const timers = useRef(/* @__PURE__ */ new Map());
507
+ useEffect(() => {
508
+ const sync = (items) => {
509
+ setRendered((current) => {
510
+ const live = new Map(items.map((item) => [item.id, item]));
511
+ const kept = current.map((entry) => {
512
+ const still = live.get(entry.notification.id);
513
+ if (still) return { notification: still, leaving: false };
514
+ if (entry.leaving) return entry;
515
+ const timer = setTimeout(() => {
516
+ timers.current.delete(entry.notification.id);
517
+ setRendered((later) => later.filter((row) => row.notification.id !== entry.notification.id));
518
+ }, exitDuration);
519
+ timers.current.set(entry.notification.id, timer);
520
+ return { ...entry, leaving: true };
521
+ });
522
+ const seen = new Set(kept.map((entry) => entry.notification.id));
523
+ const added = items.filter((item) => !seen.has(item.id)).map((item) => ({ notification: item, leaving: false }));
524
+ return [...kept, ...added];
525
+ });
526
+ };
527
+ sync(queue.items);
528
+ return queue.subscribe(sync);
529
+ }, [queue, exitDuration]);
530
+ useEffect(() => {
531
+ const pending = timers.current;
532
+ return () => {
533
+ for (const timer of pending.values()) clearTimeout(timer);
534
+ pending.clear();
535
+ };
536
+ }, []);
537
+ if (!rendered.length) return null;
538
+ return /* @__PURE__ */ jsx(
539
+ "section",
540
+ {
541
+ "aria-label": label,
542
+ "data-enigma-toaster": "",
543
+ "data-position": position,
544
+ className,
545
+ style,
546
+ onPointerEnter: () => queue.pause(),
547
+ onPointerLeave: () => queue.resume(),
548
+ onFocusCapture: () => queue.pause(),
549
+ onBlurCapture: () => queue.resume(),
550
+ children: rendered.map((entry, index) => /* @__PURE__ */ jsx(
551
+ Toast,
552
+ {
553
+ entry,
554
+ index: rendered.length - 1 - index,
555
+ position,
556
+ swipeThreshold,
557
+ onDismiss: () => queue.dismiss(entry.notification.id),
558
+ render: children
559
+ },
560
+ entry.notification.id
561
+ ))
562
+ }
563
+ );
564
+ }
565
+ function Toast({ entry, index, position, swipeThreshold, onDismiss, render }) {
566
+ const { notification, leaving } = entry;
567
+ const [offset, setOffset] = useState(0);
568
+ const [swiping, setSwiping] = useState(false);
569
+ const start = useRef(0);
570
+ const { axis, sign } = swipeAxis(position);
571
+ const onPointerDown = useCallback((event) => {
572
+ if (event.target.closest("button")) return;
573
+ if (event.pointerType === "mouse" && event.button !== 0) return;
574
+ event.currentTarget.setPointerCapture(event.pointerId);
575
+ start.current = axis === "x" ? event.clientX : event.clientY;
576
+ setSwiping(true);
577
+ }, [axis]);
578
+ const onPointerMove = useCallback((event) => {
579
+ if (!swiping) return;
580
+ const delta = (axis === "x" ? event.clientX : event.clientY) - start.current;
581
+ setOffset(sign > 0 ? Math.max(0, delta) : Math.min(0, delta));
582
+ }, [swiping, axis, sign]);
583
+ const onPointerUp = useCallback(() => {
584
+ if (!swiping) return;
585
+ setSwiping(false);
586
+ if (Math.abs(offset) >= swipeThreshold) {
587
+ onDismiss();
588
+ return;
589
+ }
590
+ setOffset(0);
591
+ }, [swiping, offset, swipeThreshold, onDismiss]);
592
+ const controls = { dismiss: onDismiss };
593
+ return /* @__PURE__ */ jsx(
594
+ "article",
595
+ {
596
+ "data-enigma-toast": "",
597
+ "data-tone": notification.tone,
598
+ "data-state": leaving ? "leaving" : "open",
599
+ "data-index": index,
600
+ "data-front": index === 0 ? "" : void 0,
601
+ "data-swiping": swiping ? "" : void 0,
602
+ role: notification.tone === "error" ? "alert" : "status",
603
+ "aria-live": notification.tone === "error" ? "assertive" : "polite",
604
+ tabIndex: 0,
605
+ onKeyDown: (event) => {
606
+ if (event.key === "Escape") onDismiss();
607
+ },
608
+ onPointerDown,
609
+ onPointerMove,
610
+ onPointerUp,
611
+ onPointerCancel: onPointerUp,
612
+ style: {
613
+ // Read by the theme, so the transform stays the stylesheet's decision.
614
+ "--enigma-toast-swipe": `${offset}px`,
615
+ "--enigma-toast-index": index
616
+ },
617
+ children: render ? render(notification, controls) : /* @__PURE__ */ jsxs(Fragment, { children: [
618
+ /* @__PURE__ */ jsx("p", { "data-enigma-toast-title": "", children: notification.title }),
619
+ notification.body && /* @__PURE__ */ jsx("p", { "data-enigma-toast-body": "", children: notification.body }),
620
+ notification.action && /* @__PURE__ */ jsx(
621
+ "button",
622
+ {
623
+ type: "button",
624
+ "data-enigma-toast-action": "",
625
+ onClick: () => {
626
+ notification.action?.onSelect();
627
+ if (notification.action?.dismiss !== false) onDismiss();
628
+ },
629
+ children: notification.action.label
630
+ }
631
+ ),
632
+ /* @__PURE__ */ jsx("button", { type: "button", "data-enigma-toast-close": "", "aria-label": "Dismiss", onClick: onDismiss, children: "\xD7" })
633
+ ] })
634
+ }
635
+ );
636
+ }
637
+ function useNetworkState() {
638
+ const monitor = useMemo(() => createNetworkMonitor(), []);
639
+ const snapshot = useRef(monitor.state);
640
+ return useSyncExternalStore(
641
+ (listener) => monitor.subscribe((next) => {
642
+ snapshot.current = next;
643
+ listener();
644
+ }),
645
+ () => snapshot.current,
646
+ () => SERVER_NETWORK_STATE
647
+ );
648
+ }
649
+ var loading = null;
650
+ function loadElement() {
651
+ if (loading || typeof window === "undefined") return;
652
+ loading = import('@github/relative-time-element').catch(() => {
653
+ return null;
654
+ });
655
+ }
656
+ function RelativeTime({
657
+ date,
658
+ fallback = null,
659
+ capitalize = false,
660
+ capitalizeFirst = true,
661
+ now,
662
+ format,
663
+ tense,
664
+ precision,
665
+ threshold,
666
+ prefix,
667
+ formatStyle,
668
+ locale,
669
+ timeZone,
670
+ second,
671
+ minute,
672
+ hour,
673
+ weekday,
674
+ day,
675
+ month,
676
+ year,
677
+ timeZoneName,
678
+ noTitle,
679
+ numericBeyondThreshold,
680
+ ...rest
681
+ }) {
682
+ useEffect(loadElement, []);
683
+ const options = {
684
+ format,
685
+ tense,
686
+ precision,
687
+ threshold,
688
+ prefix,
689
+ formatStyle,
690
+ locale,
691
+ timeZone,
692
+ second,
693
+ minute,
694
+ hour,
695
+ weekday,
696
+ day,
697
+ month,
698
+ year,
699
+ timeZoneName,
700
+ noTitle,
701
+ numericBeyondThreshold
702
+ };
703
+ const view = relativeTimeView(date, options, now);
704
+ if (!view.date) return fallback;
705
+ const marks = {
706
+ "data-relative-time-capitalize": capitalize ? "true" : void 0,
707
+ "data-relative-time-capitalize-first-letter": capitalizeFirst ? "true" : void 0,
708
+ // The text differs between the server's instant and the browser's, which is the
709
+ // one hydration difference React has an escape hatch for rather than a bug.
710
+ suppressHydrationWarning: true
711
+ };
712
+ if (view.absoluteOnly) {
713
+ return createElement("time", { dateTime: view.iso, title: noTitle ? void 0 : view.exact, ...marks, ...rest }, view.label);
714
+ }
715
+ return createElement("relative-time", { ...relativeTimeAttributes(view, options), ...marks, ...rest }, view.label);
716
+ }
465
717
 
466
- export { Button, Input, PasswordStrength, getLinkComponent, setLinkComponent, useButton, useInput, useMarquee, useSearch };
718
+ export { Button, Input, PasswordStrength, RelativeTime, Toaster, createNotificationQueue, defaultQueue, getLinkComponent, setLinkComponent, useButton, useInput, useMarquee, useNetworkState, useNotifications, useSearch };