@plitzi/sdk-server 0.37.2 → 0.37.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1170 -0
- package/README.md +100 -7
- package/dist/actions.d.ts +6 -2
- package/dist/actions.js +5 -2
- package/dist/adapters/cloudAdapters.js +4 -3
- package/dist/adapters/jsonAdapters.js +28 -6
- package/dist/adapters/jsonAdapters.test.d.ts +1 -0
- package/dist/auth.d.ts +7 -4
- package/dist/auth.js +8 -6
- package/dist/core/auth/api.d.ts +29 -1
- package/dist/core/auth/api.js +47 -13
- package/dist/core/auth/createAuth.d.ts +4 -0
- package/dist/core/auth/createAuth.js +3 -3
- package/dist/core/auth/credentials.d.ts +3 -0
- package/dist/core/auth/credentials.js +1 -1
- package/dist/core/auth/csrf.d.ts +25 -1
- package/dist/core/auth/csrf.js +29 -4
- package/dist/core/auth/renewal.d.ts +26 -0
- package/dist/core/auth/renewal.js +24 -0
- package/dist/core/auth/routes.js +8 -6
- package/dist/core/auth/session.d.ts +45 -0
- package/dist/core/auth/session.js +47 -1
- package/dist/core/auth/sessionActivity.d.ts +11 -0
- package/dist/core/auth/sessionActivity.js +14 -0
- package/dist/core/auth/throttle.d.ts +15 -0
- package/dist/core/auth/throttle.js +29 -1
- package/dist/core/auth/totp.d.ts +9 -1
- package/dist/core/auth/totp.js +11 -6
- package/dist/core/createServer.actions.test.d.ts +1 -0
- package/dist/core/createServer.d.ts +14 -1
- package/dist/core/createServer.functions.test.d.ts +1 -0
- package/dist/core/createServer.js +22 -12
- package/dist/core/fontStore.js +1 -1
- package/dist/core/handlers/authRouteHandlers.d.ts +1 -1
- package/dist/core/handlers/authRouteHandlers.js +57 -21
- package/dist/core/handlers/csrfMiddleware.d.ts +23 -0
- package/dist/core/handlers/csrfMiddleware.js +31 -7
- package/dist/core/handlers/csrfMiddleware.test.d.ts +1 -0
- package/dist/core/handlers/oauthRouteHandlers.js +5 -3
- package/dist/core/handlers/socialRouteHandlers.d.ts +1 -1
- package/dist/core/handlers/socialRouteHandlers.js +2 -1
- package/dist/core/http/dispatcher.d.ts +9 -1
- package/dist/core/http/dispatcher.js +38 -11
- package/dist/core/http/navigation.d.ts +5 -2
- package/dist/core/http/navigation.js +7 -4
- package/dist/core/http/socketResponse.d.ts +18 -0
- package/dist/core/http/socketResponse.js +59 -0
- package/dist/core/http/sse.d.ts +26 -0
- package/dist/core/http/sse.js +78 -0
- package/dist/core/http/stages/authRoutes.js +3 -1
- package/dist/core/http/stages/exchangeRoute.test.d.ts +1 -0
- package/dist/core/http/stages/middlewares.d.ts +1 -1
- package/dist/core/http/stages/middlewares.js +16 -10
- package/dist/core/http/stages/middlewares.test.d.ts +1 -0
- package/dist/core/http/stages/oauth.js +2 -2
- package/dist/core/http/stages/sessionRenewal.d.ts +16 -0
- package/dist/core/http/stages/sessionRenewal.js +81 -0
- package/dist/core/http/stages/sessionRenewal.test.d.ts +1 -0
- package/dist/core/http/stages/signInRoute.d.ts +2 -0
- package/dist/core/http/stages/signInRoute.js +139 -0
- package/dist/core/http/stages/signInRoute.test.d.ts +1 -0
- package/dist/core/http/types.d.ts +9 -0
- package/dist/core/http/webExchange.d.ts +18 -0
- package/dist/core/http/webExchange.js +53 -0
- package/dist/core/oauth/authorize.js +37 -13
- package/dist/core/oauth/consentPage.js +11 -0
- package/dist/core/oauth/issueContext.d.ts +14 -0
- package/dist/core/oauth/issueContext.js +32 -0
- package/dist/core/oauth/records.d.ts +7 -0
- package/dist/core/oauth/register.d.ts +2 -0
- package/dist/core/oauth/register.js +32 -11
- package/dist/core/oauth/register.test.d.ts +1 -0
- package/dist/core/oauth/token.js +21 -6
- package/dist/core/oauth/token.test.d.ts +1 -0
- package/dist/core/requestParser.d.ts +4 -0
- package/dist/core/requestParser.js +14 -7
- package/dist/core/sdkAssets.js +1 -1
- package/dist/core/server/baseServer.d.ts +10 -0
- package/dist/core/server/baseServer.js +42 -11
- package/dist/core/server/closeOnSignals.js +4 -3
- package/dist/core/server/drain.d.ts +18 -0
- package/dist/core/server/drain.js +62 -0
- package/dist/core/server/drain.test.d.ts +1 -0
- package/dist/core/server/fleet/channel.d.ts +103 -0
- package/dist/core/server/fleet/channel.js +191 -0
- package/dist/core/server/fleet/channel.test.d.ts +1 -0
- package/dist/core/server/fleet/fixtures/fleetServer.d.ts +1 -0
- package/dist/core/server/fleet/fleet.integration.test.d.ts +1 -0
- package/dist/core/server/fleet/link.d.ts +23 -0
- package/dist/core/server/fleet/link.js +70 -0
- package/dist/core/server/fleet/relay.d.ts +11 -0
- package/dist/core/server/fleet/relay.js +104 -0
- package/dist/core/server/fleet/relay.test.d.ts +1 -0
- package/dist/core/server/fleet/role.d.ts +46 -0
- package/dist/core/server/fleet/role.js +59 -0
- package/dist/core/server/fleet/role.test.d.ts +1 -0
- package/dist/core/server/fleet/stores.d.ts +7 -0
- package/dist/core/server/fleet/stores.js +19 -0
- package/dist/core/server/fleet/supervisor.d.ts +26 -0
- package/dist/core/server/fleet/supervisor.js +108 -0
- package/dist/core/server/fleet/supervisor.test.d.ts +1 -0
- package/dist/core/server/pageServer.js +34 -5
- package/dist/core/services/action.js +10 -2
- package/dist/core/services/functionRoutes.d.ts +9 -0
- package/dist/core/services/functionRoutes.js +47 -0
- package/dist/core/services/realtime.d.ts +8 -0
- package/dist/core/services/realtime.js +57 -0
- package/dist/core/services/registry.js +6 -2
- package/dist/core/staticFiles.js +12 -4
- package/dist/core/staticFiles.test.d.ts +1 -0
- package/dist/core/transports.d.ts +13 -3
- package/dist/core/transports.js +14 -5
- package/dist/functions-api.d.ts +281 -0
- package/dist/functions.d.ts +19 -0
- package/dist/functions.js +5 -0
- package/dist/functionsRunner.d.ts +15 -0
- package/dist/functionsRunner.js +6 -0
- package/dist/handlers.d.ts +2 -2
- package/dist/handlers.js +2 -2
- package/dist/helpers/atomicFile.d.ts +10 -0
- package/dist/helpers/atomicFile.js +33 -0
- package/dist/helpers/atomicFile.test.d.ts +1 -0
- package/dist/helpers/buildResponseHelpers.js +35 -11
- package/dist/helpers/buildServerInfo.js +4 -2
- package/dist/helpers/buildServerInfo.test.d.ts +1 -0
- package/dist/helpers/cache/CompressedFileCache.d.ts +18 -0
- package/dist/helpers/cache/CompressedFileCache.js +44 -0
- package/dist/helpers/cache/CompressedFileCache.test.d.ts +1 -0
- package/dist/helpers/cache/cacheManager.d.ts +1 -1
- package/dist/helpers/cache/cacheManager.js +2 -1
- package/dist/helpers/cache/cacheManager.test.d.ts +1 -0
- package/dist/helpers/cache/index.d.ts +1 -1
- package/dist/helpers/cache/keys.d.ts +15 -1
- package/dist/helpers/cache/keys.js +47 -5
- package/dist/helpers/cache/serverCaches.d.ts +7 -1
- package/dist/helpers/compress.d.ts +3 -1
- package/dist/helpers/compress.js +11 -3
- package/dist/helpers/hydrationPayload.d.ts +11 -0
- package/dist/helpers/hydrationPayload.js +51 -0
- package/dist/helpers/hydrationPayload.test.d.ts +1 -0
- package/dist/helpers/metrics.d.ts +1 -1
- package/dist/helpers/metrics.js +3 -2
- package/dist/helpers/outboundGuard.d.ts +14 -6
- package/dist/helpers/outboundGuard.js +132 -8
- package/dist/helpers/paintedCookie.d.ts +8 -0
- package/dist/helpers/paintedCookie.js +13 -0
- package/dist/helpers/reportReactBuild.d.ts +9 -0
- package/dist/helpers/reportReactBuild.js +18 -0
- package/dist/helpers/reportReactBuild.test.d.ts +1 -0
- package/dist/helpers/serverLog.d.ts +25 -1
- package/dist/helpers/serverLog.js +68 -3
- package/dist/helpers/serverLog.test.d.ts +1 -0
- package/dist/helpers/wsFrame.d.ts +3 -0
- package/dist/helpers/wsFrame.js +5 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +5 -2
- package/dist/kernel.d.ts +2 -1
- package/dist/kernel.js +3 -2
- package/dist/modules/actions/connectorWrite.js +2 -1
- package/dist/modules/actions/index.d.ts +29 -10
- package/dist/modules/actions/index.js +43 -6
- package/dist/modules/actions/jobs/failureStreak.d.ts +17 -0
- package/dist/modules/actions/jobs/failureStreak.js +47 -0
- package/dist/modules/actions/jobs/failureStreak.test.d.ts +1 -0
- package/dist/modules/actions/jobs/index.d.ts +2 -1
- package/dist/modules/actions/jobs/index.js +5 -3
- package/dist/modules/actions/jobs/memoryQueue.d.ts +3 -0
- package/dist/modules/actions/jobs/memoryQueue.js +17 -1
- package/dist/modules/actions/jobs/scheduler.d.ts +2 -0
- package/dist/modules/actions/jobs/scheduler.js +12 -7
- package/dist/modules/actions/jobs/worker.d.ts +3 -1
- package/dist/modules/actions/jobs/worker.js +9 -4
- package/dist/modules/actions/moduleFor.js +5 -2
- package/dist/modules/actions/runtime/errors.d.ts +12 -0
- package/dist/modules/actions/runtime/errors.js +16 -1
- package/dist/modules/actions/runtime/kvChange.d.ts +27 -0
- package/dist/modules/actions/runtime/kvChange.js +23 -0
- package/dist/modules/actions/runtime/kvList.d.ts +51 -0
- package/dist/modules/actions/runtime/kvList.js +52 -0
- package/dist/modules/actions/runtime/kvStore.d.ts +8 -0
- package/dist/modules/actions/runtime/kvStore.js +46 -20
- package/dist/modules/actions/runtime/memoryKv.d.ts +3 -0
- package/dist/modules/actions/runtime/memoryKv.js +19 -1
- package/dist/modules/actions/runtime/namespaceKv.d.ts +4 -6
- package/dist/modules/actions/runtime/namespaceKv.js +15 -3
- package/dist/modules/actions/runtime/namespaceKv.test.d.ts +1 -0
- package/dist/modules/actions/runtime/precheck.js +4 -8
- package/dist/modules/actions/runtime/rateLimit.d.ts +19 -0
- package/dist/modules/actions/runtime/rateLimit.js +19 -0
- package/dist/modules/actions/runtime/redisKv.d.ts +27 -0
- package/dist/modules/actions/runtime/redisKv.integration.test.d.ts +1 -0
- package/dist/modules/actions/runtime/redisKv.js +46 -0
- package/dist/modules/actions/runtime/report.js +2 -1
- package/dist/modules/actions/runtime/runAction.d.ts +10 -2
- package/dist/modules/actions/runtime/runAction.js +52 -116
- package/dist/modules/actions/runtime/runLogger.d.ts +5 -0
- package/dist/modules/actions/runtime/runLogger.js +8 -2
- package/dist/modules/actions/runtime/signing.d.ts +20 -0
- package/dist/modules/actions/runtime/signing.js +25 -0
- package/dist/modules/actions/runtime/signing.test.d.ts +1 -0
- package/dist/modules/actions/runtime/taskContext.d.ts +33 -0
- package/dist/modules/actions/runtime/taskContext.js +124 -0
- package/dist/modules/actions/tasks/builtins.js +5 -1
- package/dist/modules/actions/tasks/flow.js +81 -10
- package/dist/modules/actions/tasks/helpers.js +2 -2
- package/dist/modules/actions/tasks/http.js +2 -3
- package/dist/modules/actions/tasks/kv.js +39 -0
- package/dist/modules/actions/tasks/list.d.ts +2 -0
- package/dist/modules/actions/tasks/list.js +145 -0
- package/dist/modules/actions/tasks/realtime.d.ts +2 -0
- package/dist/modules/actions/tasks/realtime.js +104 -0
- package/dist/modules/actions/tasks/registry.d.ts +10 -1
- package/dist/modules/actions/tasks/registry.js +17 -6
- package/dist/modules/actions/transport/callHandler.js +5 -2
- package/dist/modules/actions/transport/catalogHandler.d.ts +5 -2
- package/dist/modules/actions/transport/catalogHandler.js +3 -2
- package/dist/modules/actions/transport/stream.d.ts +4 -5
- package/dist/modules/actions/transport/stream.js +12 -76
- package/dist/modules/actions/transport/webhookHandler.js +4 -3
- package/dist/modules/actions/types.d.ts +86 -3
- package/dist/modules/connectors/engine.js +3 -5
- package/dist/modules/functions/build.d.ts +22 -0
- package/dist/modules/functions/build.js +122 -0
- package/dist/modules/functions/build.test.d.ts +1 -0
- package/dist/modules/functions/capabilities.d.ts +22 -0
- package/dist/modules/functions/capabilities.js +228 -0
- package/dist/modules/functions/config.d.ts +34 -0
- package/dist/modules/functions/config.js +24 -0
- package/dist/modules/functions/context.d.ts +17 -0
- package/dist/modules/functions/context.js +88 -0
- package/dist/modules/functions/contract.d.ts +121 -0
- package/dist/modules/functions/contract.js +9 -0
- package/dist/modules/functions/driver.d.ts +36 -0
- package/dist/modules/functions/driver.js +150 -0
- package/dist/modules/functions/load.d.ts +15 -0
- package/dist/modules/functions/load.js +45 -0
- package/dist/modules/functions/load.test.d.ts +1 -0
- package/dist/modules/functions/local.d.ts +29 -0
- package/dist/modules/functions/local.js +61 -0
- package/dist/modules/functions/local.test.d.ts +1 -0
- package/dist/modules/functions/manifest.d.ts +24 -0
- package/dist/modules/functions/manifest.js +163 -0
- package/dist/modules/functions/manifest.test.d.ts +1 -0
- package/dist/modules/functions/native.d.ts +8 -0
- package/dist/modules/functions/native.js +21 -0
- package/dist/modules/functions/native.test.d.ts +1 -0
- package/dist/modules/functions/protocol.d.ts +216 -0
- package/dist/modules/functions/protocol.js +18 -0
- package/dist/modules/functions/routes.d.ts +36 -0
- package/dist/modules/functions/routes.js +181 -0
- package/dist/modules/functions/routes.test.d.ts +1 -0
- package/dist/modules/functions/runner/messages.d.ts +5 -0
- package/dist/modules/functions/runner/messages.js +188 -0
- package/dist/modules/functions/runner/remote.d.ts +19 -0
- package/dist/modules/functions/runner/remote.js +116 -0
- package/dist/modules/functions/runner/remote.test.d.ts +1 -0
- package/dist/modules/functions/runner/service.d.ts +32 -0
- package/dist/modules/functions/runner/service.js +209 -0
- package/dist/modules/functions/sandbox/guest.d.ts +45 -0
- package/dist/modules/functions/sandbox/guest.js +660 -0
- package/dist/modules/functions/sandbox/isolate.d.ts +37 -0
- package/dist/modules/functions/sandbox/isolate.js +347 -0
- package/dist/modules/functions/sandbox/isolate.test.d.ts +1 -0
- package/dist/modules/functions/sandbox/prelude.d.ts +2 -0
- package/dist/modules/functions/sandbox/prelude.js +49 -0
- package/dist/modules/functions/space.d.ts +40 -0
- package/dist/modules/functions/space.js +162 -0
- package/dist/modules/functions/testing/tasksOf.d.ts +4 -0
- package/dist/modules/functions/tryEntry.d.ts +9 -0
- package/dist/modules/functions/tryEntry.js +49 -0
- package/dist/modules/mongo/kv.js +31 -0
- package/dist/modules/mysql/accounts.js +19 -9
- package/dist/modules/mysql/index.d.ts +2 -1
- package/dist/modules/mysql/jobs/index.d.ts +1 -1
- package/dist/modules/mysql/jobs/kv.js +30 -0
- package/dist/modules/mysql/jobs/schema.d.ts +9 -0
- package/dist/modules/mysql/jobs/schema.js +21 -4
- package/dist/modules/mysql/schema.js +25 -0
- package/dist/modules/mysql/sessions.d.ts +1 -11
- package/dist/modules/mysql/sessions.js +6 -1
- package/dist/modules/realtime/declarations.d.ts +11 -0
- package/dist/modules/realtime/declarations.js +26 -0
- package/dist/modules/realtime/grants.d.ts +23 -0
- package/dist/modules/realtime/grants.js +61 -0
- package/dist/modules/realtime/handlers.d.ts +74 -0
- package/dist/modules/realtime/handlers.js +224 -0
- package/dist/modules/realtime/hub.d.ts +62 -0
- package/dist/modules/realtime/hub.js +141 -0
- package/dist/modules/realtime/index.d.ts +51 -0
- package/dist/modules/realtime/index.js +62 -0
- package/dist/modules/realtime/memoryPubSub.d.ts +10 -0
- package/dist/modules/realtime/memoryPubSub.js +43 -0
- package/dist/modules/realtime/realtime.test.d.ts +1 -0
- package/dist/modules/realtime/redisPubSub.d.ts +24 -0
- package/dist/modules/realtime/redisPubSub.js +36 -0
- package/dist/modules/realtime/redisPubSub.test.d.ts +1 -0
- package/dist/modules/realtime/socket.d.ts +25 -0
- package/dist/modules/realtime/socket.js +122 -0
- package/dist/modules/realtime/socket.test.d.ts +1 -0
- package/dist/modules/rsc/handler.d.ts +16 -0
- package/dist/modules/rsc/handler.js +17 -10
- package/dist/modules/rsc/handler.test.d.ts +1 -0
- package/dist/modules/rsc/resolveRscData.js +2 -1
- package/dist/modules/runtime/bundle.d.ts +24 -0
- package/dist/modules/runtime/bundle.js +102 -0
- package/dist/modules/runtime/contract.d.ts +37 -0
- package/dist/modules/runtime/contract.js +21 -0
- package/dist/modules/runtime/entry.test.d.ts +1 -0
- package/dist/modules/runtime/host.d.ts +38 -0
- package/dist/modules/runtime/host.js +68 -0
- package/dist/modules/runtime/inside.d.ts +15 -0
- package/dist/modules/runtime/inside.js +47 -0
- package/dist/modules/runtime/inside.test.d.ts +1 -0
- package/dist/modules/runtime/runner.d.ts +11 -0
- package/dist/modules/runtime/runner.js +38 -0
- package/dist/modules/runtime/runtime.test.d.ts +1 -0
- package/dist/modules/runtime/stages.d.ts +50 -0
- package/dist/modules/runtime/stages.js +117 -0
- package/dist/modules/ssr/Component.d.ts +3 -1
- package/dist/modules/ssr/Component.js +2 -1
- package/dist/modules/ssr/handler.js +10 -2
- package/dist/modules/ssr/loadPluginComponents.js +2 -1
- package/dist/modules/ssr/prepareRender.js +39 -10
- package/dist/modules/ssr/preview.d.ts +3 -0
- package/dist/modules/ssr/preview.js +17 -7
- package/dist/modules/ssr/redirect.test.d.ts +1 -0
- package/dist/modules/ssr/registerExternalPlugins.d.ts +6 -1
- package/dist/modules/ssr/registerExternalPlugins.js +19 -16
- package/dist/modules/ssr/registerExternalPlugins.test.d.ts +1 -0
- package/dist/modules/ssr/reportMissingPlugins.d.ts +9 -0
- package/dist/modules/ssr/reportMissingPlugins.js +24 -0
- package/dist/modules/ssr/reportMissingPlugins.test.d.ts +1 -0
- package/dist/modules/ssr/streamBody.d.ts +2 -2
- package/dist/modules/ssr/streamBody.js +4 -1
- package/dist/modules/ssr/template.js +1 -1
- package/dist/modules/ssr/views/template.ejs +8 -5
- package/dist/mysql.d.ts +1 -1
- package/dist/mysql.js +2 -2
- package/dist/oauth.js +2 -2
- package/dist/plugins/compile.js +9 -4
- package/dist/plugins/copy.js +3 -3
- package/dist/plugins/manager.d.ts +35 -3
- package/dist/plugins/manager.js +109 -38
- package/dist/runtime.d.ts +15 -0
- package/dist/runtime.js +7 -0
- package/package.json +39 -8
- package/skills/plitzi-authoring/SKILL.md +98 -14
- package/skills/plitzi-authoring/reference/accessibility.md +111 -0
- package/skills/plitzi-authoring/reference/authoring-errors.md +70 -0
- package/skills/plitzi-authoring/reference/data-and-visibility.md +84 -18
- package/skills/plitzi-authoring/reference/elements-and-styles.md +61 -7
- package/skills/plitzi-authoring/reference/flows.md +108 -17
- package/skills/plitzi-authoring/reference/lists.md +74 -0
- package/skills/plitzi-authoring/reference/performance.md +67 -0
- package/skills/plitzi-authoring/reference/plugins.md +167 -0
- package/skills/plitzi-authoring/reference/review-checklist.md +19 -2
- package/skills/plitzi-authoring/reference/templates.md +67 -27
- package/skills/plitzi-authoring/reference/testing.md +72 -5
- package/skills/plitzi-authoring/reference/validation.md +73 -0
|
@@ -28,17 +28,22 @@ const { schema, style, warnings } = authorSpace(space);
|
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
The published `.d.ts` is the reference for every factory, field and step builder
|
|
31
|
-
(`node_modules/@plitzi/sdk-authoring/dist/index.d.ts`) — read it instead of guessing a name or a param.
|
|
31
|
+
(`node_modules/@plitzi/sdk-authoring/dist/index.d.ts`) — read it instead of guessing a name or a param. For one element
|
|
32
|
+
type, the exported catalogues answer faster (`elementDefaultAttributes`, `elementTriggers`, `elementSlots`… — see
|
|
33
|
+
[elements and styles](reference/elements-and-styles.md)).
|
|
32
34
|
|
|
33
35
|
## How to work
|
|
34
36
|
|
|
35
37
|
1. **Read before you write.** Open `src/space.ts` (and whatever it imports) and find the layout, the classes and the
|
|
36
38
|
helpers already there. Extend them; do not add a second way of doing something the space already does.
|
|
37
|
-
2. **Change the declaration, then author it.** `npm run author` (or restart the server) runs `authorSpace`.
|
|
38
|
-
|
|
39
|
+
2. **Change the declaration, then author it.** `npm run author` (or restart the server) runs `authorSpace`. It checks
|
|
40
|
+
everything — every field, value, template, name, param and page link — and a refusal says what to write instead:
|
|
41
|
+
do exactly that. Never work around a check, cast past it, or move the logic into a plugin to escape it. The first
|
|
42
|
+
refusals come one at a time; the linter's come as ONE list — fix every line of it before running again. How it
|
|
43
|
+
checks, a one-file author script for any project, and what it cannot see: [validation](reference/validation.md).
|
|
39
44
|
3. **Read every warning.** Each one names something written that will not do what it says. Zero warnings is the bar.
|
|
40
|
-
4. **Look at it.** `npm run
|
|
41
|
-
and the page while its data is still loading
|
|
45
|
+
4. **Look at it.** `npm run shot -- /about --width 390 --scheme dark` saves a picture of one page; `npm run visual`
|
|
46
|
+
runs the checks. Look at desktop, tablet and mobile, light and dark, and the page while its data is still loading.
|
|
42
47
|
5. **Go through the [review checklist](reference/review-checklist.md) before saying it is done.** It is the feedback a
|
|
43
48
|
reviewer gives on every change, written down so you do not need to hear it.
|
|
44
49
|
|
|
@@ -59,20 +64,93 @@ The published `.d.ts` is the reference for every factory, field and step builder
|
|
|
59
64
|
loads (`condition-starts-visible` warns). When a flag HIDES it (shown → hidden: a sidebar label until the sidebar
|
|
60
65
|
is folded), it keeps the default and an absent flag must leave it shown. See
|
|
61
66
|
[data and visibility](reference/data-and-visibility.md).
|
|
62
|
-
5. **Know where a template is evaluated.** A binding's `
|
|
63
|
-
ATTRIBUTE
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
5. **Know where a template is evaluated.** A binding's template (`bindTemplate`) and a step's params evaluate full
|
|
68
|
+
Twig. An ATTRIBUTE resolves a name with filters (`{{ list_games.item.slug }}`, `{{ post.slug|url_encode }}`) against
|
|
69
|
+
the sources around the element; a condition there is used as written. Inside any template a source is spelled in
|
|
70
|
+
full — `apiContainer_stats`, `list_rows` — and a query param is `navigation.queryParams.x`. A template that feeds a
|
|
71
|
+
list its `items` hands over its value (`returns: 'value'`). `authorSpace` refuses a template it cannot read and a
|
|
72
|
+
name nothing answers to. See [templates](reference/templates.md).
|
|
66
73
|
6. **Tokens, not colours.** Every colour is a variable of the space with a `light` and a `dark` value; both themes are
|
|
67
74
|
checked from the first commit.
|
|
68
75
|
7. **Times in UTC, and say so.** Format with an explicit zone (`|date('j M · H:i', 'UTC')`) and print "UTC" beside it:
|
|
69
76
|
a page rendered on a server and hydrated in a browser in another zone must agree on the hour.
|
|
70
77
|
8. **Breakpoints are ranges.** `tablet` (48–64rem) and `mobile` (below 48rem) each inherit only from `desktop`; a rule
|
|
71
|
-
meant for both is written
|
|
78
|
+
meant for both is written under `compact`.
|
|
72
79
|
9. **Never invent data.** Numbers, names and states on screen come from a source. A panel with nothing true to say is
|
|
73
|
-
an empty state, not a placeholder figure.
|
|
80
|
+
an empty state, not a placeholder figure. With no backend, the data is JSON the project serves
|
|
81
|
+
(`public/data/*.json`) read by an `apiContainer` — see [data and visibility](reference/data-and-visibility.md).
|
|
74
82
|
10. **Never hand-write** `flat`, derived ids, `styleSelectors`, `beforeNode`/`afterNode`/`flowId`, or a
|
|
75
83
|
`styleVariant` binding's key — use the factories, `variantFrom` and `activeOn`.
|
|
84
|
+
11. **A switch is named for how it leaves its default.** `toggleState` turns a key nobody has set yet ON, so a key
|
|
85
|
+
named for the default (`showPlates`, on by default) takes a first click to do nothing: name it `platesOff`, and read
|
|
86
|
+
every switch through `computed` (`plates: '{{ state.platesOff ? false : true }}'`) so its default shows before
|
|
87
|
+
anybody touches it.
|
|
88
|
+
12. **Hand plugins over as their declarations**: `authorSpace(space, { plugins: [declaration] })` holds a plugin —
|
|
89
|
+
its own type, or a `custom({ renderType })` host — to the events, actions and attributes it declares. See
|
|
90
|
+
[plugins](reference/plugins.md).
|
|
91
|
+
13. **Usable without sight.** Screen readers and browser agents (Claude in Chrome) find a page's controls in its
|
|
92
|
+
accessibility tree: every button and link has words (an icon-only button a `title`), every field a `label`
|
|
93
|
+
(`hideLabel: true` hides it and keeps the name), every image an `alt` or `decorative: true`. Clicks go on a
|
|
94
|
+
`button` — it holds children, so a whole card can be one — or a `link`, never on a container. See
|
|
95
|
+
[accessibility](reference/accessibility.md).
|
|
96
|
+
|
|
97
|
+
## Recipes
|
|
98
|
+
|
|
99
|
+
Copy the one that fits, then change the names. Each is the shape `authorSpace` accepts on the first try.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
// Data with no backend: a JSON file the project serves (public/data/games.json), read by a provider.
|
|
103
|
+
apiContainer({ id: 'catalog', query: '/data/games.json', cache: true, children: [ /* reads catalog.data.… */ ] })
|
|
104
|
+
|
|
105
|
+
// A list of rows from that data. Inside a row: short form in bindings, full name in templates and attributes.
|
|
106
|
+
list({ id: 'games', source: 'controlled', bind: { items: 'catalog.data.games' }, children: [
|
|
107
|
+
link({ mode: 'internal', href: '/games/{{ list_games.item.slug }}', children: [
|
|
108
|
+
text('', { bind: { content: 'games.item.title' } })
|
|
109
|
+
] })
|
|
110
|
+
] })
|
|
111
|
+
|
|
112
|
+
// A filtered list: a template that hands over its VALUE.
|
|
113
|
+
list({ id: 'shown', source: 'controlled',
|
|
114
|
+
bind: [bindTemplate('items', 'catalog.data.games', "{{ source|filter(g => g.genre == state.genre) }}", { returns: 'value' })],
|
|
115
|
+
children: [ … ] })
|
|
116
|
+
|
|
117
|
+
// Text computed from data.
|
|
118
|
+
text('', { bind: [bindTemplate('content', 'catalog.data.games', '{{ source|length }} games')] })
|
|
119
|
+
|
|
120
|
+
// Shown only when a computed condition says so (starts hidden, no flash).
|
|
121
|
+
text('Nothing here yet', { visible: { source: 'catalog.data.games', template: '{{ source is defined and source|length == 0 }}' } })
|
|
122
|
+
|
|
123
|
+
// A value used in many places: declare it once in the space, read it anywhere as computed.xp.
|
|
124
|
+
computed: { xp: '{{ (state.favourites|length) * 10 }}' }
|
|
125
|
+
|
|
126
|
+
// A button that does something: a flow is [trigger, steps…].
|
|
127
|
+
button({ content: 'Save', flows: [[onClick(), setState({ key: 'saved', type: 'boolean', value: true })]] })
|
|
128
|
+
|
|
129
|
+
// A form: the flow goes on the form, which manages its own submit.
|
|
130
|
+
form({ id: 'signup', managedByInteractions: true,
|
|
131
|
+
flows: [[named('sent', onSubmit()), setState({ key: 'email', type: 'text', value: '{{ sent.values.email }}' })]],
|
|
132
|
+
children: [formControl({ name: 'email', label: 'Email', subType: 'email' }), button({ content: 'Sign up', subType: 'submit' })] })
|
|
133
|
+
|
|
134
|
+
// A modal: hidden until a flow opens it.
|
|
135
|
+
modalContainer({ id: 'details', visible: false, title: 'Details', children: [ … ] })
|
|
136
|
+
button({ content: 'Open', flows: [[onClick(), openModal('details')]] })
|
|
137
|
+
|
|
138
|
+
// A plugin: author it from its own declaration, flow on its events, call its actions — all checked.
|
|
139
|
+
const seats = defineElement<SeatPickerAttributes>(declaration);
|
|
140
|
+
seats({ id: 'seats', flows: [[named('picked', declaredTrigger(declaration, 'onPick')), setState({ key: 'seat', type: 'text', value: '{{ picked.seat }}' })]] })
|
|
141
|
+
button({ content: 'Clear', flows: [[onClick(), declaredCallback(declaration, 'reset', { on: 'seats' })]] })
|
|
142
|
+
authorSpace(space, { plugins: [declaration] })
|
|
143
|
+
|
|
144
|
+
// Usable without sight: an icon-only button says what it does; a clickable card IS a button; a field keeps its label
|
|
145
|
+
// out of sight rather than dropping it; a picture says what it shows, or that it only decorates.
|
|
146
|
+
button({ content: '', title: 'Close', children: [fontAwesome({ icon: 'fa-solid fa-xmark' })], flows: [[onClick(), closeModal('details')]] })
|
|
147
|
+
button({ content: '', class: card, children: [text('Pro'), text('12 € a month')], flows: [[onClick(), setState({ key: 'plan', type: 'text', value: 'pro' })]] })
|
|
148
|
+
formControl({ name: 'q', label: 'Search the docs', hideLabel: true, placeholder: 'Search…' })
|
|
149
|
+
image({ src: '/team.jpg', alt: 'The team at the 2026 offsite' }); image({ src: '/grain.png', decorative: true })
|
|
150
|
+
|
|
151
|
+
// A link: to a page by its id, to a path with mode 'internal', to anything else with mode 'external'.
|
|
152
|
+
link({ href: 'about' }); link({ href: '/games/nebula', mode: 'internal' }); link({ href: 'mailto:hi@x.com', mode: 'external' })
|
|
153
|
+
```
|
|
76
154
|
|
|
77
155
|
## References
|
|
78
156
|
|
|
@@ -80,10 +158,16 @@ The published `.d.ts` is the reference for every factory, field and step builder
|
|
|
80
158
|
| --- | --- |
|
|
81
159
|
| [elements-and-styles.md](reference/elements-and-styles.md) | Any element or CSS: factories, fields, classes, states, variants, tokens, fonts, lists, links |
|
|
82
160
|
| [layouts.md](reference/layouts.md) | Anything shown on more than one page; menus; reducing duplication of elements and styles |
|
|
83
|
-
| [data-and-visibility.md](reference/data-and-visibility.md) | Bindings, providers, loading/empty/error states, live data, caching, showing and hiding |
|
|
161
|
+
| [data-and-visibility.md](reference/data-and-visibility.md) | Bindings, providers, offline data, loading/empty/error states, live data, caching, showing and hiding, kept state |
|
|
162
|
+
| [lists.md](reference/lists.md) | Rendering rows, filtering and sorting them, a detail page for one record |
|
|
163
|
+
| [validation.md](reference/validation.md) | How `authorSpace` checks, the loop that wastes no attempts, and what it cannot see |
|
|
164
|
+
| [authoring-errors.md](reference/authoring-errors.md) | What `authorSpace` refuses or warns about, and what to write instead |
|
|
84
165
|
| [templates.md](reference/templates.md) | Any `{{ … }}` or `{% … %}`: where it runs, naming sources, filters, tests, dates |
|
|
85
|
-
| [flows.md](reference/flows.md) | Clicks, submits, page loads, server actions, modals, state |
|
|
166
|
+
| [flows.md](reference/flows.md) | Clicks, submits, page loads, server actions, realtime channels, modals, state |
|
|
167
|
+
| [plugins.md](reference/plugins.md) | A component of your own: props, binding them, writing state, channels, registering |
|
|
86
168
|
| [structure.md](reference/structure.md) | A space bigger than one screen: files, helpers, naming, keeping it short |
|
|
87
|
-
| [testing.md](reference/testing.md) |
|
|
169
|
+
| [testing.md](reference/testing.md) | Any test: `inspectPage` (one call, every problem), handles, fixtures, catching a flash from the first frame, shortcuts, counting renders |
|
|
170
|
+
| [performance.md](reference/performance.md) | A page with many elements, a busy flow, something that feels slow: what renders, what it costs, how to measure it |
|
|
88
171
|
| [templates-and-export.md](reference/templates-and-export.md) | Publishing a template; turning an exported JSON into code |
|
|
172
|
+
| [accessibility.md](reference/accessibility.md) | Icon buttons, fields, images, clickable cards, toggles, headings, landmarks, a canvas — anything a screen reader or a browser agent has to use |
|
|
89
173
|
| [review-checklist.md](reference/review-checklist.md) | Before you say it is done |
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Usable without sight
|
|
2
|
+
|
|
3
|
+
A screen reader and a browser agent (Claude in Chrome) read a page the same way. They use the **accessibility
|
|
4
|
+
tree** the browser builds from the markup, where every control is a role and a name. If a control has no name, or a
|
|
5
|
+
click sits on a box that is not a control, those readers can't find it, even though the page looks fine. The linter
|
|
6
|
+
warns about each case below (`control-without-name`, `image-without-alt`, `click-on-static-element`,
|
|
7
|
+
`heading-level-skipped`, `label-ignored`). Fix them the same way you fix any other warning.
|
|
8
|
+
|
|
9
|
+
## Controls have words
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
// Words of its own: nothing to add.
|
|
13
|
+
button({ content: 'Save' })
|
|
14
|
+
|
|
15
|
+
// An icon only: `title` is its name, and also its tooltip. The icon is hidden from the tree by itself.
|
|
16
|
+
button({ content: '', title: 'Close', children: [fontAwesome({ icon: 'fa-solid fa-xmark' })] })
|
|
17
|
+
|
|
18
|
+
// Words that do not say what it does — a key hint, a count: `label` is read in their place.
|
|
19
|
+
button({ content: '', label: 'Select (V)', title: 'Select — V', children: [fontAwesome({ icon: 'fa-solid fa-arrow-pointer' }), text('V')] })
|
|
20
|
+
|
|
21
|
+
// A link around a whole card: without `label` it is announced as every word inside the card.
|
|
22
|
+
link({ href: '/posts/field-notes', mode: 'internal', label: 'Read “Field notes”', children: [ /* cover, topic, title, byline */ ] })
|
|
23
|
+
|
|
24
|
+
// A field whose design says what it is: the label stays, out of sight.
|
|
25
|
+
formControl({ name: 'q', label: 'Search the docs', hideLabel: true, placeholder: 'Search…' })
|
|
26
|
+
formControl({ name: 'tint', subType: 'color', label: 'Highlight colour', hideLabel: true })
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
A `placeholder` names only a field you type into, and only until something is typed. A swatch, a date, a select or a
|
|
30
|
+
checkbox always needs its `label`.
|
|
31
|
+
|
|
32
|
+
## Pictures say what they show, or that they only decorate
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
image({ src: '/team.jpg', alt: 'The team at the 2026 offsite' }) // what it shows, for this page
|
|
36
|
+
image({ src: '/grain.png', decorative: true }) // an ornament: left out of the tree
|
|
37
|
+
image({ decorative: true, bind: { src: 'post.record.cover' } }) // a cover under its own headline adds nothing
|
|
38
|
+
image({ bind: { src: 'row.item.cover', alt: 'row.item.title' } }) // …unless it is alone in a link: then it names it
|
|
39
|
+
fontAwesome({ icon: 'fa-solid fa-triangle-exclamation', label: 'Overdue' }) // an icon that says something alone
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Clicks go on controls
|
|
43
|
+
|
|
44
|
+
A `button` holds children, so a clickable card is a button with the card's class:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
button({
|
|
48
|
+
content: '',
|
|
49
|
+
class: planCard,
|
|
50
|
+
children: [text('Pro', { class: planName }), text('12 € a month', { class: planPrice })],
|
|
51
|
+
flows: [[onClick(), setState({ key: 'plan', type: 'text', value: 'pro' })]]
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Never put a click on a `container`, `text`, `image` or list item. A keyboard can't reach it, and a browser agent
|
|
56
|
+
won't find it among the page's controls. A button's children are its name, so keep headings, links and other
|
|
57
|
+
buttons out of it. If a card needs any of those, make its title a link and let the rest of the card be plain
|
|
58
|
+
content. An empty backdrop that closes a panel is fine, because the panel's close button and Escape are the ways
|
|
59
|
+
everyone has.
|
|
60
|
+
|
|
61
|
+
A filter chip, a tab you built yourself, a row that selects: all of them are buttons. Remember that a class starts
|
|
62
|
+
with the SDK button look (padding, radius), so reset what you don't want.
|
|
63
|
+
|
|
64
|
+
A dropdown opens from a button too: put one among its children, outside the popup. The dropdown marks it
|
|
65
|
+
(`aria-haspopup`, `aria-expanded`) and moves the focus into the menu and back out.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
dropdown({ children: [dropdownPopup({ children: [ /* menu entries: buttons or links */ ] }), button({ content: '', title: 'Account', children: [avatar] })] })
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## State is said, not only styled
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
button({ content: 'Grid', bind: { ariaPressed: 'computed.gridOn' }, class: chip }) // a toggle
|
|
75
|
+
button({ content: 'Menu', bind: { ariaExpanded: 'state.menuOpen' }, flows: [[onClick(), toggleState({ key: 'menuOpen' })]] })
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
An "active" variant is for the eyes. `ariaPressed` and `ariaExpanded` are for everyone else. Use both.
|
|
79
|
+
|
|
80
|
+
## The page has an outline
|
|
81
|
+
|
|
82
|
+
- Headings go down one level at a time: h1, then h2, then h3. The class sets the size, so an `h2` can look small.
|
|
83
|
+
- Landmarks are containers with a tag: `header`, `nav`, `main`, `aside`, `footer`. When a page has two of one kind,
|
|
84
|
+
give each a `label`: `container({ subType: 'nav', label: 'Docs sections' })`. A `section` with a `label` becomes a
|
|
85
|
+
region an assistant can jump to.
|
|
86
|
+
|
|
87
|
+
## Illustrations
|
|
88
|
+
|
|
89
|
+
A mock of a page, a diagram, some art built from elements: `container({ decorative: true, children: [ … ] })`. The
|
|
90
|
+
readers skip it, and the linter asks nothing of what is inside, except that nothing in it can be tabbed to
|
|
91
|
+
(`control-in-decorative`). Whatever it lets a mouse do needs a real control somewhere else.
|
|
92
|
+
|
|
93
|
+
## Built in, nothing to add
|
|
94
|
+
|
|
95
|
+
Modals and dialogs (named by their title; they take the focus, keep Tab inside and close on Escape), tabs (arrow
|
|
96
|
+
keys), a field's error (marked invalid and described by its message), the pager's current page, and the theme
|
|
97
|
+
toggle's pressed option.
|
|
98
|
+
|
|
99
|
+
## A canvas or a plugin
|
|
100
|
+
|
|
101
|
+
Anything drawn on a canvas is one picture to these readers. Render what it shows as elements too: a list with a
|
|
102
|
+
named button per action. Keep that list visible when someone asks for it, because an agent clicks by position and
|
|
103
|
+
can't click something that only appears on focus. See `docs/en/accessibility.md` and Pizarra's List view.
|
|
104
|
+
|
|
105
|
+
## Checking
|
|
106
|
+
|
|
107
|
+
- `authorSpace` warnings: zero, as always.
|
|
108
|
+
- In a test, `page.getByRole('button', { name: 'Close' })` fails when the name is missing.
|
|
109
|
+
`expect(locator).toMatchAriaSnapshot(…)` pins the whole outline.
|
|
110
|
+
- Through MCP, `plitzi_screenshot` with `view: "accessibility"` returns the page's tree and lists everything that has
|
|
111
|
+
no name.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# When `authorSpace` refuses
|
|
2
|
+
|
|
3
|
+
`authorSpace` checks the whole declaration before it writes anything, and stops at the first thing that would not
|
|
4
|
+
render as written. The message names the element (`Element "text" (price) at shop/home/0/2`) and what to write
|
|
5
|
+
instead. **Do what the message says.** Never cast past a check, silence it, or move the logic into a plugin to avoid
|
|
6
|
+
it — the check exists because that declaration renders something other than what it says.
|
|
7
|
+
|
|
8
|
+
The one exception is a TEST fixture whose subject is the break itself — how the runtime copes with a document no author
|
|
9
|
+
would write. It names that break, and only that one: `authorSpace(spec, { allow: [{ code, element, why }] })`. See
|
|
10
|
+
[testing](testing.md#spaces-written-for-a-test). A space anybody visits never has an `allow`.
|
|
11
|
+
|
|
12
|
+
## Refused
|
|
13
|
+
|
|
14
|
+
| The message says | What was wrong | Write instead |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| `has "x", which it does not take` | a field the space, a page, an element, a binding or a step does not have | the field it suggests; an element's attribute goes inside the factory's props (`button({ content })`) |
|
|
17
|
+
| `sets "x", which a "button" never reads` | an attribute the element does not have | one it lists; an event (`onClick`) is a flow: `flows: [[onClick(), …]]` |
|
|
18
|
+
| `` `subType` is "h7" … It is one of `` | a value outside the attribute's list | one of the listed values |
|
|
19
|
+
| `reads it as true or false` / `as a list` | text where a flag or a list is read | `disabled: true`, not `'true'`; `items: [ … ]` |
|
|
20
|
+
| `holds none` | children on a type that renders only its own attributes | a `container` (for a heading made of parts, `container({ subType: 'h1', children })`) |
|
|
21
|
+
| `the template … cannot be read as written` | a template with an operator, filter or function that does not exist, or broken syntax | see [templates](templates.md) for what exists; `matches` never does |
|
|
22
|
+
| `reads "x", which nothing here answers to` | a name in a template that is not a source, a variable or a route param | the full source name (`list_rows.item`), `navigation.queryParams.x` for a query param, `source` for the bound value |
|
|
23
|
+
| `is not around it` | a source read by an element that is not inside the element publishing it | move the element inside it, or share the value through `state` |
|
|
24
|
+
| `lands on "x", which a "text" never reads` | a binding onto an attribute the element does not have — the value arrives and nothing shows it | one it lists (`content`); to follow data with a class, bind `styleSelectors.base` in `initialState` |
|
|
25
|
+
| `no element answers to the name "x"` | a binding whose source names an element that does not exist | the id of the element that publishes it |
|
|
26
|
+
| `renders text — and "items" holds a list` | a text template feeding a list | `bindTemplate('items', src, '{{ … }}', { returns: 'value' })` |
|
|
27
|
+
| `which a "x" never fires` / `never answers to it` | a flow on an event, or a step to an action, the element does not have | the element that fires it (the message names it); for a plugin, a name its declaration has — `declaredTrigger` / `declaredCallback` make a wrong one a compile error |
|
|
28
|
+
| `does not start with its trigger` | a flow whose first step is not the event that runs it | `[onClick(), setState(…)]` |
|
|
29
|
+
| `step "x" has the param "y"` / `"type" is "string"` | a step param that does not exist or a value outside its options | the params and values it lists |
|
|
30
|
+
| `step "setState" sets "x" on "y", which a "z" never reads` | an element `setState`/`toggleState` writing a field the element does not have | an attribute it lists, or for `category: 'state'` `visibility` / `styleSelectors.<selector>` |
|
|
31
|
+
| `names the page "x", and no page has that id` | a link or `navigate` to a page id that does not exist | an existing page id, or a path with its slash (`'/about'`) |
|
|
32
|
+
| `a full URL, in page mode` | a URL, `mailto:` or `tel:` in a link left in page mode | `mode: 'external'` |
|
|
33
|
+
| `is a controlled list with no items` | a list with nothing to render | `items: [ … ]` or `bind: { items: 'provider.data.rows' }` |
|
|
34
|
+
| `` has items, but its `source` is "none" `` | a list whose items nothing reads — it renders its children once | `source: 'controlled'` |
|
|
35
|
+
| `answers at /x for the same visitors as page` | two pages at one address | another slug — or `accessLevel` `'public'` on one and `'authenticated'` on the other |
|
|
36
|
+
| `is not one CSS value` | an empty CSS value, or one with `;` or `{}` | one value per property; leave a property out instead of writing it empty |
|
|
37
|
+
| `computed … declared after it` / `does not compute` | a computed value read before it is declared, or never declared | declare it in `computed`, above the one that reads it |
|
|
38
|
+
|
|
39
|
+
## Warned
|
|
40
|
+
|
|
41
|
+
A warning means the space renders, and renders something you probably did not mean. Fix every one.
|
|
42
|
+
|
|
43
|
+
| Code | Meaning | Fix |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| `condition-starts-visible` | a computed visibility shows until its data answers | `visible: { source, template }` |
|
|
46
|
+
| `template-never-resolved` | a condition in an attribute, used as written | move it into `bindTemplate` |
|
|
47
|
+
| `tablet-rule-skips-mobile` | a tablet rule phones never get | write it under `compact` |
|
|
48
|
+
| `default-content-beside-children` | a button prints "Button" beside its children | `content: ''` |
|
|
49
|
+
| `overlay-starts-open` | a modal or dialog is open when the page loads | `visible: false`, opened by `openModal` |
|
|
50
|
+
| `provider-without-source` | an `apiContainer` asks nothing | give it a `query` (or `action`, `connector`, `resource`) |
|
|
51
|
+
| `unknown-element-type` | a type no built-in element has | the built-in it suggests; a plugin's DECLARATION goes in `authorSpace(space, { plugins: [declaration] })`, which also checks its events, actions and attributes |
|
|
52
|
+
| `colour-without-dark` | a colour token with no dark value | `{ light, dark, default }` |
|
|
53
|
+
| `FORM_SUBMIT_UNMANAGED` | a form the browser would submit itself | `managedByInteractions: true` |
|
|
54
|
+
| `STYLE_WITHOUT_TAG` | style on a provider that renders no element | `subType: 'div'` on the provider, or style its parent |
|
|
55
|
+
| `server-data-without-rsc` | a `runtime: 'server'` provider with a `connector` or `action` in a space that does not turn server data on — it renders its mock data | `rsc: { enabled: true }` on the space |
|
|
56
|
+
| `route-param-undeclared` | `navigation.routeParams.x` read on a page whose slug has no `:x` — always empty | add `:x` to the slug, or read `navigation.queryParams.x` |
|
|
57
|
+
| `form-control-unnamed` | a control in a form with no `name` — its value never reaches `values` | `formControl({ name: 'email', … })` |
|
|
58
|
+
| `form-control-name-taken` | two controls in one form with one name — one overwrites the other | a name each |
|
|
59
|
+
| `while-running` | `whileRunning` on a step that is not the trigger, or a value other than `skip`, `parallel`, `queue` | `[whileRunning('queue', onClick()), …]` |
|
|
60
|
+
| `trigger-keys` | an `onKey` flow whose `keys` cannot fire: two keys in one shortcut, only modifiers, or a name that is not a key | `onKey('f')`, `onKey('shift+f')`, `onKey('mod+k, escape')` |
|
|
61
|
+
| `state-toggled-in-branches` | two `setState` steps of one key, each under a `when` on that key — the second flips back what the first wrote | `toggleState({ key })`; for something shown by default, a key named for hiding it |
|
|
62
|
+
| `form-value-compared-to-blank` | a `when` asking whether a submitted field (`….values.x`) `=` or `!=` `""` — a field nobody typed in is not sent, so it never matches | `operator: 'empty'` / `'notEmpty'` |
|
|
63
|
+
| `overlay-never-opened` | a modal or dialog that starts hidden and that no step opens | a flow with `openModal('id')` / `openDialog('id')` |
|
|
64
|
+
| `control-without-name` | a button or link with no words (an icon, a picture without `alt`, nothing), or a field nothing names — a screen reader says just "button", a browser agent cannot find it | `title` on the button, `label` on the link, `label` (with `hideLabel: true`) on the field — see [accessibility](accessibility.md) |
|
|
65
|
+
| `image-without-alt` | an image that is not `decorative` and has no `alt` | say what it shows, or `decorative: true` |
|
|
66
|
+
| `click-on-static-element` | a click flow on a container, text, heading, image or list item — no keyboard reaches it, no agent finds it | the flow on a `button` (it holds children) or a `link` |
|
|
67
|
+
| `heading-level-skipped` | an `h4` right after an `h2` on a page — the outline misses a level | the next level down; size it with its class |
|
|
68
|
+
| `label-ignored` | a `label` on a container whose tag is named by what it holds (`li`, a heading) | the words inside, or a landmark tag (`nav`, `section`, …) |
|
|
69
|
+
| `dropdown-without-control` | a dropdown opened from a box or an icon — no keyboard opens it | a `button` as what opens it (`title` if it is only an icon) |
|
|
70
|
+
| `control-in-decorative` | a control inside a `decorative` container — Tab lands on something nothing announces | move it out of the illustration |
|
|
@@ -3,11 +3,16 @@
|
|
|
3
3
|
## Bindings
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
|
-
heading({ bind: { content: 'posts.title' } })
|
|
7
|
-
text({ bind: [
|
|
8
|
-
|
|
6
|
+
heading({ bind: { content: 'posts.title' } }) // short form: attributes
|
|
7
|
+
text({ bind: [bindTemplate('content', 'stats.data.total', "{{ source|number_format(0, '.', ',') }} views")] })
|
|
8
|
+
container({ bind: [bindTemplate('width', 'state.xp', '{{ source / 10 }}%', { category: 'style' })] }) // a style
|
|
9
|
+
text({ bind: [{ to: 'content', source: 'posts.title', when: { … } }] }) // full form: anything else
|
|
9
10
|
```
|
|
10
11
|
|
|
12
|
+
`bindTemplate(to, source, template, { category?, returns? })` is the computed attribute — a template over the value
|
|
13
|
+
at `source`. `returns: 'value'` hands over what a single `{{ expression }}` evaluates to instead of its text: a list's
|
|
14
|
+
`items`, a number, a flag (see [lists](lists.md)).
|
|
15
|
+
|
|
11
16
|
**A source names the id you gave the element** — `'posts.title'`, `'postList.item.cover'` — and the prefix is
|
|
12
17
|
completed from what the element publishes (`apiContainer_posts`, `list_postList`; a `form` publishes under
|
|
13
18
|
`apiContainer`). A name nothing answers to, or a prefix that does not match, is refused.
|
|
@@ -17,8 +22,29 @@ The binding's `source` is completed for you; its template is read as written, so
|
|
|
17
22
|
|
|
18
23
|
An element re-renders when anything it reads changes: the first part of each binding's `source` and every source its
|
|
19
24
|
templates name. The globals are `variables`, `navigation` (`routeParams`, `queryParams`, `origin`, `currentPageId`),
|
|
20
|
-
`auth` (who is signed in, `status` while that is being found out), `state` (what flows wrote)
|
|
21
|
-
(`mode`, `resolved`).
|
|
25
|
+
`auth` (who is signed in, `status` while that is being found out), `state` (what flows wrote), `theme`
|
|
26
|
+
(`mode`, `resolved`), `host` (the `hostData` an application embedding the space hands the SDK) and `computed` (below).
|
|
27
|
+
|
|
28
|
+
## Values computed once
|
|
29
|
+
|
|
30
|
+
A value many elements show — a score, a level, a count of what the visitor picked — is declared once on the space and
|
|
31
|
+
read everywhere as `computed.<name>`, instead of repeating its expression in every binding:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
export const space = {
|
|
35
|
+
…,
|
|
36
|
+
computed: {
|
|
37
|
+
xp: '{{ (state.favourites|length) * 10 + (state.runs ?? 0) * 5 }}',
|
|
38
|
+
level: '{{ computed.xp // 100 + 1 }}'
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
text('', { bind: [bindTemplate('content', 'computed.level', 'Level {{ source }}')] })
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
A computed value reads the globals (`state`, `auth`, `navigation`, `variables`, `theme`, `host`) and the ones declared
|
|
46
|
+
above it — not an element's source: bind that on the element. One `{{ expression }}` gives its value (a number, a
|
|
47
|
+
list); anything else gives text. `authorSpace` refuses a name read before it is declared, or never.
|
|
22
48
|
|
|
23
49
|
## Providers
|
|
24
50
|
|
|
@@ -29,10 +55,21 @@ apiContainer({ id: 'board', runtime: 'server', action: 'queue-board' })
|
|
|
29
55
|
|
|
30
56
|
- A provider's source is readable by its **descendants** only. Wrap what reads it; a sibling cannot see it.
|
|
31
57
|
- It publishes `data` plus `isLoading`, `isEmpty`, `hasError`, `errorMessage` — bind a state to those, not to guesses.
|
|
58
|
+
`isEmpty` reads what arrived: a `query`'s body when it is missing, `null`, `''`, `[]` or `{}`; a connector list by
|
|
59
|
+
its `records`; a `singleRecord` provider by its `record`. It is also true before the first answer, so pair it with
|
|
60
|
+
`not isLoading` for an empty state.
|
|
32
61
|
- A query that depends on state is a binding on `query`; it answers `''` (and fetches nothing) until the state exists:
|
|
33
62
|
`"{{ source ? apiUrl ~ '/workspaces/' ~ source ~ '/stats' : '' }}"` with `source: 'state.workspace.id'`.
|
|
34
63
|
- A space with any `runtime: 'server'` provider needs `rsc: { enabled: true }`.
|
|
35
64
|
|
|
65
|
+
### Data in a project with no backend
|
|
66
|
+
|
|
67
|
+
An offline project (`offlineMode`) still never invents data: the content lives in JSON files the project serves
|
|
68
|
+
(`public/data/games.json` with Vite) and a provider reads them like any API — `apiContainer({ id: 'catalog', query:
|
|
69
|
+
'/data/games.json', cache: true, children: [ … ] })`. The page binds to `catalog.data.…` exactly as it would to a live
|
|
70
|
+
backend, so swapping the file for a real endpoint later changes one `query`. `mockData` is what the BUILDER shows while
|
|
71
|
+
editing; it is not a data source for the running page. Say on the page that demo content is demo content.
|
|
72
|
+
|
|
36
73
|
### Live, cached, refreshed
|
|
37
74
|
|
|
38
75
|
- **Live:** `refreshSeconds: 15` asks again on its own (either runtime); it pauses in a hidden tab and never
|
|
@@ -62,28 +99,28 @@ modalContainer({ visible: false, … }) // starts hidden; a flow opens
|
|
|
62
99
|
| hides it: shown unless a flag says otherwise | sidebar labels until the sidebar is folded, a banner until dismissed | a binding with no `visible`; an absent flag must leave it shown |
|
|
63
100
|
|
|
64
101
|
`visible` starts the element **hidden**, and it appears when its data says so. A revealing condition you have to
|
|
65
|
-
compute is `visible:
|
|
102
|
+
compute is `visible: { source, template }` — it starts hidden too, and the template's value is read as a yes or a
|
|
103
|
+
no: `false`, `0`, an empty text, an empty list and nothing at all are a no, anything else a yes. Write the condition
|
|
104
|
+
itself; `? 'true' : 'false'` is not needed:
|
|
66
105
|
|
|
67
106
|
```ts
|
|
68
107
|
container({
|
|
69
108
|
id: 'first-steps',
|
|
70
|
-
visible:
|
|
71
|
-
bind: [{
|
|
72
|
-
to: 'visibility',
|
|
109
|
+
visible: {
|
|
73
110
|
source: 'stats.data.totals',
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
template: "{{ source ? (source.spaces > 0 and source.published > 0 ? 'false' : 'true') : 'false' }}"
|
|
77
|
-
} }]
|
|
78
|
-
}]
|
|
111
|
+
template: '{{ source and not (source.spaces > 0 and source.published > 0) }}'
|
|
112
|
+
}
|
|
79
113
|
})
|
|
80
114
|
```
|
|
81
115
|
|
|
82
116
|
Rules for a condition's template:
|
|
83
117
|
|
|
84
|
-
- **A revealing condition starts hidden
|
|
85
|
-
screen until it answers — right for a hiding flag, a flash for a
|
|
86
|
-
`condition-starts-visible` when a computed one on a provider starts on screen.
|
|
118
|
+
- **A revealing condition starts hidden** — `visible: { source, template }` does that for you. A visibility binding
|
|
119
|
+
written by hand in `bind` keeps the element on screen until it answers — right for a hiding flag, a flash for a
|
|
120
|
+
condition on data. `authorSpace` warns `condition-starts-visible` when a computed one on a provider starts on screen.
|
|
121
|
+
- **A hidden element stays in the DOM** (`plitzi-component--hidden`), its subtree mounted. `loadStrategy: 'visible'`
|
|
122
|
+
mounts the subtree only while it is shown, and `'lazy'` from the first time it is shown (modals do that already).
|
|
123
|
+
Use it on heavy panels that are usually hidden; leave the default on what is usually on screen.
|
|
87
124
|
- **`'true'` shows, `'false'` hides, `''` writes nothing** and leaves the element as it is. Visibility hides only on
|
|
88
125
|
an explicit false.
|
|
89
126
|
- **Answer what "not known yet" should mean.** For a revealing condition that is `'false'` (`source ? … : 'false'`);
|
|
@@ -99,7 +136,7 @@ Four states, and each has its own element:
|
|
|
99
136
|
| State | How to tell |
|
|
100
137
|
| --- | --- |
|
|
101
138
|
| Loading | `apiContainer_x.isLoading` — or simply nothing: every condition is hidden until data arrives |
|
|
102
|
-
| Empty | the answer ARRIVED and is empty: `{{ source is defined and source is empty
|
|
139
|
+
| Empty | the answer ARRIVED and is empty: `{{ source is defined and source is empty }}` over the list |
|
|
103
140
|
| Error | `apiContainer_x.hasError` |
|
|
104
141
|
| Data | the list itself |
|
|
105
142
|
|
|
@@ -112,3 +149,32 @@ shows on every load. Test for "arrived and empty" (`is defined and … is empty`
|
|
|
112
149
|
Both sides of one question are `visible: 'x'` and `visible: '!x'`, not an `x` and a `notX` in the server's answer.
|
|
113
150
|
`!` reads a boolean that travelled as text (`"false"`, `"0"`) correctly. A three-state condition
|
|
114
151
|
(`Boolean(post) && !canEdit`) still belongs where the data is made.
|
|
152
|
+
|
|
153
|
+
## State that outlives a reload
|
|
154
|
+
|
|
155
|
+
`render(…, { state })` seeds `runtime.state` when the page starts — the way a host hands a space what it already knows.
|
|
156
|
+
`settings: { keepState: true, stateStorage: 'localStorage' }` keeps `runtime.state` across reloads, filed under
|
|
157
|
+
whoever is signed in (a guest's state is the browser's; another account never sees it). Keep only what a person would
|
|
158
|
+
expect back — favourites, a chosen theme. Everything else goes in `transientState`, which is never written and never
|
|
159
|
+
brought back:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
settings: { keepState: true, transientState: ['filter', 'tourStep', 'panelOpen'] }
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Do not reset kept state from `onPageLoad` instead: what was kept is restored late — after hydration, once auth knows
|
|
166
|
+
who this is — and lands in the middle of that flow, so half of it is undone. A filter restored on the next visit is a
|
|
167
|
+
page that looks broken. Keeping state is the SPACE's setting; a page does not take `keepState`.
|
|
168
|
+
|
|
169
|
+
That lateness shows. Kept state is restored after the server's first paint, so anything kept that changes what is
|
|
170
|
+
DRAWN — the tool a toolbar shows as last picked, a name in an avatar, a panel left off — is drawn with its default and
|
|
171
|
+
then swapped. List those keys in `paintedState`: they are kept in a cookie too, the server renders with them, and the
|
|
172
|
+
page starts from the same values.
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
settings: { keepState: true, paintedState: ['shapesPick', 'name', 'color'], transientState: ['panelOpen'] }
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Only what the first paint shows, and small values: the cookie travels with every request and holds a few kilobytes
|
|
179
|
+
(over that, the page falls back to the defaults and the dev tools say so). Never a secret — a key, a token — and never
|
|
180
|
+
a key that is also in `transientState` (`authorSpace` refuses it).
|
|
@@ -17,13 +17,14 @@ container([hero, grid]) // an array is the children
|
|
|
17
17
|
| `id` | the ONE name this element answers to — its key in the document, a binding's source, a step's target. Unique across the WHOLE space |
|
|
18
18
|
| `class` | a shared class: a `styles()` declaration or a name from the space's `classes`; a list for several. Exclusive with `css` |
|
|
19
19
|
| `css` | rules of this element's own: one set, or one per breakpoint (`{ desktop, tablet, mobile }`) |
|
|
20
|
-
| `states` | `hover`, `focus-visible`… beside the element's own `css` |
|
|
20
|
+
| `states` | `hover`, `focus-visible`, `hidden`… beside the element's own `css` |
|
|
21
21
|
| `variant` | a variant of the class it wears (when the class declares it) or of its type |
|
|
22
22
|
| `slots` | a class for one of the element's OTHER selectors — a form control's `input` |
|
|
23
23
|
| `bind` | where a value comes from |
|
|
24
|
-
| `visible` | a condition (`'src'`, `'!src'`), or `false` to start hidden |
|
|
24
|
+
| `visible` | a condition (`'src'`, `'!src'`, `{ source, template }`), or `false` to start hidden |
|
|
25
25
|
| `flows` | what happens on click, on submit, on load |
|
|
26
26
|
| `runtime` | `'server'` resolves this provider's data on the server |
|
|
27
|
+
| `loadStrategy` | when the contents mount relative to visibility: `eager` (default), `lazy`, `visible` |
|
|
27
28
|
| `children` | the tree |
|
|
28
29
|
| `meta` | `meta.label` is the element's name in the builder's tree |
|
|
29
30
|
|
|
@@ -31,8 +32,21 @@ Anything else in the object is an **attribute** (`authorSpace` warns `unknown-at
|
|
|
31
32
|
reads).
|
|
32
33
|
|
|
33
34
|
**A link's `href` is a page id.** `link({ href: 'reports' })` finds the page by id, folder included. `mode: 'internal'`
|
|
34
|
-
for a path within the space carrying `{{tokens}}
|
|
35
|
-
|
|
35
|
+
for a path within the space carrying `{{tokens}}` — a row's own slug (`'/games/{{ list_games.item.slug }}'`), a route
|
|
36
|
+
param, state; `mode: 'external'` for a full URL passed through untouched. A link is an `<a>`: never put a link inside
|
|
37
|
+
another link — make the card the link, or the button, not both.
|
|
38
|
+
|
|
39
|
+
**Some types hold no children.** A `heading`, `text`, `paragraph`, `image`, `formControl`… renders its own attributes
|
|
40
|
+
and nothing nested in it — `authorSpace` refuses children there. A heading made of parts (a word in another colour, an
|
|
41
|
+
icon, a badge) is a `container` with the heading's tag: `container({ subType: 'h1', class: title, children: [text('Space
|
|
42
|
+
'), text('Gamer', { class: accent })] })` — give the texts `display: inline` in their class.
|
|
43
|
+
|
|
44
|
+
**A `button` shows its `content` AND its children.** `content` defaults to "Button": a button whose words are children
|
|
45
|
+
says `content: ''` (`default-content-beside-children` warns otherwise). Its accessible name is everything it shows.
|
|
46
|
+
|
|
47
|
+
**A `formControl` works on its own.** Inside a `form` its value is the form's and it validates on submit; outside one it
|
|
48
|
+
keeps its own value and fires `onChange` with `{ value, name }` on every change — a search box or a select that
|
|
49
|
+
filters a screen needs no form. Bind `defaultValue` to the state it writes and a reset of that state reaches it.
|
|
36
50
|
|
|
37
51
|
**A `list` is a `<ul>`.** The browser indents it and puts bullets on it; a list of cards needs
|
|
38
52
|
`{ margin: '0px', padding: '0px', 'list-style-type': 'none' }` in its class.
|
|
@@ -42,7 +56,16 @@ children sit in the parent's layout and a `class` on it styles nothing (`STYLE_W
|
|
|
42
56
|
parent, or give it `attributes: { subType: 'div' }`.
|
|
43
57
|
|
|
44
58
|
A type this SDK does not ship — a plugin — is authored the same way: `defineElement<Props>({ type, … })`,
|
|
45
|
-
`element<Props>('type', props)`, or `elementsFromManifest(manifest)`.
|
|
59
|
+
`element<Props>('type', props)`, or `elementsFromManifest(manifest)`. See [plugins](plugins.md).
|
|
60
|
+
|
|
61
|
+
**What a type takes, answered by the package** instead of the 7,000-line `.d.ts`: every catalogue is exported, keyed by
|
|
62
|
+
type — `elementCatalog` (what it is for), `elementDefaultAttributes`, `elementAttributeNames`, `elementTriggers`,
|
|
63
|
+
`elementCallbacks`, `elementSlots`, `elementSourceTypes`, `elementLeafTypes`, plus `BUILTIN_TRANSFORMERS`. One line
|
|
64
|
+
prints any of them:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
node --input-type=module -e "import * as a from '@plitzi/sdk-authoring'; const t = 'formControl'; console.log({ attributes: a.elementDefaultAttributes[t], triggers: a.elementTriggers[t], slots: a.elementSlots[t] })"
|
|
68
|
+
```
|
|
46
69
|
|
|
47
70
|
## CSS
|
|
48
71
|
|
|
@@ -51,8 +74,11 @@ outside the vocabulary is refused with the key it should have been (`paddingTop`
|
|
|
51
74
|
|
|
52
75
|
- `column(gap, extra?)`, `row(gap, extra?)`, `grid(columns, gap, extra?)` for the three layouts every space repeats.
|
|
53
76
|
- **Breakpoints are ranges, not a cascade.** `tablet` is 48–64rem, `mobile` below 48rem, and each inherits only from
|
|
54
|
-
`desktop` — a rule for `tablet` never reaches a phone
|
|
55
|
-
`
|
|
77
|
+
`desktop` — a rule for `tablet` never reaches a phone (`authorSpace` warns `tablet-rule-skips-mobile`). A rule for
|
|
78
|
+
everything narrower than a desktop is `compact`, written to both: `css: { desktop: { … }, compact: { 'grid-template-columns':
|
|
79
|
+
'1fr' }, mobile: { gap: '8px' } }` — what `tablet` or `mobile` say for themselves wins.
|
|
80
|
+
- **`backdrop-filter`, `filter` and `transform` make an element the containing block of its `position: fixed`
|
|
81
|
+
children.** A glass header with a fixed phone dock inside puts the dock at the top of the header, not the screen.
|
|
56
82
|
- **Fonts are declared.** A `font-family` loads only if the space lists the face in `fonts`
|
|
57
83
|
(`{ source: 'google', family: 'Geist', fallback: 'system-ui, sans-serif', weights: [400, 600], styles: ['normal'] }`).
|
|
58
84
|
|
|
@@ -77,9 +103,37 @@ container({ class: card, children: [ … ] });
|
|
|
77
103
|
import it.
|
|
78
104
|
- `ancestors: { [sidebar.name]: { variants: { collapsed: { display: 'none' } } } }` styles an element by the state or
|
|
79
105
|
variant of an ancestor class — the way to react to a parent's hover or collapse without a flow.
|
|
106
|
+
- **`hidden` is how an element looks while its `visible` says no** — where it goes as it hides, and where it comes
|
|
107
|
+
from as it shows. Give the class a transition that includes `display` with `allow-discrete`, and a panel fades or
|
|
108
|
+
slides instead of blinking; the transition on the base is the way in, the one in `hidden` the way out:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
const panel = styles('panel', {
|
|
112
|
+
css: {
|
|
113
|
+
transition: 'opacity 220ms cubic-bezier(0.2, 0.9, 0.3, 1.2), transform 220ms cubic-bezier(0.2, 0.9, 0.3, 1.2), display 220ms allow-discrete'
|
|
114
|
+
},
|
|
115
|
+
states: {
|
|
116
|
+
hidden: {
|
|
117
|
+
opacity: '0',
|
|
118
|
+
transform: 'translateY(-6px) scale(0.98)',
|
|
119
|
+
transition: 'opacity 120ms ease-in, transform 120ms ease-in, display 120ms allow-discrete'
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
What is inside follows its container with `ancestors: { [panel.name]: { states: { hidden: { … } } } }` — a
|
|
126
|
+
different `transition-delay` per item staggers them. A container whose children mount only while it shows
|
|
127
|
+
(`loadStrategy: 'visible'`) empties before it can leave: use `'lazy'` on one that animates out.
|
|
80
128
|
- **`:hover` is part of the selector, not a rule in `customCss`.** `customCss` is for what no class can say:
|
|
81
129
|
keyframes, pseudo-elements, a rule across two unrelated elements, the inside of a third-party widget.
|
|
82
130
|
- Per element TYPE defaults go in `elements: { heading: { base, states, variants, slots } }` — a whole type at once.
|
|
131
|
+
- **Slots** are an element's other selectors: a `formControl`'s `input`, `label`, `error`; a `modalContainer`'s
|
|
132
|
+
`backgroundContainer`, `rootContainer`, `headerContainer`, `headerTitle`, `headerCloseButton`, `bodyContainer`; a
|
|
133
|
+
`dialogContainer`'s footer and buttons. `elementSlots` lists them all by type. Style one element's with `slots`, a
|
|
134
|
+
whole type's with `elements.<type>.slots`.
|
|
135
|
+
- A `modalContainer` / `dialogContainer` is fixed to the viewport, as tall as its content up to the screen, and 500px
|
|
136
|
+
wide up to the screen; theme its look through its slots.
|
|
83
137
|
|
|
84
138
|
**A variant chosen by the data:** `variantFrom(pill, 'jobs.item.status')`, or with a template when the data does not
|
|
85
139
|
already speak in variant names — `variantFrom(pill, 'runs.item.status', { template: "{{ source == 'completed' ?
|