@zitadel/components 0.1.0-alpha.17 → 0.1.0-alpha.18

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.
Files changed (36) hide show
  1. package/README.md +85 -19
  2. package/dist/atoms/index.d.mts +1 -1
  3. package/dist/atoms/index.mjs +1 -1
  4. package/dist/{atoms-KC7OJWXZ.mjs → atoms-5-wYIl88.mjs} +263 -188
  5. package/dist/atoms-5-wYIl88.mjs.map +1 -0
  6. package/dist/default-Bnjy2dJB.mjs +6 -0
  7. package/dist/default-Bnjy2dJB.mjs.map +1 -0
  8. package/dist/{index-BniwXaIC.d.mts → index-BRGZPZ2y.d.mts} +111 -1
  9. package/dist/index-BRGZPZ2y.d.mts.map +1 -0
  10. package/dist/{index-DjYJwejg.d.mts → index-CsJiPZtJ.d.mts} +437 -209
  11. package/dist/index-CsJiPZtJ.d.mts.map +1 -0
  12. package/dist/{index-DSZnfOJH.d.mts → index-DIGjtG4z.d.mts} +1626 -1017
  13. package/dist/index-DIGjtG4z.d.mts.map +1 -0
  14. package/dist/index.d.mts +5 -5
  15. package/dist/index.mjs +5 -5
  16. package/dist/jsx.d.ts +63 -0
  17. package/dist/manifests.mjs +1 -1
  18. package/dist/orchestrator/index.d.mts +3 -3
  19. package/dist/orchestrator/index.mjs +3 -3
  20. package/dist/{orchestrator-BWqJw4Yl.mjs → orchestrator-CctLkpmB.mjs} +1195 -764
  21. package/dist/orchestrator-CctLkpmB.mjs.map +1 -0
  22. package/dist/standalone.mjs +2484 -1284
  23. package/dist/tokens/index.d.mts +1 -1
  24. package/dist/tokens/index.mjs +1 -1
  25. package/dist/tokens-BV-PPbQd.mjs +356 -0
  26. package/dist/tokens-BV-PPbQd.mjs.map +1 -0
  27. package/package.json +14 -10
  28. package/dist/atoms-KC7OJWXZ.mjs.map +0 -1
  29. package/dist/default-CfF7GoLv.mjs +0 -6
  30. package/dist/default-CfF7GoLv.mjs.map +0 -1
  31. package/dist/index-BniwXaIC.d.mts.map +0 -1
  32. package/dist/index-DSZnfOJH.d.mts.map +0 -1
  33. package/dist/index-DjYJwejg.d.mts.map +0 -1
  34. package/dist/orchestrator-BWqJw4Yl.mjs.map +0 -1
  35. package/dist/tokens-CbH2s1jV.mjs +0 -246
  36. package/dist/tokens-CbH2s1jV.mjs.map +0 -1
@@ -1,10 +1,11 @@
1
- import { C as focusVisibleStyles, E as emit, S as __decorate, T as t, d as hookName, w as baseHostStyles } from "./atoms-KC7OJWXZ.mjs";
2
- import { r as tokensCss } from "./tokens-CbH2s1jV.mjs";
3
- import { t as default_default } from "./default-CfF7GoLv.mjs";
1
+ import { C as focusVisibleStyles, E as emit, S as __decorate, T as t, d as hookName, w as baseHostStyles } from "./atoms-5-wYIl88.mjs";
2
+ import { r as tokensCss } from "./tokens-BV-PPbQd.mjs";
4
3
  import { manifestRegistry } from "./manifests.mjs";
4
+ import { t as default_default } from "./default-Bnjy2dJB.mjs";
5
5
  import { LitElement, css, html, nothing } from "lit";
6
6
  import { customElement, property, state } from "lit/decorators.js";
7
7
  import { unsafeHTML } from "lit/directives/unsafe-html.js";
8
+ import { MANDATORY_GATES_TAG } from "@zitadel/config/template";
8
9
  import { Liquid } from "liquidjs";
9
10
  import DOMPurify from "dompurify";
10
11
  //#region ../api/dist/runtime/auth.mjs
@@ -204,10 +205,9 @@ var __exportAll = (all, no_symbols) => {
204
205
  * OpenAPI spec version: 0.0.1
205
206
  */
206
207
  var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
207
- activateFlowDefinition: () => activateFlowDefinition,
208
- authorizeDevice: () => authorizeDevice,
209
- authorizeGet: () => authorizeGet,
208
+ completeClaim: () => completeClaim,
210
209
  createAuthAttempt: () => createAuthAttempt,
210
+ createBranding: () => createBranding,
211
211
  createFlow: () => createFlow,
212
212
  createFlowDefinition: () => createFlowDefinition,
213
213
  createHandoff: () => createHandoff,
@@ -216,15 +216,16 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
216
216
  createSession: () => createSession,
217
217
  createTeam: () => createTeam,
218
218
  createUser: () => createUser,
219
- deactivateFlowDefinition: () => deactivateFlowDefinition,
220
219
  deleteFlowDefinition: () => deleteFlowDefinition,
221
- endSession: () => endSession,
220
+ deleteTeam: () => deleteTeam,
221
+ deleteUserByID: () => deleteUserByID,
222
222
  exchangeHandoff: () => exchangeHandoff,
223
- getActivateFlowDefinitionUrl: () => getActivateFlowDefinitionUrl,
224
223
  getAuthAttempt: () => getAuthAttempt,
225
- getAuthorizeDeviceUrl: () => getAuthorizeDeviceUrl,
226
- getAuthorizeGetUrl: () => getAuthorizeGetUrl,
224
+ getBrandingById: () => getBrandingById,
225
+ getClaimStatus: () => getClaimStatus,
226
+ getCompleteClaimUrl: () => getCompleteClaimUrl,
227
227
  getCreateAuthAttemptUrl: () => getCreateAuthAttemptUrl,
228
+ getCreateBrandingUrl: () => getCreateBrandingUrl,
228
229
  getCreateFlowDefinitionUrl: () => getCreateFlowDefinitionUrl,
229
230
  getCreateFlowUrl: () => getCreateFlowUrl,
230
231
  getCreateHandoffUrl: () => getCreateHandoffUrl,
@@ -233,230 +234,76 @@ var zitadelNextGen_exports = /* @__PURE__ */ __exportAll({
233
234
  getCreateSessionUrl: () => getCreateSessionUrl,
234
235
  getCreateTeamUrl: () => getCreateTeamUrl,
235
236
  getCreateUserUrl: () => getCreateUserUrl,
236
- getDeactivateFlowDefinitionUrl: () => getDeactivateFlowDefinitionUrl,
237
237
  getDeleteFlowDefinitionUrl: () => getDeleteFlowDefinitionUrl,
238
- getEndSessionUrl: () => getEndSessionUrl,
238
+ getDeleteTeamUrl: () => getDeleteTeamUrl,
239
+ getDeleteUserByIDUrl: () => getDeleteUserByIDUrl,
239
240
  getExchangeHandoffUrl: () => getExchangeHandoffUrl,
240
241
  getFlowDefinition: () => getFlowDefinition,
241
242
  getFlowStep: () => getFlowStep,
242
243
  getGetAuthAttemptUrl: () => getGetAuthAttemptUrl,
244
+ getGetBrandingByIdUrl: () => getGetBrandingByIdUrl,
245
+ getGetClaimStatusUrl: () => getGetClaimStatusUrl,
243
246
  getGetFlowDefinitionUrl: () => getGetFlowDefinitionUrl,
244
247
  getGetFlowStepUrl: () => getGetFlowStepUrl,
245
248
  getGetHealthUrl: () => getGetHealthUrl,
246
- getGetKeysUrl: () => getGetKeysUrl,
247
249
  getGetLiveUrl: () => getGetLiveUrl,
248
250
  getGetMySessionUrl: () => getGetMySessionUrl,
249
251
  getGetMyUserUrl: () => getGetMyUserUrl,
250
- getGetOpenIDConfigurationUrl: () => getGetOpenIDConfigurationUrl,
251
252
  getGetProjectUrl: () => getGetProjectUrl,
252
253
  getGetReadyUrl: () => getGetReadyUrl,
253
254
  getGetSchemaByIdUrl: () => getGetSchemaByIdUrl,
254
255
  getGetSessionUrl: () => getGetSessionUrl,
255
256
  getGetTeamUrl: () => getGetTeamUrl,
256
- getGetTokenUrl: () => getGetTokenUrl,
257
257
  getGetUserByIDUrl: () => getGetUserByIDUrl,
258
- getGetUserInfoUrl: () => getGetUserInfoUrl,
259
258
  getHealth: () => getHealth,
260
- getIntrospectUrl: () => getIntrospectUrl,
259
+ getInitClaimUrl: () => getInitClaimUrl,
261
260
  getIssueChallengeUrl: () => getIssueChallengeUrl,
262
- getKeys: () => getKeys,
261
+ getListBrandingUrl: () => getListBrandingUrl,
263
262
  getListFlowDefinitionsUrl: () => getListFlowDefinitionsUrl,
264
263
  getListSchemasUrl: () => getListSchemasUrl,
265
- getListSessionsUrl: () => getListSessionsUrl,
264
+ getListUserPasskeysUrl: () => getListUserPasskeysUrl,
265
+ getListUserTeamsUrl: () => getListUserTeamsUrl,
266
266
  getListUsersUrl: () => getListUsersUrl,
267
267
  getLive: () => getLive,
268
268
  getMySession: () => getMySession,
269
269
  getMyUser: () => getMyUser,
270
- getOpenIDConfiguration: () => getOpenIDConfiguration,
271
270
  getPatchProjectUrl: () => getPatchProjectUrl,
272
271
  getProject: () => getProject,
273
272
  getQueryProjectsUrl: () => getQueryProjectsUrl,
273
+ getQuerySessionsUrl: () => getQuerySessionsUrl,
274
+ getQueryTeamsUrl: () => getQueryTeamsUrl,
274
275
  getReady: () => getReady,
275
276
  getRevokeMySessionUrl: () => getRevokeMySessionUrl,
276
277
  getRevokeSessionUrl: () => getRevokeSessionUrl,
277
- getRevokeTokenUrl: () => getRevokeTokenUrl,
278
278
  getSchemaById: () => getSchemaById,
279
279
  getSession: () => getSession,
280
280
  getSetUserPasswordUrl: () => getSetUserPasswordUrl,
281
- getSubmitFlowEventUrl: () => getSubmitFlowEventUrl,
282
281
  getSubmitFlowStepUrl: () => getSubmitFlowStepUrl,
283
282
  getTeam: () => getTeam,
284
- getToken: () => getToken,
285
283
  getUpdateFlowDefinitionUrl: () => getUpdateFlowDefinitionUrl,
284
+ getUpdateTeamUrl: () => getUpdateTeamUrl,
286
285
  getUserByID: () => getUserByID,
287
- getUserInfo: () => getUserInfo,
288
286
  getVerifyChallengeProofUrl: () => getVerifyChallengeProofUrl,
289
- introspect: () => introspect,
287
+ initClaim: () => initClaim,
290
288
  issueChallenge: () => issueChallenge,
289
+ listBranding: () => listBranding,
291
290
  listFlowDefinitions: () => listFlowDefinitions,
292
291
  listSchemas: () => listSchemas,
293
- listSessions: () => listSessions,
292
+ listUserPasskeys: () => listUserPasskeys,
293
+ listUserTeams: () => listUserTeams,
294
294
  listUsers: () => listUsers,
295
295
  patchProject: () => patchProject,
296
296
  queryProjects: () => queryProjects,
297
+ querySessions: () => querySessions,
298
+ queryTeams: () => queryTeams,
297
299
  revokeMySession: () => revokeMySession,
298
300
  revokeSession: () => revokeSession,
299
- revokeToken: () => revokeToken,
300
301
  setUserPassword: () => setUserPassword,
301
- submitFlowEvent: () => submitFlowEvent,
302
302
  submitFlowStep: () => submitFlowStep,
303
303
  updateFlowDefinition: () => updateFlowDefinition,
304
+ updateTeam: () => updateTeam,
304
305
  verifyChallengeProof: () => verifyChallengeProof
305
306
  });
306
- const getGetOpenIDConfigurationUrl = () => {
307
- return `${getProxyPath()}/.well-known/openid-configuration`;
308
- };
309
- /**
310
- * Retrieve the OpenID Connect configuration
311
- * @summary Get OpenID Connect configuration
312
- */
313
- const getOpenIDConfiguration = async (options) => {
314
- return customFetch(getGetOpenIDConfigurationUrl(), {
315
- ...options,
316
- method: "GET"
317
- });
318
- };
319
- const getAuthorizeGetUrl = (params) => {
320
- const normalizedParams = new URLSearchParams();
321
- Object.entries(params || {}).forEach(([key, value]) => {
322
- if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
323
- });
324
- const stringifiedParams = normalizedParams.toString();
325
- return stringifiedParams.length > 0 ? `${getProxyPath()}/auth/authorize?${stringifiedParams}` : `${getProxyPath()}/auth/authorize`;
326
- };
327
- /**
328
- * @summary Authorize a user
329
- */
330
- const authorizeGet = async (params, options) => {
331
- return customFetch(getAuthorizeGetUrl(params), {
332
- ...options,
333
- method: "GET"
334
- });
335
- };
336
- const getAuthorizeDeviceUrl = (params) => {
337
- const normalizedParams = new URLSearchParams();
338
- Object.entries(params || {}).forEach(([key, value]) => {
339
- if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
340
- });
341
- const stringifiedParams = normalizedParams.toString();
342
- return stringifiedParams.length > 0 ? `${getProxyPath()}/auth/device-authorization?${stringifiedParams}` : `${getProxyPath()}/auth/device-authorization`;
343
- };
344
- /**
345
- * @summary Authorize a device
346
- */
347
- const authorizeDevice = async (params, options) => {
348
- return customFetch(getAuthorizeDeviceUrl(params), {
349
- ...options,
350
- method: "GET"
351
- });
352
- };
353
- const getEndSessionUrl = (params) => {
354
- const normalizedParams = new URLSearchParams();
355
- Object.entries(params || {}).forEach(([key, value]) => {
356
- if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
357
- });
358
- const stringifiedParams = normalizedParams.toString();
359
- return stringifiedParams.length > 0 ? `${getProxyPath()}/auth/end-session?${stringifiedParams}` : `${getProxyPath()}/auth/end-session`;
360
- };
361
- /**
362
- * @summary End a session
363
- */
364
- const endSession = async (params, options) => {
365
- return customFetch(getEndSessionUrl(params), {
366
- ...options,
367
- method: "GET"
368
- });
369
- };
370
- const getIntrospectUrl = () => {
371
- return `${getProxyPath()}/auth/introspect`;
372
- };
373
- /**
374
- * @summary Introspect a token
375
- */
376
- const introspect = async (introspectBody, options) => {
377
- const formUrlEncoded = new URLSearchParams();
378
- formUrlEncoded.append(`token`, introspectBody.token);
379
- if (introspectBody.token_type_hint !== void 0) formUrlEncoded.append(`token_type_hint`, introspectBody.token_type_hint);
380
- return customFetch(getIntrospectUrl(), {
381
- ...options,
382
- method: "POST",
383
- headers: {
384
- "Content-Type": "application/x-www-form-urlencoded",
385
- ...options?.headers
386
- },
387
- body: formUrlEncoded
388
- });
389
- };
390
- const getGetKeysUrl = () => {
391
- return `${getProxyPath()}/auth/keys`;
392
- };
393
- /**
394
- * @summary Get public keys
395
- */
396
- const getKeys = async (options) => {
397
- return customFetch(getGetKeysUrl(), {
398
- ...options,
399
- method: "GET"
400
- });
401
- };
402
- const getRevokeTokenUrl = () => {
403
- return `${getProxyPath()}/auth/revoke`;
404
- };
405
- /**
406
- * @summary Revoke an access token or refresh token
407
- */
408
- const revokeToken = async (revokeTokenBody, options) => {
409
- const formUrlEncoded = new URLSearchParams();
410
- if (revokeTokenBody.token !== void 0) formUrlEncoded.append(`token`, revokeTokenBody.token);
411
- if (revokeTokenBody.token_type_hint !== void 0) formUrlEncoded.append(`token_type_hint`, revokeTokenBody.token_type_hint);
412
- return customFetch(getRevokeTokenUrl(), {
413
- ...options,
414
- method: "POST",
415
- headers: {
416
- "Content-Type": "application/x-www-form-urlencoded",
417
- ...options?.headers
418
- },
419
- body: formUrlEncoded
420
- });
421
- };
422
- const getGetTokenUrl = () => {
423
- return `${getProxyPath()}/auth/token`;
424
- };
425
- /**
426
- * @summary Get access token
427
- */
428
- const getToken = async (getTokenBody, options) => {
429
- const formUrlEncoded = new URLSearchParams();
430
- if (getTokenBody.code !== void 0) formUrlEncoded.append(`code`, getTokenBody.code);
431
- if (getTokenBody.client_assertion !== void 0) formUrlEncoded.append(`client_assertion`, getTokenBody.client_assertion);
432
- if (getTokenBody.client_assertion_type !== void 0) formUrlEncoded.append(`client_assertion_type`, getTokenBody.client_assertion_type);
433
- if (getTokenBody.client_id !== void 0) formUrlEncoded.append(`client_id`, getTokenBody.client_id);
434
- if (getTokenBody.client_secret !== void 0) formUrlEncoded.append(`client_secret`, getTokenBody.client_secret);
435
- if (getTokenBody.code_verifier !== void 0) formUrlEncoded.append(`code_verifier`, getTokenBody.code_verifier);
436
- if (getTokenBody.grant_type !== void 0) formUrlEncoded.append(`grant_type`, getTokenBody.grant_type);
437
- if (getTokenBody.redirect_uri !== void 0) formUrlEncoded.append(`redirect_uri`, getTokenBody.redirect_uri);
438
- return customFetch(getGetTokenUrl(), {
439
- ...options,
440
- method: "POST",
441
- headers: {
442
- "Content-Type": "application/x-www-form-urlencoded",
443
- ...options?.headers
444
- },
445
- body: formUrlEncoded
446
- });
447
- };
448
- const getGetUserInfoUrl = () => {
449
- return `${getProxyPath()}/auth/userinfo`;
450
- };
451
- /**
452
- * @summary Get user info
453
- */
454
- const getUserInfo = async (options) => {
455
- return customFetch(getGetUserInfoUrl(), {
456
- ...options,
457
- method: "GET"
458
- });
459
- };
460
307
  const getGetHealthUrl = () => {
461
308
  return `${getProxyPath()}/healthz`;
462
309
  };
@@ -552,6 +399,40 @@ const getUserByID = async (userId, params, options) => {
552
399
  method: "GET"
553
400
  });
