@domainkit/react 0.10.0 → 0.11.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/README.md +23 -2
- package/dist/index.d.mts +137 -15
- package/dist/index.mjs +607 -157
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +432 -87
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -10,6 +10,9 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
10
10
|
import * as Exit from "effect/Exit";
|
|
11
11
|
import * as Option from "effect/Option";
|
|
12
12
|
import { Dialog } from "@base-ui/react/dialog";
|
|
13
|
+
import { Collapsible } from "@base-ui/react/collapsible";
|
|
14
|
+
import { Switch } from "@base-ui/react/switch";
|
|
15
|
+
import { Menu } from "@base-ui/react/menu";
|
|
13
16
|
import { Popover } from "@base-ui/react/popover";
|
|
14
17
|
import * as DateTime from "effect/DateTime";
|
|
15
18
|
import { Testing } from "domainkit/testing";
|
|
@@ -47,6 +50,7 @@ function Glyph({ children }) {
|
|
|
47
50
|
});
|
|
48
51
|
}
|
|
49
52
|
const defaultIcons = {
|
|
53
|
+
chevron: /* @__PURE__ */ jsx(Glyph, { children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" }) }),
|
|
50
54
|
close: /* @__PURE__ */ jsxs(Glyph, { children: [/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }), /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })] }),
|
|
51
55
|
copied: /* @__PURE__ */ jsx(Glyph, { children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) }),
|
|
52
56
|
copy: /* @__PURE__ */ jsxs(Glyph, { children: [/* @__PURE__ */ jsx("rect", {
|
|
@@ -161,14 +165,23 @@ const english = {
|
|
|
161
165
|
close: "Close",
|
|
162
166
|
retry: "Try again",
|
|
163
167
|
approve: "Approve",
|
|
168
|
+
addRecords: (count) => count === 1 ? "Add 1 record" : `Add ${count} records`,
|
|
164
169
|
decline: "Decline",
|
|
170
|
+
everyRecordConflicts: "Resolve the records above at your provider, then review again.",
|
|
165
171
|
reviewChanges: "Review changes",
|
|
166
172
|
cleanUp: "Remove records",
|
|
167
173
|
checkDns: "Check DNS",
|
|
168
174
|
checkAgain: "Check again",
|
|
169
175
|
moreActions: "More actions",
|
|
170
|
-
moreOptions: "
|
|
176
|
+
moreOptions: "Add an account id",
|
|
171
177
|
useAnotherProvider: "Use a different provider",
|
|
178
|
+
useTokenInstead: "Use an API token instead",
|
|
179
|
+
back: "Back",
|
|
180
|
+
connected: "Connected",
|
|
181
|
+
needsReconnect: "Needs reconnecting",
|
|
182
|
+
disconnectScope: "This connection serves other domains.",
|
|
183
|
+
disconnectThisDomain: "Only this domain",
|
|
184
|
+
disconnectEveryDomain: (count) => `All ${count} domains`,
|
|
172
185
|
copy: "Copy",
|
|
173
186
|
copied: "Copied",
|
|
174
187
|
copyZone: "Copy zone file",
|
|
@@ -196,7 +209,9 @@ const english = {
|
|
|
196
209
|
notConnected: "No DNS provider is connected.",
|
|
197
210
|
reconnectRequired: (provider) => `${provider} needs to be reconnected`,
|
|
198
211
|
disconnectTitle: (provider) => `Disconnect ${provider}?`,
|
|
199
|
-
disconnectConsent:
|
|
212
|
+
disconnectConsent: (domain, provider) => `DomainKit stops managing DNS for ${domain} through ${provider}.`,
|
|
213
|
+
disconnectWithCleanup: (count) => count === 1 ? "Remove the 1 record DomainKit added" : `Remove the ${count} records DomainKit added`,
|
|
214
|
+
disconnectKeepsRecords: (provider) => `Records stay in ${provider}.`,
|
|
200
215
|
detachConsent: "This domain is detached from the provider. Existing DNS records are preserved.",
|
|
201
216
|
detached: "Domain detached. DNS records were preserved.",
|
|
202
217
|
reusableConnections: "Connections you already have",
|
|
@@ -313,10 +328,7 @@ const english = {
|
|
|
313
328
|
description: reason.message,
|
|
314
329
|
title: reason.field === void 0 ? "Check what you entered" : `Check the ${humanize(reason.field).toLowerCase()}`
|
|
315
330
|
}),
|
|
316
|
-
unauthenticated: (
|
|
317
|
-
description: "Check the token can read and edit DNS for this zone, then try again.",
|
|
318
|
-
title: `${named(context.provider)} didn't accept this token`
|
|
319
|
-
}),
|
|
331
|
+
unauthenticated: () => ({ title: "Token not accepted" }),
|
|
320
332
|
forbidden: (_reason, context) => ({
|
|
321
333
|
description: `The connection needs permission to edit DNS for ${where(context.domain)}.`,
|
|
322
334
|
title: "This connection can't change DNS here"
|
|
@@ -403,7 +415,7 @@ const outcome = (error, catalog, context = {}) => {
|
|
|
403
415
|
/** The same failure as one sentence, for a host that renders text rather than a card. */
|
|
404
416
|
const failure = (error, catalog, context = {}) => {
|
|
405
417
|
const { description, title } = outcome(error, catalog, context);
|
|
406
|
-
return `${title}. ${description}`;
|
|
418
|
+
return description === void 0 ? `${title}.` : `${title}. ${description}`;
|
|
407
419
|
};
|
|
408
420
|
//#endregion
|
|
409
421
|
//#region src/theme.ts
|
|
@@ -939,16 +951,17 @@ function Title(props) {
|
|
|
939
951
|
"data-domainkit-part": "outcome-title"
|
|
940
952
|
});
|
|
941
953
|
}
|
|
942
|
-
/** What to do about it, from the catalog. */
|
|
954
|
+
/** What to do about it, from the catalog. An outcome whose title says everything renders none. */
|
|
943
955
|
function Description(props) {
|
|
944
956
|
const { description, layout, tone } = useOutcome();
|
|
945
|
-
|
|
957
|
+
const element = usePart("p", props, {
|
|
946
958
|
layout,
|
|
947
959
|
tone
|
|
948
960
|
}, {
|
|
949
961
|
children: description,
|
|
950
962
|
"data-domainkit-part": "outcome-description"
|
|
951
963
|
});
|
|
964
|
+
return description === "" && props.children === void 0 ? null : element;
|
|
952
965
|
}
|
|
953
966
|
/**
|
|
954
967
|
* The actions under the header. Without children it is the flow's retry, and a flow that allows
|
|
@@ -1307,12 +1320,12 @@ function Status$4({ controller, kind, ...props }) {
|
|
|
1307
1320
|
switch (state._tag) {
|
|
1308
1321
|
case "Idle": return "";
|
|
1309
1322
|
case "Planning": return kind === "provisioning" ? messages.planning : messages.planningCleanup;
|
|
1310
|
-
case "Planned": return
|
|
1323
|
+
case "Planned": return "";
|
|
1311
1324
|
case "Approving": return messages.approving;
|
|
1312
1325
|
case "Applying": return kind === "provisioning" ? messages.applying : messages.cleaning;
|
|
1313
1326
|
case "Rejecting": return messages.rejecting;
|
|
1314
1327
|
case "Rejected": return messages.declinedBy(state.attempt.rejection?.actorId ?? "");
|
|
1315
|
-
case "Applied": return
|
|
1328
|
+
case "Applied": return "";
|
|
1316
1329
|
case "Failure": return "";
|
|
1317
1330
|
}
|
|
1318
1331
|
};
|
|
@@ -1324,28 +1337,32 @@ function Status$4({ controller, kind, ...props }) {
|
|
|
1324
1337
|
});
|
|
1325
1338
|
}
|
|
1326
1339
|
/**
|
|
1327
|
-
*
|
|
1328
|
-
* flow allows. Children replace the composition and
|
|
1340
|
+
* How the attempt ended: a failure, a receipt that only partly landed, or one that landed whole.
|
|
1341
|
+
* Media, title, description, and the retry the flow allows. Children replace the composition and
|
|
1342
|
+
* keep the binding.
|
|
1329
1343
|
*/
|
|
1330
1344
|
function Outcome$4({ children, controller, kind, ...props }) {
|
|
1331
1345
|
const { messages } = useDomainKit();
|
|
1332
1346
|
const readOnly = useReadOnly();
|
|
1333
1347
|
const state = controller.state;
|
|
1334
|
-
const partial = state._tag === "Applied" && state.receipt.status === "partial";
|
|
1335
1348
|
const retryPart = kind === "provisioning" ? "provisioning-retry" : "cleanup-retry";
|
|
1336
|
-
if (state._tag !== "Failure" &&
|
|
1337
|
-
const
|
|
1349
|
+
if (state._tag !== "Failure" && state._tag !== "Applied") return null;
|
|
1350
|
+
const partial = state._tag === "Applied" && state.receipt.status === "partial";
|
|
1351
|
+
const words = state._tag === "Failure" ? outcome(state.error, messages) : partial ? {
|
|
1338
1352
|
description: kind === "provisioning" ? messages.partiallyApplied : messages.partiallyCleaned,
|
|
1339
1353
|
title: kind === "provisioning" ? messages.partiallyAppliedTitle : messages.partiallyCleanedTitle
|
|
1354
|
+
} : {
|
|
1355
|
+
description: "",
|
|
1356
|
+
title: kind === "provisioning" ? messages.applied(state.receipt) : messages.cleaned(state.receipt)
|
|
1340
1357
|
};
|
|
1341
1358
|
return /* @__PURE__ */ jsx(Provider, {
|
|
1342
1359
|
value: {
|
|
1343
|
-
description: words.description,
|
|
1360
|
+
description: words.description ?? "",
|
|
1344
1361
|
layout: props.layout ?? "card",
|
|
1345
1362
|
retry: readOnly || state._tag !== "Failure" ? null : controller.retry,
|
|
1346
1363
|
retryPart,
|
|
1347
1364
|
title: words.title,
|
|
1348
|
-
tone: state._tag === "Failure" ? "danger" : "warning"
|
|
1365
|
+
tone: state._tag === "Failure" ? "danger" : partial ? "warning" : "success"
|
|
1349
1366
|
},
|
|
1350
1367
|
children: /* @__PURE__ */ jsx(Root$6, {
|
|
1351
1368
|
...props,
|
|
@@ -1353,9 +1370,13 @@ function Outcome$4({ children, controller, kind, ...props }) {
|
|
|
1353
1370
|
})
|
|
1354
1371
|
});
|
|
1355
1372
|
}
|
|
1373
|
+
/** What a plan would actually write: an operation blocked by a conflict is not one of them. */
|
|
1374
|
+
const addable = (plan) => plan === null ? [] : plan.operations.filter((operation) => operation._tag !== "Conflict");
|
|
1356
1375
|
/**
|
|
1357
|
-
*
|
|
1358
|
-
*
|
|
1376
|
+
* The primary action, and Decline. The primary authorizes the digest and applies it in one step,
|
|
1377
|
+
* so it says what it will do; where a conflict blocks part of a plan it approves the rest by id,
|
|
1378
|
+
* and where every operation is blocked it says what to fix instead. Neither renders until there is
|
|
1379
|
+
* a plan to act on.
|
|
1359
1380
|
*/
|
|
1360
1381
|
function Actions$2({ controller, kind, ...props }) {
|
|
1361
1382
|
const { messages } = useDomainKit();
|
|
@@ -1363,24 +1384,33 @@ function Actions$2({ controller, kind, ...props }) {
|
|
|
1363
1384
|
const state = controller.state;
|
|
1364
1385
|
const plan = state._tag === "Planned" ? state.plan : null;
|
|
1365
1386
|
const running = busy(state._tag);
|
|
1387
|
+
const writes = addable(plan);
|
|
1388
|
+
const blocked = plan !== null && writes.length === 0;
|
|
1366
1389
|
const element = usePart("div", props, { status: state._tag }, {
|
|
1367
|
-
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1390
|
+
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1391
|
+
/* @__PURE__ */ jsx("button", {
|
|
1392
|
+
"data-domainkit-part": kind === "provisioning" ? "plan-apply" : "cleanup-apply",
|
|
1393
|
+
disabled: running || plan === null || writes.length === 0,
|
|
1394
|
+
onClick: () => controller.approve(writes.length === plan?.operations.length ? void 0 : writes.map((operation) => operation.id)),
|
|
1395
|
+
type: "button",
|
|
1396
|
+
children: kind === "provisioning" ? messages.addRecords(writes.length) : messages.approve
|
|
1397
|
+
}),
|
|
1398
|
+
blocked ? /* @__PURE__ */ jsx("p", {
|
|
1399
|
+
"data-domainkit-part": "plan-blocked",
|
|
1400
|
+
children: messages.everyRecordConflicts
|
|
1401
|
+
}) : null,
|
|
1402
|
+
/* @__PURE__ */ jsx("button", {
|
|
1403
|
+
"data-domainkit-part": "plan-decline",
|
|
1404
|
+
disabled: running || plan === null,
|
|
1405
|
+
onClick: () => controller.reject(),
|
|
1406
|
+
type: "button",
|
|
1407
|
+
children: messages.decline
|
|
1408
|
+
})
|
|
1409
|
+
] }),
|
|
1380
1410
|
"data-domainkit-part": "review-actions"
|
|
1381
1411
|
});
|
|
1382
1412
|
if (readOnly) return null;
|
|
1383
|
-
return plan === null
|
|
1413
|
+
return plan === null ? null : element;
|
|
1384
1414
|
}
|
|
1385
1415
|
/** The operations under review, or the reason there is nothing to review. */
|
|
1386
1416
|
function Body({ controller, kind }) {
|
|
@@ -1415,10 +1445,17 @@ function Dialog$4({ children, controller, kind, onOpenChange, open, render, trig
|
|
|
1415
1445
|
const readOnly = useReadOnly();
|
|
1416
1446
|
const [uncontrolled, setUncontrolled] = useState(false);
|
|
1417
1447
|
const isOpen = open ?? uncontrolled;
|
|
1418
|
-
const
|
|
1448
|
+
const close = useRef(() => {});
|
|
1449
|
+
close.current = (next) => {
|
|
1419
1450
|
if (open === void 0) setUncontrolled(next);
|
|
1420
1451
|
onOpenChange?.(next);
|
|
1421
1452
|
};
|
|
1453
|
+
const setOpen = (next) => close.current(next);
|
|
1454
|
+
const state = controller.state;
|
|
1455
|
+
const landed = state._tag === "Applied" && state.receipt.status === "complete";
|
|
1456
|
+
useEffect(() => {
|
|
1457
|
+
if (landed) close.current(false);
|
|
1458
|
+
}, [landed]);
|
|
1422
1459
|
const running = busy(controller.state._tag);
|
|
1423
1460
|
const body = children ?? /* @__PURE__ */ jsx(Body, {
|
|
1424
1461
|
controller,
|
|
@@ -1488,7 +1525,7 @@ function Dialog$4({ children, controller, kind, onOpenChange, open, render, trig
|
|
|
1488
1525
|
children: [/* @__PURE__ */ jsx(Actions$2, {
|
|
1489
1526
|
controller,
|
|
1490
1527
|
kind
|
|
1491
|
-
}), running ? null : /* @__PURE__ */ jsx(Dialog.Close, {
|
|
1528
|
+
}), running || kind === "provisioning" ? null : /* @__PURE__ */ jsx(Dialog.Close, {
|
|
1492
1529
|
"data-domainkit-part": "dialog-cancel",
|
|
1493
1530
|
children: messages.cancel
|
|
1494
1531
|
})]
|
|
@@ -1580,13 +1617,16 @@ function Flow$3({ domain, onCleaned, receiptId, trigger, ...props }) {
|
|
|
1580
1617
|
...onCleaned === void 0 ? {} : { onCleaned },
|
|
1581
1618
|
...receiptId === void 0 ? {} : { receiptId }
|
|
1582
1619
|
});
|
|
1620
|
+
const [open, setOpen] = useState(false);
|
|
1583
1621
|
return /* @__PURE__ */ jsxs(Root$3, {
|
|
1584
1622
|
controller,
|
|
1585
1623
|
...props,
|
|
1586
1624
|
children: [/* @__PURE__ */ jsx(Dialog$3, {
|
|
1587
1625
|
controller,
|
|
1626
|
+
onOpenChange: setOpen,
|
|
1627
|
+
open,
|
|
1588
1628
|
...trigger === void 0 ? {} : { trigger }
|
|
1589
|
-
}), /* @__PURE__ */ jsx(Outcome$3, { controller })]
|
|
1629
|
+
}), open ? null : /* @__PURE__ */ jsx(Outcome$3, { controller })]
|
|
1590
1630
|
});
|
|
1591
1631
|
}
|
|
1592
1632
|
//#endregion
|
|
@@ -1616,6 +1656,7 @@ function Mark({ provider, ...props }) {
|
|
|
1616
1656
|
var connect_exports = /* @__PURE__ */ __exportAll({
|
|
1617
1657
|
Card: () => Card,
|
|
1618
1658
|
Dialog: () => Dialog$2,
|
|
1659
|
+
DisconnectDialog: () => DisconnectDialog,
|
|
1619
1660
|
Flow: () => Flow$2,
|
|
1620
1661
|
Form: () => Form,
|
|
1621
1662
|
Outcome: () => Outcome$2,
|
|
@@ -1624,10 +1665,32 @@ var connect_exports = /* @__PURE__ */ __exportAll({
|
|
|
1624
1665
|
State: () => State$1,
|
|
1625
1666
|
Status: () => Status$2,
|
|
1626
1667
|
answeredInPlace: () => answeredInPlace,
|
|
1668
|
+
holdsConnection: () => holdsConnection,
|
|
1627
1669
|
hostProvider: () => hostProvider,
|
|
1670
|
+
offering: () => offering,
|
|
1628
1671
|
useController: () => useController$2
|
|
1629
1672
|
});
|
|
1630
1673
|
const State$1 = Data.taggedEnum();
|
|
1674
|
+
/**
|
|
1675
|
+
* An interactive method leaves the page and comes back, so what the library knew is gone with it.
|
|
1676
|
+
* It writes down the domain it sent the customer away for, and the load that follows reads it once:
|
|
1677
|
+
* that, not a query parameter a host would have to keep, is how a return is told from a reload.
|
|
1678
|
+
*/
|
|
1679
|
+
const RETURNING = "domainkit.returning";
|
|
1680
|
+
const markReturn = (domain) => {
|
|
1681
|
+
try {
|
|
1682
|
+
sessionStorage.setItem(RETURNING, domain);
|
|
1683
|
+
} catch {}
|
|
1684
|
+
};
|
|
1685
|
+
const tookReturn = (domain) => {
|
|
1686
|
+
try {
|
|
1687
|
+
if (sessionStorage.getItem(RETURNING) !== domain) return false;
|
|
1688
|
+
sessionStorage.removeItem(RETURNING);
|
|
1689
|
+
return true;
|
|
1690
|
+
} catch {
|
|
1691
|
+
return false;
|
|
1692
|
+
}
|
|
1693
|
+
};
|
|
1631
1694
|
/** Read at connect time, not at render, so it is the page the customer is actually on. */
|
|
1632
1695
|
const currentUrl = () => typeof window === "undefined" ? null : window.location.href;
|
|
1633
1696
|
const snapshotOf = (state) => {
|
|
@@ -1670,6 +1733,7 @@ function useController$2({ domain, returnTo }) {
|
|
|
1670
1733
|
});
|
|
1671
1734
|
const lastCommand = useRef(null);
|
|
1672
1735
|
const attempted = useRef(null);
|
|
1736
|
+
const [established, setEstablished] = useState(0);
|
|
1673
1737
|
const [inspected, setInspected] = useState(domain);
|
|
1674
1738
|
if (inspected !== domain) {
|
|
1675
1739
|
setInspected(domain);
|
|
@@ -1719,6 +1783,7 @@ function useController$2({ domain, returnTo }) {
|
|
|
1719
1783
|
domain,
|
|
1720
1784
|
snapshot
|
|
1721
1785
|
};
|
|
1786
|
+
if (tookReturn(domain) && snapshot.status === "connected") setEstablished((count) => count + 1);
|
|
1722
1787
|
setState(settled(snapshot, discovery));
|
|
1723
1788
|
}
|
|
1724
1789
|
});
|
|
@@ -1738,6 +1803,7 @@ function useController$2({ domain, returnTo }) {
|
|
|
1738
1803
|
domain,
|
|
1739
1804
|
snapshot: result.snapshot
|
|
1740
1805
|
};
|
|
1806
|
+
setEstablished((count) => count + 1);
|
|
1741
1807
|
setState(settled(result.snapshot, null));
|
|
1742
1808
|
if (result.snapshot.connectionId !== null) emit(Event.Connected({
|
|
1743
1809
|
connectionId: result.snapshot.connectionId,
|
|
@@ -1746,6 +1812,7 @@ function useController$2({ domain, returnTo }) {
|
|
|
1746
1812
|
}));
|
|
1747
1813
|
return;
|
|
1748
1814
|
case "Redirect":
|
|
1815
|
+
markReturn(domain);
|
|
1749
1816
|
setState(State$1.Redirecting({ url: result.authorizationUrl }));
|
|
1750
1817
|
navigate(result.authorizationUrl);
|
|
1751
1818
|
return;
|
|
@@ -1766,7 +1833,10 @@ function useController$2({ domain, returnTo }) {
|
|
|
1766
1833
|
if (held.current.domain !== domain) return;
|
|
1767
1834
|
attempted.current = attempt;
|
|
1768
1835
|
const command = () => {
|
|
1769
|
-
setState(State$1.Submitting({
|
|
1836
|
+
setState(State$1.Submitting({
|
|
1837
|
+
discovery: held.current.discovery,
|
|
1838
|
+
snapshot: held.current.snapshot
|
|
1839
|
+
}));
|
|
1770
1840
|
runner.run(effect, {
|
|
1771
1841
|
onFailure,
|
|
1772
1842
|
onSuccess: started
|
|
@@ -1825,7 +1895,10 @@ function useController$2({ domain, returnTo }) {
|
|
|
1825
1895
|
if (held.current.domain !== domain) return;
|
|
1826
1896
|
attempted.current = null;
|
|
1827
1897
|
const command = () => {
|
|
1828
|
-
setState(State$1.Submitting({
|
|
1898
|
+
setState(State$1.Submitting({
|
|
1899
|
+
discovery: held.current.discovery,
|
|
1900
|
+
snapshot: held.current.snapshot
|
|
1901
|
+
}));
|
|
1829
1902
|
runner.run(effect, {
|
|
1830
1903
|
onFailure,
|
|
1831
1904
|
onSuccess: () => {
|
|
@@ -1880,7 +1953,9 @@ function useController$2({ domain, returnTo }) {
|
|
|
1880
1953
|
connect,
|
|
1881
1954
|
detach,
|
|
1882
1955
|
disconnect,
|
|
1883
|
-
|
|
1956
|
+
domain,
|
|
1957
|
+
established,
|
|
1958
|
+
discovery: state._tag === "Disconnected" || state._tag === "Failure" || state._tag === "Submitting" ? state.discovery : null,
|
|
1884
1959
|
providers: snapshotOf(state)?.providers ?? [],
|
|
1885
1960
|
refresh: load,
|
|
1886
1961
|
retry,
|
|
@@ -1902,6 +1977,12 @@ function Root$2({ controller, ...props }) {
|
|
|
1902
1977
|
*/
|
|
1903
1978
|
const answeredInPlace = (controller) => controller.state._tag === "Failure" && controller.state.attempt !== null;
|
|
1904
1979
|
/**
|
|
1980
|
+
* Whether the surface should read as connected: it is, or a command it started is in flight over a
|
|
1981
|
+
* connection that was. The card and the dialog it opened stay on screen for the length of the
|
|
1982
|
+
* command rather than vanishing under the customer half way through it.
|
|
1983
|
+
*/
|
|
1984
|
+
const holdsConnection = (controller) => controller.state._tag === "Connected" || controller.state._tag === "Submitting" && controller.snapshot?.status === "connected";
|
|
1985
|
+
/**
|
|
1905
1986
|
* The provider whose nameservers serve this domain, as the snapshot describes it. Discovery names
|
|
1906
1987
|
* it by id; without a descriptor there is nothing to draw, so there is no host.
|
|
1907
1988
|
*/
|
|
@@ -1929,7 +2010,7 @@ function Outcome$2({ children, controller, ...props }) {
|
|
|
1929
2010
|
});
|
|
1930
2011
|
return /* @__PURE__ */ jsx(Provider, {
|
|
1931
2012
|
value: {
|
|
1932
|
-
description: words.description,
|
|
2013
|
+
description: words.description ?? "",
|
|
1933
2014
|
layout: props.layout ?? "card",
|
|
1934
2015
|
retry: readOnly ? null : controller.retry,
|
|
1935
2016
|
retryPart: "connection-retry",
|
|
@@ -1945,7 +2026,8 @@ function Outcome$2({ children, controller, ...props }) {
|
|
|
1945
2026
|
function Status$2({ controller, ...props }) {
|
|
1946
2027
|
const { messages } = useDomainKit();
|
|
1947
2028
|
const state = controller.state;
|
|
1948
|
-
const
|
|
2029
|
+
const attached = controller.snapshot?.provider ?? null;
|
|
2030
|
+
const provider = attached === null ? "" : displayName(controller, attached);
|
|
1949
2031
|
const text = () => {
|
|
1950
2032
|
switch (state._tag) {
|
|
1951
2033
|
case "Loading": return messages.loading;
|
|
@@ -1978,9 +2060,10 @@ const rejected = (reason, fields) => {
|
|
|
1978
2060
|
};
|
|
1979
2061
|
/**
|
|
1980
2062
|
* One declared field in shadcn's `Field` anatomy: the label, the control, the note under it, and
|
|
1981
|
-
* the error the provider answered with. The docs link rides the field it explains.
|
|
2063
|
+
* the error the provider answered with. The docs link rides the field it explains. The value is
|
|
2064
|
+
* the form's, not the input's, so a rejection answers beside what the customer typed.
|
|
1982
2065
|
*/
|
|
1983
|
-
function TokenField({ controller, docsUrl, field, invalid }) {
|
|
2066
|
+
function TokenField({ controller, docsUrl, field, invalid, onEnter, value }) {
|
|
1984
2067
|
const { messages } = useDomainKit();
|
|
1985
2068
|
const icons = useIcons();
|
|
1986
2069
|
const id = useId();
|
|
@@ -2003,8 +2086,10 @@ function TokenField({ controller, docsUrl, field, invalid }) {
|
|
|
2003
2086
|
"data-domainkit-part": "field-input",
|
|
2004
2087
|
id,
|
|
2005
2088
|
name: field.name,
|
|
2089
|
+
onChange: (event) => onEnter(event.target.value),
|
|
2006
2090
|
required: field.required,
|
|
2007
|
-
type: field.secret ? "password" : "text"
|
|
2091
|
+
type: field.secret ? "password" : "text",
|
|
2092
|
+
value
|
|
2008
2093
|
}),
|
|
2009
2094
|
docsUrl === null ? null : /* @__PURE__ */ jsxs("a", {
|
|
2010
2095
|
"data-domainkit-part": "field-description",
|
|
@@ -2032,8 +2117,10 @@ function TokenField({ controller, docsUrl, field, invalid }) {
|
|
|
2032
2117
|
* field, so a provider that needs an account id alongside a token needs no code here. The fields
|
|
2033
2118
|
* a provider does not need sit behind a disclosure, so the common form is one field.
|
|
2034
2119
|
*/
|
|
2035
|
-
function Method({ controller, method, provider }) {
|
|
2120
|
+
function Method({ controller, method, onEnter, provider, values }) {
|
|
2036
2121
|
const { messages } = useDomainKit();
|
|
2122
|
+
const icons = useIcons();
|
|
2123
|
+
const [more, setMore] = useState(false);
|
|
2037
2124
|
const state = controller.state;
|
|
2038
2125
|
const busy = state._tag === "Submitting";
|
|
2039
2126
|
const failed = state._tag === "Failure" && state.attempt !== null && state.attempt.provider === provider.id && state.attempt.method === method.kind;
|
|
@@ -2055,39 +2142,50 @@ function Method({ controller, method, provider }) {
|
|
|
2055
2142
|
const answered = rejected(failed && state._tag === "Failure" ? state.error.reason : null, fields);
|
|
2056
2143
|
const required = fields.filter((field) => field.required);
|
|
2057
2144
|
const optional = fields.filter((field) => !field.required);
|
|
2145
|
+
const showMore = more || optional.some((field) => field.name === answered);
|
|
2058
2146
|
const explains = (required.find((field) => field.secret) ?? required[0] ?? optional[0])?.name;
|
|
2059
2147
|
const render = (field) => /* @__PURE__ */ jsx(TokenField, {
|
|
2060
2148
|
controller,
|
|
2061
2149
|
docsUrl: field.name === explains ? method.docsUrl : null,
|
|
2062
2150
|
field,
|
|
2063
|
-
invalid: field.name === answered
|
|
2151
|
+
invalid: field.name === answered,
|
|
2152
|
+
onEnter: (value) => onEnter(field.name, value),
|
|
2153
|
+
value: values[field.name] ?? ""
|
|
2064
2154
|
}, field.name);
|
|
2065
2155
|
return /* @__PURE__ */ jsxs("form", {
|
|
2066
2156
|
"data-domainkit-part": "token-connect",
|
|
2067
2157
|
"data-provider": provider.id,
|
|
2068
2158
|
onSubmit: (event) => {
|
|
2069
2159
|
event.preventDefault();
|
|
2070
|
-
const
|
|
2071
|
-
const values = {};
|
|
2160
|
+
const submitted = {};
|
|
2072
2161
|
for (const field of fields) {
|
|
2073
|
-
const value =
|
|
2074
|
-
if (
|
|
2162
|
+
const value = values[field.name] ?? "";
|
|
2163
|
+
if (value !== "" || field.required) submitted[field.name] = value;
|
|
2075
2164
|
}
|
|
2076
2165
|
controller.connect({
|
|
2077
2166
|
method: method.kind,
|
|
2078
2167
|
provider: provider.id,
|
|
2079
|
-
values
|
|
2168
|
+
values: submitted
|
|
2080
2169
|
});
|
|
2081
2170
|
},
|
|
2082
2171
|
children: [
|
|
2083
2172
|
required.map(render),
|
|
2084
|
-
optional.length === 0 ? null : /* @__PURE__ */ jsxs(
|
|
2173
|
+
optional.length === 0 ? null : /* @__PURE__ */ jsxs(Collapsible.Root, {
|
|
2085
2174
|
"data-domainkit-part": "more-options",
|
|
2086
|
-
|
|
2087
|
-
|
|
2175
|
+
"data-state": showMore ? "open" : "closed",
|
|
2176
|
+
onOpenChange: setMore,
|
|
2177
|
+
open: showMore,
|
|
2178
|
+
children: [/* @__PURE__ */ jsxs(Collapsible.Trigger, {
|
|
2088
2179
|
"data-domainkit-part": "more-options-trigger",
|
|
2089
|
-
children: messages.moreOptions
|
|
2090
|
-
|
|
2180
|
+
children: [messages.moreOptions, /* @__PURE__ */ jsx("span", {
|
|
2181
|
+
"aria-hidden": "true",
|
|
2182
|
+
"data-icon": "inline-end",
|
|
2183
|
+
children: icons.chevron
|
|
2184
|
+
})]
|
|
2185
|
+
}), /* @__PURE__ */ jsx(Collapsible.Panel, {
|
|
2186
|
+
"data-domainkit-part": "more-options-panel",
|
|
2187
|
+
children: optional.map(render)
|
|
2188
|
+
})]
|
|
2091
2189
|
}),
|
|
2092
2190
|
failed && answered === null ? /* @__PURE__ */ jsx(Outcome$2, {
|
|
2093
2191
|
controller,
|
|
@@ -2103,15 +2201,33 @@ function Method({ controller, method, provider }) {
|
|
|
2103
2201
|
});
|
|
2104
2202
|
}
|
|
2105
2203
|
/**
|
|
2106
|
-
* One provider's methods
|
|
2107
|
-
*
|
|
2204
|
+
* One provider's methods, in the order the descriptor declares them: the interactive ones a
|
|
2205
|
+
* customer clicks through, then the token. Where a provider offers both, the interactive method is
|
|
2206
|
+
* the offer and a plain button opens the token form in its place, so the dialog asks for one
|
|
2207
|
+
* decision at a time. With a heading the whole provider is a disclosure the customer opens; the
|
|
2208
|
+
* provider a narrowed dialog is about carries no heading, because the dialog title already names
|
|
2209
|
+
* it.
|
|
2108
2210
|
*/
|
|
2109
|
-
function Authentication({ controller, onOpen, open, provider }) {
|
|
2110
|
-
const
|
|
2211
|
+
function Authentication({ controller, onEnter, onOpen, onType, open, provider, typing, values }) {
|
|
2212
|
+
const { messages } = useDomainKit();
|
|
2213
|
+
const state = controller.state;
|
|
2214
|
+
const render = (method) => /* @__PURE__ */ jsx(Method, {
|
|
2111
2215
|
controller,
|
|
2112
2216
|
method,
|
|
2113
|
-
|
|
2114
|
-
|
|
2217
|
+
onEnter,
|
|
2218
|
+
provider,
|
|
2219
|
+
values
|
|
2220
|
+
}, `${provider.id}:${method.kind}`);
|
|
2221
|
+
const interactive = provider.methods.filter((method) => method.fields === null);
|
|
2222
|
+
const typed = provider.methods.filter((method) => method.fields !== null);
|
|
2223
|
+
const methods = !(interactive.length > 0 && typed.length > 0) ? provider.methods.map(render) : typing ? typed.map(render) : /* @__PURE__ */ jsxs(Fragment, { children: [interactive.map(render), /* @__PURE__ */ jsx("button", {
|
|
2224
|
+
"data-domainkit-part": "method-alternate",
|
|
2225
|
+
"data-provider": provider.id,
|
|
2226
|
+
disabled: state._tag === "Submitting",
|
|
2227
|
+
onClick: onType,
|
|
2228
|
+
type: "button",
|
|
2229
|
+
children: messages.useTokenInstead
|
|
2230
|
+
})] });
|
|
2115
2231
|
return /* @__PURE__ */ jsxs("section", {
|
|
2116
2232
|
"data-domainkit-part": "provider-authentication",
|
|
2117
2233
|
"data-provider": provider.id,
|
|
@@ -2129,10 +2245,26 @@ function Authentication({ controller, onOpen, open, provider }) {
|
|
|
2129
2245
|
* Everything a disconnected domain can do: a connection discovery already found, the connections
|
|
2130
2246
|
* this owner already has, then each provider's declared methods.
|
|
2131
2247
|
*/
|
|
2132
|
-
function Form({ controller, ...props }) {
|
|
2248
|
+
function Form({ controller, provider: narrowed, ...props }) {
|
|
2133
2249
|
const { messages } = useDomainKit();
|
|
2134
2250
|
const [opened, setOpened] = useState(null);
|
|
2251
|
+
const [entered, setEntered] = useState(null);
|
|
2135
2252
|
const state = controller.state;
|
|
2253
|
+
const owner = controller.domain;
|
|
2254
|
+
if (entered !== null && (entered.domain !== owner || state._tag === "Connected")) setEntered(null);
|
|
2255
|
+
const kept = entered?.domain === owner ? entered : null;
|
|
2256
|
+
const enter = (provider, name, value) => setEntered((previous) => {
|
|
2257
|
+
const held = previous?.domain === owner && previous.provider === provider ? previous : null;
|
|
2258
|
+
return {
|
|
2259
|
+
domain: owner,
|
|
2260
|
+
provider,
|
|
2261
|
+
values: held === null ? { [name]: value } : {
|
|
2262
|
+
...held.values,
|
|
2263
|
+
[name]: value
|
|
2264
|
+
}
|
|
2265
|
+
};
|
|
2266
|
+
});
|
|
2267
|
+
const valuesFor = (provider) => kept?.provider === provider ? kept.values : {};
|
|
2136
2268
|
const snapshot = controller.snapshot;
|
|
2137
2269
|
const discovery = controller.discovery;
|
|
2138
2270
|
const candidates = state._tag === "SelectionRequired" ? state.candidates.map((candidate) => ({
|
|
@@ -2142,13 +2274,17 @@ function Form({ controller, ...props }) {
|
|
|
2142
2274
|
const resolved = discovery !== null && discovery._tag === "Resolved" ? discovery : null;
|
|
2143
2275
|
const reusable = snapshot?.reusable ?? [];
|
|
2144
2276
|
const providers = controller.providers;
|
|
2145
|
-
const host = hostProvider(controller);
|
|
2146
|
-
const
|
|
2277
|
+
const host = providers.find((entry) => entry.id === narrowed) ?? hostProvider(controller);
|
|
2278
|
+
const [typing, setTyping] = useState(null);
|
|
2147
2279
|
const authentication = (provider, open) => /* @__PURE__ */ jsx(Authentication, {
|
|
2148
2280
|
controller,
|
|
2281
|
+
onEnter: (name, value) => enter(provider.id, name, value),
|
|
2149
2282
|
onOpen: () => setOpened(open ? "" : provider.id),
|
|
2283
|
+
onType: () => setTyping(provider.id),
|
|
2150
2284
|
open,
|
|
2151
|
-
provider
|
|
2285
|
+
provider,
|
|
2286
|
+
typing: typing === provider.id,
|
|
2287
|
+
values: valuesFor(provider.id)
|
|
2152
2288
|
}, provider.id);
|
|
2153
2289
|
return usePart("div", props, { status: state._tag }, {
|
|
2154
2290
|
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -2196,16 +2332,19 @@ function Form({ controller, ...props }) {
|
|
|
2196
2332
|
providers.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
2197
2333
|
"data-domainkit-part": "target-unavailable",
|
|
2198
2334
|
children: messages.noProviders
|
|
2199
|
-
}) : host === null ?
|
|
2335
|
+
}) : host === null ? providers.map((provider) => authentication(provider, opened === null ? provider.id === providers[0]?.id : opened === provider.id)) : /* @__PURE__ */ jsxs(Fragment, { children: [typing === host.id ? /* @__PURE__ */ jsx("button", {
|
|
2336
|
+
"data-domainkit-part": "dialog-back",
|
|
2337
|
+
onClick: () => setTyping(null),
|
|
2338
|
+
type: "button",
|
|
2339
|
+
children: messages.back
|
|
2340
|
+
}) : null, /* @__PURE__ */ jsx(Authentication, {
|
|
2200
2341
|
controller,
|
|
2342
|
+
onEnter: (name, value) => enter(host.id, name, value),
|
|
2343
|
+
onType: () => setTyping(host.id),
|
|
2201
2344
|
open: true,
|
|
2202
|
-
provider: host
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
children: [/* @__PURE__ */ jsx("summary", {
|
|
2206
|
-
"data-domainkit-part": "other-providers-trigger",
|
|
2207
|
-
children: messages.useAnotherProvider
|
|
2208
|
-
}), others.map((provider) => authentication(provider, opened === provider.id))]
|
|
2345
|
+
provider: host,
|
|
2346
|
+
typing: typing === host.id,
|
|
2347
|
+
values: valuesFor(host.id)
|
|
2209
2348
|
})] })
|
|
2210
2349
|
] }),
|
|
2211
2350
|
"data-domainkit-part": "connection-form"
|
|
@@ -2214,19 +2353,26 @@ function Form({ controller, ...props }) {
|
|
|
2214
2353
|
/** The connect surface. Without `render` it is a Base UI dialog behind `trigger`. */
|
|
2215
2354
|
function Dialog$2({ children, controller, render, title, trigger }) {
|
|
2216
2355
|
const { colorScheme, messages, portalContainer, themeStyle } = useDomainKit();
|
|
2356
|
+
const icons = useIcons();
|
|
2217
2357
|
const readOnly = useReadOnly();
|
|
2218
2358
|
const [open, setOpen] = useState(false);
|
|
2219
2359
|
const state = controller.state;
|
|
2220
2360
|
const busy = state._tag === "Submitting";
|
|
2221
2361
|
const unattributed = state._tag === "Failure" && !answeredInPlace(controller);
|
|
2222
2362
|
const snapshot = controller.snapshot;
|
|
2363
|
+
const [chosen, setChosen] = useState(null);
|
|
2223
2364
|
const attached = snapshot?.provider ?? null;
|
|
2224
2365
|
const host = hostProvider(controller);
|
|
2225
|
-
const
|
|
2366
|
+
const narrowed = controller.providers.find((entry) => entry.id === (chosen ?? attached)) ?? host;
|
|
2367
|
+
const named = narrowed?.name ?? null;
|
|
2226
2368
|
const heading = title ?? (named === null ? messages.connectAnyTitle : messages.connectTitle(named));
|
|
2369
|
+
const alternatives = narrowed === null ? [] : controller.providers.filter((entry) => entry.id !== narrowed.id);
|
|
2227
2370
|
const running = state._tag === "Loading" || state._tag === "Submitting" || state._tag === "Redirecting" || state._tag === "SelectionRequired";
|
|
2228
2371
|
const label = named === null ? messages.connectAnyTitle : messages.connect;
|
|
2229
|
-
const body = children ?? /* @__PURE__ */ jsx(Form, {
|
|
2372
|
+
const body = children ?? /* @__PURE__ */ jsx(Form, {
|
|
2373
|
+
controller,
|
|
2374
|
+
...chosen === null ? {} : { provider: chosen }
|
|
2375
|
+
});
|
|
2230
2376
|
if (readOnly) return /* @__PURE__ */ jsx(Fragment, {});
|
|
2231
2377
|
if (render !== void 0) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
|
|
2232
2378
|
"data-domainkit-part": "connection-trigger",
|
|
@@ -2264,20 +2410,55 @@ function Dialog$2({ children, controller, render, title, trigger }) {
|
|
|
2264
2410
|
children: [
|
|
2265
2411
|
/* @__PURE__ */ jsxs("div", {
|
|
2266
2412
|
"data-domainkit-part": "dialog-header",
|
|
2267
|
-
children: [
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
"data-domainkit-part": "dialog-
|
|
2274
|
-
children:
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2413
|
+
children: [
|
|
2414
|
+
narrowed === null ? null : /* @__PURE__ */ jsx("div", {
|
|
2415
|
+
"data-domainkit-part": "dialog-media",
|
|
2416
|
+
children: /* @__PURE__ */ jsx(Mark, { provider: narrowed })
|
|
2417
|
+
}),
|
|
2418
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2419
|
+
"data-domainkit-part": "dialog-heading",
|
|
2420
|
+
children: [/* @__PURE__ */ jsx(Dialog.Title, {
|
|
2421
|
+
"data-domainkit-part": "dialog-title",
|
|
2422
|
+
children: alternatives.length === 0 || title !== void 0 ? heading : /* @__PURE__ */ jsxs(Menu.Root, { children: [/* @__PURE__ */ jsxs(Menu.Trigger, {
|
|
2423
|
+
"data-domainkit-part": "dialog-provider-menu",
|
|
2424
|
+
disabled: busy,
|
|
2425
|
+
children: [heading, /* @__PURE__ */ jsx("span", {
|
|
2426
|
+
"aria-hidden": "true",
|
|
2427
|
+
"data-icon": "inline-end",
|
|
2428
|
+
children: icons.chevron
|
|
2429
|
+
})]
|
|
2430
|
+
}), /* @__PURE__ */ jsx(Menu.Portal, {
|
|
2431
|
+
container: portalContainer,
|
|
2432
|
+
children: /* @__PURE__ */ jsx(Menu.Positioner, {
|
|
2433
|
+
align: "start",
|
|
2434
|
+
"data-domainkit-part": "provider-menu-positioner",
|
|
2435
|
+
sideOffset: 6,
|
|
2436
|
+
children: /* @__PURE__ */ jsx(Menu.Popup, {
|
|
2437
|
+
"aria-label": messages.useAnotherProvider,
|
|
2438
|
+
"data-color-scheme": colorScheme,
|
|
2439
|
+
"data-domainkit-part": "provider-menu",
|
|
2440
|
+
"data-domainkit-root": "",
|
|
2441
|
+
style: themeStyle,
|
|
2442
|
+
children: alternatives.map((entry) => /* @__PURE__ */ jsxs(Menu.Item, {
|
|
2443
|
+
"data-domainkit-part": "provider-menu-item",
|
|
2444
|
+
"data-provider": entry.id,
|
|
2445
|
+
onClick: () => setChosen(entry.id),
|
|
2446
|
+
children: [/* @__PURE__ */ jsx(Mark, { provider: entry }), entry.name]
|
|
2447
|
+
}, entry.id))
|
|
2448
|
+
})
|
|
2449
|
+
})
|
|
2450
|
+
})] })
|
|
2451
|
+
}), /* @__PURE__ */ jsx(Dialog.Description, {
|
|
2452
|
+
"data-domainkit-part": "dialog-description",
|
|
2453
|
+
children: messages.connectDescription(snapshot?.domain ?? "")
|
|
2454
|
+
})]
|
|
2455
|
+
}),
|
|
2456
|
+
busy ? null : /* @__PURE__ */ jsx(Dialog.Close, {
|
|
2457
|
+
"aria-label": messages.close,
|
|
2458
|
+
"data-domainkit-part": "dialog-close",
|
|
2459
|
+
children: "×"
|
|
2460
|
+
})
|
|
2461
|
+
]
|
|
2281
2462
|
}),
|
|
2282
2463
|
/* @__PURE__ */ jsxs("div", {
|
|
2283
2464
|
"data-domainkit-part": "dialog-body",
|
|
@@ -2300,6 +2481,21 @@ function Dialog$2({ children, controller, render, title, trigger }) {
|
|
|
2300
2481
|
});
|
|
2301
2482
|
}
|
|
2302
2483
|
/**
|
|
2484
|
+
* Whether the connect surface has anything to offer for this domain: a provider that serves the
|
|
2485
|
+
* zone, a connection discovery already found, one the owner already holds, or a command already
|
|
2486
|
+
* running. Never while a connection is held, which is what `holdsConnection` answers. `Prompt`
|
|
2487
|
+
* renders on this, and `Domain.Flow` reports it so a host can order its own offers beside
|
|
2488
|
+
* DomainKit's rather than competing with them.
|
|
2489
|
+
*/
|
|
2490
|
+
const offering = (controller, connect = "detected") => {
|
|
2491
|
+
if (holdsConnection(controller)) return false;
|
|
2492
|
+
const state = controller.state;
|
|
2493
|
+
if (state._tag === "Submitting" || state._tag === "Redirecting" || state._tag === "SelectionRequired" || state._tag === "Reconnect") return true;
|
|
2494
|
+
if (connect !== "detected") return connect === "always";
|
|
2495
|
+
const discovery = controller.discovery;
|
|
2496
|
+
return hostProvider(controller) !== null || discovery !== null && discovery._tag !== "NotFound" || (controller.snapshot?.reusable.length ?? 0) > 0;
|
|
2497
|
+
};
|
|
2498
|
+
/**
|
|
2303
2499
|
* The disconnected offer: the provider whose nameservers serve the domain, what that means, and
|
|
2304
2500
|
* the trigger that connects it. With no host detected there is nothing to offer, so nothing
|
|
2305
2501
|
* renders unless the host application asks for the dialog anyway.
|
|
@@ -2309,9 +2505,6 @@ function Prompt({ connect = "detected", controller, ...props }) {
|
|
|
2309
2505
|
const readOnly = useReadOnly();
|
|
2310
2506
|
const state = controller.state;
|
|
2311
2507
|
const host = hostProvider(controller);
|
|
2312
|
-
const discovery = controller.discovery;
|
|
2313
|
-
const offers = host !== null || discovery !== null && discovery._tag !== "NotFound" || (controller.snapshot?.reusable.length ?? 0) > 0;
|
|
2314
|
-
const inFlight = state._tag === "Submitting" || state._tag === "Redirecting" || state._tag === "SelectionRequired" || state._tag === "Reconnect";
|
|
2315
2508
|
const element = usePart("div", props, { status: state._tag }, {
|
|
2316
2509
|
children: /* @__PURE__ */ jsxs(Fragment, { children: [host === null ? null : /* @__PURE__ */ jsxs("div", {
|
|
2317
2510
|
"data-domainkit-part": "host-identity",
|
|
@@ -2330,38 +2523,242 @@ function Prompt({ connect = "detected", controller, ...props }) {
|
|
|
2330
2523
|
"data-domainkit-part": "connect-prompt",
|
|
2331
2524
|
"data-host": host?.id
|
|
2332
2525
|
});
|
|
2333
|
-
if (!
|
|
2526
|
+
if (!offering(controller, connect)) return null;
|
|
2334
2527
|
if (readOnly && host === null) return null;
|
|
2335
2528
|
return element;
|
|
2336
2529
|
}
|
|
2337
|
-
/**
|
|
2338
|
-
|
|
2530
|
+
/** The plan an attempt holds, whatever step it is on; `null` before there is one. */
|
|
2531
|
+
const planOf = (state) => {
|
|
2532
|
+
switch (state._tag) {
|
|
2533
|
+
case "Planned":
|
|
2534
|
+
case "Approving":
|
|
2535
|
+
case "Applying":
|
|
2536
|
+
case "Rejecting":
|
|
2537
|
+
case "Rejected": return state.plan;
|
|
2538
|
+
case "Applied": return state.plan;
|
|
2539
|
+
case "Idle":
|
|
2540
|
+
case "Planning":
|
|
2541
|
+
case "Failure": return null;
|
|
2542
|
+
}
|
|
2543
|
+
};
|
|
2544
|
+
/**
|
|
2545
|
+
* Letting a provider go, with the records DomainKit added as one decision rather than two.
|
|
2546
|
+
* Confirming removes what the domain's apply receipt proves DomainKit created, then releases the
|
|
2547
|
+
* connection; a plan with nothing in it is not a failure, so it goes straight to the release.
|
|
2548
|
+
*
|
|
2549
|
+
* The cleanup is this dialog's own, so nothing re-reads the snapshot in between: the disconnect
|
|
2550
|
+
* needs the connection the cleanup just used.
|
|
2551
|
+
*/
|
|
2552
|
+
function DisconnectDialog({ controller, onCleaned, trigger }) {
|
|
2553
|
+
const { capabilities, colorScheme, messages, portalContainer, themeStyle } = useDomainKit();
|
|
2554
|
+
const readOnly = useReadOnly();
|
|
2555
|
+
const [open, setOpen] = useState(false);
|
|
2556
|
+
const [alsoRemove, setAlsoRemove] = useState(true);
|
|
2557
|
+
const [removing, setRemoving] = useState(false);
|
|
2558
|
+
const snapshot = controller.snapshot;
|
|
2559
|
+
const receiptId = snapshot?.lastReceiptId ?? null;
|
|
2560
|
+
const removable = capabilities.includes("cleanup") && receiptId !== null;
|
|
2561
|
+
const shared = (snapshot?.connectionDomains ?? 0) > 1;
|
|
2562
|
+
const [everyDomain, setEveryDomain] = useState(false);
|
|
2563
|
+
const cleanup = useController$3({
|
|
2564
|
+
domain: controller.domain,
|
|
2565
|
+
...onCleaned === void 0 ? {} : { onCleaned },
|
|
2566
|
+
...receiptId === null ? {} : { receiptId: Receipt.ReceiptId.make(receiptId) }
|
|
2567
|
+
});
|
|
2568
|
+
const { approve, plan, state: cleaning } = cleanup;
|
|
2569
|
+
const removals = planOf(cleaning);
|
|
2570
|
+
useEffect(() => {
|
|
2571
|
+
if (open && removable && cleaning._tag === "Idle") plan();
|
|
2572
|
+
}, [
|
|
2573
|
+
cleaning,
|
|
2574
|
+
open,
|
|
2575
|
+
plan,
|
|
2576
|
+
removable
|
|
2577
|
+
]);
|
|
2578
|
+
const detach = controller.detach;
|
|
2579
|
+
const release = controller.disconnect;
|
|
2580
|
+
const disconnect = useCallback(() => shared && !everyDomain ? detach() : release(), [
|
|
2581
|
+
detach,
|
|
2582
|
+
everyDomain,
|
|
2583
|
+
release,
|
|
2584
|
+
shared
|
|
2585
|
+
]);
|
|
2586
|
+
const releasing = controller.state._tag === "Submitting";
|
|
2587
|
+
useEffect(() => {
|
|
2588
|
+
if (!removing) return;
|
|
2589
|
+
if (cleaning._tag === "Applied") {
|
|
2590
|
+
setRemoving(false);
|
|
2591
|
+
disconnect();
|
|
2592
|
+
return;
|
|
2593
|
+
}
|
|
2594
|
+
if (cleaning._tag === "Failure" || cleaning._tag === "Rejected") setRemoving(false);
|
|
2595
|
+
}, [
|
|
2596
|
+
cleaning,
|
|
2597
|
+
disconnect,
|
|
2598
|
+
removing
|
|
2599
|
+
]);
|
|
2600
|
+
const busy = removing || releasing;
|
|
2601
|
+
const settling = removable && removals === null;
|
|
2602
|
+
const provider = snapshot?.provider ?? null;
|
|
2603
|
+
const named = provider === null ? "" : displayName(controller, provider);
|
|
2604
|
+
const heading = provider === null ? messages.disconnect : messages.disconnectTitle(named);
|
|
2605
|
+
const count = removals?.operations.length ?? 0;
|
|
2606
|
+
const choosable = removable && count > 0;
|
|
2607
|
+
if (readOnly) return /* @__PURE__ */ jsx(Fragment, {});
|
|
2608
|
+
return /* @__PURE__ */ jsxs(Dialog.Root, {
|
|
2609
|
+
onOpenChange: (next, details) => {
|
|
2610
|
+
if (!next && busy) {
|
|
2611
|
+
details.cancel();
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
setOpen(next);
|
|
2615
|
+
},
|
|
2616
|
+
open,
|
|
2617
|
+
children: [/* @__PURE__ */ jsx(Dialog.Trigger, {
|
|
2618
|
+
"data-domainkit-part": "disconnect-action",
|
|
2619
|
+
children: trigger ?? messages.disconnect
|
|
2620
|
+
}), /* @__PURE__ */ jsxs(Dialog.Portal, {
|
|
2621
|
+
container: portalContainer,
|
|
2622
|
+
children: [/* @__PURE__ */ jsx(Dialog.Backdrop, {
|
|
2623
|
+
"data-color-scheme": colorScheme,
|
|
2624
|
+
"data-domainkit-part": "dialog-backdrop",
|
|
2625
|
+
"data-domainkit-root": "",
|
|
2626
|
+
style: themeStyle
|
|
2627
|
+
}), /* @__PURE__ */ jsxs(Dialog.Popup, {
|
|
2628
|
+
"data-color-scheme": colorScheme,
|
|
2629
|
+
"data-cleanup": choosable ? "offered" : "none",
|
|
2630
|
+
"data-domainkit-part": "disconnect-dialog",
|
|
2631
|
+
"data-domainkit-root": "",
|
|
2632
|
+
style: themeStyle,
|
|
2633
|
+
children: [
|
|
2634
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2635
|
+
"data-domainkit-part": "dialog-header",
|
|
2636
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2637
|
+
"data-domainkit-part": "dialog-heading",
|
|
2638
|
+
children: [/* @__PURE__ */ jsx(Dialog.Title, {
|
|
2639
|
+
"data-domainkit-part": "dialog-title",
|
|
2640
|
+
children: heading
|
|
2641
|
+
}), /* @__PURE__ */ jsx(Dialog.Description, {
|
|
2642
|
+
"data-domainkit-part": "dialog-description",
|
|
2643
|
+
children: messages.disconnectConsent(snapshot?.domain ?? "", named)
|
|
2644
|
+
})]
|
|
2645
|
+
}), busy ? null : /* @__PURE__ */ jsx(Dialog.Close, {
|
|
2646
|
+
"aria-label": messages.close,
|
|
2647
|
+
"data-domainkit-part": "dialog-close",
|
|
2648
|
+
children: "×"
|
|
2649
|
+
})]
|
|
2650
|
+
}),
|
|
2651
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2652
|
+
"data-domainkit-part": "dialog-body",
|
|
2653
|
+
children: [
|
|
2654
|
+
shared ? /* @__PURE__ */ jsxs("fieldset", {
|
|
2655
|
+
"data-domainkit-part": "disconnect-scope",
|
|
2656
|
+
children: [
|
|
2657
|
+
/* @__PURE__ */ jsx("legend", { children: messages.disconnectScope }),
|
|
2658
|
+
/* @__PURE__ */ jsxs("label", { children: [/* @__PURE__ */ jsx("input", {
|
|
2659
|
+
checked: !everyDomain,
|
|
2660
|
+
disabled: busy,
|
|
2661
|
+
name: "disconnect-scope",
|
|
2662
|
+
onChange: () => setEveryDomain(false),
|
|
2663
|
+
type: "radio"
|
|
2664
|
+
}), messages.disconnectThisDomain] }),
|
|
2665
|
+
/* @__PURE__ */ jsxs("label", { children: [/* @__PURE__ */ jsx("input", {
|
|
2666
|
+
checked: everyDomain,
|
|
2667
|
+
disabled: busy,
|
|
2668
|
+
name: "disconnect-scope",
|
|
2669
|
+
onChange: () => setEveryDomain(true),
|
|
2670
|
+
type: "radio"
|
|
2671
|
+
}), messages.disconnectEveryDomain(snapshot?.connectionDomains ?? 0)] })
|
|
2672
|
+
]
|
|
2673
|
+
}) : null,
|
|
2674
|
+
choosable ? /* @__PURE__ */ jsxs("div", {
|
|
2675
|
+
"data-domainkit-part": "disconnect-cleanup",
|
|
2676
|
+
"data-state": alsoRemove ? "on" : "off",
|
|
2677
|
+
children: [/* @__PURE__ */ jsxs("label", {
|
|
2678
|
+
"data-domainkit-part": "disconnect-cleanup-label",
|
|
2679
|
+
children: [/* @__PURE__ */ jsx(Switch.Root, {
|
|
2680
|
+
checked: alsoRemove,
|
|
2681
|
+
"data-domainkit-part": "disconnect-cleanup-switch",
|
|
2682
|
+
disabled: busy,
|
|
2683
|
+
onCheckedChange: setAlsoRemove,
|
|
2684
|
+
children: /* @__PURE__ */ jsx(Switch.Thumb, { "data-domainkit-part": "disconnect-cleanup-thumb" })
|
|
2685
|
+
}), messages.disconnectWithCleanup(count)]
|
|
2686
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
2687
|
+
"data-domainkit-part": "disconnect-cleanup-panel",
|
|
2688
|
+
children: [removals === null ? null : /* @__PURE__ */ jsx(List, { plan: removals }), alsoRemove ? null : /* @__PURE__ */ jsx("p", {
|
|
2689
|
+
"data-domainkit-part": "disconnect-cleanup-note",
|
|
2690
|
+
children: messages.disconnectKeepsRecords(named)
|
|
2691
|
+
})]
|
|
2692
|
+
})]
|
|
2693
|
+
}) : null,
|
|
2694
|
+
/* @__PURE__ */ jsx(Status$3, { controller: cleanup }),
|
|
2695
|
+
releasing ? /* @__PURE__ */ jsx("p", {
|
|
2696
|
+
"data-domainkit-part": "disconnect-status",
|
|
2697
|
+
role: "status",
|
|
2698
|
+
children: messages.disconnecting
|
|
2699
|
+
}) : null,
|
|
2700
|
+
/* @__PURE__ */ jsx(Outcome$3, { controller: cleanup }),
|
|
2701
|
+
/* @__PURE__ */ jsx(Outcome$2, { controller })
|
|
2702
|
+
]
|
|
2703
|
+
}),
|
|
2704
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2705
|
+
"data-domainkit-part": "dialog-footer",
|
|
2706
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
2707
|
+
"data-domainkit-part": "disconnect-confirm",
|
|
2708
|
+
disabled: busy || settling,
|
|
2709
|
+
onClick: () => {
|
|
2710
|
+
if (choosable && alsoRemove) {
|
|
2711
|
+
setRemoving(true);
|
|
2712
|
+
approve();
|
|
2713
|
+
return;
|
|
2714
|
+
}
|
|
2715
|
+
disconnect();
|
|
2716
|
+
},
|
|
2717
|
+
type: "button",
|
|
2718
|
+
children: messages.disconnect
|
|
2719
|
+
}), busy ? null : /* @__PURE__ */ jsx(Dialog.Close, {
|
|
2720
|
+
"data-domainkit-part": "dialog-cancel",
|
|
2721
|
+
children: messages.cancel
|
|
2722
|
+
})]
|
|
2723
|
+
})
|
|
2724
|
+
]
|
|
2725
|
+
})]
|
|
2726
|
+
})]
|
|
2727
|
+
});
|
|
2728
|
+
}
|
|
2729
|
+
/**
|
|
2730
|
+
* A connected domain, with the prompt card's anatomy: the provider's mark, the name the customer
|
|
2731
|
+
* knows it by, where the connection stands, and the one action that ends it.
|
|
2732
|
+
*/
|
|
2733
|
+
function Card({ controller, onCleaned, ...props }) {
|
|
2339
2734
|
const { messages } = useDomainKit();
|
|
2340
2735
|
const readOnly = useReadOnly();
|
|
2341
2736
|
const state = controller.state;
|
|
2342
2737
|
const snapshot = controller.snapshot;
|
|
2343
2738
|
const provider = controller.providers.find((entry) => entry.id === snapshot?.provider);
|
|
2739
|
+
const standing = state._tag === "Reconnect" ? messages.needsReconnect : messages.connected;
|
|
2344
2740
|
return usePart("div", props, { status: state._tag }, {
|
|
2345
2741
|
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2346
2742
|
/* @__PURE__ */ jsxs("div", {
|
|
2347
2743
|
"data-domainkit-part": "connected-identity",
|
|
2348
|
-
children: [
|
|
2744
|
+
children: [
|
|
2745
|
+
provider === void 0 ? null : /* @__PURE__ */ jsx(Mark, { provider }),
|
|
2746
|
+
/* @__PURE__ */ jsx("span", {
|
|
2747
|
+
"data-domainkit-part": "host-name",
|
|
2748
|
+
children: provider?.name ?? snapshot?.provider ?? ""
|
|
2749
|
+
}),
|
|
2750
|
+
/* @__PURE__ */ jsx("span", {
|
|
2751
|
+
"data-domainkit-part": "host-statement",
|
|
2752
|
+
children: standing
|
|
2753
|
+
})
|
|
2754
|
+
]
|
|
2349
2755
|
}),
|
|
2350
|
-
readOnly ? null : /* @__PURE__ */
|
|
2756
|
+
readOnly ? null : /* @__PURE__ */ jsx("div", {
|
|
2351
2757
|
"data-domainkit-part": "connected-actions",
|
|
2352
|
-
children:
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
type: "button",
|
|
2357
|
-
children: messages.detach
|
|
2358
|
-
}), /* @__PURE__ */ jsx("button", {
|
|
2359
|
-
"data-domainkit-part": "disconnect-action",
|
|
2360
|
-
disabled: state._tag === "Submitting",
|
|
2361
|
-
onClick: controller.disconnect,
|
|
2362
|
-
type: "button",
|
|
2363
|
-
children: messages.disconnect
|
|
2364
|
-
})]
|
|
2758
|
+
children: /* @__PURE__ */ jsx(DisconnectDialog, {
|
|
2759
|
+
controller,
|
|
2760
|
+
...onCleaned === void 0 ? {} : { onCleaned }
|
|
2761
|
+
})
|
|
2365
2762
|
}),
|
|
2366
2763
|
/* @__PURE__ */ jsx(Outcome$2, { controller })
|
|
2367
2764
|
] }),
|
|
@@ -2369,16 +2766,18 @@ function Card({ controller, ...props }) {
|
|
|
2369
2766
|
});
|
|
2370
2767
|
}
|
|
2371
2768
|
/** Connection on its own: the card once connected, the prompt until then. */
|
|
2372
|
-
function Flow$2({ connect, domain, returnTo, ...props }) {
|
|
2769
|
+
function Flow$2({ connect, domain, onCleaned, returnTo, ...props }) {
|
|
2373
2770
|
const controller = useController$2({
|
|
2374
2771
|
domain,
|
|
2375
2772
|
...returnTo === void 0 ? {} : { returnTo }
|
|
2376
2773
|
});
|
|
2377
|
-
const state = controller.state;
|
|
2378
2774
|
return /* @__PURE__ */ jsx(Root$2, {
|
|
2379
2775
|
controller,
|
|
2380
2776
|
...props,
|
|
2381
|
-
children:
|
|
2777
|
+
children: holdsConnection(controller) ? /* @__PURE__ */ jsx(Card, {
|
|
2778
|
+
controller,
|
|
2779
|
+
...onCleaned === void 0 ? {} : { onCleaned }
|
|
2780
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Prompt, {
|
|
2382
2781
|
controller,
|
|
2383
2782
|
...connect === void 0 ? {} : { connect }
|
|
2384
2783
|
}), answeredInPlace(controller) ? null : /* @__PURE__ */ jsx(Outcome$2, { controller })] })
|
|
@@ -2462,13 +2861,16 @@ function Flow$1({ domain, onApplied, requirements, trigger, ...props }) {
|
|
|
2462
2861
|
requirements,
|
|
2463
2862
|
...onApplied === void 0 ? {} : { onApplied }
|
|
2464
2863
|
});
|
|
2864
|
+
const [open, setOpen] = useState(false);
|
|
2465
2865
|
return /* @__PURE__ */ jsxs(Root$1, {
|
|
2466
2866
|
controller,
|
|
2467
2867
|
...props,
|
|
2468
2868
|
children: [/* @__PURE__ */ jsx(Dialog$1, {
|
|
2469
2869
|
controller,
|
|
2870
|
+
onOpenChange: setOpen,
|
|
2871
|
+
open,
|
|
2470
2872
|
...trigger === void 0 ? {} : { trigger }
|
|
2471
|
-
}), /* @__PURE__ */ jsx(Outcome$1, { controller })]
|
|
2873
|
+
}), open ? null : /* @__PURE__ */ jsx(Outcome$1, { controller })]
|
|
2472
2874
|
});
|
|
2473
2875
|
}
|
|
2474
2876
|
//#endregion
|
|
@@ -2666,7 +3068,7 @@ function Outcome({ children, controller, ...props }) {
|
|
|
2666
3068
|
const words = outcome(state.error, messages);
|
|
2667
3069
|
return /* @__PURE__ */ jsx(Provider, {
|
|
2668
3070
|
value: {
|
|
2669
|
-
description: words.description,
|
|
3071
|
+
description: words.description ?? "",
|
|
2670
3072
|
layout: props.layout ?? "card",
|
|
2671
3073
|
retry: controller.retry,
|
|
2672
3074
|
retryPart: "verification-retry",
|
|
@@ -2723,9 +3125,25 @@ function Status({ controller, render, ...props }) {
|
|
|
2723
3125
|
//#endregion
|
|
2724
3126
|
//#region src/domain.tsx
|
|
2725
3127
|
var domain_exports = /* @__PURE__ */ __exportAll({ Flow: () => Flow });
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
3128
|
+
/** Built from primitives alone, so the effect that announces it depends on exactly those. */
|
|
3129
|
+
const flowState = (input) => ({
|
|
3130
|
+
applied: input.receipt !== null,
|
|
3131
|
+
connected: input.connected,
|
|
3132
|
+
connection: input.status,
|
|
3133
|
+
offering: input.offering,
|
|
3134
|
+
provider: input.provider,
|
|
3135
|
+
provisioning: input.planning,
|
|
3136
|
+
receiptId: input.receipt === null ? null : Receipt.ReceiptId.make(input.receipt)
|
|
3137
|
+
});
|
|
3138
|
+
function DefaultConnection({ connect, controller, onCleaned }) {
|
|
3139
|
+
const disconnected = controller.state._tag === "Disconnected";
|
|
3140
|
+
const host = hostProvider(controller);
|
|
3141
|
+
if (disconnected && host === null && !offering(controller, connect)) return null;
|
|
3142
|
+
const stated = disconnected && host !== null && offering(controller, connect);
|
|
3143
|
+
return holdsConnection(controller) ? /* @__PURE__ */ jsx(Card, {
|
|
3144
|
+
controller,
|
|
3145
|
+
...onCleaned === void 0 ? {} : { onCleaned }
|
|
3146
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2729
3147
|
stated ? null : /* @__PURE__ */ jsx(Status$2, { controller }),
|
|
2730
3148
|
/* @__PURE__ */ jsx(Prompt, {
|
|
2731
3149
|
connect,
|
|
@@ -2734,47 +3152,24 @@ function DefaultConnection({ connect, controller }) {
|
|
|
2734
3152
|
answeredInPlace(controller) ? null : /* @__PURE__ */ jsx(Outcome$2, { controller })
|
|
2735
3153
|
] });
|
|
2736
3154
|
}
|
|
2737
|
-
function DefaultActions({
|
|
2738
|
-
const { capabilities, messages } = useDomainKit();
|
|
3155
|
+
function DefaultActions({ connection, onOpenChange, open, provisioning }) {
|
|
2739
3156
|
const readOnly = useReadOnly();
|
|
2740
|
-
const
|
|
3157
|
+
const { capabilities } = useDomainKit();
|
|
2741
3158
|
const connected = connection.state._tag === "Connected";
|
|
2742
3159
|
if (readOnly) return null;
|
|
2743
|
-
|
|
2744
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
type: "button",
|
|
2750
|
-
children: messages.reviewChanges
|
|
2751
|
-
}),
|
|
2752
|
-
/* @__PURE__ */ jsx(Status$1, { controller: provisioning }),
|
|
2753
|
-
/* @__PURE__ */ jsx(Actions, { controller: provisioning }),
|
|
2754
|
-
/* @__PURE__ */ jsx(Outcome$1, { controller: provisioning })
|
|
2755
|
-
] }) : null, capabilities.includes("cleanup") && connected && hasReceipt ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2756
|
-
/* @__PURE__ */ jsxs("button", {
|
|
2757
|
-
"data-domainkit-part": "cleanup-trigger",
|
|
2758
|
-
disabled: cleanup.state._tag === "Planning",
|
|
2759
|
-
onClick: cleanup.plan,
|
|
2760
|
-
type: "button",
|
|
2761
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
2762
|
-
"aria-hidden": "true",
|
|
2763
|
-
"data-icon": "inline-start",
|
|
2764
|
-
children: icons.close
|
|
2765
|
-
}), messages.cleanUp]
|
|
2766
|
-
}),
|
|
2767
|
-
/* @__PURE__ */ jsx(Status$3, { controller: cleanup }),
|
|
2768
|
-
/* @__PURE__ */ jsx(Actions$1, { controller: cleanup }),
|
|
2769
|
-
/* @__PURE__ */ jsx(Outcome$3, { controller: cleanup })
|
|
2770
|
-
] }) : null] });
|
|
3160
|
+
if (!capabilities.includes("provisioning") || !connected) return null;
|
|
3161
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Dialog$1, {
|
|
3162
|
+
controller: provisioning,
|
|
3163
|
+
onOpenChange,
|
|
3164
|
+
open
|
|
3165
|
+
}), open ? null : /* @__PURE__ */ jsx(Outcome$1, { controller: provisioning })] });
|
|
2771
3166
|
}
|
|
2772
3167
|
/**
|
|
2773
3168
|
* The whole lifecycle for one domain: connect, review, apply, observe, clean up. The flow renders
|
|
2774
3169
|
* only what the transport declares, and every part of it is a slot with a default. It adds no
|
|
2775
3170
|
* layout containers of its own, so a host's grid can place the slot output directly.
|
|
2776
3171
|
*/
|
|
2777
|
-
function Flow({ connect = "detected", domain, onApplied, onCleaned, readOnly, requirements, returnTo, slots = {}, ...props }) {
|
|
3172
|
+
function Flow({ connect = "detected", domain, onApplied, onCleaned, onState, readOnly, requirements, returnTo, review = "auto", slots = {}, ...props }) {
|
|
2778
3173
|
const { capabilities } = useDomainKit();
|
|
2779
3174
|
const inherited = useReadOnly();
|
|
2780
3175
|
const connection = useController$2({
|
|
@@ -2803,17 +3198,70 @@ function Flow({ connect = "detected", domain, onApplied, onCleaned, readOnly, re
|
|
|
2803
3198
|
requirements
|
|
2804
3199
|
});
|
|
2805
3200
|
const readiness = verification.readiness;
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
3201
|
+
const status = connection.state._tag;
|
|
3202
|
+
const planning = provisioning.state._tag;
|
|
3203
|
+
const [reviewing, setReviewing] = useState(false);
|
|
3204
|
+
const [reviewed, setReviewed] = useState(domain);
|
|
3205
|
+
if (reviewed !== domain) {
|
|
3206
|
+
setReviewed(domain);
|
|
3207
|
+
setReviewing(false);
|
|
3208
|
+
}
|
|
3209
|
+
const established = connection.established;
|
|
3210
|
+
const answered = useRef(established);
|
|
3211
|
+
const buildPlan = provisioning.plan;
|
|
3212
|
+
useEffect(() => {
|
|
3213
|
+
if (answered.current === established) return;
|
|
3214
|
+
answered.current = established;
|
|
3215
|
+
if (review === "manual") return;
|
|
3216
|
+
buildPlan();
|
|
3217
|
+
setReviewing(true);
|
|
3218
|
+
}, [
|
|
3219
|
+
buildPlan,
|
|
3220
|
+
established,
|
|
3221
|
+
review
|
|
3222
|
+
]);
|
|
3223
|
+
const connected = holdsConnection(connection);
|
|
3224
|
+
const offering$1 = offering(connection, connect);
|
|
3225
|
+
const provider = connection.snapshot?.provider ?? hostProvider(connection)?.id ?? null;
|
|
3226
|
+
const receipt = connection.snapshot?.lastReceiptId ?? null;
|
|
3227
|
+
const state = flowState({
|
|
3228
|
+
connected,
|
|
3229
|
+
offering: offering$1,
|
|
3230
|
+
planning,
|
|
3231
|
+
provider,
|
|
3232
|
+
receipt,
|
|
3233
|
+
status
|
|
3234
|
+
});
|
|
3235
|
+
const announce = useRef(onState);
|
|
3236
|
+
useEffect(() => {
|
|
3237
|
+
announce.current = onState;
|
|
3238
|
+
});
|
|
3239
|
+
useEffect(() => {
|
|
3240
|
+
announce.current?.(flowState({
|
|
3241
|
+
connected,
|
|
3242
|
+
offering: offering$1,
|
|
3243
|
+
planning,
|
|
3244
|
+
provider,
|
|
3245
|
+
receipt,
|
|
3246
|
+
status
|
|
3247
|
+
}));
|
|
3248
|
+
}, [
|
|
3249
|
+
connected,
|
|
3250
|
+
offering$1,
|
|
3251
|
+
planning,
|
|
3252
|
+
provider,
|
|
3253
|
+
receipt,
|
|
3254
|
+
status
|
|
3255
|
+
]);
|
|
3256
|
+
return usePart("div", props, state, {
|
|
2810
3257
|
children: /* @__PURE__ */ jsxs(ReadOnly, {
|
|
2811
3258
|
value: readOnly ?? inherited,
|
|
2812
3259
|
children: [
|
|
2813
3260
|
!capabilities.includes("connection") ? null : slots.connection === void 0 ? /* @__PURE__ */ jsx(DefaultConnection, {
|
|
2814
3261
|
connect,
|
|
2815
3262
|
controller: connection,
|
|
2816
|
-
domain
|
|
3263
|
+
domain,
|
|
3264
|
+
...onCleaned === void 0 ? {} : { onCleaned }
|
|
2817
3265
|
}) : slots.connection({
|
|
2818
3266
|
connect,
|
|
2819
3267
|
controller: connection,
|
|
@@ -2836,6 +3284,8 @@ function Flow({ connect = "detected", domain, onApplied, onCleaned, readOnly, re
|
|
|
2836
3284
|
cleanup,
|
|
2837
3285
|
connection,
|
|
2838
3286
|
domain,
|
|
3287
|
+
onOpenChange: setReviewing,
|
|
3288
|
+
open: reviewing,
|
|
2839
3289
|
provisioning
|
|
2840
3290
|
}) : slots.actions({
|
|
2841
3291
|
cleanup,
|