@domainkit/react 0.9.1 → 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 +52 -4
- package/dist/index.d.mts +333 -50
- package/dist/index.mjs +1121 -248
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +580 -50
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
3
3
|
import { DnsRecord, DomainKit, Reason, Receipt } from "domainkit";
|
|
4
4
|
import * as Effect from "effect/Effect";
|
|
5
|
-
import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
5
|
+
import { createContext, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
6
6
|
import * as Data from "effect/Data";
|
|
7
7
|
import { Transport } from "domainkit/client";
|
|
8
8
|
import { useRender } from "@base-ui/react/use-render";
|
|
@@ -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", {
|
|
@@ -84,7 +88,12 @@ const defaultIcons = {
|
|
|
84
88
|
cx: "12",
|
|
85
89
|
cy: "12",
|
|
86
90
|
r: "9"
|
|
87
|
-
}), /* @__PURE__ */ jsx("path", { d: "m8.5 12.5 2.5 2.5 4.5-5" })] })
|
|
91
|
+
}), /* @__PURE__ */ jsx("path", { d: "m8.5 12.5 2.5 2.5 4.5-5" })] }),
|
|
92
|
+
warning: /* @__PURE__ */ jsxs(Glyph, { children: [
|
|
93
|
+
/* @__PURE__ */ jsx("path", { d: "M10.3 3.9 2.4 17.5a2 2 0 0 0 1.7 3h15.8a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z" }),
|
|
94
|
+
/* @__PURE__ */ jsx("path", { d: "M12 9v4" }),
|
|
95
|
+
/* @__PURE__ */ jsx("path", { d: "M12 17h.01" })
|
|
96
|
+
] })
|
|
88
97
|
};
|
|
89
98
|
const IconsContext = createContext(defaultIcons);
|
|
90
99
|
function IconsProvider({ children, icons }) {
|
|
@@ -105,7 +114,8 @@ function useIcons() {
|
|
|
105
114
|
var messages_exports = /* @__PURE__ */ __exportAll({
|
|
106
115
|
english: () => english,
|
|
107
116
|
failure: () => failure,
|
|
108
|
-
merge: () => merge
|
|
117
|
+
merge: () => merge,
|
|
118
|
+
outcome: () => outcome
|
|
109
119
|
});
|
|
110
120
|
const entity = {
|
|
111
121
|
approval: "approval",
|
|
@@ -135,6 +145,10 @@ const recordTypes = {
|
|
|
135
145
|
SRV: "SRV",
|
|
136
146
|
TXT: "TXT"
|
|
137
147
|
};
|
|
148
|
+
/** The provider the customer acted on, or a neutral stand-in when the flow does not know it. */
|
|
149
|
+
const named = (provider) => provider ?? "The provider";
|
|
150
|
+
/** The domain the failure is about, or a neutral stand-in. */
|
|
151
|
+
const where = (domain) => domain ?? "this domain";
|
|
138
152
|
/** `accountId` reads as "Account id" until a host names the field itself. */
|
|
139
153
|
const humanize = (name) => {
|
|
140
154
|
const spaced = name.replaceAll(/([a-z0-9])([A-Z])/g, "$1 $2").replaceAll(/[_-]+/g, " ").trim();
|
|
@@ -142,19 +156,32 @@ const humanize = (name) => {
|
|
|
142
156
|
};
|
|
143
157
|
const english = {
|
|
144
158
|
connect: "Connect",
|
|
145
|
-
|
|
159
|
+
methodToken: "Connect with an API token",
|
|
160
|
+
methodOAuth: (provider) => `Continue with ${provider}`,
|
|
161
|
+
methodIntegration: (provider) => `Install the ${provider} integration`,
|
|
146
162
|
disconnect: "Disconnect",
|
|
147
163
|
detach: "Detach domain",
|
|
148
164
|
cancel: "Cancel",
|
|
149
165
|
close: "Close",
|
|
150
166
|
retry: "Try again",
|
|
151
167
|
approve: "Approve",
|
|
168
|
+
addRecords: (count) => count === 1 ? "Add 1 record" : `Add ${count} records`,
|
|
152
169
|
decline: "Decline",
|
|
170
|
+
everyRecordConflicts: "Resolve the records above at your provider, then review again.",
|
|
153
171
|
reviewChanges: "Review changes",
|
|
154
172
|
cleanUp: "Remove records",
|
|
155
173
|
checkDns: "Check DNS",
|
|
156
174
|
checkAgain: "Check again",
|
|
157
175
|
moreActions: "More actions",
|
|
176
|
+
moreOptions: "Add an account id",
|
|
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`,
|
|
158
185
|
copy: "Copy",
|
|
159
186
|
copied: "Copied",
|
|
160
187
|
copyZone: "Copy zone file",
|
|
@@ -175,13 +202,16 @@ const english = {
|
|
|
175
202
|
cleaning: "Removing records…",
|
|
176
203
|
observing: "Checking DNS…",
|
|
177
204
|
connectTitle: (provider) => `Connect ${provider}`,
|
|
178
|
-
connectAnyTitle: "Connect
|
|
205
|
+
connectAnyTitle: "Connect a DNS provider",
|
|
206
|
+
hostOwnsZone: "Owns DNS for this domain.",
|
|
179
207
|
connectDescription: (domain) => `Authorize DNS changes for ${domain}.`,
|
|
180
208
|
connectedTo: (provider) => `${provider} connected`,
|
|
181
209
|
notConnected: "No DNS provider is connected.",
|
|
182
210
|
reconnectRequired: (provider) => `${provider} needs to be reconnected`,
|
|
183
211
|
disconnectTitle: (provider) => `Disconnect ${provider}?`,
|
|
184
|
-
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}.`,
|
|
185
215
|
detachConsent: "This domain is detached from the provider. Existing DNS records are preserved.",
|
|
186
216
|
detached: "Domain detached. DNS records were preserved.",
|
|
187
217
|
reusableConnections: "Connections you already have",
|
|
@@ -240,9 +270,11 @@ const english = {
|
|
|
240
270
|
recordType: (record) => record._tag === "Opaque" ? record.type : recordTypes[record._tag],
|
|
241
271
|
applied: (receipt) => receipt.status === "complete" ? "DNS records added." : "Some DNS records were not added.",
|
|
242
272
|
cleaned: (receipt) => receipt.status === "complete" ? "DNS records removed." : "Some DNS records were not removed.",
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
273
|
+
partiallyAppliedTitle: "Some DNS changes did not land",
|
|
274
|
+
partiallyApplied: "Review what applied, then retry. Retrying is safe.",
|
|
275
|
+
partiallyCleanedTitle: "Some DNS records are still there",
|
|
276
|
+
partiallyCleaned: "Review what is left, then retry. Retrying is safe.",
|
|
277
|
+
receiptOutcome: (outcome) => {
|
|
246
278
|
switch (outcome._tag) {
|
|
247
279
|
case "Applied": return "Done";
|
|
248
280
|
case "Skipped": return "Skipped";
|
|
@@ -292,49 +324,99 @@ const english = {
|
|
|
292
324
|
nextCheckAt: (at) => `Next check ${at}`,
|
|
293
325
|
hostEvidence: "Other checks",
|
|
294
326
|
noEvidence: "Nothing has been checked yet.",
|
|
295
|
-
invalidInput: (reason) =>
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
327
|
+
invalidInput: (reason) => ({
|
|
328
|
+
description: reason.message,
|
|
329
|
+
title: reason.field === void 0 ? "Check what you entered" : `Check the ${humanize(reason.field).toLowerCase()}`
|
|
330
|
+
}),
|
|
331
|
+
unauthenticated: () => ({ title: "Token not accepted" }),
|
|
332
|
+
forbidden: (_reason, context) => ({
|
|
333
|
+
description: `The connection needs permission to edit DNS for ${where(context.domain)}.`,
|
|
334
|
+
title: "This connection can't change DNS here"
|
|
335
|
+
}),
|
|
336
|
+
notFound: (reason) => ({
|
|
337
|
+
description: "Reload the page and start this step again.",
|
|
338
|
+
title: `That ${entity[reason.entity]} no longer exists`
|
|
339
|
+
}),
|
|
340
|
+
conflict: (reason) => ({
|
|
341
|
+
description: "Review what the zone already holds, then build a new plan.",
|
|
342
|
+
title: reason.operations.length === 1 ? "A record is in the way" : `${reason.operations.length} records are in the way`
|
|
343
|
+
}),
|
|
344
|
+
stale: () => ({
|
|
345
|
+
description: "Review the new plan before you apply it.",
|
|
346
|
+
title: "The zone changed since you reviewed"
|
|
347
|
+
}),
|
|
348
|
+
expired: (reason) => ({
|
|
349
|
+
description: "Start this step again.",
|
|
350
|
+
title: `That ${expiredEntity[reason.entity]} expired`
|
|
351
|
+
}),
|
|
352
|
+
busy: () => ({
|
|
353
|
+
description: "Wait for it to finish, then try again.",
|
|
354
|
+
title: "Another change is running"
|
|
355
|
+
}),
|
|
356
|
+
providerRejected: (reason, context) => ({
|
|
357
|
+
description: reason.message,
|
|
358
|
+
title: `${named(context.provider ?? reason.provider)} refused the change`
|
|
359
|
+
}),
|
|
360
|
+
providerUnavailable: (reason, context) => ({
|
|
361
|
+
description: "Nothing changed. Try again in a minute.",
|
|
362
|
+
title: `${named(context.provider ?? reason.provider)} isn't responding`
|
|
363
|
+
}),
|
|
364
|
+
providerConflict: (reason, context) => ({
|
|
365
|
+
description: "Remove or rename that record at the provider, then try again.",
|
|
366
|
+
title: `${named(context.provider ?? reason.provider)} already holds a conflicting record`
|
|
367
|
+
}),
|
|
368
|
+
unsupported: (reason, context) => ({
|
|
369
|
+
description: "Nothing changed. This step needs a provider that can do it.",
|
|
370
|
+
title: `${named(context.provider ?? reason.provider)} cannot ${reason.operation}`
|
|
371
|
+
}),
|
|
372
|
+
reconnect: (reason, context) => ({
|
|
373
|
+
description: "The stored credential no longer works. Connect again to continue.",
|
|
374
|
+
title: `Reconnect ${named(context.provider ?? reason.provider)}`
|
|
375
|
+
}),
|
|
376
|
+
storageFailed: () => ({
|
|
377
|
+
description: "Nothing changed. Try again.",
|
|
378
|
+
title: "The change could not be saved"
|
|
379
|
+
}),
|
|
380
|
+
cryptoFailed: () => ({
|
|
381
|
+
description: "Connect the provider again to replace it.",
|
|
382
|
+
title: "A stored credential could not be read"
|
|
383
|
+
}),
|
|
384
|
+
resolverFailed: (reason) => ({
|
|
385
|
+
description: "The lookup failed, which says nothing about your DNS. Try again in a moment.",
|
|
386
|
+
title: `DNS lookups through ${reason.resolver} failed`
|
|
387
|
+
})
|
|
311
388
|
};
|
|
312
389
|
const merge = (overrides = {}) => ({
|
|
313
390
|
...english,
|
|
314
391
|
...overrides
|
|
315
392
|
});
|
|
316
|
-
/** The
|
|
317
|
-
const
|
|
393
|
+
/** The title and description for a failure, chosen by the error's reason. */
|
|
394
|
+
const outcome = (error, catalog, context = {}) => {
|
|
318
395
|
const reason = error.reason;
|
|
319
396
|
switch (reason._tag) {
|
|
320
|
-
case "InvalidInput": return catalog.invalidInput(reason);
|
|
321
|
-
case "Unauthenticated": return catalog.unauthenticated(reason);
|
|
322
|
-
case "Forbidden": return catalog.forbidden(reason);
|
|
323
|
-
case "NotFound": return catalog.notFound(reason);
|
|
324
|
-
case "Conflict": return catalog.conflict(reason);
|
|
325
|
-
case "Stale": return catalog.stale(reason);
|
|
326
|
-
case "Expired": return catalog.expired(reason);
|
|
327
|
-
case "Busy": return catalog.busy(reason);
|
|
328
|
-
case "ProviderRejected": return catalog.providerRejected(reason);
|
|
329
|
-
case "ProviderUnavailable": return catalog.providerUnavailable(reason);
|
|
330
|
-
case "ProviderConflict": return catalog.providerConflict(reason);
|
|
331
|
-
case "Unsupported": return catalog.unsupported(reason);
|
|
332
|
-
case "Reconnect": return catalog.reconnect(reason);
|
|
333
|
-
case "StorageFailed": return catalog.storageFailed(reason);
|
|
334
|
-
case "CryptoFailed": return catalog.cryptoFailed(reason);
|
|
335
|
-
case "ResolverFailed": return catalog.resolverFailed(reason);
|
|
397
|
+
case "InvalidInput": return catalog.invalidInput(reason, context);
|
|
398
|
+
case "Unauthenticated": return catalog.unauthenticated(reason, context);
|
|
399
|
+
case "Forbidden": return catalog.forbidden(reason, context);
|
|
400
|
+
case "NotFound": return catalog.notFound(reason, context);
|
|
401
|
+
case "Conflict": return catalog.conflict(reason, context);
|
|
402
|
+
case "Stale": return catalog.stale(reason, context);
|
|
403
|
+
case "Expired": return catalog.expired(reason, context);
|
|
404
|
+
case "Busy": return catalog.busy(reason, context);
|
|
405
|
+
case "ProviderRejected": return catalog.providerRejected(reason, context);
|
|
406
|
+
case "ProviderUnavailable": return catalog.providerUnavailable(reason, context);
|
|
407
|
+
case "ProviderConflict": return catalog.providerConflict(reason, context);
|
|
408
|
+
case "Unsupported": return catalog.unsupported(reason, context);
|
|
409
|
+
case "Reconnect": return catalog.reconnect(reason, context);
|
|
410
|
+
case "StorageFailed": return catalog.storageFailed(reason, context);
|
|
411
|
+
case "CryptoFailed": return catalog.cryptoFailed(reason, context);
|
|
412
|
+
case "ResolverFailed": return catalog.resolverFailed(reason, context);
|
|
336
413
|
}
|
|
337
414
|
};
|
|
415
|
+
/** The same failure as one sentence, for a host that renders text rather than a card. */
|
|
416
|
+
const failure = (error, catalog, context = {}) => {
|
|
417
|
+
const { description, title } = outcome(error, catalog, context);
|
|
418
|
+
return description === void 0 ? `${title}.` : `${title}. ${description}`;
|
|
419
|
+
};
|
|
338
420
|
//#endregion
|
|
339
421
|
//#region src/theme.ts
|
|
340
422
|
var theme_exports = /* @__PURE__ */ __exportAll({ toStyle: () => toStyle });
|
|
@@ -352,7 +434,8 @@ const variables = {
|
|
|
352
434
|
radius: "--domainkit-radius",
|
|
353
435
|
shadow: "--domainkit-shadow",
|
|
354
436
|
success: "--domainkit-success",
|
|
355
|
-
text: "--domainkit-text"
|
|
437
|
+
text: "--domainkit-text",
|
|
438
|
+
warning: "--domainkit-warning"
|
|
356
439
|
};
|
|
357
440
|
function toStyle(theme = {}) {
|
|
358
441
|
return Object.fromEntries(Object.entries(theme).flatMap(([key, value]) => value === void 0 ? [] : [[variables[key], value]]));
|
|
@@ -361,13 +444,13 @@ function toStyle(theme = {}) {
|
|
|
361
444
|
//#region src/domain-kit.tsx
|
|
362
445
|
var domain_kit_exports = /* @__PURE__ */ __exportAll({
|
|
363
446
|
ReadOnly: () => ReadOnly,
|
|
364
|
-
Root: () => Root$
|
|
447
|
+
Root: () => Root$7,
|
|
365
448
|
useCapabilities: () => useCapabilities,
|
|
366
449
|
useDomainKit: () => useDomainKit,
|
|
367
450
|
useReadOnly: () => useReadOnly,
|
|
368
451
|
useTransport: () => useTransport
|
|
369
452
|
});
|
|
370
|
-
const Context = createContext(null);
|
|
453
|
+
const Context$1 = createContext(null);
|
|
371
454
|
const navigateInBrowser = (url) => {
|
|
372
455
|
if (typeof window === "undefined") throw new Error("DomainKit provider authorization requires a browser or a `navigate` prop");
|
|
373
456
|
window.location.assign(url);
|
|
@@ -417,7 +500,7 @@ const usePortalContainer = (container) => {
|
|
|
417
500
|
const getSnapshot = useCallback(() => resolvePortalContainer(container, ownerDocument), [container, ownerDocument]);
|
|
418
501
|
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
419
502
|
};
|
|
420
|
-
function Root$
|
|
503
|
+
function Root$7({ children, colorScheme = "inherit", icons, marks = {}, messages, navigate = navigateInBrowser, onEvent, portalContainer = null, readOnly = false, revision = 0, theme, transport, ...props }) {
|
|
421
504
|
const { capabilities, transport: stable } = useStableTransport(transport);
|
|
422
505
|
const onEventRef = useRef(onEvent);
|
|
423
506
|
useLayoutEffect(() => {
|
|
@@ -450,7 +533,7 @@ function Root$6({ children, colorScheme = "inherit", icons, marks = {}, messages
|
|
|
450
533
|
"data-domainkit-root": "",
|
|
451
534
|
style: themeStyle
|
|
452
535
|
});
|
|
453
|
-
return /* @__PURE__ */ jsx(Context.Provider, {
|
|
536
|
+
return /* @__PURE__ */ jsx(Context$1.Provider, {
|
|
454
537
|
value,
|
|
455
538
|
children: /* @__PURE__ */ jsx(IconsProvider, {
|
|
456
539
|
...icons === void 0 ? {} : { icons },
|
|
@@ -459,7 +542,7 @@ function Root$6({ children, colorScheme = "inherit", icons, marks = {}, messages
|
|
|
459
542
|
});
|
|
460
543
|
}
|
|
461
544
|
function useDomainKit() {
|
|
462
|
-
const value = useContext(Context);
|
|
545
|
+
const value = useContext(Context$1);
|
|
463
546
|
if (value === null) throw new Error("DomainKit components must be rendered inside DomainKit.Root");
|
|
464
547
|
return value;
|
|
465
548
|
}
|
|
@@ -760,6 +843,156 @@ function useAttempt(options) {
|
|
|
760
843
|
};
|
|
761
844
|
}
|
|
762
845
|
//#endregion
|
|
846
|
+
//#region src/outcome.tsx
|
|
847
|
+
/**
|
|
848
|
+
* What a flow says when a step ends badly: media, title, description, and the action that follows.
|
|
849
|
+
* Every flow's `Outcome` binds its controller to these parts, so a failure reads the same way in
|
|
850
|
+
* the connect dialog, the review dialog, and the verification popover.
|
|
851
|
+
*
|
|
852
|
+
* <Connect.Outcome controller={connection} /> the default composition
|
|
853
|
+
*
|
|
854
|
+
* <Connect.Outcome controller={connection}> a composition of your own
|
|
855
|
+
* <Outcome.Header>
|
|
856
|
+
* <Outcome.Media><MyIcon /></Outcome.Media>
|
|
857
|
+
* <Outcome.Title />
|
|
858
|
+
* <Outcome.Description />
|
|
859
|
+
* </Outcome.Header>
|
|
860
|
+
* <Outcome.Content />
|
|
861
|
+
* </Connect.Outcome>
|
|
862
|
+
*/
|
|
863
|
+
var outcome_exports = /* @__PURE__ */ __exportAll({
|
|
864
|
+
Composition: () => Composition,
|
|
865
|
+
Content: () => Content,
|
|
866
|
+
Description: () => Description,
|
|
867
|
+
Header: () => Header$1,
|
|
868
|
+
Media: () => Media,
|
|
869
|
+
Provider: () => Provider,
|
|
870
|
+
Root: () => Root$6,
|
|
871
|
+
Title: () => Title,
|
|
872
|
+
useOutcome: () => useOutcome
|
|
873
|
+
});
|
|
874
|
+
const fallback = {
|
|
875
|
+
description: "",
|
|
876
|
+
layout: "card",
|
|
877
|
+
retry: null,
|
|
878
|
+
retryPart: "flow-retry",
|
|
879
|
+
title: "",
|
|
880
|
+
tone: "danger"
|
|
881
|
+
};
|
|
882
|
+
const Context = createContext(null);
|
|
883
|
+
/** What the surrounding flow resolved, for a part of your own inside `X.Outcome`. */
|
|
884
|
+
function useOutcome() {
|
|
885
|
+
return useContext(Context) ?? fallback;
|
|
886
|
+
}
|
|
887
|
+
/** Binds a resolved outcome to the parts below it. Flows render this; hosts rarely do. */
|
|
888
|
+
function Provider({ children, value }) {
|
|
889
|
+
return /* @__PURE__ */ jsx(Context.Provider, {
|
|
890
|
+
value,
|
|
891
|
+
children
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* The outcome itself. It announces, so a customer reading with assistive tech hears it. A `tone`
|
|
896
|
+
* or `layout` of its own reaches every part below it, so the glyph and the words agree with the
|
|
897
|
+
* root's own attributes.
|
|
898
|
+
*/
|
|
899
|
+
function Root$6({ layout, tone, ...props }) {
|
|
900
|
+
const value = useOutcome();
|
|
901
|
+
const resolved = {
|
|
902
|
+
...value,
|
|
903
|
+
layout: layout ?? value.layout,
|
|
904
|
+
tone: tone ?? value.tone
|
|
905
|
+
};
|
|
906
|
+
const element = usePart("div", props, {
|
|
907
|
+
layout: resolved.layout,
|
|
908
|
+
tone: resolved.tone
|
|
909
|
+
}, {
|
|
910
|
+
"data-domainkit-part": "outcome",
|
|
911
|
+
"data-layout": resolved.layout,
|
|
912
|
+
"data-tone": resolved.tone,
|
|
913
|
+
role: "alert"
|
|
914
|
+
});
|
|
915
|
+
return /* @__PURE__ */ jsx(Provider, {
|
|
916
|
+
value: resolved,
|
|
917
|
+
children: element
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
/** Media, title, and description together, so the action below them can move on its own. */
|
|
921
|
+
function Header$1(props) {
|
|
922
|
+
const { layout, tone } = useOutcome();
|
|
923
|
+
return usePart("div", props, {
|
|
924
|
+
layout,
|
|
925
|
+
tone
|
|
926
|
+
}, { "data-domainkit-part": "outcome-header" });
|
|
927
|
+
}
|
|
928
|
+
/** The glyph for the tone. Children replace it; `icons` on `DomainKit.Root` replaces the set. */
|
|
929
|
+
function Media({ variant = "icon", ...props }) {
|
|
930
|
+
const { layout, tone } = useOutcome();
|
|
931
|
+
const icons = useIcons();
|
|
932
|
+
const glyph = tone === "success" ? icons.success : tone === "warning" ? icons.warning : icons.failure;
|
|
933
|
+
return usePart("div", props, {
|
|
934
|
+
layout,
|
|
935
|
+
tone
|
|
936
|
+
}, {
|
|
937
|
+
"aria-hidden": "true",
|
|
938
|
+
children: glyph,
|
|
939
|
+
"data-domainkit-part": "outcome-media",
|
|
940
|
+
"data-variant": variant
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
/** The heading from the catalog. Children win, for a host that words it itself. */
|
|
944
|
+
function Title(props) {
|
|
945
|
+
const { layout, title, tone } = useOutcome();
|
|
946
|
+
return usePart("div", props, {
|
|
947
|
+
layout,
|
|
948
|
+
tone
|
|
949
|
+
}, {
|
|
950
|
+
children: title,
|
|
951
|
+
"data-domainkit-part": "outcome-title"
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
/** What to do about it, from the catalog. An outcome whose title says everything renders none. */
|
|
955
|
+
function Description(props) {
|
|
956
|
+
const { description, layout, tone } = useOutcome();
|
|
957
|
+
const element = usePart("p", props, {
|
|
958
|
+
layout,
|
|
959
|
+
tone
|
|
960
|
+
}, {
|
|
961
|
+
children: description,
|
|
962
|
+
"data-domainkit-part": "outcome-description"
|
|
963
|
+
});
|
|
964
|
+
return description === "" && props.children === void 0 ? null : element;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* The actions under the header. Without children it is the flow's retry, and a flow that allows
|
|
968
|
+
* none renders nothing: read-only hides every control that would start a write again.
|
|
969
|
+
*/
|
|
970
|
+
function Content(props) {
|
|
971
|
+
const { messages } = useDomainKit();
|
|
972
|
+
const { layout, retry, retryPart, tone } = useOutcome();
|
|
973
|
+
const element = usePart("div", props, {
|
|
974
|
+
layout,
|
|
975
|
+
tone
|
|
976
|
+
}, {
|
|
977
|
+
children: retry === null ? null : /* @__PURE__ */ jsx("button", {
|
|
978
|
+
"data-domainkit-part": retryPart,
|
|
979
|
+
onClick: retry,
|
|
980
|
+
type: "button",
|
|
981
|
+
children: messages.retry
|
|
982
|
+
}),
|
|
983
|
+
"data-domainkit-part": "outcome-content"
|
|
984
|
+
});
|
|
985
|
+
return retry === null && props.children === void 0 ? null : element;
|
|
986
|
+
}
|
|
987
|
+
/** Media, title, and description in a header, then the action. What `X.Outcome` renders alone. */
|
|
988
|
+
function Composition() {
|
|
989
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Header$1, { children: [
|
|
990
|
+
/* @__PURE__ */ jsx(Media, {}),
|
|
991
|
+
/* @__PURE__ */ jsx(Title, {}),
|
|
992
|
+
/* @__PURE__ */ jsx(Description, {})
|
|
993
|
+
] }), /* @__PURE__ */ jsx(Content, {})] });
|
|
994
|
+
}
|
|
995
|
+
//#endregion
|
|
763
996
|
//#region src/records.tsx
|
|
764
997
|
var records_exports = /* @__PURE__ */ __exportAll({
|
|
765
998
|
Body: () => Body$1,
|
|
@@ -1087,12 +1320,12 @@ function Status$4({ controller, kind, ...props }) {
|
|
|
1087
1320
|
switch (state._tag) {
|
|
1088
1321
|
case "Idle": return "";
|
|
1089
1322
|
case "Planning": return kind === "provisioning" ? messages.planning : messages.planningCleanup;
|
|
1090
|
-
case "Planned": return
|
|
1323
|
+
case "Planned": return "";
|
|
1091
1324
|
case "Approving": return messages.approving;
|
|
1092
1325
|
case "Applying": return kind === "provisioning" ? messages.applying : messages.cleaning;
|
|
1093
1326
|
case "Rejecting": return messages.rejecting;
|
|
1094
1327
|
case "Rejected": return messages.declinedBy(state.attempt.rejection?.actorId ?? "");
|
|
1095
|
-
case "Applied": return
|
|
1328
|
+
case "Applied": return "";
|
|
1096
1329
|
case "Failure": return "";
|
|
1097
1330
|
}
|
|
1098
1331
|
};
|
|
@@ -1103,28 +1336,47 @@ function Status$4({ controller, kind, ...props }) {
|
|
|
1103
1336
|
role: busy(state._tag) ? "status" : void 0
|
|
1104
1337
|
});
|
|
1105
1338
|
}
|
|
1106
|
-
/**
|
|
1107
|
-
|
|
1339
|
+
/**
|
|
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.
|
|
1343
|
+
*/
|
|
1344
|
+
function Outcome$4({ children, controller, kind, ...props }) {
|
|
1108
1345
|
const { messages } = useDomainKit();
|
|
1109
1346
|
const readOnly = useReadOnly();
|
|
1110
1347
|
const state = controller.state;
|
|
1348
|
+
const retryPart = kind === "provisioning" ? "provisioning-retry" : "cleanup-retry";
|
|
1349
|
+
if (state._tag !== "Failure" && state._tag !== "Applied") return null;
|
|
1111
1350
|
const partial = state._tag === "Applied" && state.receipt.status === "partial";
|
|
1112
|
-
const
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1351
|
+
const words = state._tag === "Failure" ? outcome(state.error, messages) : partial ? {
|
|
1352
|
+
description: kind === "provisioning" ? messages.partiallyApplied : messages.partiallyCleaned,
|
|
1353
|
+
title: kind === "provisioning" ? messages.partiallyAppliedTitle : messages.partiallyCleanedTitle
|
|
1354
|
+
} : {
|
|
1355
|
+
description: "",
|
|
1356
|
+
title: kind === "provisioning" ? messages.applied(state.receipt) : messages.cleaned(state.receipt)
|
|
1357
|
+
};
|
|
1358
|
+
return /* @__PURE__ */ jsx(Provider, {
|
|
1359
|
+
value: {
|
|
1360
|
+
description: words.description ?? "",
|
|
1361
|
+
layout: props.layout ?? "card",
|
|
1362
|
+
retry: readOnly || state._tag !== "Failure" ? null : controller.retry,
|
|
1363
|
+
retryPart,
|
|
1364
|
+
title: words.title,
|
|
1365
|
+
tone: state._tag === "Failure" ? "danger" : partial ? "warning" : "success"
|
|
1366
|
+
},
|
|
1367
|
+
children: /* @__PURE__ */ jsx(Root$6, {
|
|
1368
|
+
...props,
|
|
1369
|
+
children: children ?? /* @__PURE__ */ jsx(Composition, {})
|
|
1370
|
+
})
|
|
1122
1371
|
});
|
|
1123
|
-
return state._tag === "Failure" || partial ? element : null;
|
|
1124
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");
|
|
1125
1375
|
/**
|
|
1126
|
-
*
|
|
1127
|
-
*
|
|
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.
|
|
1128
1380
|
*/
|
|
1129
1381
|
function Actions$2({ controller, kind, ...props }) {
|
|
1130
1382
|
const { messages } = useDomainKit();
|
|
@@ -1132,24 +1384,33 @@ function Actions$2({ controller, kind, ...props }) {
|
|
|
1132
1384
|
const state = controller.state;
|
|
1133
1385
|
const plan = state._tag === "Planned" ? state.plan : null;
|
|
1134
1386
|
const running = busy(state._tag);
|
|
1387
|
+
const writes = addable(plan);
|
|
1388
|
+
const blocked = plan !== null && writes.length === 0;
|
|
1135
1389
|
const element = usePart("div", props, { status: state._tag }, {
|
|
1136
|
-
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
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
|
+
] }),
|
|
1149
1410
|
"data-domainkit-part": "review-actions"
|
|
1150
1411
|
});
|
|
1151
1412
|
if (readOnly) return null;
|
|
1152
|
-
return plan === null
|
|
1413
|
+
return plan === null ? null : element;
|
|
1153
1414
|
}
|
|
1154
1415
|
/** The operations under review, or the reason there is nothing to review. */
|
|
1155
1416
|
function Body({ controller, kind }) {
|
|
@@ -1184,10 +1445,17 @@ function Dialog$4({ children, controller, kind, onOpenChange, open, render, trig
|
|
|
1184
1445
|
const readOnly = useReadOnly();
|
|
1185
1446
|
const [uncontrolled, setUncontrolled] = useState(false);
|
|
1186
1447
|
const isOpen = open ?? uncontrolled;
|
|
1187
|
-
const
|
|
1448
|
+
const close = useRef(() => {});
|
|
1449
|
+
close.current = (next) => {
|
|
1188
1450
|
if (open === void 0) setUncontrolled(next);
|
|
1189
1451
|
onOpenChange?.(next);
|
|
1190
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]);
|
|
1191
1459
|
const running = busy(controller.state._tag);
|
|
1192
1460
|
const body = children ?? /* @__PURE__ */ jsx(Body, {
|
|
1193
1461
|
controller,
|
|
@@ -1257,7 +1525,7 @@ function Dialog$4({ children, controller, kind, onOpenChange, open, render, trig
|
|
|
1257
1525
|
children: [/* @__PURE__ */ jsx(Actions$2, {
|
|
1258
1526
|
controller,
|
|
1259
1527
|
kind
|
|
1260
|
-
}), running ? null : /* @__PURE__ */ jsx(Dialog.Close, {
|
|
1528
|
+
}), running || kind === "provisioning" ? null : /* @__PURE__ */ jsx(Dialog.Close, {
|
|
1261
1529
|
"data-domainkit-part": "dialog-cancel",
|
|
1262
1530
|
children: messages.cancel
|
|
1263
1531
|
})]
|
|
@@ -1349,13 +1617,16 @@ function Flow$3({ domain, onCleaned, receiptId, trigger, ...props }) {
|
|
|
1349
1617
|
...onCleaned === void 0 ? {} : { onCleaned },
|
|
1350
1618
|
...receiptId === void 0 ? {} : { receiptId }
|
|
1351
1619
|
});
|
|
1620
|
+
const [open, setOpen] = useState(false);
|
|
1352
1621
|
return /* @__PURE__ */ jsxs(Root$3, {
|
|
1353
1622
|
controller,
|
|
1354
1623
|
...props,
|
|
1355
1624
|
children: [/* @__PURE__ */ jsx(Dialog$3, {
|
|
1356
1625
|
controller,
|
|
1626
|
+
onOpenChange: setOpen,
|
|
1627
|
+
open,
|
|
1357
1628
|
...trigger === void 0 ? {} : { trigger }
|
|
1358
|
-
}), /* @__PURE__ */ jsx(Outcome$3, { controller })]
|
|
1629
|
+
}), open ? null : /* @__PURE__ */ jsx(Outcome$3, { controller })]
|
|
1359
1630
|
});
|
|
1360
1631
|
}
|
|
1361
1632
|
//#endregion
|
|
@@ -1385,15 +1656,41 @@ function Mark({ provider, ...props }) {
|
|
|
1385
1656
|
var connect_exports = /* @__PURE__ */ __exportAll({
|
|
1386
1657
|
Card: () => Card,
|
|
1387
1658
|
Dialog: () => Dialog$2,
|
|
1659
|
+
DisconnectDialog: () => DisconnectDialog,
|
|
1388
1660
|
Flow: () => Flow$2,
|
|
1389
1661
|
Form: () => Form,
|
|
1390
1662
|
Outcome: () => Outcome$2,
|
|
1663
|
+
Prompt: () => Prompt,
|
|
1391
1664
|
Root: () => Root$2,
|
|
1392
1665
|
State: () => State$1,
|
|
1393
1666
|
Status: () => Status$2,
|
|
1667
|
+
answeredInPlace: () => answeredInPlace,
|
|
1668
|
+
holdsConnection: () => holdsConnection,
|
|
1669
|
+
hostProvider: () => hostProvider,
|
|
1670
|
+
offering: () => offering,
|
|
1394
1671
|
useController: () => useController$2
|
|
1395
1672
|
});
|
|
1396
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
|
+
};
|
|
1397
1694
|
/** Read at connect time, not at render, so it is the page the customer is actually on. */
|
|
1398
1695
|
const currentUrl = () => typeof window === "undefined" ? null : window.location.href;
|
|
1399
1696
|
const snapshotOf = (state) => {
|
|
@@ -1403,9 +1700,9 @@ const snapshotOf = (state) => {
|
|
|
1403
1700
|
case "Disconnected": return state.snapshot;
|
|
1404
1701
|
case "Loading":
|
|
1405
1702
|
case "Submitting":
|
|
1406
|
-
case "SelectionRequired":
|
|
1407
|
-
case "
|
|
1408
|
-
case "
|
|
1703
|
+
case "SelectionRequired":
|
|
1704
|
+
case "Failure": return state.snapshot;
|
|
1705
|
+
case "Redirecting": return null;
|
|
1409
1706
|
}
|
|
1410
1707
|
};
|
|
1411
1708
|
const settled = (snapshot, discovery) => {
|
|
@@ -1435,6 +1732,8 @@ function useController$2({ domain, returnTo }) {
|
|
|
1435
1732
|
snapshot: null
|
|
1436
1733
|
});
|
|
1437
1734
|
const lastCommand = useRef(null);
|
|
1735
|
+
const attempted = useRef(null);
|
|
1736
|
+
const [established, setEstablished] = useState(0);
|
|
1438
1737
|
const [inspected, setInspected] = useState(domain);
|
|
1439
1738
|
if (inspected !== domain) {
|
|
1440
1739
|
setInspected(domain);
|
|
@@ -1448,7 +1747,12 @@ function useController$2({ domain, returnTo }) {
|
|
|
1448
1747
|
setState(State$1.Loading({ snapshot: null }));
|
|
1449
1748
|
}
|
|
1450
1749
|
const onFailure = useCallback((error) => {
|
|
1451
|
-
setState(State$1.Failure({
|
|
1750
|
+
setState(State$1.Failure({
|
|
1751
|
+
attempt: attempted.current,
|
|
1752
|
+
discovery: held.current.discovery,
|
|
1753
|
+
error,
|
|
1754
|
+
snapshot: held.current.snapshot
|
|
1755
|
+
}));
|
|
1452
1756
|
emit(Event.Failed({
|
|
1453
1757
|
domain,
|
|
1454
1758
|
error
|
|
@@ -1457,6 +1761,7 @@ function useController$2({ domain, returnTo }) {
|
|
|
1457
1761
|
const load = useCallback(() => {
|
|
1458
1762
|
if (connection === void 0) return;
|
|
1459
1763
|
lastCommand.current = null;
|
|
1764
|
+
attempted.current = null;
|
|
1460
1765
|
held.current = {
|
|
1461
1766
|
discovery: null,
|
|
1462
1767
|
domain,
|
|
@@ -1478,6 +1783,7 @@ function useController$2({ domain, returnTo }) {
|
|
|
1478
1783
|
domain,
|
|
1479
1784
|
snapshot
|
|
1480
1785
|
};
|
|
1786
|
+
if (tookReturn(domain) && snapshot.status === "connected") setEstablished((count) => count + 1);
|
|
1481
1787
|
setState(settled(snapshot, discovery));
|
|
1482
1788
|
}
|
|
1483
1789
|
});
|
|
@@ -1497,6 +1803,7 @@ function useController$2({ domain, returnTo }) {
|
|
|
1497
1803
|
domain,
|
|
1498
1804
|
snapshot: result.snapshot
|
|
1499
1805
|
};
|
|
1806
|
+
setEstablished((count) => count + 1);
|
|
1500
1807
|
setState(settled(result.snapshot, null));
|
|
1501
1808
|
if (result.snapshot.connectionId !== null) emit(Event.Connected({
|
|
1502
1809
|
connectionId: result.snapshot.connectionId,
|
|
@@ -1505,6 +1812,7 @@ function useController$2({ domain, returnTo }) {
|
|
|
1505
1812
|
}));
|
|
1506
1813
|
return;
|
|
1507
1814
|
case "Redirect":
|
|
1815
|
+
markReturn(domain);
|
|
1508
1816
|
setState(State$1.Redirecting({ url: result.authorizationUrl }));
|
|
1509
1817
|
navigate(result.authorizationUrl);
|
|
1510
1818
|
return;
|
|
@@ -1521,10 +1829,14 @@ function useController$2({ domain, returnTo }) {
|
|
|
1521
1829
|
emit,
|
|
1522
1830
|
navigate
|
|
1523
1831
|
]);
|
|
1524
|
-
const submit = useCallback((effect) => {
|
|
1832
|
+
const submit = useCallback((effect, attempt) => {
|
|
1525
1833
|
if (held.current.domain !== domain) return;
|
|
1834
|
+
attempted.current = attempt;
|
|
1526
1835
|
const command = () => {
|
|
1527
|
-
setState(State$1.Submitting({
|
|
1836
|
+
setState(State$1.Submitting({
|
|
1837
|
+
discovery: held.current.discovery,
|
|
1838
|
+
snapshot: held.current.snapshot
|
|
1839
|
+
}));
|
|
1528
1840
|
runner.run(effect, {
|
|
1529
1841
|
onFailure,
|
|
1530
1842
|
onSuccess: started
|
|
@@ -1550,7 +1862,10 @@ function useController$2({ domain, returnTo }) {
|
|
|
1550
1862
|
domain,
|
|
1551
1863
|
method,
|
|
1552
1864
|
provider: input.provider
|
|
1553
|
-
})
|
|
1865
|
+
}), {
|
|
1866
|
+
method: input.method,
|
|
1867
|
+
provider: input.provider
|
|
1868
|
+
});
|
|
1554
1869
|
}, [
|
|
1555
1870
|
connection,
|
|
1556
1871
|
domain,
|
|
@@ -1563,7 +1878,7 @@ function useController$2({ domain, returnTo }) {
|
|
|
1563
1878
|
connectionId: input.connectionId,
|
|
1564
1879
|
domain,
|
|
1565
1880
|
...input.zone === void 0 ? {} : { zone: input.zone }
|
|
1566
|
-
}));
|
|
1881
|
+
}), null);
|
|
1567
1882
|
}, [
|
|
1568
1883
|
connection,
|
|
1569
1884
|
domain,
|
|
@@ -1578,8 +1893,12 @@ function useController$2({ domain, returnTo }) {
|
|
|
1578
1893
|
}, [reuse, state]);
|
|
1579
1894
|
const release = useCallback((effect, event) => {
|
|
1580
1895
|
if (held.current.domain !== domain) return;
|
|
1896
|
+
attempted.current = null;
|
|
1581
1897
|
const command = () => {
|
|
1582
|
-
setState(State$1.Submitting({
|
|
1898
|
+
setState(State$1.Submitting({
|
|
1899
|
+
discovery: held.current.discovery,
|
|
1900
|
+
snapshot: held.current.snapshot
|
|
1901
|
+
}));
|
|
1583
1902
|
runner.run(effect, {
|
|
1584
1903
|
onFailure,
|
|
1585
1904
|
onSuccess: () => {
|
|
@@ -1634,7 +1953,9 @@ function useController$2({ domain, returnTo }) {
|
|
|
1634
1953
|
connect,
|
|
1635
1954
|
detach,
|
|
1636
1955
|
disconnect,
|
|
1637
|
-
|
|
1956
|
+
domain,
|
|
1957
|
+
established,
|
|
1958
|
+
discovery: state._tag === "Disconnected" || state._tag === "Failure" || state._tag === "Submitting" ? state.discovery : null,
|
|
1638
1959
|
providers: snapshotOf(state)?.providers ?? [],
|
|
1639
1960
|
refresh: load,
|
|
1640
1961
|
retry,
|
|
@@ -1650,28 +1971,63 @@ function Root$2({ controller, ...props }) {
|
|
|
1650
1971
|
"data-state": controller.state._tag
|
|
1651
1972
|
});
|
|
1652
1973
|
}
|
|
1653
|
-
/**
|
|
1654
|
-
|
|
1974
|
+
/**
|
|
1975
|
+
* Whether a failure is already answered where it was raised: beside the method the customer used.
|
|
1976
|
+
* A flow rendering its own `Outcome` beside the dialog skips it, so the failure announces once.
|
|
1977
|
+
*/
|
|
1978
|
+
const answeredInPlace = (controller) => controller.state._tag === "Failure" && controller.state.attempt !== null;
|
|
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
|
+
/**
|
|
1986
|
+
* The provider whose nameservers serve this domain, as the snapshot describes it. Discovery names
|
|
1987
|
+
* it by id; without a descriptor there is nothing to draw, so there is no host.
|
|
1988
|
+
*/
|
|
1989
|
+
const hostProvider = (controller) => {
|
|
1990
|
+
const discovery = controller.discovery;
|
|
1991
|
+
if (discovery === null || discovery._tag !== "NotFound" || discovery.host === null) return null;
|
|
1992
|
+
const named = discovery.host.provider;
|
|
1993
|
+
return controller.providers.find((entry) => entry.id === named) ?? null;
|
|
1994
|
+
};
|
|
1995
|
+
/** The provider as the customer knows it, falling back to the id when the snapshot has no name. */
|
|
1996
|
+
const displayName = (controller, provider) => controller.providers.find((entry) => entry.id === provider)?.name ?? provider;
|
|
1997
|
+
/**
|
|
1998
|
+
* The last failure as a card: media, the catalog's title and description, and the retry the flow
|
|
1999
|
+
* allows. Children replace the composition and keep the binding.
|
|
2000
|
+
*/
|
|
2001
|
+
function Outcome$2({ children, controller, ...props }) {
|
|
1655
2002
|
const { messages } = useDomainKit();
|
|
1656
2003
|
const readOnly = useReadOnly();
|
|
1657
2004
|
const state = controller.state;
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
2005
|
+
if (state._tag !== "Failure") return null;
|
|
2006
|
+
const acted = state.attempt === null ? void 0 : displayName(controller, state.attempt.provider);
|
|
2007
|
+
const words = outcome(state.error, messages, {
|
|
2008
|
+
...controller.snapshot === null ? {} : { domain: controller.snapshot.domain },
|
|
2009
|
+
...acted === void 0 ? {} : { provider: acted }
|
|
2010
|
+
});
|
|
2011
|
+
return /* @__PURE__ */ jsx(Provider, {
|
|
2012
|
+
value: {
|
|
2013
|
+
description: words.description ?? "",
|
|
2014
|
+
layout: props.layout ?? "card",
|
|
2015
|
+
retry: readOnly ? null : controller.retry,
|
|
2016
|
+
retryPart: "connection-retry",
|
|
2017
|
+
title: words.title,
|
|
2018
|
+
tone: "danger"
|
|
2019
|
+
},
|
|
2020
|
+
children: /* @__PURE__ */ jsx(Root$6, {
|
|
2021
|
+
...props,
|
|
2022
|
+
children: children ?? /* @__PURE__ */ jsx(Composition, {})
|
|
2023
|
+
})
|
|
1668
2024
|
});
|
|
1669
|
-
return state._tag === "Failure" ? element : null;
|
|
1670
2025
|
}
|
|
1671
2026
|
function Status$2({ controller, ...props }) {
|
|
1672
2027
|
const { messages } = useDomainKit();
|
|
1673
2028
|
const state = controller.state;
|
|
1674
|
-
const
|
|
2029
|
+
const attached = controller.snapshot?.provider ?? null;
|
|
2030
|
+
const provider = attached === null ? "" : displayName(controller, attached);
|
|
1675
2031
|
const text = () => {
|
|
1676
2032
|
switch (state._tag) {
|
|
1677
2033
|
case "Loading": return messages.loading;
|
|
@@ -1692,14 +2048,83 @@ function Status$2({ controller, ...props }) {
|
|
|
1692
2048
|
});
|
|
1693
2049
|
}
|
|
1694
2050
|
/**
|
|
2051
|
+
* The one field a rejected command was about, if any. A provider that turns down credentials is
|
|
2052
|
+
* answering the secret it was given, and it names no field, so the first secret carries the
|
|
2053
|
+
* answer; anything else is about the request rather than one input.
|
|
2054
|
+
*/
|
|
2055
|
+
const rejected = (reason, fields) => {
|
|
2056
|
+
if (reason === null) return null;
|
|
2057
|
+
if (reason._tag === "InvalidInput") return fields.find((field) => field.name === reason.field)?.name ?? null;
|
|
2058
|
+
if (reason._tag !== "Unauthenticated" && reason._tag !== "Forbidden") return null;
|
|
2059
|
+
return fields.find((field) => field.secret)?.name ?? null;
|
|
2060
|
+
};
|
|
2061
|
+
/**
|
|
2062
|
+
* One declared field in shadcn's `Field` anatomy: the label, the control, the note under it, and
|
|
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.
|
|
2065
|
+
*/
|
|
2066
|
+
function TokenField({ controller, docsUrl, field, invalid, onEnter, value }) {
|
|
2067
|
+
const { messages } = useDomainKit();
|
|
2068
|
+
const icons = useIcons();
|
|
2069
|
+
const id = useId();
|
|
2070
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2071
|
+
"data-domainkit-part": "field",
|
|
2072
|
+
"data-invalid": invalid ? "" : void 0,
|
|
2073
|
+
"data-orientation": "vertical",
|
|
2074
|
+
children: [
|
|
2075
|
+
/* @__PURE__ */ jsxs("label", {
|
|
2076
|
+
"data-domainkit-part": "field-label",
|
|
2077
|
+
htmlFor: id,
|
|
2078
|
+
children: [messages.fieldLabel(field.name), field.required ? null : /* @__PURE__ */ jsx("span", {
|
|
2079
|
+
"data-domainkit-part": "field-optional",
|
|
2080
|
+
children: messages.optionalField
|
|
2081
|
+
})]
|
|
2082
|
+
}),
|
|
2083
|
+
/* @__PURE__ */ jsx("input", {
|
|
2084
|
+
"aria-invalid": invalid ? true : void 0,
|
|
2085
|
+
autoComplete: "off",
|
|
2086
|
+
"data-domainkit-part": "field-input",
|
|
2087
|
+
id,
|
|
2088
|
+
name: field.name,
|
|
2089
|
+
onChange: (event) => onEnter(event.target.value),
|
|
2090
|
+
required: field.required,
|
|
2091
|
+
type: field.secret ? "password" : "text",
|
|
2092
|
+
value
|
|
2093
|
+
}),
|
|
2094
|
+
docsUrl === null ? null : /* @__PURE__ */ jsxs("a", {
|
|
2095
|
+
"data-domainkit-part": "field-description",
|
|
2096
|
+
href: docsUrl,
|
|
2097
|
+
rel: "noreferrer",
|
|
2098
|
+
target: "_blank",
|
|
2099
|
+
children: [messages.getToken, /* @__PURE__ */ jsx("span", {
|
|
2100
|
+
"aria-hidden": "true",
|
|
2101
|
+
"data-icon": "inline-end",
|
|
2102
|
+
children: icons.external
|
|
2103
|
+
})]
|
|
2104
|
+
}),
|
|
2105
|
+
invalid ? /* @__PURE__ */ jsx("div", {
|
|
2106
|
+
"data-domainkit-part": "field-error",
|
|
2107
|
+
children: /* @__PURE__ */ jsx(Outcome$2, {
|
|
2108
|
+
controller,
|
|
2109
|
+
layout: "inline"
|
|
2110
|
+
})
|
|
2111
|
+
}) : null
|
|
2112
|
+
]
|
|
2113
|
+
});
|
|
2114
|
+
}
|
|
2115
|
+
/**
|
|
1695
2116
|
* One auth method, rendered from its descriptor. A token method draws an input per declared
|
|
1696
|
-
* field, so a provider that needs an account id alongside a token needs no code here.
|
|
2117
|
+
* field, so a provider that needs an account id alongside a token needs no code here. The fields
|
|
2118
|
+
* a provider does not need sit behind a disclosure, so the common form is one field.
|
|
1697
2119
|
*/
|
|
1698
|
-
function Method({ controller, method, provider }) {
|
|
2120
|
+
function Method({ controller, method, onEnter, provider, values }) {
|
|
1699
2121
|
const { messages } = useDomainKit();
|
|
1700
2122
|
const icons = useIcons();
|
|
1701
|
-
const
|
|
1702
|
-
|
|
2123
|
+
const [more, setMore] = useState(false);
|
|
2124
|
+
const state = controller.state;
|
|
2125
|
+
const busy = state._tag === "Submitting";
|
|
2126
|
+
const failed = state._tag === "Failure" && state.attempt !== null && state.attempt.provider === provider.id && state.attempt.method === method.kind;
|
|
2127
|
+
if (method.fields === null) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
|
|
1703
2128
|
"data-domainkit-part": method.kind === "oauth" ? "oauth-connect" : "integration-connect",
|
|
1704
2129
|
"data-provider": provider.id,
|
|
1705
2130
|
disabled: busy,
|
|
@@ -1708,70 +2133,138 @@ function Method({ controller, method, provider }) {
|
|
|
1708
2133
|
provider: provider.id
|
|
1709
2134
|
}),
|
|
1710
2135
|
type: "button",
|
|
1711
|
-
children: messages.
|
|
1712
|
-
})
|
|
2136
|
+
children: method.kind === "oauth" ? messages.methodOAuth(provider.name) : messages.methodIntegration(provider.name)
|
|
2137
|
+
}), failed ? /* @__PURE__ */ jsx(Outcome$2, {
|
|
2138
|
+
controller,
|
|
2139
|
+
layout: "inline"
|
|
2140
|
+
}) : null] });
|
|
1713
2141
|
const fields = method.fields;
|
|
2142
|
+
const answered = rejected(failed && state._tag === "Failure" ? state.error.reason : null, fields);
|
|
2143
|
+
const required = fields.filter((field) => field.required);
|
|
2144
|
+
const optional = fields.filter((field) => !field.required);
|
|
2145
|
+
const showMore = more || optional.some((field) => field.name === answered);
|
|
2146
|
+
const explains = (required.find((field) => field.secret) ?? required[0] ?? optional[0])?.name;
|
|
2147
|
+
const render = (field) => /* @__PURE__ */ jsx(TokenField, {
|
|
2148
|
+
controller,
|
|
2149
|
+
docsUrl: field.name === explains ? method.docsUrl : null,
|
|
2150
|
+
field,
|
|
2151
|
+
invalid: field.name === answered,
|
|
2152
|
+
onEnter: (value) => onEnter(field.name, value),
|
|
2153
|
+
value: values[field.name] ?? ""
|
|
2154
|
+
}, field.name);
|
|
1714
2155
|
return /* @__PURE__ */ jsxs("form", {
|
|
1715
2156
|
"data-domainkit-part": "token-connect",
|
|
1716
2157
|
"data-provider": provider.id,
|
|
1717
2158
|
onSubmit: (event) => {
|
|
1718
2159
|
event.preventDefault();
|
|
1719
|
-
const
|
|
1720
|
-
const values = {};
|
|
2160
|
+
const submitted = {};
|
|
1721
2161
|
for (const field of fields) {
|
|
1722
|
-
const value =
|
|
1723
|
-
if (
|
|
2162
|
+
const value = values[field.name] ?? "";
|
|
2163
|
+
if (value !== "" || field.required) submitted[field.name] = value;
|
|
1724
2164
|
}
|
|
1725
2165
|
controller.connect({
|
|
1726
2166
|
method: method.kind,
|
|
1727
2167
|
provider: provider.id,
|
|
1728
|
-
values
|
|
2168
|
+
values: submitted
|
|
1729
2169
|
});
|
|
1730
2170
|
},
|
|
1731
2171
|
children: [
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
2172
|
+
required.map(render),
|
|
2173
|
+
optional.length === 0 ? null : /* @__PURE__ */ jsxs(Collapsible.Root, {
|
|
2174
|
+
"data-domainkit-part": "more-options",
|
|
2175
|
+
"data-state": showMore ? "open" : "closed",
|
|
2176
|
+
onOpenChange: setMore,
|
|
2177
|
+
open: showMore,
|
|
2178
|
+
children: [/* @__PURE__ */ jsxs(Collapsible.Trigger, {
|
|
2179
|
+
"data-domainkit-part": "more-options-trigger",
|
|
2180
|
+
children: [messages.moreOptions, /* @__PURE__ */ jsx("span", {
|
|
2181
|
+
"aria-hidden": "true",
|
|
2182
|
+
"data-icon": "inline-end",
|
|
2183
|
+
children: icons.chevron
|
|
1739
2184
|
})]
|
|
1740
|
-
}), /* @__PURE__ */ jsx(
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
name: field.name,
|
|
1744
|
-
required: field.required,
|
|
1745
|
-
type: field.secret ? "password" : "text"
|
|
1746
|
-
})]
|
|
1747
|
-
}, field.name)),
|
|
1748
|
-
method.docsUrl === null ? null : /* @__PURE__ */ jsxs("a", {
|
|
1749
|
-
"data-domainkit-part": "token-docs",
|
|
1750
|
-
href: method.docsUrl,
|
|
1751
|
-
rel: "noreferrer",
|
|
1752
|
-
target: "_blank",
|
|
1753
|
-
children: [messages.getToken, /* @__PURE__ */ jsx("span", {
|
|
1754
|
-
"aria-hidden": "true",
|
|
1755
|
-
"data-icon": "inline-end",
|
|
1756
|
-
children: icons.external
|
|
2185
|
+
}), /* @__PURE__ */ jsx(Collapsible.Panel, {
|
|
2186
|
+
"data-domainkit-part": "more-options-panel",
|
|
2187
|
+
children: optional.map(render)
|
|
1757
2188
|
})]
|
|
1758
2189
|
}),
|
|
2190
|
+
failed && answered === null ? /* @__PURE__ */ jsx(Outcome$2, {
|
|
2191
|
+
controller,
|
|
2192
|
+
layout: "inline"
|
|
2193
|
+
}) : null,
|
|
1759
2194
|
/* @__PURE__ */ jsx("button", {
|
|
1760
2195
|
"data-domainkit-part": "token-submit",
|
|
1761
2196
|
disabled: busy,
|
|
1762
2197
|
type: "submit",
|
|
1763
|
-
children: messages.
|
|
2198
|
+
children: messages.methodToken
|
|
1764
2199
|
})
|
|
1765
2200
|
]
|
|
1766
2201
|
});
|
|
1767
2202
|
}
|
|
1768
2203
|
/**
|
|
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.
|
|
2210
|
+
*/
|
|
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, {
|
|
2215
|
+
controller,
|
|
2216
|
+
method,
|
|
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
|
+
})] });
|
|
2231
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
2232
|
+
"data-domainkit-part": "provider-authentication",
|
|
2233
|
+
"data-provider": provider.id,
|
|
2234
|
+
"data-state": open ? "open" : "closed",
|
|
2235
|
+
children: [onOpen === void 0 ? null : /* @__PURE__ */ jsxs("button", {
|
|
2236
|
+
"aria-expanded": open,
|
|
2237
|
+
"data-domainkit-part": "provider-heading",
|
|
2238
|
+
onClick: onOpen,
|
|
2239
|
+
type: "button",
|
|
2240
|
+
children: [/* @__PURE__ */ jsx(Mark, { provider }), /* @__PURE__ */ jsx("strong", { children: provider.name })]
|
|
2241
|
+
}), open ? methods : null]
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
/**
|
|
1769
2245
|
* Everything a disconnected domain can do: a connection discovery already found, the connections
|
|
1770
2246
|
* this owner already has, then each provider's declared methods.
|
|
1771
2247
|
*/
|
|
1772
|
-
function Form({ controller, ...props }) {
|
|
2248
|
+
function Form({ controller, provider: narrowed, ...props }) {
|
|
1773
2249
|
const { messages } = useDomainKit();
|
|
2250
|
+
const [opened, setOpened] = useState(null);
|
|
2251
|
+
const [entered, setEntered] = useState(null);
|
|
1774
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 : {};
|
|
1775
2268
|
const snapshot = controller.snapshot;
|
|
1776
2269
|
const discovery = controller.discovery;
|
|
1777
2270
|
const candidates = state._tag === "SelectionRequired" ? state.candidates.map((candidate) => ({
|
|
@@ -1781,6 +2274,18 @@ function Form({ controller, ...props }) {
|
|
|
1781
2274
|
const resolved = discovery !== null && discovery._tag === "Resolved" ? discovery : null;
|
|
1782
2275
|
const reusable = snapshot?.reusable ?? [];
|
|
1783
2276
|
const providers = controller.providers;
|
|
2277
|
+
const host = providers.find((entry) => entry.id === narrowed) ?? hostProvider(controller);
|
|
2278
|
+
const [typing, setTyping] = useState(null);
|
|
2279
|
+
const authentication = (provider, open) => /* @__PURE__ */ jsx(Authentication, {
|
|
2280
|
+
controller,
|
|
2281
|
+
onEnter: (name, value) => enter(provider.id, name, value),
|
|
2282
|
+
onOpen: () => setOpened(open ? "" : provider.id),
|
|
2283
|
+
onType: () => setTyping(provider.id),
|
|
2284
|
+
open,
|
|
2285
|
+
provider,
|
|
2286
|
+
typing: typing === provider.id,
|
|
2287
|
+
values: valuesFor(provider.id)
|
|
2288
|
+
}, provider.id);
|
|
1784
2289
|
return usePart("div", props, { status: state._tag }, {
|
|
1785
2290
|
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1786
2291
|
resolved === null ? null : /* @__PURE__ */ jsxs("section", {
|
|
@@ -1827,17 +2332,20 @@ function Form({ controller, ...props }) {
|
|
|
1827
2332
|
providers.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
1828
2333
|
"data-domainkit-part": "target-unavailable",
|
|
1829
2334
|
children: messages.noProviders
|
|
1830
|
-
}) : providers.map((provider) => /* @__PURE__ */ jsxs("
|
|
1831
|
-
"data-domainkit-part": "
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
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, {
|
|
2341
|
+
controller,
|
|
2342
|
+
onEnter: (name, value) => enter(host.id, name, value),
|
|
2343
|
+
onType: () => setTyping(host.id),
|
|
2344
|
+
open: true,
|
|
2345
|
+
provider: host,
|
|
2346
|
+
typing: typing === host.id,
|
|
2347
|
+
values: valuesFor(host.id)
|
|
2348
|
+
})] })
|
|
1841
2349
|
] }),
|
|
1842
2350
|
"data-domainkit-part": "connection-form"
|
|
1843
2351
|
});
|
|
@@ -1845,19 +2353,32 @@ function Form({ controller, ...props }) {
|
|
|
1845
2353
|
/** The connect surface. Without `render` it is a Base UI dialog behind `trigger`. */
|
|
1846
2354
|
function Dialog$2({ children, controller, render, title, trigger }) {
|
|
1847
2355
|
const { colorScheme, messages, portalContainer, themeStyle } = useDomainKit();
|
|
2356
|
+
const icons = useIcons();
|
|
1848
2357
|
const readOnly = useReadOnly();
|
|
1849
2358
|
const [open, setOpen] = useState(false);
|
|
1850
|
-
const
|
|
2359
|
+
const state = controller.state;
|
|
2360
|
+
const busy = state._tag === "Submitting";
|
|
2361
|
+
const unattributed = state._tag === "Failure" && !answeredInPlace(controller);
|
|
1851
2362
|
const snapshot = controller.snapshot;
|
|
1852
|
-
const
|
|
1853
|
-
const
|
|
1854
|
-
const
|
|
2363
|
+
const [chosen, setChosen] = useState(null);
|
|
2364
|
+
const attached = snapshot?.provider ?? null;
|
|
2365
|
+
const host = hostProvider(controller);
|
|
2366
|
+
const narrowed = controller.providers.find((entry) => entry.id === (chosen ?? attached)) ?? host;
|
|
2367
|
+
const named = narrowed?.name ?? null;
|
|
2368
|
+
const heading = title ?? (named === null ? messages.connectAnyTitle : messages.connectTitle(named));
|
|
2369
|
+
const alternatives = narrowed === null ? [] : controller.providers.filter((entry) => entry.id !== narrowed.id);
|
|
2370
|
+
const running = state._tag === "Loading" || state._tag === "Submitting" || state._tag === "Redirecting" || state._tag === "SelectionRequired";
|
|
2371
|
+
const label = named === null ? messages.connectAnyTitle : messages.connect;
|
|
2372
|
+
const body = children ?? /* @__PURE__ */ jsx(Form, {
|
|
2373
|
+
controller,
|
|
2374
|
+
...chosen === null ? {} : { provider: chosen }
|
|
2375
|
+
});
|
|
1855
2376
|
if (readOnly) return /* @__PURE__ */ jsx(Fragment, {});
|
|
1856
2377
|
if (render !== void 0) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
|
|
1857
2378
|
"data-domainkit-part": "connection-trigger",
|
|
1858
2379
|
onClick: () => setOpen(!open),
|
|
1859
2380
|
type: "button",
|
|
1860
|
-
children: trigger ??
|
|
2381
|
+
children: trigger ?? label
|
|
1861
2382
|
}), render({
|
|
1862
2383
|
children: body,
|
|
1863
2384
|
open
|
|
@@ -1873,7 +2394,7 @@ function Dialog$2({ children, controller, render, title, trigger }) {
|
|
|
1873
2394
|
open,
|
|
1874
2395
|
children: [/* @__PURE__ */ jsx(Dialog.Trigger, {
|
|
1875
2396
|
"data-domainkit-part": "connection-trigger",
|
|
1876
|
-
children: trigger ??
|
|
2397
|
+
children: trigger ?? label
|
|
1877
2398
|
}), /* @__PURE__ */ jsxs(Dialog.Portal, {
|
|
1878
2399
|
container: portalContainer,
|
|
1879
2400
|
children: [/* @__PURE__ */ jsx(Dialog.Backdrop, {
|
|
@@ -1886,6 +2407,229 @@ function Dialog$2({ children, controller, render, title, trigger }) {
|
|
|
1886
2407
|
"data-domainkit-part": "connection-dialog",
|
|
1887
2408
|
"data-domainkit-root": "",
|
|
1888
2409
|
style: themeStyle,
|
|
2410
|
+
children: [
|
|
2411
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2412
|
+
"data-domainkit-part": "dialog-header",
|
|
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
|
+
]
|
|
2462
|
+
}),
|
|
2463
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2464
|
+
"data-domainkit-part": "dialog-body",
|
|
2465
|
+
children: [
|
|
2466
|
+
running ? /* @__PURE__ */ jsx(Status$2, { controller }) : null,
|
|
2467
|
+
body,
|
|
2468
|
+
unattributed ? /* @__PURE__ */ jsx(Outcome$2, { controller }) : null
|
|
2469
|
+
]
|
|
2470
|
+
}),
|
|
2471
|
+
busy ? null : /* @__PURE__ */ jsx("div", {
|
|
2472
|
+
"data-domainkit-part": "dialog-footer",
|
|
2473
|
+
children: /* @__PURE__ */ jsx(Dialog.Close, {
|
|
2474
|
+
"data-domainkit-part": "dialog-cancel",
|
|
2475
|
+
children: messages.cancel
|
|
2476
|
+
})
|
|
2477
|
+
})
|
|
2478
|
+
]
|
|
2479
|
+
})]
|
|
2480
|
+
})]
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
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
|
+
/**
|
|
2499
|
+
* The disconnected offer: the provider whose nameservers serve the domain, what that means, and
|
|
2500
|
+
* the trigger that connects it. With no host detected there is nothing to offer, so nothing
|
|
2501
|
+
* renders unless the host application asks for the dialog anyway.
|
|
2502
|
+
*/
|
|
2503
|
+
function Prompt({ connect = "detected", controller, ...props }) {
|
|
2504
|
+
const { messages } = useDomainKit();
|
|
2505
|
+
const readOnly = useReadOnly();
|
|
2506
|
+
const state = controller.state;
|
|
2507
|
+
const host = hostProvider(controller);
|
|
2508
|
+
const element = usePart("div", props, { status: state._tag }, {
|
|
2509
|
+
children: /* @__PURE__ */ jsxs(Fragment, { children: [host === null ? null : /* @__PURE__ */ jsxs("div", {
|
|
2510
|
+
"data-domainkit-part": "host-identity",
|
|
2511
|
+
children: [
|
|
2512
|
+
/* @__PURE__ */ jsx(Mark, { provider: host }),
|
|
2513
|
+
/* @__PURE__ */ jsx("span", {
|
|
2514
|
+
"data-domainkit-part": "host-name",
|
|
2515
|
+
children: host.name
|
|
2516
|
+
}),
|
|
2517
|
+
/* @__PURE__ */ jsx("span", {
|
|
2518
|
+
"data-domainkit-part": "host-statement",
|
|
2519
|
+
children: messages.hostOwnsZone
|
|
2520
|
+
})
|
|
2521
|
+
]
|
|
2522
|
+
}), /* @__PURE__ */ jsx(Dialog$2, { controller })] }),
|
|
2523
|
+
"data-domainkit-part": "connect-prompt",
|
|
2524
|
+
"data-host": host?.id
|
|
2525
|
+
});
|
|
2526
|
+
if (!offering(controller, connect)) return null;
|
|
2527
|
+
if (readOnly && host === null) return null;
|
|
2528
|
+
return element;
|
|
2529
|
+
}
|
|
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,
|
|
1889
2633
|
children: [
|
|
1890
2634
|
/* @__PURE__ */ jsxs("div", {
|
|
1891
2635
|
"data-domainkit-part": "dialog-header",
|
|
@@ -1896,7 +2640,7 @@ function Dialog$2({ children, controller, render, title, trigger }) {
|
|
|
1896
2640
|
children: heading
|
|
1897
2641
|
}), /* @__PURE__ */ jsx(Dialog.Description, {
|
|
1898
2642
|
"data-domainkit-part": "dialog-description",
|
|
1899
|
-
children: messages.
|
|
2643
|
+
children: messages.disconnectConsent(snapshot?.domain ?? "", named)
|
|
1900
2644
|
})]
|
|
1901
2645
|
}), busy ? null : /* @__PURE__ */ jsx(Dialog.Close, {
|
|
1902
2646
|
"aria-label": messages.close,
|
|
@@ -1907,68 +2651,136 @@ function Dialog$2({ children, controller, render, title, trigger }) {
|
|
|
1907
2651
|
/* @__PURE__ */ jsxs("div", {
|
|
1908
2652
|
"data-domainkit-part": "dialog-body",
|
|
1909
2653
|
children: [
|
|
1910
|
-
/* @__PURE__ */
|
|
1911
|
-
|
|
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 }),
|
|
1912
2701
|
/* @__PURE__ */ jsx(Outcome$2, { controller })
|
|
1913
2702
|
]
|
|
1914
2703
|
}),
|
|
1915
|
-
|
|
2704
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1916
2705
|
"data-domainkit-part": "dialog-footer",
|
|
1917
|
-
children: /* @__PURE__ */ jsx(
|
|
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, {
|
|
1918
2720
|
"data-domainkit-part": "dialog-cancel",
|
|
1919
2721
|
children: messages.cancel
|
|
1920
|
-
})
|
|
2722
|
+
})]
|
|
1921
2723
|
})
|
|
1922
2724
|
]
|
|
1923
2725
|
})]
|
|
1924
2726
|
})]
|
|
1925
2727
|
});
|
|
1926
2728
|
}
|
|
1927
|
-
/**
|
|
1928
|
-
|
|
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 }) {
|
|
1929
2734
|
const { messages } = useDomainKit();
|
|
1930
2735
|
const readOnly = useReadOnly();
|
|
1931
2736
|
const state = controller.state;
|
|
1932
2737
|
const snapshot = controller.snapshot;
|
|
1933
2738
|
const provider = controller.providers.find((entry) => entry.id === snapshot?.provider);
|
|
2739
|
+
const standing = state._tag === "Reconnect" ? messages.needsReconnect : messages.connected;
|
|
1934
2740
|
return usePart("div", props, { status: state._tag }, {
|
|
1935
2741
|
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1936
2742
|
/* @__PURE__ */ jsxs("div", {
|
|
1937
2743
|
"data-domainkit-part": "connected-identity",
|
|
1938
|
-
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
|
+
]
|
|
1939
2755
|
}),
|
|
1940
|
-
readOnly ? null : /* @__PURE__ */
|
|
2756
|
+
readOnly ? null : /* @__PURE__ */ jsx("div", {
|
|
1941
2757
|
"data-domainkit-part": "connected-actions",
|
|
1942
|
-
children:
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
type: "button",
|
|
1947
|
-
children: messages.detach
|
|
1948
|
-
}), /* @__PURE__ */ jsx("button", {
|
|
1949
|
-
"data-domainkit-part": "disconnect-action",
|
|
1950
|
-
disabled: state._tag === "Submitting",
|
|
1951
|
-
onClick: controller.disconnect,
|
|
1952
|
-
type: "button",
|
|
1953
|
-
children: messages.disconnect
|
|
1954
|
-
})]
|
|
2758
|
+
children: /* @__PURE__ */ jsx(DisconnectDialog, {
|
|
2759
|
+
controller,
|
|
2760
|
+
...onCleaned === void 0 ? {} : { onCleaned }
|
|
2761
|
+
})
|
|
1955
2762
|
}),
|
|
1956
2763
|
/* @__PURE__ */ jsx(Outcome$2, { controller })
|
|
1957
2764
|
] }),
|
|
1958
2765
|
"data-domainkit-part": "connected-card"
|
|
1959
2766
|
});
|
|
1960
2767
|
}
|
|
1961
|
-
/** Connection on its own: the card once connected, the
|
|
1962
|
-
function Flow$2({ domain, returnTo, ...props }) {
|
|
2768
|
+
/** Connection on its own: the card once connected, the prompt until then. */
|
|
2769
|
+
function Flow$2({ connect, domain, onCleaned, returnTo, ...props }) {
|
|
1963
2770
|
const controller = useController$2({
|
|
1964
2771
|
domain,
|
|
1965
2772
|
...returnTo === void 0 ? {} : { returnTo }
|
|
1966
2773
|
});
|
|
1967
|
-
const state = controller.state;
|
|
1968
2774
|
return /* @__PURE__ */ jsx(Root$2, {
|
|
1969
2775
|
controller,
|
|
1970
2776
|
...props,
|
|
1971
|
-
children:
|
|
2777
|
+
children: holdsConnection(controller) ? /* @__PURE__ */ jsx(Card, {
|
|
2778
|
+
controller,
|
|
2779
|
+
...onCleaned === void 0 ? {} : { onCleaned }
|
|
2780
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Prompt, {
|
|
2781
|
+
controller,
|
|
2782
|
+
...connect === void 0 ? {} : { connect }
|
|
2783
|
+
}), answeredInPlace(controller) ? null : /* @__PURE__ */ jsx(Outcome$2, { controller })] })
|
|
1972
2784
|
});
|
|
1973
2785
|
}
|
|
1974
2786
|
//#endregion
|
|
@@ -2049,13 +2861,16 @@ function Flow$1({ domain, onApplied, requirements, trigger, ...props }) {
|
|
|
2049
2861
|
requirements,
|
|
2050
2862
|
...onApplied === void 0 ? {} : { onApplied }
|
|
2051
2863
|
});
|
|
2864
|
+
const [open, setOpen] = useState(false);
|
|
2052
2865
|
return /* @__PURE__ */ jsxs(Root$1, {
|
|
2053
2866
|
controller,
|
|
2054
2867
|
...props,
|
|
2055
2868
|
children: [/* @__PURE__ */ jsx(Dialog$1, {
|
|
2056
2869
|
controller,
|
|
2870
|
+
onOpenChange: setOpen,
|
|
2871
|
+
open,
|
|
2057
2872
|
...trigger === void 0 ? {} : { trigger }
|
|
2058
|
-
}), /* @__PURE__ */ jsx(Outcome$1, { controller })]
|
|
2873
|
+
}), open ? null : /* @__PURE__ */ jsx(Outcome$1, { controller })]
|
|
2059
2874
|
});
|
|
2060
2875
|
}
|
|
2061
2876
|
//#endregion
|
|
@@ -2242,25 +3057,29 @@ function Evidence({ readiness }) {
|
|
|
2242
3057
|
})]
|
|
2243
3058
|
});
|
|
2244
3059
|
}
|
|
2245
|
-
|
|
3060
|
+
/**
|
|
3061
|
+
* The last observation that failed. Observing reads the world rather than changing the domain, so
|
|
3062
|
+
* the retry stays even in read-only.
|
|
3063
|
+
*/
|
|
3064
|
+
function Outcome({ children, controller, ...props }) {
|
|
2246
3065
|
const { messages } = useDomainKit();
|
|
2247
3066
|
const state = controller.state;
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
3067
|
+
if (state._tag !== "Failure") return null;
|
|
3068
|
+
const words = outcome(state.error, messages);
|
|
3069
|
+
return /* @__PURE__ */ jsx(Provider, {
|
|
3070
|
+
value: {
|
|
3071
|
+
description: words.description ?? "",
|
|
3072
|
+
layout: props.layout ?? "card",
|
|
3073
|
+
retry: controller.retry,
|
|
3074
|
+
retryPart: "verification-retry",
|
|
3075
|
+
title: words.title,
|
|
3076
|
+
tone: "danger"
|
|
3077
|
+
},
|
|
3078
|
+
children: /* @__PURE__ */ jsx(Root$6, {
|
|
3079
|
+
...props,
|
|
3080
|
+
children: children ?? /* @__PURE__ */ jsx(Composition, {})
|
|
3081
|
+
})
|
|
2262
3082
|
});
|
|
2263
|
-
return state._tag === "Failure" ? element : null;
|
|
2264
3083
|
}
|
|
2265
3084
|
/** The default verification slot: a button that observes, with the evidence behind it. */
|
|
2266
3085
|
function Status({ controller, render, ...props }) {
|
|
@@ -2306,54 +3125,51 @@ function Status({ controller, render, ...props }) {
|
|
|
2306
3125
|
//#endregion
|
|
2307
3126
|
//#region src/domain.tsx
|
|
2308
3127
|
var domain_exports = /* @__PURE__ */ __exportAll({ Flow: () => Flow });
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
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: [
|
|
3147
|
+
stated ? null : /* @__PURE__ */ jsx(Status$2, { controller }),
|
|
3148
|
+
/* @__PURE__ */ jsx(Prompt, {
|
|
3149
|
+
connect,
|
|
3150
|
+
controller
|
|
3151
|
+
}),
|
|
3152
|
+
answeredInPlace(controller) ? null : /* @__PURE__ */ jsx(Outcome$2, { controller })
|
|
2314
3153
|
] });
|
|
2315
3154
|
}
|
|
2316
|
-
function DefaultActions({
|
|
2317
|
-
const { capabilities, messages } = useDomainKit();
|
|
3155
|
+
function DefaultActions({ connection, onOpenChange, open, provisioning }) {
|
|
2318
3156
|
const readOnly = useReadOnly();
|
|
2319
|
-
const
|
|
3157
|
+
const { capabilities } = useDomainKit();
|
|
2320
3158
|
const connected = connection.state._tag === "Connected";
|
|
2321
3159
|
if (readOnly) return null;
|
|
2322
|
-
|
|
2323
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
type: "button",
|
|
2329
|
-
children: messages.reviewChanges
|
|
2330
|
-
}),
|
|
2331
|
-
/* @__PURE__ */ jsx(Status$1, { controller: provisioning }),
|
|
2332
|
-
/* @__PURE__ */ jsx(Actions, { controller: provisioning }),
|
|
2333
|
-
/* @__PURE__ */ jsx(Outcome$1, { controller: provisioning })
|
|
2334
|
-
] }) : null, capabilities.includes("cleanup") && connected && hasReceipt ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2335
|
-
/* @__PURE__ */ jsxs("button", {
|
|
2336
|
-
"data-domainkit-part": "cleanup-trigger",
|
|
2337
|
-
disabled: cleanup.state._tag === "Planning",
|
|
2338
|
-
onClick: cleanup.plan,
|
|
2339
|
-
type: "button",
|
|
2340
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
2341
|
-
"aria-hidden": "true",
|
|
2342
|
-
"data-icon": "inline-start",
|
|
2343
|
-
children: icons.close
|
|
2344
|
-
}), messages.cleanUp]
|
|
2345
|
-
}),
|
|
2346
|
-
/* @__PURE__ */ jsx(Status$3, { controller: cleanup }),
|
|
2347
|
-
/* @__PURE__ */ jsx(Actions$1, { controller: cleanup }),
|
|
2348
|
-
/* @__PURE__ */ jsx(Outcome$3, { controller: cleanup })
|
|
2349
|
-
] }) : 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 })] });
|
|
2350
3166
|
}
|
|
2351
3167
|
/**
|
|
2352
3168
|
* The whole lifecycle for one domain: connect, review, apply, observe, clean up. The flow renders
|
|
2353
3169
|
* only what the transport declares, and every part of it is a slot with a default. It adds no
|
|
2354
3170
|
* layout containers of its own, so a host's grid can place the slot output directly.
|
|
2355
3171
|
*/
|
|
2356
|
-
function Flow({ domain, onApplied, onCleaned, readOnly, requirements, returnTo, slots = {}, ...props }) {
|
|
3172
|
+
function Flow({ connect = "detected", domain, onApplied, onCleaned, onState, readOnly, requirements, returnTo, review = "auto", slots = {}, ...props }) {
|
|
2357
3173
|
const { capabilities } = useDomainKit();
|
|
2358
3174
|
const inherited = useReadOnly();
|
|
2359
3175
|
const connection = useController$2({
|
|
@@ -2382,17 +3198,72 @@ function Flow({ domain, onApplied, onCleaned, readOnly, requirements, returnTo,
|
|
|
2382
3198
|
requirements
|
|
2383
3199
|
});
|
|
2384
3200
|
const readiness = verification.readiness;
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
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, {
|
|
2389
3257
|
children: /* @__PURE__ */ jsxs(ReadOnly, {
|
|
2390
3258
|
value: readOnly ?? inherited,
|
|
2391
3259
|
children: [
|
|
2392
3260
|
!capabilities.includes("connection") ? null : slots.connection === void 0 ? /* @__PURE__ */ jsx(DefaultConnection, {
|
|
3261
|
+
connect,
|
|
2393
3262
|
controller: connection,
|
|
2394
|
-
domain
|
|
3263
|
+
domain,
|
|
3264
|
+
...onCleaned === void 0 ? {} : { onCleaned }
|
|
2395
3265
|
}) : slots.connection({
|
|
3266
|
+
connect,
|
|
2396
3267
|
controller: connection,
|
|
2397
3268
|
domain
|
|
2398
3269
|
}),
|
|
@@ -2413,6 +3284,8 @@ function Flow({ domain, onApplied, onCleaned, readOnly, requirements, returnTo,
|
|
|
2413
3284
|
cleanup,
|
|
2414
3285
|
connection,
|
|
2415
3286
|
domain,
|
|
3287
|
+
onOpenChange: setReviewing,
|
|
3288
|
+
open: reviewing,
|
|
2416
3289
|
provisioning
|
|
2417
3290
|
}) : slots.actions({
|
|
2418
3291
|
cleanup,
|
|
@@ -2438,6 +3311,6 @@ const transport = Testing.transport;
|
|
|
2438
3311
|
//#region src/failure.ts
|
|
2439
3312
|
const Failure = Data.taggedEnum();
|
|
2440
3313
|
//#endregion
|
|
2441
|
-
export { cleanup_exports as Cleanup, connect_exports as Connect, domain_exports as Domain, domain_kit_exports as DomainKit, Event, Failure, messages_exports as Messages, operations_exports as Operations, provider_exports as Provider, provision_exports as Provision, records_exports as Records, testing_exports as Testing, theme_exports as Theme, verify_exports as Verify, defaultIcons, useIcons };
|
|
3314
|
+
export { cleanup_exports as Cleanup, connect_exports as Connect, domain_exports as Domain, domain_kit_exports as DomainKit, Event, Failure, messages_exports as Messages, operations_exports as Operations, outcome_exports as Outcome, provider_exports as Provider, provision_exports as Provision, records_exports as Records, testing_exports as Testing, theme_exports as Theme, verify_exports as Verify, defaultIcons, useIcons };
|
|
2442
3315
|
|
|
2443
3316
|
//# sourceMappingURL=index.mjs.map
|