554
401
  };
402
+ const getDeleteUserByIDUrl = (userId, params) => {
403
+ const normalizedParams = new URLSearchParams();
404
+ Object.entries(params || {}).forEach(([key, value]) => {
405
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
406
+ });
407
+ const stringifiedParams = normalizedParams.toString();
408
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/users/${userId}?${stringifiedParams}` : `${getProxyPath()}/users/${userId}`;
409
+ };
410
+ /**
411
+ * @summary Delete user by ID
412
+ */
413
+ const deleteUserByID = async (userId, params, options) => {
414
+ return customFetch(getDeleteUserByIDUrl(userId, params), {
415
+ ...options,
416
+ method: "DELETE"
417
+ });
418
+ };
419
+ const getListUserPasskeysUrl = (userId, params) => {
420
+ const normalizedParams = new URLSearchParams();
421
+ Object.entries(params || {}).forEach(([key, value]) => {
422
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
423
+ });
424
+ const stringifiedParams = normalizedParams.toString();
425
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/users/${userId}/passkeys?${stringifiedParams}` : `${getProxyPath()}/users/${userId}/passkeys`;
426
+ };
427
+ /**
428
+ * @summary List user passkeys
429
+ */
430
+ const listUserPasskeys = async (userId, params, options) => {
431
+ return customFetch(getListUserPasskeysUrl(userId, params), {
432
+ ...options,
433
+ method: "GET"
434
+ });
435
+ };
555
436
  const getSetUserPasswordUrl = (userId, params) => {
556
437
  const normalizedParams = new URLSearchParams();
557
438
  Object.entries(params || {}).forEach(([key, value]) => {
@@ -574,6 +455,31 @@ const setUserPassword = async (userId, setUserPasswordBody, params, options) =>
574
455
  body: JSON.stringify(setUserPasswordBody)
575
456
  });
576
457
  };
458
+ const getListUserTeamsUrl = (userId, params) => {
459
+ const normalizedParams = new URLSearchParams();
460
+ Object.entries(params || {}).forEach(([key, value]) => {
461
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
462
+ });
463
+ const stringifiedParams = normalizedParams.toString();
464
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/users/${userId}/teams?${stringifiedParams}` : `${getProxyPath()}/users/${userId}/teams`;
465
+ };
466
+ /**
467
+ * The user's team roster, ordered by team name. Each entry carries the
468
+ team's name, so a client renders a page without resolving ids one by one.
469
+
470
+ This is the N:N roster and it is not lifecycle ownership: the single team
471
+ that owns the user's lifecycle is reported as `metadata.lifecycle_owner_team_id`
472
+ on the user read endpoints. Memberships the user was removed from are not
473
+ returned.
474
+
475
+ * @summary List the teams a user belongs to
476
+ */
477
+ const listUserTeams = async (userId, params, options) => {
478
+ return customFetch(getListUserTeamsUrl(userId, params), {
479
+ ...options,
480
+ method: "GET"
481
+ });
482
+ };
577
483
  const getGetMyUserUrl = () => {
578
484
  return `${getProxyPath()}/users/me`;
579
485
  };
@@ -595,7 +501,7 @@ the first capability step. Creates a new session implicitly unless
595
501
  `session_id` is provided (for step-up / reauth on an existing session).
596
502
 
597
503
  The response contains an `id` field — the flow handle. Use it as the path
598
- parameter for all subsequent `/flow/{id}/submit` and `/flow/{id}/event` calls.
504
+ parameter for all subsequent `/flow/{id}/submit` calls.
599
505
 
600
506
  The response also sets an encrypted `HttpOnly` cookie (`_zflow`) containing
601
507
  the flow's orchestration state (current step, collected data, history).
@@ -675,26 +581,6 @@ const submitFlowStep = async (id, submitFlowStepBody, options) => {
675
581
  body: JSON.stringify(submitFlowStepBody)
676
582
  });
677
583
  };
678
- const getSubmitFlowEventUrl = (id) => {
679
- return `${getProxyPath()}/flow/${id}/event`;
680
- };
681
- /**
682
- * Submits telemetry or fingerprint data from the frontend.
683
- Does not advance the state machine. Used for risk evaluation.
684
-
685
- * @summary Submit client-side event
686
- */
687
- const submitFlowEvent = async (id, submitFlowEventBody, options) => {
688
- return customFetch(getSubmitFlowEventUrl(id), {
689
- ...options,
690
- method: "POST",
691
- headers: {
692
- "Content-Type": "application/json",
693
- ...options?.headers
694
- },
695
- body: JSON.stringify(submitFlowEventBody)
696
- });
697
- };
698
584
  const getCreateAuthAttemptUrl = () => {
699
585
  return `${getProxyPath()}/auth_attempts`;
700
586
  };
@@ -882,6 +768,68 @@ const patchProject = async (projectId, patchProjectBody, options) => {
882
768
  body: JSON.stringify(patchProjectBody)
883
769
  });
884
770
  };
771
+ const getInitClaimUrl = (projectId) => {
772
+ return `${getProxyPath()}/projects/${projectId}/claim/init`;
773
+ };
774
+ /**
775
+ * Starts a claim challenge for an unclaimed project. Authenticated by the
776
+ project secret, this mints a single-use, short-lived challenge and returns
777
+ the `claim_url` the developer opens in a browser to complete the claim,
778
+ together with the `challenge_id` the CLI polls with. The exact expiry is
779
+ carried by `expires_at` on the response.
780
+
781
+ * @summary Initialize a project claim
782
+ */
783
+ const initClaim = async (projectId, options) => {
784
+ return customFetch(getInitClaimUrl(projectId), {
785
+ ...options,
786
+ method: "POST"
787
+ });
788
+ };
789
+ const getGetClaimStatusUrl = (projectId, params) => {
790
+ const normalizedParams = new URLSearchParams();
791
+ Object.entries(params || {}).forEach(([key, value]) => {
792
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
793
+ });
794
+ const stringifiedParams = normalizedParams.toString();
795
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/projects/${projectId}/claim/status?${stringifiedParams}` : `${getProxyPath()}/projects/${projectId}/claim/status`;
796
+ };
797
+ /**
798
+ * Polled by the CLI while a browser completes the claim. Authorized by the
799
+ project secret that initiated the challenge. Returns `pending`, or
800
+ `completed` with the owning team, the claim timestamp, and the dashboard
801
+ URL once the browser leg has finished.
802
+
803
+ * @summary Get claim status
804
+ */
805
+ const getClaimStatus = async (projectId, params, options) => {
806
+ return customFetch(getGetClaimStatusUrl(projectId, params), {
807
+ ...options,
808
+ method: "GET"
809
+ });
810
+ };
811
+ const getCompleteClaimUrl = (projectId) => {
812
+ return `${getProxyPath()}/projects/${projectId}/claim/complete`;
813
+ };
814
+ /**
815
+ * Called by the browser after the developer authenticates on the claim page.
816
+ Authenticated by the `__nextgen_session` cookie, it attaches the project to
817
+ the developer's personal team using the `challenge_id` from the claim URL as
818
+ its single-use, browser-safe authorization.
819
+
820
+ * @summary Complete a project claim
821
+ */
822
+ const completeClaim = async (projectId, completeClaimBody, options) => {
823
+ return customFetch(getCompleteClaimUrl(projectId), {
824
+ ...options,
825
+ method: "POST",
826
+ headers: {
827
+ "Content-Type": "application/json",
828
+ ...options?.headers
829
+ },
830
+ body: JSON.stringify(completeClaimBody)
831
+ });
832
+ };
885
833
  const getCreateSessionUrl = () => {
886
834
  return `${getProxyPath()}/sessions`;
887
835
  };
@@ -894,8 +842,16 @@ Use this explicitly when you want to:
894
842
  can be correlated with the eventual authenticated session from the start.
895
843
  - Track anonymous state (bot detection, device fingerprint) that survives until authentication.
896
844
 
897
- The returned `session_token` authorises GET and DELETE on this session.
898
- It is superseded when a handoff exchange completes — clients must replace it at that point.
845
+ Creating a session is an app-plane operation on the project credential
846
+ (`session.write`). The returned `session_token` is a session credential for the
847
+ end-user client, not a management scope: it is delivered as the
848
+ `__nextgen_session` cookie and authorises the self-service operations
849
+ `GET /sessions/me` and `DELETE /sessions/me` (`nextgenSession` scheme). The
850
+ by-id operations `GET /sessions/{session_id}` and `DELETE /sessions/{session_id}`
851
+ are operator endpoints and require `session.read` / `session.delete` instead.
852
+
853
+ The `session_token` is superseded when a handoff exchange completes — clients must
854
+ replace it at that point.
899
855
 
900
856
  Anonymous sessions expire aggressively (10-minute TTL). The TTL resets to the configured
901
857
  full session TTL when the first authentication factor is written via a completing `auth_attempt`.
@@ -913,24 +869,30 @@ const createSession = async (createSessionBody, options) => {
913
869
  body: JSON.stringify(createSessionBody)
914
870
  });
915
871
  };
916
- const getListSessionsUrl = (params) => {
872
+ const getQuerySessionsUrl = (params) => {
917
873
  const normalizedParams = new URLSearchParams();
918
874
  Object.entries(params || {}).forEach(([key, value]) => {
919
875
  if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
920
876
  });
921
877
  const stringifiedParams = normalizedParams.toString();
922
- return stringifiedParams.length > 0 ? `${getProxyPath()}/sessions?${stringifiedParams}` : `${getProxyPath()}/sessions`;
878
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/sessions/query?${stringifiedParams}` : `${getProxyPath()}/sessions/query`;
923
879
  };
924
880
  /**
925
- * Returns a paginated list of sessions for a project.
926
- Requires a project service key (OAuth2 client credentials).
881
+ * Returns the sessions of a project, paginated with a cursor.
882
+ Sessions of every lifecycle state are returned; each carries its `state`.
883
+ Requires `session.read` permission.
927
884
 
928
- * @summary List sessions
885
+ * @summary Query sessions
929
886
  */
930
- const listSessions = async (params, options) => {
931
- return customFetch(getListSessionsUrl(params), {
887
+ const querySessions = async (querySessionsBody, params, options) => {
888
+ return customFetch(getQuerySessionsUrl(params), {
932
889
  ...options,
933
- method: "GET"
890
+ method: "POST",
891
+ headers: {
892
+ "Content-Type": "application/json",
893
+ ...options?.headers
894
+ },
895
+ body: JSON.stringify(querySessionsBody)
934
896
  });
935
897
  };
936
898
  const getExchangeHandoffUrl = (params) => {
@@ -1006,10 +968,15 @@ const getRevokeSessionUrl = (sessionId, params) => {
1006
968
  return stringifiedParams.length > 0 ? `${getProxyPath()}/sessions/${sessionId}?${stringifiedParams}` : `${getProxyPath()}/sessions/${sessionId}`;
1007
969
  };
1008
970
  /**
1009
- * Revokes the session immediately (`state: revoked`). This is the logout operation.
971
+ * Permanently deletes the session, terminating it immediately.
972
+
973
+ This is the operator revoke path and requires the `session.delete` scope on a
974
+ project-bound credential. End-user logout with the `__nextgen_session` cookie is
975
+ `DELETE /sessions/me` (`nextgenSession` scheme).
1010
976
 
1011
- The session_token issued at creation (or superseded by a handoff exchange) is required.
1012
- After revocation, any tokens derived from this session are invalidated.
977
+ Idempotent: deleting a session that does not exist (or was already deleted)
978
+ still returns 204. After deletion, any tokens derived from the session are
979
+ invalidated.
1013
980
 
1014
981
  * @summary Revoke session
1015
982
  */
@@ -1042,10 +1009,12 @@ const getRevokeMySessionUrl = () => {
1042
1009
  return `${getProxyPath()}/sessions/me`;
1043
1010
  };
1044
1011
  /**
1045
- * Revokes the session immediately (`state: revoked`). This is the logout operation.
1012
+ * Logs out by permanently deleting the session.
1046
1013
 
1047
- The __nextgen_session cookie issued at creation (or superseded by a handoff exchange) is required.
1048
- After revocation, any tokens derived from this session are invalidated including the cookie itself, which is cleared in the response.
1014
+ The `__nextgen_session` cookie issued at creation (or superseded by a handoff
1015
+ exchange) is required. Idempotent: if the session is already gone this still
1016
+ returns 204. Any tokens derived from the session are invalidated, and the
1017
+ cookie itself is cleared in the response.
1049
1018
 
1050
1019
  * @summary Revoke my session
1051
1020
  */
@@ -1064,10 +1033,12 @@ const getCreateSchemaUrl = (params) => {
1064
1033
  return stringifiedParams.length > 0 ? `${getProxyPath()}/schemas?${stringifiedParams}` : `${getProxyPath()}/schemas`;
1065
1034
  };
1066
1035
  /**
1067
- * Create a new schema. The schema definition must include a unique $id field,
1068
- which will be used to identify the schema in future requests. The $id must
1069
- be a valid URI and should ideally point to the location where the schema
1070
- can be accessed.
1036
+ * Create a new schema. The optional `$id` field is the JSON Schema document
1037
+ URI used to identify the schema in future requests (GitOps-stable identity,
1038
+ not a free-form resource primary key). When `$id` is omitted, the server
1039
+ generates a `sch_*` URL. When provided, `$id` must be unique within the
1040
+ project and should ideally be a valid URI pointing at where the schema can
1041
+ be accessed.
1071
1042
 
1072
1043
  The schema can either be a concrete schema, e.g. a user schema, or a
1073
1044
  schema-url which will be resolved by the server.
@@ -1230,93 +1201,200 @@ const deleteFlowDefinition = async (id, params, options) => {
1230
1201
  method: "DELETE"
1231
1202
  });
1232
1203
  };
1233
- const getActivateFlowDefinitionUrl = (id, params) => {
1204
+ const getCreateTeamUrl = (params) => {
1205
+ const normalizedParams = new URLSearchParams();
1206
+ Object.entries(params || {}).forEach(([key, value]) => {
1207
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
1208
+ });
1209
+ const stringifiedParams = normalizedParams.toString();
1210
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/teams?${stringifiedParams}` : `${getProxyPath()}/teams`;
1211
+ };
1212
+ /**
1213
+ * @summary Create team
1214
+ */
1215
+ const createTeam = async (createTeamBody, params, options) => {
1216
+ return customFetch(getCreateTeamUrl(params), {
1217
+ ...options,
1218
+ method: "POST",
1219
+ headers: {
1220
+ "Content-Type": "application/json",
1221
+ ...options?.headers
1222
+ },
1223
+ body: JSON.stringify(createTeamBody)
1224
+ });
1225
+ };
1226
+ const getQueryTeamsUrl = (params) => {
1234
1227
  const normalizedParams = new URLSearchParams();
1235
1228
  Object.entries(params || {}).forEach(([key, value]) => {
1236
1229
  if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
1237
1230
  });
1238
1231
  const stringifiedParams = normalizedParams.toString();
1239
- return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions/${id}/activate?${stringifiedParams}` : `${getProxyPath()}/flow_definitions/${id}/activate`;
1232
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/teams/query?${stringifiedParams}` : `${getProxyPath()}/teams/query`;
1240
1233
  };
1241
1234
  /**
1242
- * Activate a flow definition by transitioning it from a `draft` state to an `active` state.
1243
- Alternatively, the status of a flow definition can also be set via the `POST /flow_definitions` and `PUT /flow_definitions/{id}` endpoints by setting the `status` attribute in the flow definition payload.
1235
+ * Returns the teams of a project, paginated with a cursor.
1236
+ Teams of every lifecycle status are returned; each carries its `status`.
1244
1237
 
1245
- * @summary Activate a flow definition by ID.
1238
+ * @summary Query teams
1246
1239
  */
1247
- const activateFlowDefinition = async (id, params, options) => {
1248
- return customFetch(getActivateFlowDefinitionUrl(id, params), {
1240
+ const queryTeams = async (queryTeamsBody, params, options) => {
1241
+ return customFetch(getQueryTeamsUrl(params), {
1249
1242
  ...options,
1250
- method: "POST"
1243
+ method: "POST",
1244
+ headers: {
1245
+ "Content-Type": "application/json",
1246
+ ...options?.headers
1247
+ },
1248
+ body: JSON.stringify(queryTeamsBody)
1251
1249
  });
1252
1250
  };
1253
- const getDeactivateFlowDefinitionUrl = (id, params) => {
1251
+ const getGetTeamUrl = (teamId, params) => {
1254
1252
  const normalizedParams = new URLSearchParams();
1255
1253
  Object.entries(params || {}).forEach(([key, value]) => {
1256
1254
  if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
1257
1255
  });
1258
1256
  const stringifiedParams = normalizedParams.toString();
1259
- return stringifiedParams.length > 0 ? `${getProxyPath()}/flow_definitions/${id}/deactivate?${stringifiedParams}` : `${getProxyPath()}/flow_definitions/${id}/deactivate`;
1257
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/teams/${teamId}?${stringifiedParams}` : `${getProxyPath()}/teams/${teamId}`;
1260
1258
  };
1261
1259
  /**
1262
- * Deactivates a flow definition in the `active` state by transitioning it to the `draft` state.
1263
- Flow definitions in `draft` state cannot be used to start new flows. Existing flows that use the deactivated flow definition must gracefully handle this.
1264
- Alternatively, the status of a flow definition can also be set via the `POST /flow_definitions` and `PUT /flow_definitions/{id}` endpoints by setting the `status` attribute in the flow definition payload.
1260
+ * Returns a Team by its id.
1265
1261
 
1266
- * @summary Deactivate a flow definition by ID.
1262
+ * @summary Get team
1267
1263
  */
1268
- const deactivateFlowDefinition = async (id, params, options) => {
1269
- return customFetch(getDeactivateFlowDefinitionUrl(id, params), {
1264
+ const getTeam = async (teamId, params, options) => {
1265
+ return customFetch(getGetTeamUrl(teamId, params), {
1270
1266
  ...options,
1271
- method: "POST"
1267
+ method: "GET"
1272
1268
  });
1273
1269
  };
1274
- const getCreateTeamUrl = (params) => {
1270
+ const getDeleteTeamUrl = (teamId, params) => {
1275
1271
  const normalizedParams = new URLSearchParams();
1276
1272
  Object.entries(params || {}).forEach(([key, value]) => {
1277
1273
  if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
1278
1274
  });
1279
1275
  const stringifiedParams = normalizedParams.toString();
1280
- return stringifiedParams.length > 0 ? `${getProxyPath()}/teams?${stringifiedParams}` : `${getProxyPath()}/teams`;
1276
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/teams/${teamId}?${stringifiedParams}` : `${getProxyPath()}/teams/${teamId}`;
1281
1277
  };
1282
1278
  /**
1283
- * @summary Create team
1279
+ * Deactivates the team.
1280
+
1281
+ The team is tombstoned rather than erased: it stays readable through
1282
+ getTeam with status `deactivated`. Its memberships are removed and the
1283
+ users whose lifecycle it owns are deactivated with it.
1284
+
1285
+ The request is idempotent. Deleting a team that is already deactivated
1286
+ or doesn't exist succeeds without changing anything.
1287
+
1288
+ * @summary Delete team
1284
1289
  */
1285
- const createTeam = async (params, createTeamBody, options) => {
1286
- return customFetch(getCreateTeamUrl(params), {
1290
+ const deleteTeam = async (teamId, params, options) => {
1291
+ return customFetch(getDeleteTeamUrl(teamId, params), {
1287
1292
  ...options,
1288
- method: "POST",
1293
+ method: "DELETE"
1294
+ });
1295
+ };
1296
+ const getUpdateTeamUrl = (teamId, params) => {
1297
+ const normalizedParams = new URLSearchParams();
1298
+ Object.entries(params || {}).forEach(([key, value]) => {
1299
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
1300
+ });
1301
+ const stringifiedParams = normalizedParams.toString();
1302
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/teams/${teamId}?${stringifiedParams}` : `${getProxyPath()}/teams/${teamId}`;
1303
+ };
1304
+ /**
1305
+ * Update team. Only active teams can be updated.
1306
+
1307
+ * @summary Update team
1308
+ */
1309
+ const updateTeam = async (teamId, updateTeamBody, params, options) => {
1310
+ return customFetch(getUpdateTeamUrl(teamId, params), {
1311
+ ...options,
1312
+ method: "PATCH",
1289
1313
  headers: {
1290
1314
  "Content-Type": "application/json",
1291
1315
  ...options?.headers
1292
1316
  },
1293
- body: JSON.stringify(createTeamBody)
1317
+ body: JSON.stringify(updateTeamBody)
1294
1318
  });
1295
1319
  };
1296
- const getGetTeamUrl = (teamId, params) => {
1320
+ const getCreateBrandingUrl = (params) => {
1297
1321
  const normalizedParams = new URLSearchParams();
1298
1322
  Object.entries(params || {}).forEach(([key, value]) => {
1299
1323
  if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
1300
1324
  });
1301
1325
  const stringifiedParams = normalizedParams.toString();
1302
- return stringifiedParams.length > 0 ? `${getProxyPath()}/teams/${teamId}?${stringifiedParams}` : `${getProxyPath()}/teams/${teamId}`;
1326
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/branding?${stringifiedParams}` : `${getProxyPath()}/branding`;
1303
1327
  };
1304
1328
  /**
1305
- * Returns the current state of a team.
1329
+ * Publishes a new immutable branding revision for the project. Branding
1330
+ revisions cannot be updated or deleted; every edit publishes a new
1331
+ revision, and flow responses resolve the latest revision per project
1332
+ (see ADR 040).
1306
1333
 
1307
- * @summary Get team
1334
+ The `liquid_template` is validated lexically on save (size, encoding,
1335
+ banned patterns such as `<script>` tags, inline event handlers, and the
1336
+ `| raw` filter). Authoritative LiquidJS validation runs at authoring
1337
+ time via `zitadel plan` / `zitadel apply`.
1338
+
1339
+ * @summary Publish a new branding revision
1308
1340
  */
1309
- const getTeam = async (teamId, params, options) => {
1310
- return customFetch(getGetTeamUrl(teamId, params), {
1341
+ const createBranding = async (createBrandingBody, params, options) => {
1342
+ return customFetch(getCreateBrandingUrl(params), {
1311
1343
  ...options,
1312
- method: "GET"
1344
+ method: "POST",
1345
+ headers: {
1346
+ "Content-Type": "application/json",
1347
+ ...options?.headers
1348
+ },
1349
+ body: JSON.stringify(createBrandingBody)
1313
1350
  });
1314
1351
  };
1315
- //#endregion
1316
- //#region ../api/dist/runtime/api-factory.mjs
1317
- /**
1318
- * Factory for the typed Zitadel client every consumer reaches for.
1319
- *
1352
+ const getListBrandingUrl = (params) => {
1353
+ const normalizedParams = new URLSearchParams();
1354
+ Object.entries(params || {}).forEach(([key, value]) => {
1355
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
1356
+ });
1357
+ const stringifiedParams = normalizedParams.toString();
1358
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/branding?${stringifiedParams}` : `${getProxyPath()}/branding`;
1359
+ };
1360
+ /**
1361
+ * Lists branding revisions for the project, newest first, capped at the
1362
+ 100 most recent. The first entry is the revision flow responses
1363
+ currently resolve. Deliberately unpaginated in v1 — list endpoints
1364
+ gain a real query mechanism together (ADR 031); advertising pagination
1365
+ parameters the server ignores would be worse than none.
1366
+
1367
+ * @summary List branding revisions
1368
+ */
1369
+ const listBranding = async (params, options) => {
1370
+ return customFetch(getListBrandingUrl(params), {
1371
+ ...options,
1372
+ method: "GET"
1373
+ });
1374
+ };
1375
+ const getGetBrandingByIdUrl = (id, params) => {
1376
+ const normalizedParams = new URLSearchParams();
1377
+ Object.entries(params || {}).forEach(([key, value]) => {
1378
+ if (value !== void 0) normalizedParams.append(key, value === null ? "null" : value.toString());
1379
+ });
1380
+ const stringifiedParams = normalizedParams.toString();
1381
+ return stringifiedParams.length > 0 ? `${getProxyPath()}/branding/${id}?${stringifiedParams}` : `${getProxyPath()}/branding/${id}`;
1382
+ };
1383
+ /**
1384
+ * Retrieves a single branding revision, including its stored configuration.
1385
+ * @summary Get a branding revision by ID
1386
+ */
1387
+ const getBrandingById = async (id, params, options) => {
1388
+ return customFetch(getGetBrandingByIdUrl(id, params), {
1389
+ ...options,
1390
+ method: "GET"
1391
+ });
1392
+ };
1393
+ //#endregion
1394
+ //#region ../api/dist/runtime/api-factory.mjs
1395
+ /**
1396
+ * Factory for the typed Zitadel client every consumer reaches for.
1397
+ *
1320
1398
  * Wraps the orval-generated endpoints in a {@link Proxy} so each
1321
1399
  * generated function sees the right base URL and (optionally) the
1322
1400
  * right bearer token at call time — without the caller having to
@@ -1338,11 +1416,12 @@ const getTeam = async (teamId, params, options) => {
1338
1416
  * generated orval function:
1339
1417
  *
1340
1418
  * const client = createZitadelClient({ baseUrl, token });
1341
- * await client.createProject({ previewOrigins: [] });
1419
+ * await client.createProject({ preview_origins: [] });
1342
1420
  * await client.createSchema(body, { project_id });
1343
1421
  */
1344
1422
  function createZitadelClient(opts) {
1345
- const baseUrl = opts.baseUrl.replace(/\/+$/, "");
1423
+ let baseUrl = opts.baseUrl;
1424
+ while (baseUrl.endsWith("/")) baseUrl = baseUrl.slice(0, -1);
1346
1425
  return new Proxy(zitadelNextGen_exports, { get(target, prop, receiver) {
1347
1426
  const value = Reflect.get(target, prop, receiver);
1348
1427
  if (typeof value !== "function") return value;
@@ -1353,15 +1432,6 @@ function createZitadelClient(opts) {
1353
1432
  };
1354
1433
  } });
1355
1434
  }
1356
- /**
1357
- * Legacy entry point: builds a client with only the base URL set, no
1358
- * token. Components and SDKs that don't carry an auth token (browser
1359
- * flows authenticated by platform cookies) still consume this name.
1360
- * Equivalent to `createZitadelClient({ baseUrl: apiBase })`.
1361
- */
1362
- function createApi(apiBase) {
1363
- return createZitadelClient({ baseUrl: apiBase });
1364
- }
1365
1435
  //#endregion
1366
1436
  //#region ../api/dist/runtime/config.mjs
1367
1437
  /**
@@ -1401,7 +1471,10 @@ const apiCache = /* @__PURE__ */ new WeakMap();
1401
1471
  function getApi(project) {
1402
1472
  let api = apiCache.get(project);
1403
1473
  if (!api) {
1404
- api = createApi(project.proxyPath);
1474
+ api = createZitadelClient({
1475
+ baseUrl: project.proxyPath,
1476
+ token: project.publishableKey
1477
+ });
1405
1478
  apiCache.set(project, api);
1406
1479
  }
1407
1480
  return api;
@@ -1489,183 +1562,6 @@ function resolveApi(project, attrs, element) {
1489
1562
  };
1490
1563
  }
1491
1564
  //#endregion
1492
- //#region src/orchestrator/branding-to-tokens.ts
1493
- /**
1494
- * Branding -> CSS token bridge.
1495
- *
1496
- * Per `docs/design/branding/README.md` the orchestrator owns theming. Templates
1497
- * never emit `<style>` blocks; they only structure HTML. This module translates
1498
- * the `Branding` JSON into a CSSStyleSheet of `:host { --zl-* }` declarations
1499
- * and applies it via `shadowRoot.adoptedStyleSheets`.
1500
- *
1501
- * Dark-mode overrides are emitted as `:host([data-theme="dark"]) { ... }`
1502
- * (matching `docs/design/branding/tokens.md`). Resolution between
1503
- * `light | dark | auto` happens in `<zitadel-login>`.
1504
- *
1505
- * Base layer: the design-tokens package ships the full `--zl-*` set as both a
1506
- * `.css` file (for host pages) and a `tokensCss` string (for shadow roots and
1507
- * SSR). We adopt the string into every orchestrator shadow root so atoms
1508
- * paint correctly even when the host page didn't `@import` tokens.css — the
1509
- * orchestrator is meant to drop into any page.
1510
- */
1511
- const RADIUS_MAP = {
1512
- none: "0",
1513
- sm: "0.25rem",
1514
- md: "0.5rem",
1515
- lg: "0.75rem",
1516
- full: "9999px"
1517
- };
1518
- const DENSITY_MAP = {
1519
- compact: {
1520
- "--zl-spacing-03": "0.75rem",
1521
- "--zl-spacing-05": "1.5rem"
1522
- },
1523
- regular: {},
1524
- comfortable: {
1525
- "--zl-spacing-03": "1.25rem",
1526
- "--zl-spacing-05": "2.25rem"
1527
- }
1528
- };
1529
- const PALETTE_MAP = {
1530
- primary: ["--zl-color-surface-default-white"],
1531
- on_primary: ["--zl-color-text-button-default"],
1532
- background: ["--zl-color-surface-default-black"],
1533
- surface: ["--zl-color-surface-default-primary-gray"],
1534
- muted: ["--zl-color-surface-default-secondary-gray"],
1535
- border: ["--zl-color-border-default-gray-200", "--zl-color-border-default-gray-100"],
1536
- text: ["--zl-color-text-primary-white"],
1537
- text_muted: ["--zl-color-text-secondary-gray"],
1538
- link: ["--zl-color-text-subtitle-pink"],
1539
- success: [
1540
- "--zl-color-text-success",
1541
- "--zl-color-border-success",
1542
- "--zl-color-icon-success"
1543
- ],
1544
- warning: ["--zl-color-text-subtitle-orange"],
1545
- error: [
1546
- "--zl-color-text-error",
1547
- "--zl-color-border-error",
1548
- "--zl-color-icon-error"
1549
- ]
1550
- };
1551
- /**
1552
- * Build a CSS string of `:host { --zl-* }` declarations from a Branding
1553
- * payload. Light values land on `:host`; dark overrides land on
1554
- * `:host([data-theme="dark"])`.
1555
- */
1556
- function buildBrandingStylesheet(branding, options = {}) {
1557
- const lightDecls = collectDeclarations(branding);
1558
- const darkPalette = branding?.theme?.dark?.palette;
1559
- const darkDecls = darkPalette ? mapPalette(darkPalette) : {};
1560
- const blocks = [];
1561
- if (Object.keys(lightDecls).length > 0) blocks.push(formatBlock(":host", lightDecls));
1562
- if (Object.keys(darkDecls).length > 0) blocks.push(formatBlock(":host([data-theme=\"dark\"])", darkDecls));
1563
- if (options.resolvedTheme === "dark" && darkPalette) {
1564
- const merged = {
1565
- ...lightDecls,
1566
- ...darkDecls
1567
- };
1568
- blocks.length = 0;
1569
- blocks.push(formatBlock(":host", merged));
1570
- }
1571
- return blocks.join("\n");
1572
- }
1573
- function collectDeclarations(branding) {
1574
- if (!branding) return {};
1575
- const decls = {};
1576
- Object.assign(decls, mapPalette(branding.palette));
1577
- Object.assign(decls, mapTypography(branding.typography));
1578
- Object.assign(decls, mapShape(branding.shape));
1579
- return decls;
1580
- }
1581
- function mapPalette(palette) {
1582
- if (!palette) return {};
1583
- const out = {};
1584
- for (const [key, varNames] of Object.entries(PALETTE_MAP)) {
1585
- const value = palette[key];
1586
- if (typeof value === "string" && value.length > 0) for (const varName of varNames) out[varName] = value;
1587
- }
1588
- return out;
1589
- }
1590
- function mapTypography(typography) {
1591
- if (!typography) return {};
1592
- const out = {};
1593
- if (typography.font_family) {
1594
- out["--zl-font-family-sans"] = typography.font_family;
1595
- out["--zl-font-family-heading"] = typography.font_family;
1596
- }
1597
- if (typography.font_family_mono) out["--zl-font-family-mono"] = typography.font_family_mono;
1598
- const scale = clamp(typography.scale ?? 1, .75, 1.25);
1599
- if (scale !== 1) out["--zl-font-scale"] = `${scale}`;
1600
- return out;
1601
- }
1602
- function mapShape(shape) {
1603
- if (!shape) return {};
1604
- const out = {};
1605
- if (shape.radius && RADIUS_MAP[shape.radius]) {
1606
- const radius = RADIUS_MAP[shape.radius];
1607
- out["--zl-radius-s"] = radius;
1608
- out["--zl-radius-m"] = radius === "0" ? "0" : `calc(${radius} * 1.5)`;
1609
- out["--zl-radius-l"] = radius === "0" ? "0" : `calc(${radius} * 2)`;
1610
- }
1611
- if (shape.density) Object.assign(out, DENSITY_MAP[shape.density]);
1612
- return out;
1613
- }
1614
- function formatBlock(selector, decls) {
1615
- return `${selector} {\n${Object.entries(decls).map(([prop, value]) => ` ${prop}: ${value};`).join("\n")}\n}`;
1616
- }
1617
- function clamp(value, min, max) {
1618
- if (Number.isNaN(value)) return min;
1619
- return Math.min(max, Math.max(min, value));
1620
- }
1621
- let baseTokenSheet;
1622
- function getBaseTokenSheet() {
1623
- if (typeof CSSStyleSheet === "undefined") return void 0;
1624
- if (!baseTokenSheet) {
1625
- baseTokenSheet = new CSSStyleSheet();
1626
- baseTokenSheet.replaceSync(tokensCss.replaceAll(":root,\n[data-theme=\"dark\"]", ":host,\n:host([data-theme=\"dark\"])").replaceAll("[data-theme=\"light\"]", ":host([data-theme=\"light\"])"));
1627
- }
1628
- return baseTokenSheet;
1629
- }
1630
- /**
1631
- * Adopt the design-system base token layer onto a `ShadowRoot`. Safe to call
1632
- * many times — the underlying constructable sheet is shared across every
1633
- * orchestrator instance and de-duplicated in the adopted list.
1634
- */
1635
- function applyBaseTokens(shadowRoot) {
1636
- const sheet = getBaseTokenSheet();
1637
- if (!sheet) return;
1638
- const existing = Array.isArray(shadowRoot.adoptedStyleSheets) ? shadowRoot.adoptedStyleSheets : [];
1639
- if (existing.includes(sheet)) return;
1640
- try {
1641
- shadowRoot.adoptedStyleSheets = [sheet, ...existing];
1642
- } catch {}
1643
- }
1644
- /**
1645
- * Apply a branding payload as a `--zl-*` overrides layer on top of the base
1646
- * token sheet. Subsequent calls replace the previous override sheet so a new
1647
- * branding payload paints cleanly without leaking older declarations.
1648
- *
1649
- * Callers should run `applyBaseTokens(shadowRoot)` first (once per shadow
1650
- * root) so the base values exist before branding patches them.
1651
- */
1652
- function applyBrandingTokens(shadowRoot, branding, resolvedTheme) {
1653
- const css = buildBrandingStylesheet(branding, { resolvedTheme });
1654
- if (typeof CSSStyleSheet === "undefined") return;
1655
- const sheet = new CSSStyleSheet();
1656
- sheet.replaceSync(css);
1657
- const previous = shadowRoot.__zlTokenSheet;
1658
- shadowRoot.adoptedStyleSheets = [...shadowRoot.adoptedStyleSheets.filter((s) => s !== previous), sheet];
1659
- shadowRoot.__zlTokenSheet = sheet;
1660
- }
1661
- function resolveTheme(branding) {
1662
- const mode = branding?.theme?.mode ?? "dark";
1663
- if (mode === "light") return "light";
1664
- if (mode === "dark") return "dark";
1665
- if (typeof matchMedia === "function") return matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
1666
- return "dark";
1667
- }
1668
- //#endregion
1669
1565
  //#region ../api/dist/generated/model/index.mjs
1670
1566
  const CreateFlow201BrandingLayout = {
1671
1567
  centered: "centered",
@@ -1728,83 +1624,57 @@ function sanitiseUrl(value, field, issues) {
1728
1624
  }
1729
1625
  }
1730
1626
  //#endregion
1731
- //#region src/orchestrator/font-loader.ts
1627
+ //#region src/orchestrator/exportparts.ts
1732
1628
  /**
1733
- * Document-level font stylesheet injection for `<zitadel-login>`.
1734
- *
1735
- * Two faces can be linked into the host document's `<head>`:
1736
- *
1737
- * - The **design-system default** (`applyDefaultFont`) — the brand body face
1738
- * (Arimo, named first in `--zl-font-family-sans`). The component ships this
1739
- * so the auth screens render in the brand font out of the box, even when the
1740
- * server returns no branding. This is the "default users can override" layer.
1741
- * - The **tenant override** (`applyFontUrl`) — `branding.font_url`, resolved
1742
- * server-side from the app → team → project branding hierarchy.
1629
+ * Forwards atom-internal CSS Shadow Parts through the orchestrator boundary.
1743
1630
  *
1744
- * Both must live at document level, not inside the shadow root: browsers ignore
1745
- * `@font-face` rules declared in shadow-tree stylesheets, so a shadow-scoped
1746
- * link would never register the font faces and every branded font silently
1747
- * falls back to the system stack. `font-family` references inside the shadow
1748
- * tree resolve against document-level faces.
1631
+ * Atoms stamp `part="..."` on their internals and declare the public set in
1632
+ * their manifest (`parts`). Those parts are visible one shadow boundary up
1633
+ * inside the orchestrator's shadow root but not to the embedding page,
1634
+ * because `::part()` pierces a single boundary unless each host in the chain
1635
+ * re-exports (`exportparts`). Templates never author the forwarding (the
1636
+ * sanitiser strips `exportparts`); the orchestrator stamps it after every
1637
+ * render commit so the mapping stays canonical for template-rendered and
1638
+ * gate-patched atoms alike.
1749
1639
  *
1750
- * Both injectors are idempotent: calling with the same URL is a no-op; calling
1751
- * with a new URL replaces the previous link; calling with `null`/`undefined`
1752
- * removes any previously injected link.
1753
- */
1754
- const TENANT_LINK_ID = "zl-font-link";
1755
- const DEFAULT_LINK_ID = "zl-default-font-link";
1756
- /**
1757
- * Design-system default brand font. Arimo is the Figma body face and leads the
1758
- * `--zl-font-family-sans` stack; loading it here makes the unbranded auth UI
1759
- * paint in the brand font instead of the system fallback. Tenants override it
1760
- * with `branding.font_url`. Exported so a privacy- or offline-sensitive
1761
- * deployment can self-host the same face and swap this single URL.
1640
+ * Exported names are mechanical, collision-free, and public contract
1641
+ * (override-ladder Tier 2, same stability rules as token names):
1642
+ * `<atom>-<part>` with the `zl-` prefix dropped — `zl-field`'s `input` part
1643
+ * becomes `zitadel-login::part(field-input)` on the host page. Manifests are
1644
+ * the canonical part catalogue; this module derives, never curates.
1762
1645
  */
1763
- const DEFAULT_BRAND_FONT_HREF = "https://fonts.googleapis.com/css2?family=Arimo:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap";
1646
+ const exportpartsByTag = new Map(manifestRegistry.filter((manifest) => manifest.parts.length > 0).map((manifest) => {
1647
+ const short = manifest.tag.replace(/^zl-/, "");
1648
+ const value = manifest.parts.map((part) => `${part}: ${short}-${part}`).join(", ");
1649
+ return [manifest.tag, value];
1650
+ }));
1764
1651
  /**
1765
- * Inject the tenant's `branding.font_url` stylesheet. Pass `null`/`undefined`
1766
- * to remove it (e.g. when a flow resolves with no tenant font).
1652
+ * Stamps `exportparts` onto every known atom in `scope`. Idempotent call
1653
+ * after each render commit: `unsafeHTML` re-parses replace previously
1654
+ * stamped nodes, and the mandatory-gates patcher appends atoms after
1655
+ * sanitisation.
1767
1656
  */
1768
- function applyFontUrl(shadowRoot, fontUrl) {
1769
- applyLinkedStylesheet(shadowRoot, TENANT_LINK_ID, fontUrl);
1657
+ function stampExportparts(scope) {
1658
+ for (const [tag, value] of exportpartsByTag) for (const el of scope.querySelectorAll(tag)) if (el.getAttribute("exportparts") !== value) el.setAttribute("exportparts", value);
1770
1659
  }
1660
+ //#endregion
1661
+ //#region src/internal/escape-html.ts
1771
1662
  /**
1772
- * Inject the design-system default font stylesheet. Defaults to
1773
- * {@link DEFAULT_BRAND_FONT_HREF}; pass `null` to remove it (the orchestrator
1774
- * does this when a tenant `font_url` takes over, to avoid a redundant request).
1663
+ * Escapes the five HTML-significant characters so a string is safe to
1664
+ * interpolate into either element text or a double-quoted attribute value.
1665
+ *
1666
+ * Used for the orchestrator-owned attribution markup, which is appended after
1667
+ * the Liquid output has already been sanitised — so it can't rely on the
1668
+ * DOMPurify pass and must escape its own (tenant-supplied) values.
1775
1669
  */
1776
- function applyDefaultFont(shadowRoot, href = DEFAULT_BRAND_FONT_HREF) {
1777
- applyLinkedStylesheet(shadowRoot, DEFAULT_LINK_ID, href);
1778
- }
1779
- function applyLinkedStylesheet(shadowRoot, linkId, href) {
1780
- const ownerDocument = shadowRoot.ownerDocument ?? document;
1781
- const existing = ownerDocument.head.querySelector(`link#${linkId}`);
1782
- if (!href) {
1783
- existing?.remove();
1784
- return;
1785
- }
1786
- const link = existing ?? ownerDocument.createElement("link");
1787
- link.id = linkId;
1788
- link.rel = "stylesheet";
1789
- if (link.href !== href) link.href = href;
1790
- if (!existing) ownerDocument.head.appendChild(link);
1791
- }
1792
- //#endregion
1793
- //#region src/internal/escape-html.ts
1794
- /**
1795
- * Escapes the five HTML-significant characters so a string is safe to
1796
- * interpolate into either element text or a double-quoted attribute value.
1797
- *
1798
- * Used for the orchestrator-owned attribution markup, which is appended after
1799
- * the Liquid output has already been sanitised — so it can't rely on the
1800
- * DOMPurify pass and must escape its own (tenant-supplied) values.
1801
- */
1802
- function escapeHtml(value) {
1803
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1670
+ function escapeHtml(value) {
1671
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1804
1672
  }
1805
1673
  //#endregion
1806
1674
  //#region src/orchestrator/mandatory-gates.ts
1807
1675
  const MANDATORY_GATES_MARKER = "ZL_MANDATORY_GATES";
1676
+ /** Every atom tag that participates as a named form field. */
1677
+ const FIELD_ATOM_TAGS = "zl-field, zl-select, zl-checkbox";
1808
1678
  const mandatoryGatesMarkerComment = `<!--${MANDATORY_GATES_MARKER}-->`;
1809
1679
  function patchMandatoryGates(html, step, locale) {
1810
1680
  const template = document.createElement("template");
@@ -1835,7 +1705,7 @@ function hasPrimaryButton(fragment) {
1835
1705
  return Boolean(fragment.querySelector("zl-button[hierarchy=\"primary\"]"));
1836
1706
  }
1837
1707
  function hasFieldFor(fragment, name) {
1838
- for (const field of fragment.querySelectorAll("zl-field")) if (field.getAttribute("name") === name) return true;
1708
+ for (const field of fragment.querySelectorAll(FIELD_ATOM_TAGS)) if (field.getAttribute("name") === name) return true;
1839
1709
  return false;
1840
1710
  }
1841
1711
  function findMarkerComment(fragment) {
@@ -1887,11 +1757,15 @@ function lookup(locale, key) {
1887
1757
  */
1888
1758
  const TEMPLATE_NAMES$1 = { default: "default" };
1889
1759
  /**
1890
- * Maps `error.*` text keys to a field name (Figma inline-error
1891
- * annotations). Shared by the template filters (`fieldError` routes these
1892
- * inline, `formLevelError` suppresses their banner) and by
1893
- * {@link localiseFlowErrorKeys}, which must downgrade them to a banner
1894
- * message when the step doesn't render the mapped field.
1760
+ * Maps catalog `error.*` keys with no `_<rule>` suffix (so
1761
+ * {@link localiseFlowErrorKeys} can't derive the field from the key itself)
1762
+ * to their field name the credential errors with bespoke catalog copy
1763
+ * (`error.email_exists`, `error.invalid_credentials`, …). Rule-suffixed keys
1764
+ * (`error.<field>_<rule>`) derive their field from the key. Consumed only by
1765
+ * {@link localiseFlowErrorKeys}, which stamps the resolved `field` onto the
1766
+ * error; the template filters (`fieldError` / `formLevelError`) then route by
1767
+ * that `field`, and an error whose field the step omits is downgraded to a
1768
+ * fieldless banner message.
1895
1769
  */
1896
1770
  const fieldErrorKeys = {
1897
1771
  "error.email_required": "email",
@@ -1915,9 +1789,18 @@ function createLiquidEngine(options) {
1915
1789
  outputEscape: "escape"
1916
1790
  });
1917
1791
  engine.registerFilter("raw", (value) => stringify(value));
1792
+ const warnedMissingKeys = /* @__PURE__ */ new Set();
1918
1793
  engine.registerFilter("t", function tFilter(key, ...args) {
1919
1794
  const lookupKey = stringify(key);
1920
- return interpolate(options.locale[lookupKey] ?? injectedKeyFallback(options.locale, lookupKey) ?? fieldLabelFallback(lookupKey) ?? lookupKey, args.map(stringify));
1795
+ let template = options.locale[lookupKey] ?? injectedKeyFallback(options.locale, lookupKey) ?? fieldLabelFallback(lookupKey);
1796
+ if (template === void 0) {
1797
+ template = lookupKey;
1798
+ if (lookupKey !== "" && !warnedMissingKeys.has(lookupKey)) {
1799
+ warnedMissingKeys.add(lookupKey);
1800
+ console.warn(`[zitadel-login] missing text key "${lookupKey}" — rendering the raw key`);
1801
+ }
1802
+ }
1803
+ return interpolate(template, args.map(stringify));
1921
1804
  });
1922
1805
  /** Resolves `{text_key}.placeholder` — empty when undefined (not the raw key). */
1923
1806
  engine.registerFilter("fieldPlaceholder", (textKey) => {
@@ -1960,8 +1843,9 @@ function createLiquidEngine(options) {
1960
1843
  const name = stringify(fieldName);
1961
1844
  if (!Array.isArray(errors)) return "";
1962
1845
  for (const item of errors) {
1963
- const key = item.text_key ?? "";
1964
- if (fieldErrorKeys[key] === name) return options.locale[key] ?? key;
1846
+ const err = item;
1847
+ if (err.field !== name) continue;
1848
+ return err.text_key ? options.locale[err.text_key] ?? err.text_key : err.message ?? "";
1965
1849
  }
1966
1850
  return "";
1967
1851
  });
@@ -1972,12 +1856,17 @@ function createLiquidEngine(options) {
1972
1856
  * raw wire key. See hookName for the contract.
1973
1857
  */
1974
1858
  engine.registerFilter("testid", (fieldName) => hookName(stringify(fieldName)));
1975
- /** True when the error should render as `<zl-alert>`, not on a field. */
1859
+ /**
1860
+ * True when the error should render as `<zl-alert>`, not on a field. A
1861
+ * field-scoped error (`field` set by {@link localiseFlowErrorKeys} when the
1862
+ * step renders that field) routes inline instead; everything else — engine
1863
+ * errors, and orphaned field errors already downgraded to a fieldless
1864
+ * message — falls to the banner.
1865
+ */
1976
1866
  engine.registerFilter("formLevelError", (err) => {
1977
- const key = err?.text_key ?? "";
1978
- return key === "" || !(key in fieldErrorKeys);
1867
+ return !err?.field;
1979
1868
  });
1980
- engine.registerTag("mandatory_gates", {
1869
+ engine.registerTag(MANDATORY_GATES_TAG, {
1981
1870
  parse() {},
1982
1871
  render() {
1983
1872
  return mandatoryGatesMarkerComment;
@@ -2077,12 +1966,19 @@ function localiseFlowErrorKeys(raw, ctx) {
2077
1966
  function localiseFlowErrorKey(key, ctx) {
2078
1967
  const inlineField = fieldErrorKeys[key];
2079
1968
  const orphanedInline = inlineField !== void 0 && ctx.fields !== void 0 && !ctx.fields.includes(inlineField);
2080
- if (!orphanedInline && ctx.locale[key] !== void 0) return { text_key: key };
1969
+ if (!orphanedInline && ctx.locale[key] !== void 0) return inlineField !== void 0 ? {
1970
+ field: inlineField,
1971
+ text_key: key
1972
+ } : { text_key: key };
2081
1973
  for (const { suffix, generic } of FLOW_ERROR_RULE_FALLBACKS) {
2082
1974
  if (!key.endsWith(suffix)) continue;
2083
1975
  const field = key.slice(6, key.length - suffix.length);
2084
1976
  if (field === "") break;
2085
- return { message: capitaliseFirst(interpolate(ctx.locale[generic] ?? ctx.locale[FLOW_ERROR_CATCH_ALL_KEY] ?? "Please check {0}.", [fieldLabel(ctx, field)])) };
1977
+ const message = capitaliseFirst(interpolate(ctx.locale[generic] ?? ctx.locale[FLOW_ERROR_CATCH_ALL_KEY] ?? "Please check {0}.", [fieldLabel(ctx, field)]));
1978
+ return ctx.fields !== void 0 && ctx.fields.includes(field) ? {
1979
+ field,
1980
+ message
1981
+ } : { message };
2086
1982
  }
2087
1983
  if (orphanedInline && ctx.locale[key] !== void 0) return { message: ctx.locale[key] };
2088
1984
  return { text_key: key };
@@ -2161,8 +2057,8 @@ const TEMPLATE_NAMES = {
2161
2057
  const en = {
2162
2058
  "identifier.title": "Sign in",
2163
2059
  "identifier.description": "Enter your email to continue",
2164
- "identifier.field.email": "Work email",
2165
- "identifier.field.email.placeholder": "you@company.com",
2060
+ "identifier.field.email": "Email",
2061
+ "identifier.field.email.placeholder": "you@example.com",
2166
2062
  "identifier.field.password": "Password",
2167
2063
  "identifier.action.submit": "Sign in",
2168
2064
  "identifier.action.continue": "Sign in",
@@ -2179,8 +2075,8 @@ const en = {
2179
2075
  "choose-register.action.register-with-passkey": "Sign up with passkey",
2180
2076
  "collect-credentials.title": "Create your account",
2181
2077
  "collect-credentials.description": "Set up your email and password",
2182
- "collect-credentials.field.email": "Work email",
2183
- "collect-credentials.field.email.placeholder": "you@company.com",
2078
+ "collect-credentials.field.email": "Email",
2079
+ "collect-credentials.field.email.placeholder": "you@example.com",
2184
2080
  "collect-credentials.field.password": "Password",
2185
2081
  "collect-credentials.action.submit": "Sign up",
2186
2082
  "register-password.title": "Create your password",
@@ -2197,8 +2093,8 @@ const en = {
2197
2093
  "passkey-upsell.action.setup": "Set up passkey",
2198
2094
  "collect-passkey-email.title": "Create your account",
2199
2095
  "collect-passkey-email.description": "Enter your email to set up a passkey",
2200
- "collect-passkey-email.field.email": "Work email",
2201
- "collect-passkey-email.field.email.placeholder": "you@company.com",
2096
+ "collect-passkey-email.field.email": "Email",
2097
+ "collect-passkey-email.field.email.placeholder": "you@example.com",
2202
2098
  "collect-passkey-email.action.submit": "Continue",
2203
2099
  "passkey-enroll.title": "Set up your passkey",
2204
2100
  "passkey-enroll.description": "Use Face ID, Touch ID, or your device PIN to create a passkey.",
@@ -2215,11 +2111,12 @@ const en = {
2215
2111
  "password.action.register.link": "Sign up",
2216
2112
  "register.title": "Create your account",
2217
2113
  "register.description": "",
2218
- "register.field.email": "Work email",
2219
- "register.field.email.placeholder": "you@company.com",
2114
+ "register.field.email": "Email",
2115
+ "register.field.email.placeholder": "you@example.com",
2220
2116
  "register.field.password": "Password",
2221
2117
  "register.field.givenName": "Given name",
2222
2118
  "register.field.familyName": "Family name",
2119
+ "register.field.companyName": "Company name",
2223
2120
  "register.field.dateOfBirth": "Date of birth",
2224
2121
  "register.field.country": "Country",
2225
2122
  "register.field.country.placeholder": "Select your country",
@@ -2278,7 +2175,7 @@ const de = {
2278
2175
  "identifier.title": "Anmelden",
2279
2176
  "identifier.description": "Gib deine E-Mail-Adresse ein, um fortzufahren",
2280
2177
  "identifier.field.email": "E-Mail",
2281
- "identifier.field.email.placeholder": "du@unternehmen.com",
2178
+ "identifier.field.email.placeholder": "du@beispiel.de",
2282
2179
  "identifier.field.password": "Passwort",
2283
2180
  "identifier.action.submit": "Anmelden",
2284
2181
  "identifier.action.continue": "Anmelden",
@@ -2296,7 +2193,7 @@ const de = {
2296
2193
  "collect-credentials.title": "Konto erstellen",
2297
2194
  "collect-credentials.description": "Richte E-Mail und Passwort ein",
2298
2195
  "collect-credentials.field.email": "E-Mail",
2299
- "collect-credentials.field.email.placeholder": "du@unternehmen.com",
2196
+ "collect-credentials.field.email.placeholder": "du@beispiel.de",
2300
2197
  "collect-credentials.field.password": "Passwort",
2301
2198
  "collect-credentials.action.submit": "Registrieren",
2302
2199
  "register-password.title": "Passwort erstellen",
@@ -2314,7 +2211,7 @@ const de = {
2314
2211
  "collect-passkey-email.title": "Konto erstellen",
2315
2212
  "collect-passkey-email.description": "Gib deine E-Mail-Adresse ein, um einen Passkey einzurichten",
2316
2213
  "collect-passkey-email.field.email": "E-Mail",
2317
- "collect-passkey-email.field.email.placeholder": "du@unternehmen.com",
2214
+ "collect-passkey-email.field.email.placeholder": "du@beispiel.de",
2318
2215
  "collect-passkey-email.action.submit": "Weiter",
2319
2216
  "passkey-enroll.title": "Passkey einrichten",
2320
2217
  "passkey-enroll.description": "Verwende Face ID, Touch ID oder die Geräte-PIN, um einen Passkey zu erstellen.",
@@ -2332,10 +2229,11 @@ const de = {
2332
2229
  "register.title": "Konto erstellen",
2333
2230
  "register.description": "",
2334
2231
  "register.field.email": "E-Mail",
2335
- "register.field.email.placeholder": "du@unternehmen.com",
2232
+ "register.field.email.placeholder": "du@beispiel.de",
2336
2233
  "register.field.password": "Passwort",
2337
2234
  "register.field.givenName": "Vorname",
2338
2235
  "register.field.familyName": "Nachname",
2236
+ "register.field.companyName": "Firmenname",
2339
2237
  "register.field.dateOfBirth": "Geburtsdatum",
2340
2238
  "register.field.country": "Land",
2341
2239
  "register.field.country.placeholder": "Land auswählen",
@@ -2392,8 +2290,8 @@ const de = {
2392
2290
  const it = {
2393
2291
  "identifier.title": "Accedi",
2394
2292
  "identifier.description": "Inserisci la tua e-mail per continuare",
2395
- "identifier.field.email": "E-mail aziendale",
2396
- "identifier.field.email.placeholder": "tu@azienda.com",
2293
+ "identifier.field.email": "E-mail",
2294
+ "identifier.field.email.placeholder": "tu@esempio.com",
2397
2295
  "identifier.field.password": "Password",
2398
2296
  "identifier.action.submit": "Accedi",
2399
2297
  "identifier.action.continue": "Accedi",
@@ -2410,8 +2308,8 @@ const it = {
2410
2308
  "choose-register.action.register-with-passkey": "Registrati con passkey",
2411
2309
  "collect-credentials.title": "Crea il tuo account",
2412
2310
  "collect-credentials.description": "Configura e-mail e password",
2413
- "collect-credentials.field.email": "E-mail aziendale",
2414
- "collect-credentials.field.email.placeholder": "tu@azienda.com",
2311
+ "collect-credentials.field.email": "E-mail",
2312
+ "collect-credentials.field.email.placeholder": "tu@esempio.com",
2415
2313
  "collect-credentials.field.password": "Password",
2416
2314
  "collect-credentials.action.submit": "Registrati",
2417
2315
  "register-password.title": "Crea la tua password",
@@ -2428,8 +2326,8 @@ const it = {
2428
2326
  "passkey-upsell.action.setup": "Configura passkey",
2429
2327
  "collect-passkey-email.title": "Crea il tuo account",
2430
2328
  "collect-passkey-email.description": "Inserisci la tua e-mail per configurare una passkey",
2431
- "collect-passkey-email.field.email": "E-mail aziendale",
2432
- "collect-passkey-email.field.email.placeholder": "tu@azienda.com",
2329
+ "collect-passkey-email.field.email": "E-mail",
2330
+ "collect-passkey-email.field.email.placeholder": "tu@esempio.com",
2433
2331
  "collect-passkey-email.action.submit": "Continua",
2434
2332
  "passkey-enroll.title": "Configura la tua passkey",
2435
2333
  "passkey-enroll.description": "Usa Face ID, Touch ID o il PIN del dispositivo per creare una passkey.",
@@ -2446,11 +2344,12 @@ const it = {
2446
2344
  "password.action.register.link": "Registrati",
2447
2345
  "register.title": "Crea il tuo account",
2448
2346
  "register.description": "",
2449
- "register.field.email": "E-mail aziendale",
2450
- "register.field.email.placeholder": "tu@azienda.com",
2347
+ "register.field.email": "E-mail",
2348
+ "register.field.email.placeholder": "tu@esempio.com",
2451
2349
  "register.field.password": "Password",
2452
2350
  "register.field.givenName": "Nome",
2453
2351
  "register.field.familyName": "Cognome",
2352
+ "register.field.companyName": "Nome azienda",
2454
2353
  "register.field.dateOfBirth": "Data di nascita",
2455
2354
  "register.field.country": "Paese",
2456
2355
  "register.field.country.placeholder": "Seleziona il tuo paese",
@@ -2503,6 +2402,40 @@ const it = {
2503
2402
  "error.sign_in_server.body": "Riprova tra qualche minuto"
2504
2403
  };
2505
2404
  //#endregion
2405
+ //#region src/orchestrator/locales/business.ts
2406
+ const businessLocales = {
2407
+ en: {
2408
+ "identifier.field.email": "Work email",
2409
+ "identifier.field.email.placeholder": "you@company.com",
2410
+ "collect-credentials.field.email": "Work email",
2411
+ "collect-credentials.field.email.placeholder": "you@company.com",
2412
+ "collect-passkey-email.field.email": "Work email",
2413
+ "collect-passkey-email.field.email.placeholder": "you@company.com",
2414
+ "register.field.email": "Work email",
2415
+ "register.field.email.placeholder": "you@company.com"
2416
+ },
2417
+ de: {
2418
+ "identifier.field.email": "Geschäftliche E-Mail",
2419
+ "identifier.field.email.placeholder": "du@unternehmen.com",
2420
+ "collect-credentials.field.email": "Geschäftliche E-Mail",
2421
+ "collect-credentials.field.email.placeholder": "du@unternehmen.com",
2422
+ "collect-passkey-email.field.email": "Geschäftliche E-Mail",
2423
+ "collect-passkey-email.field.email.placeholder": "du@unternehmen.com",
2424
+ "register.field.email": "Geschäftliche E-Mail",
2425
+ "register.field.email.placeholder": "du@unternehmen.com"
2426
+ },
2427
+ it: {
2428
+ "identifier.field.email": "E-mail aziendale",
2429
+ "identifier.field.email.placeholder": "tu@azienda.com",
2430
+ "collect-credentials.field.email": "E-mail aziendale",
2431
+ "collect-credentials.field.email.placeholder": "tu@azienda.com",
2432
+ "collect-passkey-email.field.email": "E-mail aziendale",
2433
+ "collect-passkey-email.field.email.placeholder": "tu@azienda.com",
2434
+ "register.field.email": "E-mail aziendale",
2435
+ "register.field.email.placeholder": "tu@azienda.com"
2436
+ }
2437
+ };
2438
+ //#endregion
2506
2439
  //#region src/orchestrator/locales/index.ts
2507
2440
  const builtinLocales = {
2508
2441
  en,
@@ -2541,128 +2474,411 @@ function zitadelAttributionPillInnerHtml() {
2541
2474
  * `<zl-*>` atoms. Anything outside this list and the manifest registry is
2542
2475
  * stripped at sanitise-time.
2543
2476
  */
2544
- const STRUCTURAL_TAGS = [
2545
- "div",
2546
- "span",
2547
- "p",
2548
- "small",
2549
- "strong",
2550
- "em",
2551
- "br",
2552
- "hr",
2553
- "h1",
2554
- "h2",
2555
- "h3",
2556
- "h4",
2557
- "h5",
2558
- "h6",
2559
- "ul",
2560
- "ol",
2561
- "li",
2562
- "a",
2563
- "img",
2564
- "section",
2565
- "header",
2566
- "footer",
2567
- "main",
2568
- "nav",
2569
- "aside",
2570
- "article",
2571
- "figure",
2572
- "figcaption",
2573
- "label",
2574
- "fieldset",
2575
- "legend"
2576
- ];
2477
+ const STRUCTURAL_TAGS = [
2478
+ "div",
2479
+ "span",
2480
+ "p",
2481
+ "small",
2482
+ "strong",
2483
+ "em",
2484
+ "br",
2485
+ "hr",
2486
+ "h1",
2487
+ "h2",
2488
+ "h3",
2489
+ "h4",
2490
+ "h5",
2491
+ "h6",
2492
+ "ul",
2493
+ "ol",
2494
+ "li",
2495
+ "a",
2496
+ "img",
2497
+ "section",
2498
+ "header",
2499
+ "footer",
2500
+ "main",
2501
+ "nav",
2502
+ "aside",
2503
+ "article",
2504
+ "figure",
2505
+ "figcaption",
2506
+ "label",
2507
+ "fieldset",
2508
+ "legend"
2509
+ ];
2510
+ /**
2511
+ * Attributes templates may carry on structural tags. Per-atom attributes are
2512
+ * derived from the manifest registry below — manifests are the single source
2513
+ * of truth for "what attributes does `<zl-field>` accept?".
2514
+ */
2515
+ const COMMON_ATTRS = [
2516
+ "id",
2517
+ "class",
2518
+ "role",
2519
+ "slot",
2520
+ "tabindex",
2521
+ "lang",
2522
+ "dir",
2523
+ "alt",
2524
+ "title",
2525
+ "src",
2526
+ "srcset",
2527
+ "sizes",
2528
+ "href",
2529
+ "target",
2530
+ "rel",
2531
+ "style",
2532
+ "name",
2533
+ "for",
2534
+ "data-testid",
2535
+ "data-theme",
2536
+ "hidden"
2537
+ ];
2538
+ const CUSTOM_TAG_PATTERN = /^zl-[a-z0-9-]+$/;
2539
+ function buildAllowedTagList() {
2540
+ const set = new Set(STRUCTURAL_TAGS);
2541
+ for (const manifest of manifestRegistry) set.add(manifest.tag);
2542
+ return [...set];
2543
+ }
2544
+ function buildAllowedAttrList() {
2545
+ const set = new Set(COMMON_ATTRS);
2546
+ for (const manifest of manifestRegistry) for (const attr of manifest.attrs) set.add(attr);
2547
+ return [...set];
2548
+ }
2549
+ /**
2550
+ * Returns a sanitiser bound to the current manifest registry. Callers should
2551
+ * cache the result; rebuilding is cheap but allocates a fresh DOMPurify config.
2552
+ */
2553
+ function createSanitiser() {
2554
+ const config = {
2555
+ USE_PROFILES: { html: true },
2556
+ ADD_TAGS: buildAllowedTagList(),
2557
+ ADD_ATTR: buildAllowedAttrList(),
2558
+ FORBID_TAGS: [
2559
+ "script",
2560
+ "style",
2561
+ "iframe",
2562
+ "object",
2563
+ "embed",
2564
+ "form",
2565
+ "input",
2566
+ "button"
2567
+ ],
2568
+ FORBID_ATTR: [
2569
+ "onerror",
2570
+ "onclick",
2571
+ "onload",
2572
+ "onfocus",
2573
+ "onblur"
2574
+ ],
2575
+ CUSTOM_ELEMENT_HANDLING: {
2576
+ tagNameCheck: CUSTOM_TAG_PATTERN,
2577
+ attributeNameCheck: /^(?:[a-z][a-z0-9-]*)$/,
2578
+ allowCustomizedBuiltInElements: false
2579
+ },
2580
+ KEEP_CONTENT: true,
2581
+ ALLOW_DATA_ATTR: true,
2582
+ ALLOW_ARIA_ATTR: true,
2583
+ RETURN_TRUSTED_TYPE: false
2584
+ };
2585
+ return function sanitise(html) {
2586
+ const result = DOMPurify.sanitize(html, config);
2587
+ return typeof result === "string" ? result : String(result);
2588
+ };
2589
+ }
2590
+ //#endregion
2591
+ //#region src/orchestrator/branding-to-tokens.ts
2592
+ /**
2593
+ * Branding -> CSS token bridge.
2594
+ *
2595
+ * Per `docs/design/branding/README.md` the orchestrator owns theming. Templates
2596
+ * never emit `<style>` blocks; they only structure HTML. This module translates
2597
+ * the `Branding` JSON into a CSSStyleSheet of `:host { --zl-* }` declarations
2598
+ * and applies it via `shadowRoot.adoptedStyleSheets`.
2599
+ *
2600
+ * Dark-mode overrides are emitted as `:host([data-theme="dark"]) { ... }`
2601
+ * (matching `docs/design/branding/tokens.md`). Resolution between
2602
+ * `light | dark | auto` happens in `<zitadel-login>`.
2603
+ *
2604
+ * Base layer: the design-tokens package ships the full `--zl-*` set as both a
2605
+ * `.css` file (for host pages) and a `tokensCss` string (for shadow roots and
2606
+ * SSR). We adopt the string into every orchestrator shadow root so atoms
2607
+ * paint correctly even when the host page didn't `@import` tokens.css — the
2608
+ * orchestrator is meant to drop into any page.
2609
+ */
2610
+ const RADIUS_MAP = {
2611
+ none: "0",
2612
+ sm: "0.25rem",
2613
+ md: "0.5rem",
2614
+ lg: "0.75rem",
2615
+ full: "9999px"
2616
+ };
2617
+ const DENSITY_MAP = {
2618
+ compact: {
2619
+ "--zl-spacing-03": "0.75rem",
2620
+ "--zl-spacing-05": "1.5rem"
2621
+ },
2622
+ regular: {},
2623
+ comfortable: {
2624
+ "--zl-spacing-03": "1.25rem",
2625
+ "--zl-spacing-05": "2.25rem"
2626
+ }
2627
+ };
2628
+ const PALETTE_MAP = {
2629
+ primary: ["--zl-color-surface-default-white"],
2630
+ on_primary: ["--zl-color-text-button-default"],
2631
+ background: ["--zl-color-surface-default-black"],
2632
+ surface: ["--zl-color-surface-default-primary-gray"],
2633
+ muted: ["--zl-color-surface-default-secondary-gray"],
2634
+ border: ["--zl-color-border-default-gray-200", "--zl-color-border-default-gray-100"],
2635
+ text: ["--zl-color-text-primary-white"],
2636
+ text_muted: ["--zl-color-text-secondary-gray"],
2637
+ link: ["--zl-color-text-subtitle-pink"],
2638
+ success: [
2639
+ "--zl-color-text-success",
2640
+ "--zl-color-border-success",
2641
+ "--zl-color-icon-success"
2642
+ ],
2643
+ warning: ["--zl-color-text-subtitle-orange"],
2644
+ error: [
2645
+ "--zl-color-text-error",
2646
+ "--zl-color-border-error",
2647
+ "--zl-color-icon-error"
2648
+ ]
2649
+ };
2650
+ /**
2651
+ * Build a CSS string of `:host { --zl-* }` declarations from a Branding
2652
+ * payload. Light values land on `:host`; dark overrides land on
2653
+ * `:host([data-theme="dark"])`.
2654
+ */
2655
+ function buildBrandingStylesheet(branding, options = {}) {
2656
+ const lightDecls = collectDeclarations(branding);
2657
+ const darkPalette = branding?.theme?.dark?.palette;
2658
+ const darkDecls = darkPalette ? mapPalette(darkPalette) : {};
2659
+ const blocks = [];
2660
+ if (Object.keys(lightDecls).length > 0) blocks.push(formatBlock(":host", lightDecls));
2661
+ if (Object.keys(darkDecls).length > 0) blocks.push(formatBlock(":host([data-theme=\"dark\"])", darkDecls));
2662
+ if (options.resolvedTheme === "dark" && darkPalette) {
2663
+ const merged = {
2664
+ ...lightDecls,
2665
+ ...darkDecls
2666
+ };
2667
+ blocks.length = 0;
2668
+ blocks.push(formatBlock(":host", merged));
2669
+ }
2670
+ return blocks.join("\n");
2671
+ }
2672
+ function collectDeclarations(branding) {
2673
+ if (!branding) return {};
2674
+ const decls = {};
2675
+ Object.assign(decls, mapPalette(branding.palette));
2676
+ Object.assign(decls, mapTypography(branding.typography));
2677
+ Object.assign(decls, mapShape(branding.shape));
2678
+ return decls;
2679
+ }
2680
+ function mapPalette(palette) {
2681
+ if (!palette) return {};
2682
+ const out = {};
2683
+ for (const [key, varNames] of Object.entries(PALETTE_MAP)) {
2684
+ const value = palette[key];
2685
+ if (typeof value === "string" && value.length > 0) for (const varName of varNames) out[varName] = value;
2686
+ }
2687
+ return out;
2688
+ }
2689
+ function mapTypography(typography) {
2690
+ if (!typography) return {};
2691
+ const out = {};
2692
+ if (typography.font_family) {
2693
+ out["--zl-font-family-sans"] = typography.font_family;
2694
+ out["--zl-font-family-heading"] = typography.font_family;
2695
+ }
2696
+ if (typography.font_family_mono) out["--zl-font-family-mono"] = typography.font_family_mono;
2697
+ const scale = clamp(typography.scale ?? 1, .75, 1.25);
2698
+ if (scale !== 1) out["--zl-font-scale"] = `${scale}`;
2699
+ return out;
2700
+ }
2701
+ function mapShape(shape) {
2702
+ if (!shape) return {};
2703
+ const out = {};
2704
+ if (shape.radius && RADIUS_MAP[shape.radius]) {
2705
+ const radius = RADIUS_MAP[shape.radius];
2706
+ out["--zl-radius-s"] = radius;
2707
+ out["--zl-radius-m"] = radius === "0" ? "0" : `calc(${radius} * 1.5)`;
2708
+ out["--zl-radius-l"] = radius === "0" ? "0" : `calc(${radius} * 2)`;
2709
+ }
2710
+ if (shape.density) Object.assign(out, DENSITY_MAP[shape.density]);
2711
+ return out;
2712
+ }
2713
+ function formatBlock(selector, decls) {
2714
+ return `${selector} {\n${Object.entries(decls).map(([prop, value]) => ` ${prop}: ${value};`).join("\n")}\n}`;
2715
+ }
2716
+ function clamp(value, min, max) {
2717
+ if (Number.isNaN(value)) return min;
2718
+ return Math.min(max, Math.max(min, value));
2719
+ }
2720
+ let baseTokenSheet;
2721
+ function getBaseTokenSheet() {
2722
+ if (typeof CSSStyleSheet === "undefined") return void 0;
2723
+ if (!baseTokenSheet) {
2724
+ baseTokenSheet = new CSSStyleSheet();
2725
+ baseTokenSheet.replaceSync(tokensCss.replaceAll(":root,\n[data-theme=\"dark\"]", ":host,\n:host([data-theme=\"dark\"])").replaceAll("[data-theme=\"light\"]", ":host([data-theme=\"light\"])"));
2726
+ }
2727
+ return baseTokenSheet;
2728
+ }
2729
+ /**
2730
+ * Adopt the design-system base token layer onto a `ShadowRoot`. Safe to call
2731
+ * many times — the underlying constructable sheet is shared across every
2732
+ * orchestrator instance and de-duplicated in the adopted list.
2733
+ */
2734
+ function applyBaseTokens(shadowRoot) {
2735
+ const sheet = getBaseTokenSheet();
2736
+ if (!sheet) return;
2737
+ const existing = Array.isArray(shadowRoot.adoptedStyleSheets) ? shadowRoot.adoptedStyleSheets : [];
2738
+ if (existing.includes(sheet)) return;
2739
+ try {
2740
+ shadowRoot.adoptedStyleSheets = [sheet, ...existing];
2741
+ } catch {}
2742
+ }
2743
+ /**
2744
+ * Apply a branding payload as a `--zl-*` overrides layer on top of the base
2745
+ * token sheet. Subsequent calls replace the previous override sheet so a new
2746
+ * branding payload paints cleanly without leaking older declarations.
2747
+ *
2748
+ * Callers should run `applyBaseTokens(shadowRoot)` first (once per shadow
2749
+ * root) so the base values exist before branding patches them.
2750
+ */
2751
+ function applyBrandingTokens(shadowRoot, branding, resolvedTheme) {
2752
+ const css = buildBrandingStylesheet(branding, { resolvedTheme });
2753
+ if (typeof CSSStyleSheet === "undefined") return;
2754
+ const sheet = new CSSStyleSheet();
2755
+ sheet.replaceSync(css);
2756
+ const previous = shadowRoot.__zlTokenSheet;
2757
+ shadowRoot.adoptedStyleSheets = [...shadowRoot.adoptedStyleSheets.filter((s) => s !== previous), sheet];
2758
+ shadowRoot.__zlTokenSheet = sheet;
2759
+ }
2760
+ /**
2761
+ * Standalone branding→theme resolution for callers outside the orchestrator
2762
+ * (SSR, tests, embedders composing atoms by hand). `<zitadel-login>` itself
2763
+ * uses {@link ThemeController}, which layers the element's `theme` property
2764
+ * and a variant-derived fallback on top of the same branding input.
2765
+ *
2766
+ * Defaults to dark: the design system's primary surface, and the mode a
2767
+ * hosted login page renders when a tenant states no preference.
2768
+ */
2769
+ function resolveTheme(branding) {
2770
+ const mode = branding?.theme?.mode ?? "dark";
2771
+ if (mode === "light") return "light";
2772
+ if (mode === "dark") return "dark";
2773
+ if (typeof matchMedia === "function") return matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
2774
+ return "dark";
2775
+ }
2776
+ //#endregion
2777
+ //#region src/orchestrator/font-loader.ts
2778
+ /**
2779
+ * Document-level font stylesheet injection for the orchestrator surfaces.
2780
+ *
2781
+ * Two faces can be linked into the host document's `<head>`:
2782
+ *
2783
+ * - The **design-system default** (`applyDefaultFont`) — the brand body face
2784
+ * (Arimo, named first in `--zl-font-family-sans`). The component ships this
2785
+ * so the auth screens render in the brand font out of the box, even when the
2786
+ * server returns no branding. This is the "default users can override" layer.
2787
+ * - The **tenant override** (`applyFontUrl`) — `branding.font_url`, resolved
2788
+ * server-side from the app → team → project branding hierarchy.
2789
+ *
2790
+ * Both must live at document level, not inside the shadow root: browsers ignore
2791
+ * `@font-face` rules declared in shadow-tree stylesheets, so a shadow-scoped
2792
+ * link would never register the font faces and every branded font silently
2793
+ * falls back to the system stack. `font-family` references inside the shadow
2794
+ * tree resolve against document-level faces.
2795
+ *
2796
+ * Each `<link>` is shared per document but **owned collectively**: every
2797
+ * calling surface (identified by its shadow root) registers the href it wants,
2798
+ * and passing `null`/`undefined` withdraws only that surface's registration —
2799
+ * never the link another surface still needs. Multiple surfaces now call these
2800
+ * helpers on every update (`<zitadel-login>` and `<zitadel-session>` via the
2801
+ * shared surface base), so a widget-mode session mounting next to a page-mode
2802
+ * login must not strip the login's fonts. The rendered href is the most
2803
+ * recently registered one (matching the old single-owner behavior when only
2804
+ * one surface is present); the link is removed once no surface wants it.
2805
+ * Registrations from since-disconnected surfaces are pruned lazily on the
2806
+ * next call, so a removed element neither pins a link forever nor leaks its
2807
+ * shadow root — while the link itself stays in place until another surface
2808
+ * actually updates, exactly like the pre-ownership behavior.
2809
+ */
2810
+ const TENANT_LINK_ID = "zl-font-link";
2811
+ const DEFAULT_LINK_ID = "zl-default-font-link";
2812
+ /**
2813
+ * Design-system default brand font. Arimo is the Figma body face and leads the
2814
+ * `--zl-font-family-sans` stack; loading it here makes the unbranded auth UI
2815
+ * paint in the brand font instead of the system fallback. Tenants override it
2816
+ * with `branding.font_url`. Exported so a privacy- or offline-sensitive
2817
+ * deployment can self-host the same face and swap this single URL.
2818
+ */
2819
+ const DEFAULT_BRAND_FONT_HREF = "https://fonts.googleapis.com/css2?family=Arimo:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap";
2820
+ /** Per-document, per-link-id map of each owner's desired href. */
2821
+ const registries = /* @__PURE__ */ new WeakMap();
2577
2822
  /**
2578
- * Attributes templates may carry on structural tags. Per-atom attributes are
2579
- * derived from the manifest registry below manifests are the single source
2580
- * of truth for "what attributes does `<zl-field>` accept?".
2823
+ * Inject the tenant's `branding.font_url` stylesheet. Pass `null`/`undefined`
2824
+ * to withdraw this surface's registration (e.g. when a flow resolves with no
2825
+ * tenant font); the link disappears once no surface registers a URL.
2581
2826
  */
2582
- const COMMON_ATTRS = [
2583
- "id",
2584
- "class",
2585
- "role",
2586
- "slot",
2587
- "tabindex",
2588
- "lang",
2589
- "dir",
2590
- "alt",
2591
- "title",
2592
- "src",
2593
- "srcset",
2594
- "sizes",
2595
- "href",
2596
- "target",
2597
- "rel",
2598
- "style",
2599
- "name",
2600
- "for",
2601
- "data-testid",
2602
- "data-theme",
2603
- "hidden"
2604
- ];
2605
- const CUSTOM_TAG_PATTERN = /^zl-[a-z0-9-]+$/;
2606
- function buildAllowedTagList() {
2607
- const set = new Set(STRUCTURAL_TAGS);
2608
- for (const manifest of manifestRegistry) set.add(manifest.tag);
2609
- return [...set];
2610
- }
2611
- function buildAllowedAttrList() {
2612
- const set = new Set(COMMON_ATTRS);
2613
- for (const manifest of manifestRegistry) for (const attr of manifest.attrs) set.add(attr);
2614
- return [...set];
2827
+ function applyFontUrl(shadowRoot, fontUrl) {
2828
+ applyLinkedStylesheet(shadowRoot, TENANT_LINK_ID, fontUrl);
2615
2829
  }
2616
2830
  /**
2617
- * Returns a sanitiser bound to the current manifest registry. Callers should
2618
- * cache the result; rebuilding is cheap but allocates a fresh DOMPurify config.
2831
+ * Inject the design-system default font stylesheet. Defaults to
2832
+ * {@link DEFAULT_BRAND_FONT_HREF}; pass `null` to withdraw this surface's
2833
+ * registration (the orchestrator does this when a tenant `font_url` takes
2834
+ * over, to avoid a redundant request).
2619
2835
  */
2620
- function createSanitiser() {
2621
- const config = {
2622
- USE_PROFILES: { html: true },
2623
- ADD_TAGS: buildAllowedTagList(),
2624
- ADD_ATTR: buildAllowedAttrList(),
2625
- FORBID_TAGS: [
2626
- "script",
2627
- "style",
2628
- "iframe",
2629
- "object",
2630
- "embed",
2631
- "form",
2632
- "input",
2633
- "button"
2634
- ],
2635
- FORBID_ATTR: [
2636
- "onerror",
2637
- "onclick",
2638
- "onload",
2639
- "onfocus",
2640
- "onblur"
2641
- ],
2642
- CUSTOM_ELEMENT_HANDLING: {
2643
- tagNameCheck: CUSTOM_TAG_PATTERN,
2644
- attributeNameCheck: /^(?:[a-z][a-z0-9-]*)$/,
2645
- allowCustomizedBuiltInElements: false
2646
- },
2647
- KEEP_CONTENT: true,
2648
- ALLOW_DATA_ATTR: true,
2649
- ALLOW_ARIA_ATTR: true,
2650
- RETURN_TRUSTED_TYPE: false
2651
- };
2652
- return function sanitise(html) {
2653
- const result = DOMPurify.sanitize(html, config);
2654
- return typeof result === "string" ? result : String(result);
2655
- };
2836
+ function applyDefaultFont(shadowRoot, href = DEFAULT_BRAND_FONT_HREF) {
2837
+ applyLinkedStylesheet(shadowRoot, DEFAULT_LINK_ID, href);
2838
+ }
2839
+ function applyLinkedStylesheet(shadowRoot, linkId, href) {
2840
+ const ownerDocument = shadowRoot.ownerDocument ?? document;
2841
+ let registry = registries.get(ownerDocument);
2842
+ if (!registry) {
2843
+ registry = /* @__PURE__ */ new Map();
2844
+ registries.set(ownerDocument, registry);
2845
+ }
2846
+ let desires = registry.get(linkId);
2847
+ if (!desires) {
2848
+ desires = /* @__PURE__ */ new Map();
2849
+ registry.set(linkId, desires);
2850
+ }
2851
+ for (const owner of desires.keys()) if (!owner.host.isConnected) desires.delete(owner);
2852
+ if (href) {
2853
+ desires.delete(shadowRoot);
2854
+ desires.set(shadowRoot, href);
2855
+ } else desires.delete(shadowRoot);
2856
+ const effective = [...desires.values()].at(-1) ?? null;
2857
+ const existing = ownerDocument.head.querySelector(`link#${linkId}`);
2858
+ if (!effective) {
2859
+ existing?.remove();
2860
+ return;
2861
+ }
2862
+ if (!href) {
2863
+ if (existing && existing.href !== effective) existing.href = effective;
2864
+ return;
2865
+ }
2866
+ const link = existing ?? ownerDocument.createElement("link");
2867
+ link.id = linkId;
2868
+ link.rel = "stylesheet";
2869
+ if (link.href !== effective) link.href = effective;
2870
+ if (!existing) ownerDocument.head.appendChild(link);
2656
2871
  }
2657
- //#endregion
2658
- //#region src/orchestrator/templates/layout-chrome.css?inline
2659
- var layout_chrome_default = ":host {\n background: var(--zl-color-surface-default-black, #0f0f11);\n min-height: 100%;\n color: var(--zl-color-text-primary-white, #f4f4f6);\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n display: block;\n}\n\n.zl-mount {\n background: inherit;\n min-height: 100vh;\n color: inherit;\n flex-direction: column;\n display: flex;\n}\n\n.zl-mount > zl-page-shell, .zl-mount > [data-zl-template-root] {\n flex: auto;\n}\n\n.zl-attribution {\n background: none;\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.zl-attribution[hidden] {\n display: none;\n}\n\n.zl-card-title {\n font-family: var(--zl-font-family-heading, var(--zl-font-family-sans, system-ui, sans-serif));\n letter-spacing: -.02em;\n color: var(--zl-color-text-primary-white, #f4f4f6);\n text-align: left;\n margin: 0;\n font-size: 2rem;\n font-weight: 700;\n line-height: 2.5rem;\n}\n\n.zl-card-subtitle {\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n color: var(--zl-color-text-primary-white, #f4f4f6);\n margin: 0;\n font-size: .875rem;\n font-weight: 400;\n line-height: 1.25rem;\n}\n\n.zl-card-nav {\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n color: var(--zl-color-text-primary-white, #f4f4f6);\n text-align: left;\n margin: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5rem;\n}\n\n.zl-card-nav__link {\n font: inherit;\n line-height: inherit;\n color: var(--zl-color-icon-default-purple, #bba5e4);\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n text-decoration: none;\n display: inline;\n}\n\n.zl-card-nav__link:hover {\n text-decoration: underline;\n}\n\n.zl-card-nav__link:focus-visible {\n outline: var(--zl-focus-width, 2px) solid var(--zl-focus-color, #f4f4f6);\n outline-offset: var(--zl-focus-offset, 2px);\n}\n\n.zl-card-forgot {\n text-align: left;\n margin: 0;\n}\n\n.zl-card-forgot__link {\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n color: var(--zl-color-icon-default-purple, #bba5e4);\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5rem;\n text-decoration: none;\n display: inline;\n}\n\n.zl-card-forgot__link:hover {\n text-decoration: underline;\n}\n\n.zl-card-forgot__link:focus-visible {\n outline: var(--zl-focus-width, 2px) solid var(--zl-focus-color, #f4f4f6);\n outline-offset: var(--zl-focus-offset, 2px);\n}\n";
2660
2872
  //#endregion
2661
2873
  //#region src/orchestrator/theme-controller.ts
2662
2874
  const DARK_QUERY = "(prefers-color-scheme: dark)";
2663
2875
  var ThemeController = class {
2664
2876
  host;
2665
2877
  branding;
2878
+ /** Element-level `theme` property — the embedding developer's explicit call. */
2879
+ explicitMode;
2880
+ /** Used when neither the element nor the branding payload names a mode. */
2881
+ fallbackMode = "dark";
2666
2882
  mediaQuery = null;
2667
2883
  _theme = "dark";
2668
2884
  constructor(host) {
@@ -2676,6 +2892,19 @@ var ThemeController = class {
2676
2892
  this.branding = branding;
2677
2893
  this.refresh();
2678
2894
  }
2895
+ /**
2896
+ * Sets the element-level preference and the fallback used when neither the
2897
+ * element nor the branding payload names a mode. Precedence, strongest
2898
+ * first: element `theme` property → `branding.theme.mode` → fallback. The
2899
+ * element wins because the page embedding the widget knows its own surface
2900
+ * better than the tenant's stored branding does.
2901
+ */
2902
+ setModePreference(explicit, fallback) {
2903
+ if (this.explicitMode === explicit && this.fallbackMode === fallback) return;
2904
+ this.explicitMode = explicit;
2905
+ this.fallbackMode = fallback;
2906
+ this.refresh();
2907
+ }
2679
2908
  hostConnected() {
2680
2909
  this.refresh();
2681
2910
  }
@@ -2683,7 +2912,7 @@ var ThemeController = class {
2683
2912
  this.detach();
2684
2913
  }
2685
2914
  refresh() {
2686
- const mode = this.branding?.theme?.mode ?? "dark";
2915
+ const mode = this.explicitMode ?? this.branding?.theme?.mode ?? this.fallbackMode;
2687
2916
  if (mode === "auto") {
2688
2917
  this.attach();
2689
2918
  this.update(this.mediaQuery?.matches === false ? "light" : "dark");
@@ -2712,8 +2941,72 @@ var ThemeController = class {
2712
2941
  }
2713
2942
  };
2714
2943
  //#endregion
2944
+ //#region src/orchestrator/surface.ts
2945
+ /**
2946
+ * Shared base for the orchestrator host elements (`<zitadel-login>`,
2947
+ * `<zitadel-session>`) that render a themable page/widget surface.
2948
+ *
2949
+ * Owns the `variant` / `theme` public properties, the {@link ThemeController},
2950
+ * and the per-update surface application (base tokens, branding overrides,
2951
+ * font links, and the `data-theme` host stamp). Subclasses call
2952
+ * {@link applySurfaceTheme} from their `willUpdate` so the resolved theme is
2953
+ * committed in the same update cycle that reads it.
2954
+ *
2955
+ * Not a registered custom element — purely an implementation-sharing base.
2956
+ */
2957
+ var ZitadelSurface = class extends LitElement {
2958
+ #_variant_accessor_storage = "widget";
2959
+ get variant() {
2960
+ return this.#_variant_accessor_storage;
2961
+ }
2962
+ set variant(value) {
2963
+ this.#_variant_accessor_storage = value;
2964
+ }
2965
+ #_theme_accessor_storage = "";
2966
+ get theme() {
2967
+ return this.#_theme_accessor_storage;
2968
+ }
2969
+ set theme(value) {
2970
+ this.#_theme_accessor_storage = value;
2971
+ }
2972
+ themeController = new ThemeController(this);
2973
+ /**
2974
+ * Resolve the active theme and commit it to this element's surface: adopt
2975
+ * the base token sheet, layer the branding overrides, manage the
2976
+ * document-level font links, and stamp `data-theme` / `data-theme-dark` on
2977
+ * the host so token lookups (and host-page selectors) flip with the mode.
2978
+ * `branding` is `undefined` for surfaces without a tenant payload — the
2979
+ * theme then resolves through the element preference and variant fallback.
2980
+ */
2981
+ applySurfaceTheme(branding) {
2982
+ this.themeController.setModePreference(this.theme === "" ? void 0 : this.theme, this.variant === "page" ? "dark" : "auto");
2983
+ const root = this.shadowRoot;
2984
+ if (root) {
2985
+ applyBaseTokens(root);
2986
+ applyBrandingTokens(root, branding, this.themeController.theme);
2987
+ const tenantFontUrl = branding?.font_url ?? null;
2988
+ applyDefaultFont(root, this.variant === "page" && !tenantFontUrl ? void 0 : null);
2989
+ applyFontUrl(root, tenantFontUrl);
2990
+ }
2991
+ this.dataset.theme = this.themeController.theme;
2992
+ this.toggleAttribute("data-theme-dark", this.themeController.theme === "dark");
2993
+ }
2994
+ };
2995
+ __decorate([property({
2996
+ type: String,
2997
+ reflect: true
2998
+ })], ZitadelSurface.prototype, "variant", null);
2999
+ __decorate([property({ type: String })], ZitadelSurface.prototype, "theme", null);
3000
+ //#endregion
3001
+ //#region src/orchestrator/templates/layout-chrome.css?inline
3002
+ var layout_chrome_default = ":host {\n color: var(--zl-color-text-primary-white, #f4f4f6);\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n background: none;\n display: block;\n}\n\n:host([variant=\"page\"]) {\n background: var(--zl-color-surface-default-black, #0f0f11);\n min-height: 100%;\n}\n\n.zl-mount {\n min-height: var(--zl-page-min-height, auto);\n background: inherit;\n color: inherit;\n flex-direction: column;\n display: flex;\n container-type: inline-size;\n}\n\n:host([variant=\"page\"]) .zl-mount {\n min-height: var(--zl-page-min-height, 100vh);\n}\n\n.zl-mount > zl-page-shell, .zl-mount > [data-zl-template-root] {\n flex: auto;\n}\n\n.zl-attribution {\n background: none;\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.zl-attribution[hidden] {\n display: none;\n}\n\n.zl-card-title {\n font-family: var(--zl-font-family-heading, var(--zl-font-family-sans, system-ui, sans-serif));\n letter-spacing: -.02em;\n color: var(--zl-color-text-primary-white, #f4f4f6);\n text-align: left;\n margin: 0;\n font-size: 2rem;\n font-weight: 700;\n line-height: 2.5rem;\n}\n\n.zl-card-subtitle {\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n color: var(--zl-color-text-primary-white, #f4f4f6);\n margin: 0;\n font-size: .875rem;\n font-weight: 400;\n line-height: 1.25rem;\n}\n\n.zl-card-nav {\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n color: var(--zl-color-text-primary-white, #f4f4f6);\n text-align: left;\n margin: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5rem;\n}\n\n.zl-card-nav__link {\n font: inherit;\n line-height: inherit;\n color: var(--zl-color-icon-default-purple, #bba5e4);\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n text-decoration: none;\n display: inline;\n}\n\n.zl-card-nav__link:hover {\n text-decoration: underline;\n}\n\n.zl-card-nav__link:focus-visible {\n outline: var(--zl-focus-width, 2px) solid var(--zl-focus-color, #f4f4f6);\n outline-offset: var(--zl-focus-offset, 2px);\n}\n\n.zl-card-forgot {\n text-align: left;\n margin: 0;\n}\n\n.zl-card-forgot__link {\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n color: var(--zl-color-icon-default-purple, #bba5e4);\n cursor: pointer;\n background: none;\n border: none;\n margin: 0;\n padding: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5rem;\n text-decoration: none;\n display: inline;\n}\n\n.zl-card-forgot__link:hover {\n text-decoration: underline;\n}\n\n.zl-card-forgot__link:focus-visible {\n outline: var(--zl-focus-width, 2px) solid var(--zl-focus-color, #f4f4f6);\n outline-offset: var(--zl-focus-offset, 2px);\n}\n\n.zl-split {\n grid-template-columns: var(--zl-split-columns, minmax(0, 1fr) minmax(0, 1fr));\n align-items: var(--zl-split-align, center);\n gap: var(--zl-spacing-05, 2rem);\n min-height: 100%;\n display: grid;\n}\n\n.zl-split__brand {\n justify-content: center;\n align-items: center;\n gap: var(--zl-spacing-04, 1.5rem);\n flex-direction: column;\n padding: 52px 16px;\n display: flex;\n}\n\n.zl-split__logo {\n max-width: min(16rem, 60%);\n}\n\n.zl-split__hero {\n border-radius: var(--zl-radius-m, .75rem);\n object-fit: cover;\n max-width: 100%;\n}\n\n.zl-split__form {\n justify-content: center;\n align-items: center;\n padding: 16px;\n display: flex;\n}\n\n.zl-split__compact {\n max-width: min(12rem, 60%);\n display: none;\n}\n\nimg.zl-split__compact {\n max-height: 2.5rem;\n}\n\nimg.zl-split__compact--hero {\n border-radius: var(--zl-radius-m, .75rem);\n object-fit: cover;\n width: 100%;\n max-width: 100%;\n max-height: 6rem;\n}\n\n.zl-split--right .zl-split__brand {\n order: 2;\n}\n\n.zl-split--right .zl-split__form {\n order: 1;\n}\n\n@container (width <= 48rem) {\n .zl-split {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .zl-split__brand {\n display: var(--zl-split-brand-mobile, none);\n }\n\n .zl-split__form {\n gap: var(--zl-spacing-04, 1.5rem);\n flex-direction: column;\n }\n\n .zl-split__compact {\n display: block;\n }\n}\n\n.zl-hero {\n text-align: left;\n width: 100%;\n max-width: 34rem;\n color: var(--zl-color-text-primary-white, #f4f4f6);\n font-family: var(--zl-font-family-sans, system-ui, sans-serif);\n flex-direction: column;\n display: flex;\n}\n\n.zl-hero__nav {\n margin-bottom: var(--zl-spacing-05, 2rem);\n justify-content: space-between;\n align-items: center;\n display: flex;\n}\n\n.zl-hero__brand {\n align-items: center;\n gap: var(--zl-spacing-02, .5rem);\n letter-spacing: -.01em;\n font-size: 1.125rem;\n font-weight: 700;\n display: inline-flex;\n}\n\n.zl-hero__brand-logo {\n max-height: 1.75rem;\n}\n\n.zl-hero__brand-mark {\n border-radius: var(--zl-radius-s, .5rem);\n background: linear-gradient(135deg,\n var(--zl-color-icon-default-purple, #bba5e4),\n var(--zl-color-icon-default-pink, #f48aa0));\n width: 1.75rem;\n height: 1.75rem;\n display: inline-flex;\n}\n\n.zl-hero__compact-brand {\n overflow-wrap: anywhere;\n letter-spacing: -.01em;\n max-width: 100%;\n color: var(--zl-color-text-primary-white, #f4f4f6);\n margin: 0;\n font-size: 1.125rem;\n font-weight: 700;\n}\n\n.zl-hero__nav-links {\n gap: var(--zl-spacing-04, 1.5rem);\n color: var(--zl-color-text-secondary-gray, #bfbfcf);\n font-size: .9375rem;\n display: flex;\n}\n\n.zl-hero__nav-links a {\n color: inherit;\n text-decoration: none;\n}\n\n.zl-hero__nav-links a:hover {\n text-decoration: underline;\n}\n\n.zl-hero__eyebrow {\n margin: 0 0 var(--zl-spacing-03, 1rem);\n letter-spacing: .08em;\n text-transform: uppercase;\n color: var(--zl-color-text-subtitle-purple, #bba5e4);\n font-size: .8125rem;\n font-weight: 600;\n}\n\n.zl-hero__headline {\n margin: 0 0 var(--zl-spacing-03, 1rem);\n font-family: var(--zl-font-family-heading, var(--zl-font-family-sans, system-ui, sans-serif));\n letter-spacing: -.02em;\n font-size: 2.75rem;\n font-weight: 700;\n line-height: 1.1;\n}\n\n.zl-hero__sub {\n margin: 0 0 var(--zl-spacing-05, 2rem);\n max-width: 30rem;\n color: var(--zl-color-text-secondary-gray, #bfbfcf);\n font-size: 1.0625rem;\n line-height: 1.6;\n}\n\n.zl-hero__bullets {\n gap: var(--zl-spacing-02, .5rem);\n margin: 0 0 var(--zl-spacing-05, 2rem);\n flex-direction: column;\n padding: 0;\n list-style: none;\n display: flex;\n}\n\n.zl-hero__bullets li {\n align-items: center;\n gap: var(--zl-spacing-02, .5rem);\n font-size: 1rem;\n display: flex;\n}\n\n.zl-hero__bullets li:before {\n content: \"✓\";\n color: var(--zl-color-icon-default-purple, #bba5e4);\n font-weight: 700;\n}\n\n.zl-hero__footnote {\n padding-top: var(--zl-spacing-04, 1.5rem);\n border-top: 1px solid var(--zl-color-border-default-black, #252528);\n color: var(--zl-color-text-disabled, #686883);\n margin: 0;\n font-size: .8125rem;\n}\n\n.zl-hero__footnote strong {\n color: var(--zl-color-text-secondary-gray, #bfbfcf);\n font-weight: 600;\n}\n\n.zl-minimal {\n gap: var(--zl-spacing-04, 1.5rem);\n flex-direction: column;\n width: min(24rem, 100%);\n margin-inline: auto;\n display: flex;\n}\n\n:host([variant=\"page\"]) .zl-minimal {\n padding: 52px 16px;\n}\n";
3003
+ //#endregion
2715
3004
  //#region src/orchestrator/zitadel-login.ts
2716
- let ZitadelLogin = class ZitadelLogin extends LitElement {
3005
+ /** Narrow a rendered named element to the `formValue` field-atom contract. */
3006
+ function isFieldAtom(el) {
3007
+ return typeof el.formValue === "string";
3008
+ }
3009
+ let ZitadelLogin = class ZitadelLogin extends ZitadelSurface {
2717
3010
  static shadowRootOptions = {
2718
3011
  ...LitElement.shadowRootOptions,
2719
3012
  delegatesFocus: true
@@ -2722,7 +3015,6 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
2722
3015
  :host {
2723
3016
  display: block;
2724
3017
  width: 100%;
2725
- min-height: 100vh;
2726
3018
  }
2727
3019
  `;
2728
3020
  #_purpose_accessor_storage = "login";
@@ -2830,7 +3122,6 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
2830
3122
  set formValues(value) {
2831
3123
  this.#_formValues_accessor_storage = value;
2832
3124
  }
2833
- themeController = new ThemeController(this);
2834
3125
  engine = null;
2835
3126
  sanitise = createSanitiser();
2836
3127
  /**
@@ -2838,6 +3129,25 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
2838
3129
  * every `formValues` change otherwise re-parses the same template.
2839
3130
  */
2840
3131
  tenantTemplateCache = null;
3132
+ /**
3133
+ * Whether the widget currently owns a same-document history entry (the
3134
+ * "sentinel"). The sentinel exists so the browser's back gesture fires
3135
+ * `popstate` instead of leaving the page. Exactly one sentinel is on
3136
+ * the stack at a time: it is pushed when a step with a `kind: "back"`
3137
+ * action renders, re-armed by `onPopState` after the browser consumes
3138
+ * it, and retired by `applyResponse` when a step without a back action
3139
+ * renders. The entry reuses the current URL, so the host page's
3140
+ * location (including any hash-router fragment) is never modified.
3141
+ */
3142
+ armed = false;
3143
+ /**
3144
+ * Set immediately before a self-initiated `history.back()` so the
3145
+ * resulting `popstate` is ignored instead of being interpreted as a
3146
+ * user back gesture.
3147
+ */
3148
+ ignoreNextPop = false;
3149
+ /** Bound `popstate` handler stored for cleanup in `disconnectedCallback`. */
3150
+ handlePopState = this.onPopState.bind(this);
2841
3151
  createRenderRoot() {
2842
3152
  const root = super.createRenderRoot();
2843
3153
  if (root instanceof ShadowRoot) {
@@ -2856,6 +3166,14 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
2856
3166
  }
2857
3167
  return root;
2858
3168
  }
3169
+ connectedCallback() {
3170
+ super.connectedCallback();
3171
+ if (typeof window !== "undefined") window.addEventListener("popstate", this.handlePopState);
3172
+ }
3173
+ disconnectedCallback() {
3174
+ super.disconnectedCallback();
3175
+ if (typeof window !== "undefined") window.removeEventListener("popstate", this.handlePopState);
3176
+ }
2859
3177
  /**
2860
3178
  * Start the flow after the first render rather than in `connectedCallback`.
2861
3179
  * Frameworks that wrap web components (e.g. `@lit/react` in the console)
@@ -2882,28 +3200,25 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
2882
3200
  const primary = ((this.lang || (typeof navigator !== "undefined" ? navigator.language : "") || (typeof document !== "undefined" ? document.documentElement.lang : "")).split("-")[0] ?? "").toLowerCase();
2883
3201
  const builtin = builtinLocales[primary] ?? en;
2884
3202
  const custom = this.locales?.[primary];
2885
- return custom ? {
2886
- ...builtin,
2887
- ...custom
2888
- } : builtin;
3203
+ if (!custom) return builtin;
3204
+ const merged = { ...builtin };
3205
+ for (const [key, value] of Object.entries(custom)) if (value !== void 0) merged[key] = value;
3206
+ return merged;
2889
3207
  }
2890
3208
  willUpdate(changed) {
2891
3209
  if (!this.engine || changed.has("locales") || changed.has("lang")) this.engine = createLiquidEngine({ locale: this.resolveLocale() });
2892
- const root = this.shadowRoot;
2893
- if (root) {
2894
- applyBaseTokens(root);
2895
- applyBrandingTokens(root, this.branding, this.themeController.theme);
2896
- const tenantFontUrl = this.branding?.font_url ?? null;
2897
- applyDefaultFont(root, tenantFontUrl ? null : void 0);
2898
- applyFontUrl(root, tenantFontUrl);
2899
- }
2900
- this.dataset.theme = this.themeController.theme;
2901
- this.toggleAttribute("data-theme-dark", this.themeController.theme === "dark");
3210
+ this.applySurfaceTheme(this.branding);
2902
3211
  this.setAttribute("aria-busy", this.loading ? "true" : "false");
2903
3212
  }
2904
3213
  updated(changed) {
2905
- if (!changed.has("response")) return;
2906
- this.hydrateStepAfterRender();
3214
+ if (this.shadowRoot) {
3215
+ stampExportparts(this.shadowRoot);
3216
+ const widget = this.variant !== "page";
3217
+ for (const shell of this.shadowRoot.querySelectorAll("zl-page-shell")) shell.toggleAttribute("data-widget", widget);
3218
+ }
3219
+ const props = changed;
3220
+ if (!props.has("response")) return;
3221
+ this.hydrateStepAfterRender(props.get("response") == null);
2907
3222
  }
2908
3223
  /**
2909
3224
  * Apply captured values and move focus once the new step has fully
@@ -2911,13 +3226,18 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
2911
3226
  * but those render their own shadow DOM on a later microtask — so await
2912
3227
  * this element's update *and* the child atoms' first render before touching
2913
3228
  * them, rather than guessing a frame with `requestAnimationFrame`.
3229
+ *
3230
+ * Focus on the *initial* response is page-mode-only: a dedicated login
3231
+ * route should focus its first field, but a widget embedded further down
3232
+ * an arbitrary page must not steal focus and scroll-jump on load. Step
3233
+ * swaps are user-initiated, so focus moves in both modes.
2914
3234
  */
2915
- async hydrateStepAfterRender() {
3235
+ async hydrateStepAfterRender(initial = false) {
2916
3236
  await this.updateComplete;
2917
3237
  const atoms = this.shadowRoot?.querySelectorAll("zl-field, zl-button");
2918
3238
  if (atoms) await Promise.all(Array.from(atoms).map((atom) => atom.updateComplete));
2919
3239
  this.applyValuesToFields();
2920
- this.moveFocusToFirstField();
3240
+ if (!initial || this.variant === "page") this.moveFocusToFirstField();
2921
3241
  }
2922
3242
  render() {
2923
3243
  if (this.startupError) return html`<form class="zl-mount" novalidate>
@@ -2991,6 +3311,7 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
2991
3311
  });
2992
3312
  }
2993
3313
  this.applyResponse(wire);
3314
+ emit(this, "zitadel-flow-step", { step: wire.step });
2994
3315
  } catch (error) {
2995
3316
  this.handleTransportError(this.describeFlowSelectionError(error));
2996
3317
  } finally {
@@ -3021,6 +3342,22 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
3021
3342
  ...collectInitialValues(wire.step),
3022
3343
  ...this.formValues
3023
3344
  };
3345
+ if (typeof window !== "undefined") {
3346
+ const hasBack = Boolean(wire.step.actions?.some((a) => a.kind === "back"));
3347
+ if (hasBack && !this.armed) {
3348
+ history.pushState({
3349
+ ...history.state,
3350
+ zl: true
3351
+ }, "");
3352
+ this.armed = true;
3353
+ } else if (!hasBack && this.armed) {
3354
+ this.armed = false;
3355
+ if (history.state?.zl === true) {
3356
+ this.ignoreNextPop = true;
3357
+ history.back();
3358
+ }
3359
+ }
3360
+ }
3024
3361
  this.maybeCompleteFlow(wire);
3025
3362
  }
3026
3363
  /**
@@ -3127,42 +3464,89 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
3127
3464
  ...display ? { display_name: display } : email ? { display_name: email } : {}
3128
3465
  };
3129
3466
  }
3130
- applyValuesToFields() {
3467
+ /** All rendered input atoms exposing the `formValue` contract. */
3468
+ fieldAtoms() {
3131
3469
  const root = this.shadowRoot;
3132
- if (!root) return;
3133
- const fields = root.querySelectorAll("zl-field");
3134
- if (fields.length === 0) return;
3135
- for (const field of fields) {
3136
- const name = field.getAttribute("name");
3137
- if (!name || !(name in this.formValues)) continue;
3470
+ if (!root) return [];
3471
+ return Array.from(root.querySelectorAll("[name]")).filter(isFieldAtom);
3472
+ }
3473
+ applyValuesToFields() {
3474
+ for (const atom of this.fieldAtoms()) {
3475
+ const name = atom.getAttribute("name");
3476
+ if (!name) continue;
3138
3477
  const next = this.formValues[name];
3139
- if (field.value !== next) field.value = next;
3478
+ if (next !== void 0 && atom.formValue !== next) atom.formValue = next;
3140
3479
  }
3141
3480
  }
3142
- captureValuesFromFields() {
3143
- const root = this.shadowRoot;
3144
- if (!root) return this.formValues;
3145
- const fields = root.querySelectorAll("zl-field");
3146
- if (fields.length === 0) return this.formValues;
3147
- const next = { ...this.formValues };
3148
- let changed = false;
3149
- for (const field of fields) {
3150
- const name = field.getAttribute("name");
3151
- const value = this.currentFieldValue(field);
3152
- if (!name || value === void 0) continue;
3153
- if (field.value !== value) field.value = value;
3154
- if (next[name] !== value) {
3155
- next[name] = value;
3156
- changed = true;
3157
- }
3481
+ /**
3482
+ * Names of the current step's `required` fields whose captured value is
3483
+ * empty. Reads each atom's live `formValue` (the getter reflects the native
3484
+ * control, so autofill that skipped `input` events is still seen), so this
3485
+ * is browser-independent and does not rely on native constraint validation.
3486
+ */
3487
+ missingRequiredFields() {
3488
+ const values = /* @__PURE__ */ new Map();
3489
+ for (const atom of this.fieldAtoms()) {
3490
+ const name = atom.getAttribute("name");
3491
+ if (name) values.set(name, atom.formValue);
3158
3492
  }
3159
- if (changed) this.formValues = next;
3160
- return next;
3493
+ const missing = [];
3494
+ for (const field of this.response?.step.fields ?? []) {
3495
+ if (field.type === "checkbox") continue;
3496
+ if (field.required && (values.get(field.name) ?? "") === "") missing.push(field.name);
3497
+ }
3498
+ return missing;
3499
+ }
3500
+ /**
3501
+ * Surface a client-side required-field error using the server's own
3502
+ * validation dialect (`error.<field>_required`, "; "-joined), so it flows
3503
+ * through the same localisation and inline/banner routing as a real
3504
+ * backend rejection — no native browser bubble. Idempotent: re-running with
3505
+ * the same keys (both submit entry points fire on one click) is a no-op.
3506
+ */
3507
+ reportRequiredErrors(fields) {
3508
+ if (!this.response) return;
3509
+ const errorKey = fields.map((name) => `error.${name}_required`).join("; ");
3510
+ if (this.response.step.error === errorKey) return;
3511
+ this.stepErrorDismissed = false;
3512
+ this.response = {
3513
+ ...this.response,
3514
+ step: {
3515
+ ...this.response.step,
3516
+ error: errorKey
3517
+ }
3518
+ };
3161
3519
  }
3162
- currentFieldValue(field) {
3163
- const native = field.shadowRoot?.querySelector("input");
3164
- if (native && native.value !== field.value) return native.value;
3165
- return typeof field.value === "string" ? field.value : void 0;
3520
+ /**
3521
+ * Snapshot the current step's field values straight from the rendered input
3522
+ * atoms through their uniform `formValue` contract. Tag-agnostic: every
3523
+ * form-participating atom is read the same way, so a new field type needs no
3524
+ * change here. Declared fields default to "" so the backend still runs its
3525
+ * required-checks and challenge dispatch instead of silently advancing on a
3526
+ * field-less payload. Captured values are folded into `formValues` for
3527
+ * cross-step identity (the signed-in greeting) and post-error restoration.
3528
+ */
3529
+ collectSubmitFields() {
3530
+ const current = /* @__PURE__ */ new Map();
3531
+ for (const atom of this.fieldAtoms()) {
3532
+ const name = atom.getAttribute("name");
3533
+ if (name) current.set(name, atom.formValue);
3534
+ }
3535
+ if (current.size > 0) this.formValues = {
3536
+ ...this.formValues,
3537
+ ...Object.fromEntries(current)
3538
+ };
3539
+ const fields = {};
3540
+ for (const f of this.response?.step.fields ?? []) {
3541
+ const value = current.get(f.name) ?? "";
3542
+ if (f.type === "checkbox") {
3543
+ fields[f.name] = value !== "";
3544
+ continue;
3545
+ }
3546
+ if (f.type === "select" && !isAllowedSelectValue(f, value)) continue;
3547
+ fields[f.name] = value;
3548
+ }
3549
+ return fields;
3166
3550
  }
3167
3551
  /**
3168
3552
  * True once the user retired the current step error by editing a field or
@@ -3190,10 +3574,25 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
3190
3574
  value
3191
3575
  });
3192
3576
  };
3193
- /** `zl-change` from <zl-checkbox>/<zl-select>: only error clearing. */
3577
+ /**
3578
+ * `zl-change` from <zl-checkbox>/<zl-select>. Persist the atom's value into
3579
+ * `formValues` — mirroring `handleAtomInput` for text fields — so a later
3580
+ * step re-render (a validation error re-parses the template via `unsafeHTML`
3581
+ * and rebuilds the atoms) restores the selection/checked state through
3582
+ * `applyValuesToFields` instead of dropping back to the template default.
3583
+ * Reads the live `formValue` off the atom rather than the event's `value`
3584
+ * token, because an unchecked checkbox reports "" there but keeps its token
3585
+ * in the detail. Also clears stale errors on the edited field.
3586
+ */
3194
3587
  handleAtomEdited = (event) => {
3195
3588
  const name = event.detail?.name;
3196
- if (name) this.clearStaleErrors(name);
3589
+ if (!name) return;
3590
+ const atom = event.target;
3591
+ if (atom instanceof HTMLElement && isFieldAtom(atom)) this.formValues = {
3592
+ ...this.formValues,
3593
+ [name]: atom.formValue
3594
+ };
3595
+ this.clearStaleErrors(name);
3197
3596
  };
3198
3597
  /** Explicit dismiss of the step-error alert (it removes itself). */
3199
3598
  handleAlertDismiss = (event) => {
@@ -3219,16 +3618,9 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
3219
3618
  field.error = "";
3220
3619
  }
3221
3620
  }
3621
+ /** Mirror a value onto the matching rendered atom (used after atom events). */
3222
3622
  syncFieldElementValue(name, value) {
3223
- const root = this.shadowRoot;
3224
- if (!root) return;
3225
- const fields = root.querySelectorAll("zl-field");
3226
- for (const field of fields) {
3227
- if (field.getAttribute("name") !== name) continue;
3228
- if (field.value !== value) field.value = value;
3229
- const native = field.shadowRoot?.querySelector("input");
3230
- if (native && native.value !== value) native.value = value;
3231
- }
3623
+ for (const atom of this.fieldAtoms()) if (atom.getAttribute("name") === name && atom.formValue !== value) atom.formValue = value;
3232
3624
  }
3233
3625
  handleAtomSubmit = (event) => {
3234
3626
  if (this.loading) return;
@@ -3246,6 +3638,11 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
3246
3638
  handleFormSubmit = (event) => {
3247
3639
  event.preventDefault();
3248
3640
  if (this.loading) return;
3641
+ const missing = this.missingRequiredFields();
3642
+ if (missing.length > 0) {
3643
+ this.reportRequiredErrors(missing);
3644
+ return;
3645
+ }
3249
3646
  const action = event.submitter?.getAttribute?.("action") ?? null ?? this.findPrimaryAction();
3250
3647
  this.submit(action);
3251
3648
  };
@@ -3308,13 +3705,7 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
3308
3705
  const { id, session_token } = this.response;
3309
3706
  this.loading = true;
3310
3707
  try {
3311
- const formValues = this.captureValuesFromFields();
3312
- const stepFields = this.response.step.fields ?? [];
3313
- const fields = {};
3314
- for (const f of stepFields) {
3315
- const value = formValues[f.name];
3316
- if (value !== void 0) fields[f.name] = value;
3317
- }
3708
+ const fields = this.collectSubmitFields();
3318
3709
  const body = {
3319
3710
  session_token,
3320
3711
  action: action ?? "submit",
@@ -3331,6 +3722,47 @@ let ZitadelLogin = class ZitadelLogin extends LitElement {
3331
3722
  this.loading = false;
3332
3723
  }
3333
3724
  }
3725
+ /**
3726
+ * Handle the browser's back/forward gesture (ADR 022). When `popstate`
3727
+ * fires:
3728
+ *
3729
+ * - **Self-initiated** (`ignoreNextPop`) → `applyResponse` is retiring
3730
+ * the sentinel; ignore.
3731
+ * - **Back press while armed** → the browser consumed the sentinel.
3732
+ * Re-arm it immediately — so the stack shape is identical on every
3733
+ * step and repeated presses behave the same at any flow depth — then
3734
+ * submit the step's `kind: "back"` action.
3735
+ * - **Landing on the sentinel while armed** → the host page pushed an
3736
+ * entry above the sentinel (e.g. an in-page `#anchor` click) and the
3737
+ * user backed out of it. They are back where the widget expects them
3738
+ * — not asking the flow to go back; do nothing.
3739
+ * - **Forward press onto a retired sentinel** (it survives as a forward
3740
+ * entry after `history.back()`) → bounce back: flow state is
3741
+ * server-authoritative, the browser cannot skip ahead
3742
+ * (ADR 022 §Edge cases).
3743
+ * - Anything else is host-page traversal — leave the browser alone.
3744
+ */
3745
+ onPopState(event) {
3746
+ if (this.ignoreNextPop) {
3747
+ this.ignoreNextPop = false;
3748
+ return;
3749
+ }
3750
+ if (this.armed) {
3751
+ if (event.state?.zl === true) return;
3752
+ this.armed = false;
3753
+ const backAction = this.response?.step?.actions?.find((a) => a.kind === "back");
3754
+ if (backAction) {
3755
+ history.pushState({ zl: true }, "");
3756
+ this.armed = true;
3757
+ this.submit(backAction.name);
3758
+ }
3759
+ return;
3760
+ }
3761
+ if (event.state?.zl === true) {
3762
+ this.ignoreNextPop = true;
3763
+ history.back();
3764
+ }
3765
+ }
3334
3766
  handleTransportError(error) {
3335
3767
  const message = error instanceof ApiError ? apiErrorMessage(error) : error instanceof Error ? error.message : "Unexpected error contacting the Flow API.";
3336
3768
  this.startupError = message;
@@ -3369,6 +3801,16 @@ __decorate([state()], ZitadelLogin.prototype, "loading", null);
3369
3801
  __decorate([state()], ZitadelLogin.prototype, "startupError", null);
3370
3802
  __decorate([state()], ZitadelLogin.prototype, "formValues", null);
3371
3803
  ZitadelLogin = __decorate([customElement("zitadel-login")], ZitadelLogin);
3804
+ /**
3805
+ * Whether `value` is a member of a select field's closed `enum`. A select
3806
+ * with no explicit enum has no submittable value, so this returns `false`
3807
+ * and the caller omits the field. Because the enum never contains "" unless
3808
+ * the schema deliberately lists it, an untouched placeholder ("") is omitted
3809
+ * rather than sent and rejected by the server's enum validation.
3810
+ */
3811
+ function isAllowedSelectValue(field, value) {
3812
+ return field.validation?.enum?.includes(value) ?? false;
3813
+ }
3372
3814
  function collectInitialValues(step) {
3373
3815
  const values = {};
3374
3816
  if (!step.fields) return values;
@@ -3550,12 +3992,12 @@ let ZitadelLogout = class ZitadelLogout extends LitElement {
3550
3992
  set postSignOutUrl(value) {
3551
3993
  this.#_postSignOutUrl_accessor_storage = value;
3552
3994
  }
3553
- #_clientId_accessor_storage = "";
3554
- get clientId() {
3555
- return this.#_clientId_accessor_storage;
3995
+ #_theme_accessor_storage = "";
3996
+ get theme() {
3997
+ return this.#_theme_accessor_storage;
3556
3998
  }
3557
- set clientId(value) {
3558
- this.#_clientId_accessor_storage = value;
3999
+ set theme(value) {
4000
+ this.#_theme_accessor_storage = value;
3559
4001
  }
3560
4002
  #_displayName_accessor_storage = "";
3561
4003
  get displayName() {
@@ -3603,9 +4045,9 @@ let ZitadelLogout = class ZitadelLogout extends LitElement {
3603
4045
  pendingTemplate = null;
3604
4046
  projectedContainer = null;
3605
4047
  identityRequested = false;
4048
+ themeController = new ThemeController(this);
3606
4049
  connectedCallback() {
3607
4050
  super.connectedCallback();
3608
- this.dataset.theme = "dark";
3609
4051
  const tmpl = this.querySelector("template");
3610
4052
  if (tmpl instanceof HTMLTemplateElement) {
3611
4053
  this.templateMode = true;
@@ -3621,9 +4063,17 @@ let ZitadelLogout = class ZitadelLogout extends LitElement {
3621
4063
  document.removeEventListener("click", this.handleDocumentClick);
3622
4064
  document.removeEventListener("keydown", this.handleDocumentKeydown);
3623
4065
  }
3624
- updated() {
4066
+ willUpdate() {
4067
+ this.themeController.setModePreference(this.theme === "" ? void 0 : this.theme, "auto");
3625
4068
  const root = this.shadowRoot;
3626
- if (root && !this.templateMode) applyBaseTokens(root);
4069
+ if (root && !this.templateMode) {
4070
+ applyBaseTokens(root);
4071
+ applyBrandingTokens(root, void 0, this.themeController.theme);
4072
+ }
4073
+ this.dataset.theme = this.themeController.theme;
4074
+ this.toggleAttribute("data-theme-dark", this.themeController.theme === "dark");
4075
+ }
4076
+ updated() {
3627
4077
  this.maybeLoadIdentity();
3628
4078
  }
3629
4079
  /** Declarative config read from this element's attributes. */
@@ -3734,20 +4184,6 @@ let ZitadelLogout = class ZitadelLogout extends LitElement {
3734
4184
  });
3735
4185
  if (this.postSignOutUrl && typeof window !== "undefined") window.location.href = this.postSignOutUrl;
3736
4186
  }
3737
- /**
3738
- * Returns the absolute URL the end-session request will hit. Useful for
3739
- * test assertions and for consumers that prefer to navigate the browser
3740
- * directly (instead of fetching) so the OIDC session-end redirect is
3741
- * driven by the user agent.
3742
- */
3743
- getEndSessionUrl() {
3744
- const params = {
3745
- ...this.clientId ? { client_id: this.clientId } : {},
3746
- ...this.postSignOutUrl ? { post_logout_redirect_uri: this.postSignOutUrl } : {}
3747
- };
3748
- const { api } = resolveApi(this.project, this.projectAttrs, "<zitadel-logout>");
3749
- return api.getEndSessionUrl(params);
3750
- }
3751
4187
  handleSignOutClick(event) {
3752
4188
  event.preventDefault();
3753
4189
  this.doLogout();
@@ -3826,10 +4262,7 @@ __decorate([property({
3826
4262
  type: String,
3827
4263
  attribute: "post-sign-out-url"
3828
4264
  })], ZitadelLogout.prototype, "postSignOutUrl", null);
3829
- __decorate([property({
3830
- type: String,
3831
- attribute: "client-id"
3832
- })], ZitadelLogout.prototype, "clientId", null);
4265
+ __decorate([property({ type: String })], ZitadelLogout.prototype, "theme", null);
3833
4266
  __decorate([state()], ZitadelLogout.prototype, "displayName", null);
3834
4267
  __decorate([state()], ZitadelLogout.prototype, "displayEmail", null);
3835
4268
  __decorate([state()], ZitadelLogout.prototype, "displayUserId", null);
@@ -3852,13 +4285,11 @@ function fillTemplateTokens(fragment, name, email, initial) {
3852
4285
  }
3853
4286
  //#endregion
3854
4287
  //#region src/orchestrator/zitadel-session.ts
3855
- let ZitadelSession = class ZitadelSession extends LitElement {
4288
+ let ZitadelSession = class ZitadelSession extends ZitadelSurface {
3856
4289
  static styles = [baseHostStyles, css`
3857
4290
  :host {
3858
4291
  display: block;
3859
- min-height: 100vh;
3860
- background: ${t.color.surface.defaultBlack};
3861
- color: ${t.color.text.primaryWhite};
4292
+ width: 100%;
3862
4293
  }
3863
4294
  .title {
3864
4295
  margin: 0;
@@ -3974,16 +4405,16 @@ let ZitadelSession = class ZitadelSession extends LitElement {
3974
4405
  identityRequested = false;
3975
4406
  connectedCallback() {
3976
4407
  super.connectedCallback();
3977
- this.dataset.theme = "dark";
3978
4408
  this.maybeLoadIdentity();
3979
4409
  }
3980
4410
  /** Single identity line: human-readable name, then email, then user_id. */
3981
4411
  get identityLabel() {
3982
4412
  return this.displayName || this.displayEmail || this.displayUserId;
3983
4413
  }
4414
+ willUpdate() {
4415
+ this.applySurfaceTheme(void 0);
4416
+ }
3984
4417
  updated() {
3985
- const root = this.shadowRoot;
3986
- if (root) applyBaseTokens(root);
3987
4418
  this.maybeLoadIdentity();
3988
4419
  }
3989
4420
  /** Declarative config read from this element's attributes. */
@@ -4048,7 +4479,7 @@ let ZitadelSession = class ZitadelSession extends LitElement {
4048
4479
  }
4049
4480
  render() {
4050
4481
  return html`
4051
- <zl-page-shell>
4482
+ <zl-page-shell ?data-widget=${this.variant !== "page"}>
4052
4483
  <zl-card>
4053
4484
  <h1 slot="header" class="title">${this.heading}</h1>
4054
4485
  ${this.identityLabel ? html`<p slot="header" class="identity">${this.identityLabel}</p>` : nothing}
@@ -4095,6 +4526,6 @@ __decorate([state()], ZitadelSession.prototype, "loading", null);
4095
4526
  __decorate([state()], ZitadelSession.prototype, "errorMessage", null);
4096
4527
  ZitadelSession = __decorate([customElement("zitadel-session")], ZitadelSession);
4097
4528
  //#endregion
4098
- export { submitStep as S, applyBrandingTokens as _, layout_chrome_default as a, getCurrentStep as b, it as c, TEMPLATE_NAMES as d, MANDATORY_GATES_MARKER as f, validateBranding as g, applyFontUrl as h, ThemeController as i, de as l, patchMandatoryGates as m, ZitadelLogout as n, createSanitiser as o, mandatoryGatesMarkerComment as p, ZitadelLogin as r, builtinLocales as s, ZitadelSession as t, en as u, buildBrandingStylesheet as v, startFlow as x, resolveTheme as y };
4529
+ export { submitStep as C, startFlow as S, MANDATORY_GATES_MARKER as _, ThemeController as a, validateBranding as b, buildBrandingStylesheet as c, builtinLocales as d, businessLocales as f, TEMPLATE_NAMES as g, en as h, layout_chrome_default as i, resolveTheme as l, de as m, ZitadelLogout as n, applyFontUrl as o, it as p, ZitadelLogin as r, applyBrandingTokens as s, ZitadelSession as t, createSanitiser as u, mandatoryGatesMarkerComment as v, getCurrentStep as x, patchMandatoryGates as y };
4099
4530
 
4100
- //# sourceMappingURL=orchestrator-BWqJw4Yl.mjs.map
4531
+ //# sourceMappingURL=orchestrator-CctLkpmB.mjs.map