@mistriwork/engine 0.0.0-stage → 1.0.0-rc.9
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/LICENSE +21 -0
- package/README.md +17 -2
- package/dist/adapters/adapter.d.ts +693 -0
- package/dist/adapters/adapter.js +444 -0
- package/dist/adapters/cache/cacheStore.d.ts +75 -0
- package/dist/adapters/cache/cacheStore.js +160 -0
- package/dist/adapters/cache/resp.d.ts +52 -0
- package/dist/adapters/cache/resp.js +237 -0
- package/dist/adapters/commands/canonicalJson.d.ts +19 -0
- package/dist/adapters/commands/canonicalJson.js +154 -0
- package/dist/adapters/connect.d.ts +29 -0
- package/dist/adapters/connect.js +40 -0
- package/dist/adapters/connectionBudget.d.ts +101 -0
- package/dist/adapters/connectionBudget.js +128 -0
- package/dist/adapters/dbLatency.d.ts +35 -0
- package/dist/adapters/dbLatency.js +79 -0
- package/dist/adapters/epochCache.d.ts +50 -0
- package/dist/adapters/epochCache.js +85 -0
- package/dist/adapters/http/egress.d.ts +48 -0
- package/dist/adapters/http/egress.js +175 -0
- package/dist/adapters/http/httpAdapter.d.ts +71 -0
- package/dist/adapters/http/httpAdapter.js +512 -0
- package/dist/adapters/http/httpDescription.d.ts +32 -0
- package/dist/adapters/http/httpDescription.js +246 -0
- package/dist/adapters/http/openapi.d.ts +98 -0
- package/dist/adapters/http/openapi.js +487 -0
- package/dist/adapters/layoutRefusal.d.ts +24 -0
- package/dist/adapters/layoutRefusal.js +35 -0
- package/dist/adapters/net/networkPolicy.d.ts +44 -0
- package/dist/adapters/net/networkPolicy.js +123 -0
- package/dist/adapters/net/untrustedDatabase.d.ts +18 -0
- package/dist/adapters/net/untrustedDatabase.js +120 -0
- package/dist/adapters/poolAdmission.d.ts +43 -0
- package/dist/adapters/poolAdmission.js +184 -0
- package/dist/adapters/poolMetrics.d.ts +121 -0
- package/dist/adapters/poolMetrics.js +185 -0
- package/dist/adapters/postgres.d.ts +436 -0
- package/dist/adapters/postgres.js +1890 -0
- package/dist/adapters/postgresCatalog.d.ts +96 -0
- package/dist/adapters/postgresCatalog.js +387 -0
- package/dist/adapters/postgresCatalogDigest.d.ts +30 -0
- package/dist/adapters/postgresCatalogDigest.js +87 -0
- package/dist/adapters/postgresCatalogRows.d.ts +62 -0
- package/dist/adapters/postgresCatalogRows.js +37 -0
- package/dist/adapters/postgresDates.d.ts +1 -0
- package/dist/adapters/postgresDates.js +39 -0
- package/dist/adapters/postgresMaintenance.d.ts +33 -0
- package/dist/adapters/postgresMaintenance.js +179 -0
- package/dist/adapters/postgresPool.d.ts +255 -0
- package/dist/adapters/postgresPool.js +838 -0
- package/dist/adapters/postgresPooler.d.ts +28 -0
- package/dist/adapters/postgresPooler.js +267 -0
- package/dist/adapters/postgresReadCatalog.d.ts +31 -0
- package/dist/adapters/postgresReadCatalog.js +226 -0
- package/dist/adapters/postgresReadSnapshot.d.ts +46 -0
- package/dist/adapters/postgresReadSnapshot.js +133 -0
- package/dist/adapters/postgresSchemaEpoch.d.ts +24 -0
- package/dist/adapters/postgresSchemaEpoch.js +149 -0
- package/dist/adapters/postgresTransactionHandle.d.ts +173 -0
- package/dist/adapters/postgresTransactionHandle.js +524 -0
- package/dist/adapters/postgresValues.d.ts +30 -0
- package/dist/adapters/postgresValues.js +75 -0
- package/dist/adapters/replicas.d.ts +70 -0
- package/dist/adapters/replicas.js +208 -0
- package/dist/adapters/reservedSql.d.ts +38 -0
- package/dist/adapters/reservedSql.js +264 -0
- package/dist/adapters/rolePrivileges.d.ts +17 -0
- package/dist/adapters/rolePrivileges.js +22 -0
- package/dist/adapters/scoped/keyRows.d.ts +65 -0
- package/dist/adapters/scoped/keyRows.js +173 -0
- package/dist/adapters/scoped/postgresScopeCatalog.d.ts +16 -0
- package/dist/adapters/scoped/postgresScopeCatalog.js +117 -0
- package/dist/adapters/scoped/scopedCommandOperations.d.ts +5 -0
- package/dist/adapters/scoped/scopedCommandOperations.js +48 -0
- package/dist/adapters/scoped/scopedReads.d.ts +20 -0
- package/dist/adapters/scoped/scopedReads.js +174 -0
- package/dist/adapters/scoped/scopedTransaction.d.ts +89 -0
- package/dist/adapters/scoped/scopedTransaction.js +303 -0
- package/dist/adapters/scoped/sqliteScopeCatalog.d.ts +16 -0
- package/dist/adapters/scoped/sqliteScopeCatalog.js +241 -0
- package/dist/adapters/scoped/updateGuard.d.ts +3 -0
- package/dist/adapters/scoped/updateGuard.js +40 -0
- package/dist/adapters/serverSettings.d.ts +45 -0
- package/dist/adapters/serverSettings.js +46 -0
- package/dist/adapters/sqlite.d.ts +262 -0
- package/dist/adapters/sqlite.js +1434 -0
- package/dist/adapters/sqliteCostlyReads.d.ts +33 -0
- package/dist/adapters/sqliteCostlyReads.js +49 -0
- package/dist/adapters/sqliteDecimals.d.ts +22 -0
- package/dist/adapters/sqliteDecimals.js +57 -0
- package/dist/adapters/sqliteHandles.d.ts +43 -0
- package/dist/adapters/sqliteHandles.js +143 -0
- package/dist/adapters/sqliteStatementProcess.d.ts +29 -0
- package/dist/adapters/sqliteStatementProcess.js +131 -0
- package/dist/adapters/statementControl.d.ts +34 -0
- package/dist/adapters/statementControl.js +90 -0
- package/dist/adapters/storeHost.d.ts +130 -0
- package/dist/adapters/storeHost.js +13 -0
- package/dist/adapters/telemetry/drivers.d.ts +21 -0
- package/dist/adapters/telemetry/drivers.js +321 -0
- package/dist/adapters/telemetry/metrics.d.ts +63 -0
- package/dist/adapters/telemetry/metrics.js +122 -0
- package/dist/adapters/telemetry/otlp.d.ts +12 -0
- package/dist/adapters/telemetry/otlp.js +320 -0
- package/dist/adapters/telemetry/otlpEncode.d.ts +48 -0
- package/dist/adapters/telemetry/otlpEncode.js +366 -0
- package/dist/adapters/telemetry/otlpEnv.d.ts +1 -0
- package/dist/adapters/telemetry/otlpEnv.js +12 -0
- package/dist/adapters/telemetry/serverTiming.d.ts +83 -0
- package/dist/adapters/telemetry/serverTiming.js +503 -0
- package/dist/adapters/telemetry/start.d.ts +10 -0
- package/dist/adapters/telemetry/start.js +25 -0
- package/dist/adapters/telemetry/statement.d.ts +16 -0
- package/dist/adapters/telemetry/statement.js +134 -0
- package/dist/adapters/telemetry/trace.d.ts +127 -0
- package/dist/adapters/telemetry/trace.js +295 -0
- package/dist/adapters/writeAhead.d.ts +27 -0
- package/dist/adapters/writeAhead.js +46 -0
- package/dist/adapters/writeAttribution.d.ts +18 -0
- package/dist/adapters/writeAttribution.js +23 -0
- package/dist/adapters/writeCapture.d.ts +73 -0
- package/dist/adapters/writeCapture.js +20 -0
- package/dist/adapters/writeLanes.d.ts +94 -0
- package/dist/adapters/writeLanes.js +232 -0
- package/dist/engine.d.ts +15 -0
- package/dist/engine.js +13 -0
- package/dist/server/auth/credentials.d.ts +61 -0
- package/dist/server/auth/credentials.js +260 -0
- package/dist/server/cacheService.d.ts +50 -0
- package/dist/server/cacheService.js +110 -0
- package/dist/server/cli/args.d.ts +176 -0
- package/dist/server/cli/args.js +342 -0
- package/dist/server/cli/bin.d.ts +5 -0
- package/dist/server/cli/bin.js +66 -0
- package/dist/server/cli/browser.d.ts +15 -0
- package/dist/server/cli/browser.js +37 -0
- package/dist/server/cli/config.d.ts +50 -0
- package/dist/server/cli/config.js +206 -0
- package/dist/server/cli/startup.d.ts +36 -0
- package/dist/server/cli/startup.js +128 -0
- package/dist/server/cli/startupOptions.d.ts +63 -0
- package/dist/server/cli/startupOptions.js +143 -0
- package/dist/server/cli/subcommand.d.ts +28 -0
- package/dist/server/cli/subcommand.js +32 -0
- package/dist/server/cli/usage.d.ts +13 -0
- package/dist/server/cli/usage.js +31 -0
- package/dist/server/clientHttp.d.ts +184 -0
- package/dist/server/clientHttp.js +700 -0
- package/dist/server/clientSurface.d.ts +128 -0
- package/dist/server/clientSurface.js +182 -0
- package/dist/server/columnGrants.d.ts +103 -0
- package/dist/server/columnGrants.js +730 -0
- package/dist/server/compileCache.d.ts +1 -0
- package/dist/server/compileCache.js +30 -0
- package/dist/server/config/locations.d.ts +1 -0
- package/dist/server/config/locations.js +15 -0
- package/dist/server/configPaths.d.ts +7 -0
- package/dist/server/configPaths.js +34 -0
- package/dist/server/configSections.d.ts +64 -0
- package/dist/server/configSections.js +72 -0
- package/dist/server/connectionRedaction.d.ts +30 -0
- package/dist/server/connectionRedaction.js +77 -0
- package/dist/server/connections.d.ts +353 -0
- package/dist/server/connections.js +978 -0
- package/dist/server/constraints.d.ts +82 -0
- package/dist/server/constraints.js +169 -0
- package/dist/server/demo.d.ts +9 -0
- package/dist/server/demo.js +554 -0
- package/dist/server/devclient.d.ts +108 -0
- package/dist/server/devclient.js +361 -0
- package/dist/server/evidence.d.ts +55 -0
- package/dist/server/evidence.js +323 -0
- package/dist/server/hiddenColumns.d.ts +32 -0
- package/dist/server/hiddenColumns.js +238 -0
- package/dist/server/http/baselineHeaders.d.ts +24 -0
- package/dist/server/http/baselineHeaders.js +12 -0
- package/dist/server/http/captured.d.ts +35 -0
- package/dist/server/http/captured.js +73 -0
- package/dist/server/http/clock.d.ts +1 -0
- package/dist/server/http/clock.js +14 -0
- package/dist/server/http/connectionSource.d.ts +4 -0
- package/dist/server/http/connectionSource.js +48 -0
- package/dist/server/http/context.d.ts +189 -0
- package/dist/server/http/context.js +11 -0
- package/dist/server/http/crawlerText.d.ts +15 -0
- package/dist/server/http/crawlerText.js +32 -0
- package/dist/server/http/dataRoutes.d.ts +88 -0
- package/dist/server/http/dataRoutes.js +137 -0
- package/dist/server/http/door.d.ts +57 -0
- package/dist/server/http/door.js +39 -0
- package/dist/server/http/exportRoute.d.ts +15 -0
- package/dist/server/http/exportRoute.js +320 -0
- package/dist/server/http/firstPaint.d.ts +108 -0
- package/dist/server/http/firstPaint.js +262 -0
- package/dist/server/http/httpOperationRoutes.d.ts +11 -0
- package/dist/server/http/httpOperationRoutes.js +50 -0
- package/dist/server/http/pageReads.d.ts +20 -0
- package/dist/server/http/pageReads.js +205 -0
- package/dist/server/http/pageRoutes.d.ts +39 -0
- package/dist/server/http/pageRoutes.js +291 -0
- package/dist/server/http/probes.d.ts +22 -0
- package/dist/server/http/probes.js +27 -0
- package/dist/server/http/rateLimit.d.ts +131 -0
- package/dist/server/http/rateLimit.js +351 -0
- package/dist/server/http/readRoutes.d.ts +9 -0
- package/dist/server/http/readRoutes.js +131 -0
- package/dist/server/http/readRouting.d.ts +20 -0
- package/dist/server/http/readRouting.js +80 -0
- package/dist/server/http/recordCompose.d.ts +67 -0
- package/dist/server/http/recordCompose.js +167 -0
- package/dist/server/http/recordWrites.d.ts +89 -0
- package/dist/server/http/recordWrites.js +298 -0
- package/dist/server/http/refusalReport.d.ts +38 -0
- package/dist/server/http/refusalReport.js +63 -0
- package/dist/server/http/reply.d.ts +130 -0
- package/dist/server/http/reply.js +394 -0
- package/dist/server/http/requestOrigin.d.ts +11 -0
- package/dist/server/http/requestOrigin.js +8 -0
- package/dist/server/http/router.d.ts +45 -0
- package/dist/server/http/router.js +22 -0
- package/dist/server/http/serverCore.d.ts +68 -0
- package/dist/server/http/serverCore.js +177 -0
- package/dist/server/http/traceRequest.d.ts +30 -0
- package/dist/server/http/traceRequest.js +224 -0
- package/dist/server/http/trust.d.ts +261 -0
- package/dist/server/http/trust.js +472 -0
- package/dist/server/http/writeRefusal.d.ts +33 -0
- package/dist/server/http/writeRefusal.js +24 -0
- package/dist/server/httpConnection.d.ts +49 -0
- package/dist/server/httpConnection.js +170 -0
- package/dist/server/impact.d.ts +55 -0
- package/dist/server/impact.js +47 -0
- package/dist/server/journal.d.ts +134 -0
- package/dist/server/journal.js +398 -0
- package/dist/server/keysetReadHttp.d.ts +52 -0
- package/dist/server/keysetReadHttp.js +85 -0
- package/dist/server/layouts.d.ts +49 -0
- package/dist/server/layouts.js +192 -0
- package/dist/server/migrate/cli.d.ts +55 -0
- package/dist/server/migrate/cli.js +517 -0
- package/dist/server/migrate/database.d.ts +37 -0
- package/dist/server/migrate/database.js +247 -0
- package/dist/server/migrate/desired.d.ts +76 -0
- package/dist/server/migrate/desired.js +292 -0
- package/dist/server/migrate/files.d.ts +34 -0
- package/dist/server/migrate/files.js +141 -0
- package/dist/server/migrate/flyway.d.ts +7 -0
- package/dist/server/migrate/flyway.js +69 -0
- package/dist/server/migrate/history.d.ts +70 -0
- package/dist/server/migrate/history.js +211 -0
- package/dist/server/migrate/runner.d.ts +100 -0
- package/dist/server/migrate/runner.js +409 -0
- package/dist/server/migrate/snapshot.d.ts +30 -0
- package/dist/server/migrate/snapshot.js +239 -0
- package/dist/server/migrate/target.d.ts +33 -0
- package/dist/server/migrate/target.js +82 -0
- package/dist/server/openapiDraft.d.ts +25 -0
- package/dist/server/openapiDraft.js +171 -0
- package/dist/server/operations/diagnostics.d.ts +101 -0
- package/dist/server/operations/diagnostics.js +284 -0
- package/dist/server/operations/requestMetrics.d.ts +33 -0
- package/dist/server/operations/requestMetrics.js +94 -0
- package/dist/server/packageVersion.d.ts +1 -0
- package/dist/server/packageVersion.js +11 -0
- package/dist/server/poolConfig.d.ts +34 -0
- package/dist/server/poolConfig.js +114 -0
- package/dist/server/product.d.ts +20 -0
- package/dist/server/product.js +12 -0
- package/dist/server/recordHistoryOperations.d.ts +39 -0
- package/dist/server/recordHistoryOperations.js +43 -0
- package/dist/server/recordWriteOperations.d.ts +111 -0
- package/dist/server/recordWriteOperations.js +329 -0
- package/dist/server/resourceReadHttp.d.ts +81 -0
- package/dist/server/resourceReadHttp.js +606 -0
- package/dist/server/resourceReadOperations.d.ts +147 -0
- package/dist/server/resourceReadOperations.js +452 -0
- package/dist/server/scaffoldMachine.d.ts +54 -0
- package/dist/server/scaffoldMachine.js +184 -0
- package/dist/server/seed/cli.d.ts +20 -0
- package/dist/server/seed/cli.js +164 -0
- package/dist/server/seed/lexicon.d.ts +26 -0
- package/dist/server/seed/lexicon.js +585 -0
- package/dist/server/seed/seed.d.ts +70 -0
- package/dist/server/seed/seed.js +779 -0
- package/dist/server/seed/words.d.ts +21 -0
- package/dist/server/seed/words.js +164 -0
- package/dist/server/serverRequests.d.ts +6 -0
- package/dist/server/serverRequests.js +33 -0
- package/dist/server/serverTimeouts.d.ts +33 -0
- package/dist/server/serverTimeouts.js +32 -0
- package/dist/server/shapeonly.d.ts +32 -0
- package/dist/server/shapeonly.js +110 -0
- package/dist/server/startupWarnings.d.ts +32 -0
- package/dist/server/startupWarnings.js +92 -0
- package/dist/shared/addressParams.d.ts +28 -0
- package/dist/shared/addressParams.js +47 -0
- package/dist/shared/appCards.d.ts +123 -0
- package/dist/shared/appCards.js +385 -0
- package/dist/shared/appComponents.d.ts +177 -0
- package/dist/shared/appComponents.js +422 -0
- package/dist/shared/appDocuments.d.ts +43 -0
- package/dist/shared/appDocuments.js +131 -0
- package/dist/shared/appEmbeds.d.ts +21 -0
- package/dist/shared/appEmbeds.js +24 -0
- package/dist/shared/appIcons.d.ts +28 -0
- package/dist/shared/appIcons.js +223 -0
- package/dist/shared/appLinks.d.ts +17 -0
- package/dist/shared/appLinks.js +124 -0
- package/dist/shared/appListColumns.d.ts +3 -0
- package/dist/shared/appListColumns.js +66 -0
- package/dist/shared/appLocale.d.ts +36 -0
- package/dist/shared/appLocale.js +86 -0
- package/dist/shared/appMessages.d.ts +44 -0
- package/dist/shared/appMessages.js +78 -0
- package/dist/shared/appNoun.d.ts +21 -0
- package/dist/shared/appNoun.js +32 -0
- package/dist/shared/autoBucket.d.ts +98 -0
- package/dist/shared/autoBucket.js +305 -0
- package/dist/shared/autoValues.d.ts +25 -0
- package/dist/shared/autoValues.js +25 -0
- package/dist/shared/breakdown.d.ts +228 -0
- package/dist/shared/breakdown.js +557 -0
- package/dist/shared/breakdowntext.d.ts +41 -0
- package/dist/shared/breakdowntext.js +400 -0
- package/dist/shared/builtInThemes.d.ts +2 -0
- package/dist/shared/builtInThemes.js +771 -0
- package/dist/shared/chartSeparation.d.ts +11 -0
- package/dist/shared/chartSeparation.js +58 -0
- package/dist/shared/clauses.d.ts +22 -0
- package/dist/shared/clauses.js +97 -0
- package/dist/shared/columnCaption.d.ts +16 -0
- package/dist/shared/columnCaption.js +90 -0
- package/dist/shared/commandFieldPath.d.ts +2 -0
- package/dist/shared/commandFieldPath.js +7 -0
- package/dist/shared/commandWire.d.ts +26 -0
- package/dist/shared/commandWire.js +41 -0
- package/dist/shared/computed.d.ts +161 -0
- package/dist/shared/computed.js +378 -0
- package/dist/shared/computedParse.d.ts +13 -0
- package/dist/shared/computedParse.js +278 -0
- package/dist/shared/computedValue.d.ts +47 -0
- package/dist/shared/computedValue.js +191 -0
- package/dist/shared/connection.d.ts +30 -0
- package/dist/shared/connection.js +1 -0
- package/dist/shared/csv.d.ts +20 -0
- package/dist/shared/csv.js +63 -0
- package/dist/shared/dashLayout.d.ts +30 -0
- package/dist/shared/dashLayout.js +132 -0
- package/dist/shared/dashboardAsks.d.ts +93 -0
- package/dist/shared/dashboardAsks.js +129 -0
- package/dist/shared/dashboardTrend.d.ts +20 -0
- package/dist/shared/dashboardTrend.js +50 -0
- package/dist/shared/declaredReferences.d.ts +50 -0
- package/dist/shared/declaredReferences.js +202 -0
- package/dist/shared/definedFields.d.ts +63 -0
- package/dist/shared/definedFields.js +103 -0
- package/dist/shared/definedValues.d.ts +63 -0
- package/dist/shared/definedValues.js +184 -0
- package/dist/shared/displayDefaults.d.ts +135 -0
- package/dist/shared/displayDefaults.js +529 -0
- package/dist/shared/dueDate.d.ts +6 -0
- package/dist/shared/dueDate.js +6 -0
- package/dist/shared/duration.d.ts +4 -0
- package/dist/shared/duration.js +23 -0
- package/dist/shared/fileBounds.d.ts +9 -0
- package/dist/shared/fileBounds.js +9 -0
- package/dist/shared/filter.d.ts +217 -0
- package/dist/shared/filter.js +305 -0
- package/dist/shared/firstPaint.d.ts +24 -0
- package/dist/shared/firstPaint.js +33 -0
- package/dist/shared/form.d.ts +21 -0
- package/dist/shared/form.js +25 -0
- package/dist/shared/formValues.d.ts +104 -0
- package/dist/shared/formValues.js +352 -0
- package/dist/shared/funnel.d.ts +45 -0
- package/dist/shared/funnel.js +108 -0
- package/dist/shared/httpOperations.d.ts +48 -0
- package/dist/shared/httpOperations.js +9 -0
- package/dist/shared/iconGlyphs.d.ts +4 -0
- package/dist/shared/iconGlyphs.js +151 -0
- package/dist/shared/iconWeights.d.ts +3 -0
- package/dist/shared/iconWeights.js +162 -0
- package/dist/shared/imageWidths.d.ts +32 -0
- package/dist/shared/imageWidths.js +38 -0
- package/dist/shared/keysetPage.d.ts +62 -0
- package/dist/shared/keysetPage.js +159 -0
- package/dist/shared/like.d.ts +20 -0
- package/dist/shared/like.js +29 -0
- package/dist/shared/listEmpty.d.ts +12 -0
- package/dist/shared/listEmpty.js +36 -0
- package/dist/shared/listFilters.d.ts +28 -0
- package/dist/shared/listFilters.js +84 -0
- package/dist/shared/listPresentations.d.ts +199 -0
- package/dist/shared/listPresentations.js +7 -0
- package/dist/shared/listQuestion.d.ts +52 -0
- package/dist/shared/listQuestion.js +137 -0
- package/dist/shared/lookPalettes.d.ts +62 -0
- package/dist/shared/lookPalettes.js +306 -0
- package/dist/shared/maskQuoted.d.ts +24 -0
- package/dist/shared/maskQuoted.js +48 -0
- package/dist/shared/mentionLabel.d.ts +19 -0
- package/dist/shared/mentionLabel.js +38 -0
- package/dist/shared/mentionLookup.d.ts +17 -0
- package/dist/shared/mentionLookup.js +64 -0
- package/dist/shared/menuCounts.d.ts +16 -0
- package/dist/shared/menuCounts.js +74 -0
- package/dist/shared/model.d.ts +79 -0
- package/dist/shared/model.js +153 -0
- package/dist/shared/money.d.ts +44 -0
- package/dist/shared/money.js +81 -0
- package/dist/shared/moneyEntry.d.ts +7 -0
- package/dist/shared/moneyEntry.js +27 -0
- package/dist/shared/mount.d.ts +118 -0
- package/dist/shared/mount.js +248 -0
- package/dist/shared/niceTicks.d.ts +26 -0
- package/dist/shared/niceTicks.js +50 -0
- package/dist/shared/order.d.ts +59 -0
- package/dist/shared/order.js +133 -0
- package/dist/shared/page.d.ts +135 -0
- package/dist/shared/page.js +1187 -0
- package/dist/shared/pageAnatomy.d.ts +99 -0
- package/dist/shared/pageAnatomy.js +370 -0
- package/dist/shared/pageConfig.d.ts +25 -0
- package/dist/shared/pageConfig.js +1308 -0
- package/dist/shared/pageEmptyActions.d.ts +12 -0
- package/dist/shared/pageEmptyActions.js +20 -0
- package/dist/shared/pageLayouts.d.ts +72 -0
- package/dist/shared/pageLayouts.js +205 -0
- package/dist/shared/pageModel.d.ts +785 -0
- package/dist/shared/pageModel.js +121 -0
- package/dist/shared/pagePaths.d.ts +180 -0
- package/dist/shared/pagePaths.js +869 -0
- package/dist/shared/pagePlans.d.ts +52 -0
- package/dist/shared/pagePlans.js +108 -0
- package/dist/shared/pageRoutes.d.ts +9 -0
- package/dist/shared/pageRoutes.js +9 -0
- package/dist/shared/query.d.ts +166 -0
- package/dist/shared/query.js +1302 -0
- package/dist/shared/queryDates.d.ts +12 -0
- package/dist/shared/queryDates.js +101 -0
- package/dist/shared/queryOperands.d.ts +99 -0
- package/dist/shared/queryOperands.js +131 -0
- package/dist/shared/reads.d.ts +103 -0
- package/dist/shared/reads.js +154 -0
- package/dist/shared/recordLabel.d.ts +51 -0
- package/dist/shared/recordLabel.js +123 -0
- package/dist/shared/recordReadKeys.d.ts +30 -0
- package/dist/shared/recordReadKeys.js +51 -0
- package/dist/shared/recordReads.d.ts +268 -0
- package/dist/shared/recordReads.js +368 -0
- package/dist/shared/recordview.d.ts +198 -0
- package/dist/shared/recordview.js +356 -0
- package/dist/shared/referenceModel.d.ts +58 -0
- package/dist/shared/referenceModel.js +194 -0
- package/dist/shared/refusal.d.ts +64 -0
- package/dist/shared/refusal.js +144 -0
- package/dist/shared/relatedCreate.d.ts +30 -0
- package/dist/shared/relatedCreate.js +162 -0
- package/dist/shared/resourceHttp.d.ts +464 -0
- package/dist/shared/resourceHttp.js +1 -0
- package/dist/shared/richText.d.ts +85 -0
- package/dist/shared/richText.js +305 -0
- package/dist/shared/rollups.d.ts +143 -0
- package/dist/shared/rollups.js +64 -0
- package/dist/shared/rowScope.d.ts +72 -0
- package/dist/shared/rowScope.js +376 -0
- package/dist/shared/runtimeTables.d.ts +44 -0
- package/dist/shared/runtimeTables.js +79 -0
- package/dist/shared/schema.d.ts +523 -0
- package/dist/shared/schema.js +627 -0
- package/dist/shared/schematext.d.ts +38 -0
- package/dist/shared/schematext.js +101 -0
- package/dist/shared/scopeCatalog.d.ts +29 -0
- package/dist/shared/scopeCatalog.js +468 -0
- package/dist/shared/servedApp.d.ts +681 -0
- package/dist/shared/servedApp.js +45 -0
- package/dist/shared/sessionLease.d.ts +34 -0
- package/dist/shared/sessionLease.js +3 -0
- package/dist/shared/shape.d.ts +55 -0
- package/dist/shared/shape.js +168 -0
- package/dist/shared/slug.d.ts +1 -0
- package/dist/shared/slug.js +12 -0
- package/dist/shared/sortText.d.ts +14 -0
- package/dist/shared/sortText.js +26 -0
- package/dist/shared/sqlRelation.d.ts +4 -0
- package/dist/shared/sqlRelation.js +8 -0
- package/dist/shared/sqlsaved.d.ts +44 -0
- package/dist/shared/sqlsaved.js +63 -0
- package/dist/shared/stitchedPaths.d.ts +27 -0
- package/dist/shared/stitchedPaths.js +38 -0
- package/dist/shared/structuredFilter.d.ts +22 -0
- package/dist/shared/structuredFilter.js +1 -0
- package/dist/shared/subtitlePath.d.ts +20 -0
- package/dist/shared/subtitlePath.js +24 -0
- package/dist/shared/suggest.d.ts +18 -0
- package/dist/shared/suggest.js +59 -0
- package/dist/shared/tableSizing.d.ts +4 -0
- package/dist/shared/tableSizing.js +9 -0
- package/dist/shared/tagLists.d.ts +57 -0
- package/dist/shared/tagLists.js +34 -0
- package/dist/shared/textRules.d.ts +54 -0
- package/dist/shared/textRules.js +133 -0
- package/dist/shared/theme.d.ts +94 -0
- package/dist/shared/theme.js +800 -0
- package/dist/shared/themeColor.d.ts +34 -0
- package/dist/shared/themeColor.js +109 -0
- package/dist/shared/themeFaces.d.ts +30 -0
- package/dist/shared/themeFaces.js +42 -0
- package/dist/shared/themeInk.d.ts +20 -0
- package/dist/shared/themeInk.js +52 -0
- package/dist/shared/themeLooks.d.ts +9 -0
- package/dist/shared/themeLooks.js +572 -0
- package/dist/shared/themeParts.d.ts +26 -0
- package/dist/shared/themeParts.js +71 -0
- package/dist/shared/themePartsCheck.d.ts +21 -0
- package/dist/shared/themePartsCheck.js +91 -0
- package/dist/shared/themePresets.d.ts +85 -0
- package/dist/shared/themePresets.js +139 -0
- package/dist/shared/themeRecipeWords.d.ts +156 -0
- package/dist/shared/themeRecipeWords.js +156 -0
- package/dist/shared/themeRecipes.d.ts +213 -0
- package/dist/shared/themeRecipes.js +234 -0
- package/dist/shared/themeResolve.d.ts +55 -0
- package/dist/shared/themeResolve.js +52 -0
- package/dist/shared/themeTokens.d.ts +107 -0
- package/dist/shared/themeTokens.js +116 -0
- package/dist/shared/themeType.d.ts +38 -0
- package/dist/shared/themeType.js +205 -0
- package/dist/shared/unitWord.d.ts +4 -0
- package/dist/shared/unitWord.js +37 -0
- package/dist/shared/view.d.ts +195 -0
- package/dist/shared/view.js +987 -0
- package/dist/shared/viewLabels.d.ts +47 -0
- package/dist/shared/viewLabels.js +459 -0
- package/dist/shared/viewPaths.d.ts +248 -0
- package/dist/shared/viewPaths.js +294 -0
- package/dist/shared/viewtext.d.ts +57 -0
- package/dist/shared/viewtext.js +282 -0
- package/dist/shared/vocabulary.d.ts +71 -0
- package/dist/shared/vocabulary.js +249 -0
- package/dist/shared/weekStart.d.ts +7 -0
- package/dist/shared/weekStart.js +36 -0
- package/mark.svg +1 -0
- package/package.json +261 -4
- package/schema/model.d.ts +298 -0
- package/schema/theme.d.ts +587 -0
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
import { effectiveThemeDefinition, effectiveThemePalette, parseScheme, THEME_PRESETS, themeModes, withInks, } from './themePresets.js';
|
|
2
|
+
import { colorHex, diagnosticColor, HEAT_TOP, heatTop, measuredContrast, mixColor, over } from './themeColor.js';
|
|
3
|
+
import { DENSITIES, ELEVATIONS, LIFTS, MOTIONS, RECIPE_MENU, RECIPES_RENAMED, RHYTHMS } from './themeRecipes.js';
|
|
4
|
+
import { families } from './themeFaces.js';
|
|
5
|
+
import { iconsOf, partsOf, pathRole, patternStrength, surfaceOf } from './themePartsCheck.js';
|
|
6
|
+
import { TYPE_MOVED, typeOf } from './themeType.js';
|
|
7
|
+
import { BORDER_MENU, CONTROL_HEIGHT, controlHeightFits, cornerFits, FONT_TOKENS, PALETTE_TOKENS, RADIUS_MAX, RADIUS_TOKENS, rolePath, ROUND, ROUND_CORNERS, THEME_BASES, } from './themeTokens.js';
|
|
8
|
+
export { FONT_TOKENS, PALETTE_TOKENS, RADIUS_TOKENS, shapeTokens, THEME_BASES } from './themeTokens.js';
|
|
9
|
+
function object(raw, where) {
|
|
10
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
11
|
+
throw new Error(`${where} must be an object.`);
|
|
12
|
+
return raw;
|
|
13
|
+
}
|
|
14
|
+
function strings(raw, keys, valid, where) {
|
|
15
|
+
const out = {};
|
|
16
|
+
for (const [key, value] of Object.entries(object(raw, where))) {
|
|
17
|
+
if (!keys.includes(key))
|
|
18
|
+
throw new Error(`${where} has no "${key}". It takes: ${keys.join(', ')}.`);
|
|
19
|
+
if (typeof value !== 'string' || !valid(value.trim()))
|
|
20
|
+
throw new Error(`${where}.${key} has an invalid value.`);
|
|
21
|
+
out[key] = value.trim();
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
/** The roles' names before they were the spec's own (0.9.2), each to the one that replaced it. */
|
|
26
|
+
const OLD_ROLES = {
|
|
27
|
+
background: 'ground',
|
|
28
|
+
subtle: 'raised',
|
|
29
|
+
border: 'line',
|
|
30
|
+
borderStrong: 'lineStrong',
|
|
31
|
+
borderControl: 'lineControl',
|
|
32
|
+
text: 'ink',
|
|
33
|
+
textMuted: 'inkMuted',
|
|
34
|
+
textSubtle: 'inkSubtle',
|
|
35
|
+
positive: 'statusGood',
|
|
36
|
+
warning: 'statusWarning',
|
|
37
|
+
negative: 'statusCritical',
|
|
38
|
+
key: 'statusInfo',
|
|
39
|
+
navSurface: 'navGround',
|
|
40
|
+
navText: 'navInk',
|
|
41
|
+
navBorder: 'navLine',
|
|
42
|
+
navCurrentText: 'navOnCurrent',
|
|
43
|
+
navCurrentInk: 'navOnCurrent',
|
|
44
|
+
sectionRule: 'rulesSection',
|
|
45
|
+
headRule: 'rulesHead',
|
|
46
|
+
ruleWeight: 'rulesWeight',
|
|
47
|
+
};
|
|
48
|
+
/** A palette's roles: opaque six-digit hex colors, but for `rulesWeight` and `rulesControl`, widths. */
|
|
49
|
+
const RULE_WEIGHTS = ['1px', '2px', '3px'];
|
|
50
|
+
const CONTROL_WEIGHTS = ['0px', '1px', '2px'];
|
|
51
|
+
function paletteStrings(raw, where) {
|
|
52
|
+
const colors = Object.keys(PALETTE_TOKENS).filter((key) => key !== 'rulesWeight' && key !== 'rulesControl');
|
|
53
|
+
const { rulesWeight, rulesControl, ...rest } = object(raw, where);
|
|
54
|
+
for (const key of Object.keys(rest))
|
|
55
|
+
if (Object.hasOwn(OLD_ROLES, key))
|
|
56
|
+
throw new Error(`${where}.${key} is now ${where}.${OLD_ROLES[key]}.`);
|
|
57
|
+
const result = strings(rest, colors, (color) => /^#[0-9a-f]{6}$/i.test(color), where);
|
|
58
|
+
if (rulesWeight !== undefined) {
|
|
59
|
+
if (typeof rulesWeight !== 'string' || !RULE_WEIGHTS.includes(rulesWeight.trim())) {
|
|
60
|
+
throw new Error(`${where}.rulesWeight must be one of ${RULE_WEIGHTS.join(', ')}.`);
|
|
61
|
+
}
|
|
62
|
+
result.rulesWeight = rulesWeight.trim();
|
|
63
|
+
}
|
|
64
|
+
if (rulesControl !== undefined) {
|
|
65
|
+
if (typeof rulesControl !== 'string' || !CONTROL_WEIGHTS.includes(rulesControl.trim())) {
|
|
66
|
+
throw new Error(`${where}.rulesControl must be one of ${CONTROL_WEIGHTS.join(', ')}.`);
|
|
67
|
+
}
|
|
68
|
+
result.rulesControl = rulesControl.trim();
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
export function parseThemes(raw, where, key = 'themes') {
|
|
73
|
+
if (raw === undefined)
|
|
74
|
+
return undefined;
|
|
75
|
+
if (!Array.isArray(raw) || raw.length > 20)
|
|
76
|
+
throw new Error(`${where}.${key} must be an array of at most 20 themes.`);
|
|
77
|
+
const used = new Set(['default', ...THEME_BASES]);
|
|
78
|
+
return raw.map((value, index) => {
|
|
79
|
+
const path = `${where}.${key}[${index}]`;
|
|
80
|
+
const theme = object(value, path);
|
|
81
|
+
for (const key of Object.keys(theme)) {
|
|
82
|
+
if (key === 'scale' || key === 'headingWeight')
|
|
83
|
+
throw new Error(`${path}.${key} is now ${path}.${TYPE_MOVED[`type.${key}`]}.`);
|
|
84
|
+
if (![
|
|
85
|
+
'id',
|
|
86
|
+
'label',
|
|
87
|
+
'base',
|
|
88
|
+
'light',
|
|
89
|
+
'dark',
|
|
90
|
+
'palettes',
|
|
91
|
+
'defaultPalette',
|
|
92
|
+
'scheme',
|
|
93
|
+
'fonts',
|
|
94
|
+
'radius',
|
|
95
|
+
'border',
|
|
96
|
+
'height',
|
|
97
|
+
...STRUCTURE_KEYS,
|
|
98
|
+
].includes(key))
|
|
99
|
+
throw new Error(`${path} has no "${key}".`);
|
|
100
|
+
}
|
|
101
|
+
if (typeof theme.id !== 'string' || !/^[a-z][a-z0-9-]{0,47}$/.test(theme.id) || used.has(theme.id)) {
|
|
102
|
+
throw new Error(`${path}.id must be a unique slug, not a built-in theme name.`);
|
|
103
|
+
}
|
|
104
|
+
used.add(theme.id);
|
|
105
|
+
if (typeof theme.label !== 'string' || !theme.label.trim() || theme.label.trim().length > 60)
|
|
106
|
+
throw new Error(`${path}.label must have 1 to 60 characters.`);
|
|
107
|
+
if (theme.base !== undefined && (typeof theme.base !== 'string' || !THEME_BASES.includes(theme.base)))
|
|
108
|
+
throw new Error(`${path}.base must be one of ${THEME_BASES.join(', ')}.`);
|
|
109
|
+
const palette = (mode) => {
|
|
110
|
+
const result = paletteStrings(theme[mode], `${path}.${mode}`);
|
|
111
|
+
for (const required of ['surface', 'ink', 'accent']) {
|
|
112
|
+
if (!result[required])
|
|
113
|
+
throw new Error(`${path}.${mode}.${required} is required (a six-digit hex color).`);
|
|
114
|
+
}
|
|
115
|
+
return { ...result, surface: result.surface, ink: result.ink, accent: result.accent };
|
|
116
|
+
};
|
|
117
|
+
const scheme = parseScheme(theme.scheme, path);
|
|
118
|
+
const dark = palette('dark');
|
|
119
|
+
/* One set of colors worn in both slots (themeBuild.ts): a light slot of its own is the colors a dark-only theme lacks. */
|
|
120
|
+
const light = scheme === 'dark-only' ? dark : palette('light');
|
|
121
|
+
if (scheme === 'dark-only' && theme.light !== undefined && JSON.stringify(palette('light')) !== JSON.stringify(dark))
|
|
122
|
+
throw new Error(`${path}.light: a dark-only theme has no light colors; name dark alone, or drop scheme: 'dark-only'.`);
|
|
123
|
+
const palettes = parsePaletteVariants(theme.palettes, path);
|
|
124
|
+
if (theme.defaultPalette !== undefined &&
|
|
125
|
+
(typeof theme.defaultPalette !== 'string' ||
|
|
126
|
+
(theme.defaultPalette !== 'default' && !palettes?.some((item) => item.id === theme.defaultPalette)))) {
|
|
127
|
+
throw new Error(`${path}.defaultPalette must be "default" or one of this theme's palettes.`);
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
id: theme.id,
|
|
131
|
+
label: theme.label.trim(),
|
|
132
|
+
base: (theme.base ?? 'mono'),
|
|
133
|
+
light,
|
|
134
|
+
dark,
|
|
135
|
+
...(scheme === undefined ? {} : { scheme }),
|
|
136
|
+
...(palettes === undefined ? {} : { palettes }),
|
|
137
|
+
...(theme.defaultPalette === undefined ? {} : { defaultPalette: theme.defaultPalette }),
|
|
138
|
+
...(theme.fonts === undefined
|
|
139
|
+
? {}
|
|
140
|
+
: {
|
|
141
|
+
fonts: strings(theme.fonts, Object.keys(FONT_TOKENS), (font) => font.length > 0 && font.length <= 200 && /^[\w\s'",.-]+$/.test(font), `${path}.fonts`),
|
|
142
|
+
}),
|
|
143
|
+
...(theme.radius === undefined ? {} : { radius: radii(theme.radius, `${path}.radius`) }),
|
|
144
|
+
...(theme.border === undefined ? {} : { border: borders(theme.border, `${path}.border`) }),
|
|
145
|
+
...(theme.height === undefined ? {} : { height: heights(theme.height, `${path}.height`) }),
|
|
146
|
+
...structure(theme, path),
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/** A compiled theme's corners, each within its ceiling, a control's and a pill's `round` as ROUND. */
|
|
151
|
+
function radii(raw, where) {
|
|
152
|
+
const out = {};
|
|
153
|
+
for (const [key, value] of Object.entries(object(raw, where))) {
|
|
154
|
+
if (!Object.hasOwn(RADIUS_TOKENS, key))
|
|
155
|
+
throw new Error(`${where} has no "${key}". It takes: ${Object.keys(RADIUS_TOKENS).join(', ')}.`);
|
|
156
|
+
const fits = cornerFits(value, RADIUS_MAX[key]) || (ROUND_CORNERS.has(key) && value === ROUND);
|
|
157
|
+
if (!fits)
|
|
158
|
+
throw new Error(`${where}.${key} has an invalid value.`);
|
|
159
|
+
out[key] = value;
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
/** A compiled theme's panel edge and focus ring, each from its menu. */
|
|
164
|
+
function borders(raw, where) {
|
|
165
|
+
const out = {};
|
|
166
|
+
for (const [key, value] of Object.entries(object(raw, where))) {
|
|
167
|
+
if (key !== 'panel' && key !== 'focus')
|
|
168
|
+
throw new Error(`${where} has no "${key}". It takes: panel, focus.`);
|
|
169
|
+
if (!BORDER_MENU[key].includes(value))
|
|
170
|
+
throw new Error(`${where}.${key} has an invalid value.`);
|
|
171
|
+
out[key] = value;
|
|
172
|
+
}
|
|
173
|
+
return out;
|
|
174
|
+
}
|
|
175
|
+
/** A compiled theme's control height: whole pixels from CONTROL_HEIGHT.min to .max. */
|
|
176
|
+
function heights(raw, where) {
|
|
177
|
+
const out = {};
|
|
178
|
+
for (const [key, value] of Object.entries(object(raw, where))) {
|
|
179
|
+
if (key !== 'control')
|
|
180
|
+
throw new Error(`${where} has no "${key}". It takes: control.`);
|
|
181
|
+
if (!controlHeightFits(value))
|
|
182
|
+
throw new Error(`${where}.control must be a length from ${CONTROL_HEIGHT.min}px to ${CONTROL_HEIGHT.max}px.`);
|
|
183
|
+
out.control = value;
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* What a theme's shape says that `check` warns of (theme-radius): a panel squarer than the controls it holds, its
|
|
189
|
+
* corner cutting under theirs. A round control is a pill, not a corner, so it is never measured against a panel.
|
|
190
|
+
*/
|
|
191
|
+
export function themeShapeWarnings(themes) {
|
|
192
|
+
return themes.flatMap((theme, index) => {
|
|
193
|
+
const control = theme.radius?.control, panel = theme.radius?.panel;
|
|
194
|
+
if (!control || !panel || control === ROUND || parseFloat(panel) >= parseFloat(control))
|
|
195
|
+
return [];
|
|
196
|
+
return [
|
|
197
|
+
`ui.customThemes[${index}] ("${theme.id}") shape.radius.panel ${panel} is squarer than shape.radius.control ${control}: a panel holds controls, so give its corner at least theirs.`,
|
|
198
|
+
];
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* What a theme's shadow says that `check` warns of (theme-shadow, the contract's C2): a shadow lighter than the ground
|
|
203
|
+
* it falls on, which draws a glow round a card or a menu rather than lifting it off the page — the edge the lift was
|
|
204
|
+
* for is lost. The theme still wears.
|
|
205
|
+
*/
|
|
206
|
+
export function themeShadowWarnings(themes) {
|
|
207
|
+
return themes.flatMap((theme, index) => {
|
|
208
|
+
const worn = effectiveThemeDefinition(theme, 'default');
|
|
209
|
+
return themeModes(theme).flatMap((mode) => {
|
|
210
|
+
const shadow = worn[mode].shadow, ground = worn[mode].ground ?? worn[mode].surface;
|
|
211
|
+
if (!shadow || !/^#[0-9a-f]{6}$/i.test(shadow) || !/^#[0-9a-f]{6}$/i.test(ground) || luminance(shadow) <= luminance(ground))
|
|
212
|
+
return [];
|
|
213
|
+
return [
|
|
214
|
+
`ui.customThemes[${index}] ("${theme.id}") colors.${mode}.shadow ${shadow} is lighter than its ground ${ground}: a shadow lighter than the page is a glow, not a lift, so give it a color darker than the ground.`,
|
|
215
|
+
];
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* What a banded table's parts say that `check` warns of (theme-band, the contract's C13): a `status` or `tag` part
|
|
221
|
+
* given an opaque ground. Part rules live in the `theme` cascade layer, above the `look` layer's band, so the chip
|
|
222
|
+
* keeps its own ground on every other row and shows as a pale column. The author's color is not touched: a
|
|
223
|
+
* translucent ground lets the band through, and so does the default.
|
|
224
|
+
*/
|
|
225
|
+
export function themeBandWarnings(themes) {
|
|
226
|
+
return themes.flatMap((theme, index) => {
|
|
227
|
+
if (theme.recipes?.table !== 'banded')
|
|
228
|
+
return [];
|
|
229
|
+
const worn = effectiveThemeDefinition(theme, 'default');
|
|
230
|
+
return ['status', 'tag'].flatMap((part) => {
|
|
231
|
+
const role = theme.parts?.[part]?.ground;
|
|
232
|
+
if (!role)
|
|
233
|
+
return [];
|
|
234
|
+
const key = pathRole(role);
|
|
235
|
+
const opaque = themeModes(theme).filter((mode) => {
|
|
236
|
+
const value = worn[mode][key];
|
|
237
|
+
const color = value ? diagnosticColor(value) : undefined;
|
|
238
|
+
return color !== undefined && color[3] >= 1;
|
|
239
|
+
});
|
|
240
|
+
if (!opaque.length)
|
|
241
|
+
return [];
|
|
242
|
+
return [
|
|
243
|
+
`ui.customThemes[${index}] ("${theme.id}") parts.${part}.ground ${role} is opaque (${opaque.join(', ')}) on a banded table: a ${part} cell will not take the band and shows as a pale column on every other row, so give it a translucent ground or leave it out for the look's.`,
|
|
244
|
+
];
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
/** The stack a role's face reads when the role names none: the titles' the heading face, the rest the interface's. */
|
|
249
|
+
const ROLE_FACE = { display: 'heading', title: 'heading', heading: 'heading', section: 'heading' };
|
|
250
|
+
/**
|
|
251
|
+
* What a theme's type says that `check` warns of (theme-type, the contract's C8): a role heavier than 700 whose face
|
|
252
|
+
* leads with a family the theme does not ship at that weight. design.md keeps titles at 700 or under for this: a
|
|
253
|
+
* heavier weight a family has no file for is drawn by the browser as a smeared 700, or resolves a book face to its
|
|
254
|
+
* Black cut, a masthead where a title was meant.
|
|
255
|
+
*/
|
|
256
|
+
export function themeTypeWarnings(themes) {
|
|
257
|
+
return themes.flatMap((theme, index) => {
|
|
258
|
+
const worn = effectiveThemeDefinition(theme, 'default');
|
|
259
|
+
return Object.entries(worn.type?.roles ?? {}).flatMap(([role, said]) => {
|
|
260
|
+
/* A line under 1.0 sets its descenders into the next: a title that wraps (a long name, a phone) collides with
|
|
261
|
+
itself. Allowed down to 0.85 for a short large line, so it warns rather than refuses. */
|
|
262
|
+
const leading = said.leading !== undefined && said.leading < 1
|
|
263
|
+
? [
|
|
264
|
+
`ui.customThemes[${index}] ("${theme.id}") type.roles.${role}.leading ${said.leading} is under 1.0: a ${role} that wraps sets its descenders into the next line, so keep it at 1.0 or over unless it is one short line.`,
|
|
265
|
+
]
|
|
266
|
+
: [];
|
|
267
|
+
const weight = said.weight;
|
|
268
|
+
if (weight === undefined || weight <= 700)
|
|
269
|
+
return leading;
|
|
270
|
+
const face = said.face ?? ROLE_FACE[role] ?? 'ui';
|
|
271
|
+
const stack = (face === 'heading' ? (worn.fonts?.heading ?? worn.fonts?.ui) : worn.fonts?.[face]) ?? '';
|
|
272
|
+
const lead = families(stack)[0];
|
|
273
|
+
const shipped = (worn.faces ?? []).some((file) => {
|
|
274
|
+
const [low, high = low] = file.weight.split(' ').map(Number);
|
|
275
|
+
return file.family.toLowerCase() === lead?.toLowerCase() && weight >= low && weight <= high;
|
|
276
|
+
});
|
|
277
|
+
if (shipped)
|
|
278
|
+
return leading;
|
|
279
|
+
return [
|
|
280
|
+
...leading,
|
|
281
|
+
`ui.customThemes[${index}] ("${theme.id}") type.roles.${role}.weight ${weight} is past 700 in a face the theme does not ship at that weight${lead ? ` (${lead})` : ''}: the browser draws a smeared 700 or a Black cut, so ship the face at ${weight} in type.faces or keep the weight at 700 or under.`,
|
|
282
|
+
];
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
const STRUCTURE_KEYS = ['recipes', 'elevation', 'type', 'density', 'motion', 'rhythm', 'lifts', 'surface', 'parts', 'icons'];
|
|
287
|
+
/** A compiled theme's structure, as `compileTheme` writes it: every value from the menu. */
|
|
288
|
+
function structure(theme, path) {
|
|
289
|
+
const out = {};
|
|
290
|
+
if (theme.recipes !== undefined) {
|
|
291
|
+
const recipes = object(theme.recipes, `${path}.recipes`);
|
|
292
|
+
for (const [part, value] of Object.entries(recipes)) {
|
|
293
|
+
if (TYPE_MOVED[`recipes.${part}`])
|
|
294
|
+
throw new Error(`${path}.recipes.${part} is now ${path}.${TYPE_MOVED[`recipes.${part}`]}.`);
|
|
295
|
+
if (Object.hasOwn(RECIPES_RENAMED, part))
|
|
296
|
+
throw new Error(`${path}.recipes.${part} is now ${path}.recipes.${RECIPES_RENAMED[part]}.`);
|
|
297
|
+
const menu = RECIPE_MENU[part];
|
|
298
|
+
if (!menu || typeof value !== 'string' || !Object.hasOwn(menu, value))
|
|
299
|
+
throw new Error(`${path}.recipes.${part} is not on the menu.`);
|
|
300
|
+
}
|
|
301
|
+
out.recipes = { ...recipes };
|
|
302
|
+
}
|
|
303
|
+
if (theme.elevation !== undefined) {
|
|
304
|
+
if (typeof theme.elevation !== 'string' || !Object.hasOwn(ELEVATIONS, theme.elevation))
|
|
305
|
+
throw new Error(`${path}.elevation is not on the menu.`);
|
|
306
|
+
out.elevation = theme.elevation;
|
|
307
|
+
}
|
|
308
|
+
if (theme.type !== undefined) {
|
|
309
|
+
const type = object(theme.type, `${path}.type`);
|
|
310
|
+
for (const key of Object.keys(type))
|
|
311
|
+
if (key !== 'base' && key !== 'roles')
|
|
312
|
+
throw new Error(`${path}.type has no "${key}". It takes: base, roles.`);
|
|
313
|
+
out.type = typeOf(type.base, type.roles, `${path}.type`);
|
|
314
|
+
}
|
|
315
|
+
if (theme.density !== undefined) {
|
|
316
|
+
if (!DENSITIES.includes(theme.density))
|
|
317
|
+
throw new Error(`${path}.density must be one of ${DENSITIES.join(', ')}.`);
|
|
318
|
+
out.density = theme.density;
|
|
319
|
+
}
|
|
320
|
+
if (theme.lifts !== undefined) {
|
|
321
|
+
const lifts = object(theme.lifts, `${path}.lifts`);
|
|
322
|
+
for (const [key, step] of Object.entries(lifts))
|
|
323
|
+
if ((key !== 'card' && key !== 'float') || typeof step !== 'string' || step === 'look' || !Object.hasOwn(LIFTS, step))
|
|
324
|
+
throw new Error(`${path}.lifts.${key} is not on the menu.`);
|
|
325
|
+
out.lifts = { ...lifts };
|
|
326
|
+
}
|
|
327
|
+
if (theme.motion !== undefined) {
|
|
328
|
+
if (typeof theme.motion !== 'string' || !Object.hasOwn(MOTIONS, theme.motion))
|
|
329
|
+
throw new Error(`${path}.motion must be one of ${Object.keys(MOTIONS).join(', ')}.`);
|
|
330
|
+
out.motion = theme.motion;
|
|
331
|
+
}
|
|
332
|
+
if (theme.rhythm !== undefined) {
|
|
333
|
+
if (typeof theme.rhythm !== 'string' || !Object.hasOwn(RHYTHMS, theme.rhythm))
|
|
334
|
+
throw new Error(`${path}.rhythm must be one of ${Object.keys(RHYTHMS).join(', ')}.`);
|
|
335
|
+
out.rhythm = theme.rhythm;
|
|
336
|
+
}
|
|
337
|
+
if (theme.surface !== undefined)
|
|
338
|
+
out.surface = surfaceOf(theme.surface, `${path}.surface`);
|
|
339
|
+
if (theme.parts !== undefined)
|
|
340
|
+
out.parts = partsOf(theme.parts, `${path}.parts`);
|
|
341
|
+
if (theme.icons !== undefined)
|
|
342
|
+
out.icons = iconsOf(theme.icons, `${path}.icons`);
|
|
343
|
+
return out;
|
|
344
|
+
}
|
|
345
|
+
function parsePaletteVariants(raw, where) {
|
|
346
|
+
if (raw === undefined)
|
|
347
|
+
return undefined;
|
|
348
|
+
if (!Array.isArray(raw) || raw.length > 20)
|
|
349
|
+
throw new Error(`${where}.palettes must be an array of at most 20 palettes.`);
|
|
350
|
+
const used = new Set(['default']);
|
|
351
|
+
return raw.map((value, index) => {
|
|
352
|
+
const path = `${where}.palettes[${index}]`, variant = object(value, path);
|
|
353
|
+
for (const key of Object.keys(variant)) {
|
|
354
|
+
if (!['id', 'label', 'light', 'dark'].includes(key))
|
|
355
|
+
throw new Error(`${path} has no "${key}".`);
|
|
356
|
+
}
|
|
357
|
+
if (typeof variant.id !== 'string' || !/^[a-z][a-z0-9-]{0,47}$/.test(variant.id) || used.has(variant.id)) {
|
|
358
|
+
throw new Error(`${path}.id must be a unique slug, not "default".`);
|
|
359
|
+
}
|
|
360
|
+
used.add(variant.id);
|
|
361
|
+
if (typeof variant.label !== 'string' || !variant.label.trim() || variant.label.trim().length > 60)
|
|
362
|
+
throw new Error(`${path}.label must have 1 to 60 characters.`);
|
|
363
|
+
const palette = (mode) => paletteStrings(variant[mode], `${path}.${mode}`);
|
|
364
|
+
return { id: variant.id, label: variant.label.trim(), light: palette('light'), dark: palette('dark') };
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
/** WCAG relative luminance, for the opaque hex colors accepted by the contract. */
|
|
368
|
+
function luminance(hex) {
|
|
369
|
+
const channels = [1, 3, 5]
|
|
370
|
+
.map((start) => parseInt(hex.slice(start, start + 2), 16) / 255)
|
|
371
|
+
.map((value) => (value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4));
|
|
372
|
+
return channels[0] * 0.2126 + channels[1] * 0.7152 + channels[2] * 0.0722;
|
|
373
|
+
}
|
|
374
|
+
export function contrastRatio(a, b) {
|
|
375
|
+
const left = luminance(a), right = luminance(b);
|
|
376
|
+
return (Math.max(left, right) + 0.05) / (Math.min(left, right) + 0.05);
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* The least luminance ratio a field's fill may be from the ground and the surface it sits on, when the field draws
|
|
380
|
+
* no edge. 1.08:1 is Lintel's Hearth (a warm #eeede7 well on a #f7f6f2 page, 1.085, and on white, 1.173): the faintest
|
|
381
|
+
* field we ship that still reads as a well, and the faintest a calibrated laptop screen shows in daylight. Below it
|
|
382
|
+
* (white on #fcfbf8, 1.03) the field is gone. WCAG 1.4.11 asks 3:1 of a control's boundary unless it is identified
|
|
383
|
+
* some other way; the label stands over the field and the focus ring draws a 2px edge, which is that other way.
|
|
384
|
+
*/
|
|
385
|
+
export const EDGELESS_FIELD_DELTA = 1.08;
|
|
386
|
+
/** What startup checks in each mode, and the ratio each pair must reach: text
|
|
387
|
+
at 4.5:1 (WCAG 1.4.3) — the meaning colors on the page as well as on a
|
|
388
|
+
panel, since a status word sits on both, and the page is where Meridian's
|
|
389
|
+
red read at 4.4:1 while its panels passed — and a form control's edge at
|
|
390
|
+
3:1 (1.4.11) on the field it frames and on the page around it. Section and
|
|
391
|
+
table hairlines are decoration, and are not checked. */
|
|
392
|
+
const CONTRAST_PAIRS = [
|
|
393
|
+
['ink', 'surface', 4.5],
|
|
394
|
+
['inkMuted', 'surface', 4.5],
|
|
395
|
+
['inkSubtle', 'surface', 4.5],
|
|
396
|
+
['ink', 'ground', 4.5],
|
|
397
|
+
['ink', 'raised', 4.5],
|
|
398
|
+
/* A field's own fill (`colors.field`, the surface where it names none): the words typed in it, the faint ink of its
|
|
399
|
+
placeholder and the muted ink of its hint stand on it, and its edge. Skipped where the fill is the surface, which
|
|
400
|
+
the pairs above already measure. */
|
|
401
|
+
['ink', 'field', 4.5, 'the words in a field'],
|
|
402
|
+
['inkMuted', 'field', 4.5, "a field's muted words"],
|
|
403
|
+
['inkSubtle', 'field', 4.5, "a field's placeholder"],
|
|
404
|
+
['lineControl', 'field', 3, "a control's edge on a field's fill"],
|
|
405
|
+
/* Every ink is drawn where the text it is for is drawn, not only on the panel it was picked on: a caption under a
|
|
406
|
+
page's title and a count beside a tab stand on the page (TC13, a theme check passed and its pages failed). */
|
|
407
|
+
['inkMuted', 'ground', 4.5, 'a caption on the page'],
|
|
408
|
+
['inkSubtle', 'ground', 4.5, 'a faint caption on the page'],
|
|
409
|
+
['link', 'ground', 4.5],
|
|
410
|
+
['link', 'raised', 4.5],
|
|
411
|
+
['navAccent', 'navGround', 4.5, "the menu's current entry"],
|
|
412
|
+
/* The accent is a fill, and is read as text in its ink (`accentInk`, the accent itself where a theme names none and
|
|
413
|
+
it reads); the words drawn on a fill are `onAccent`. A bright brand accent can be a good fill and a poor text. */
|
|
414
|
+
['accentInk', 'surface', 4.5],
|
|
415
|
+
// A quiet button's word and a current tab are the accent's ink on the page, and on a raised panel or a hovered row.
|
|
416
|
+
['accentInk', 'ground', 4.5, 'an action in the accent on the page'],
|
|
417
|
+
['accentInk', 'raised', 4.5, 'an action in the accent on a raised panel or a hovered row'],
|
|
418
|
+
['onAccent', 'accent', 4.5, 'the words on an accent fill'],
|
|
419
|
+
['onPrimary', 'primary', 4.5, "the words on a form's primary action"],
|
|
420
|
+
['link', 'surface', 4.5],
|
|
421
|
+
['navInk', 'navGround', 4.5, "the menu's entries"],
|
|
422
|
+
['navMuted', 'navGround', 4.5, "the menu's group headings, counts and icons"],
|
|
423
|
+
...['statusGood', 'statusWarning', 'statusCritical', 'statusInfo'].flatMap((role) => [
|
|
424
|
+
[role, 'surface', 4.5],
|
|
425
|
+
[role, 'ground', 4.5],
|
|
426
|
+
[role, 'raised', 4.5, 'a status word in a hovered row or on a raised panel'],
|
|
427
|
+
]),
|
|
428
|
+
['lineControl', 'surface', 3],
|
|
429
|
+
['lineControl', 'ground', 3],
|
|
430
|
+
/* A field stands on a raised panel too, and a list's sort and filter controls on its head: a control's edge that
|
|
431
|
+
passed on the panel and the page read 2.94:1 there. */
|
|
432
|
+
['lineControl', 'raised', 3, "a control's edge on a raised panel or a table's head"],
|
|
433
|
+
// The focus ring is a graphic the keyboard reader follows (1.4.11): on the field, the page and the subtle panel it can sit on.
|
|
434
|
+
['focus', 'surface', 3],
|
|
435
|
+
['focus', 'ground', 3],
|
|
436
|
+
['focus', 'raised', 3],
|
|
437
|
+
/* A chart's first four series are graphics a reader has to tell from the
|
|
438
|
+
card they sit on; past four, a chart is already a legend's job. */
|
|
439
|
+
...['chart1', 'chart2', 'chart3', 'chart4'].map((series) => [series, 'surface', 3]),
|
|
440
|
+
];
|
|
441
|
+
export { HEAT_TOP, heatTop };
|
|
442
|
+
const SERIES = ['chart1', 'chart2', 'chart3', 'chart4', 'chart5', 'chart6', 'chart7', 'chart8'];
|
|
443
|
+
/**
|
|
444
|
+
* The colours a platform component mixes from a theme's series and its ink rather than wears as a token: no pair of
|
|
445
|
+
* tokens is what it draws, so each is measured as drawn, and named by the series it comes from. A board card's
|
|
446
|
+
* category chip (TriageBoard.css: 45% of its series in the ink, except under the `strip` board, which says it in words, on a 13% tint of it over the card), a person's
|
|
447
|
+
* initials on their avatar (features.css: 45% in the ink, on a 15% tint over a 7% ink wash), and under the `icon`
|
|
448
|
+
* stat recipe, a stat card's mark (lumenLook.css: 80% in the ink on a 12% tint, a graphic at 3:1). `check` holds a
|
|
449
|
+
* theme to each, and `grade` names the series behind a reading of one.
|
|
450
|
+
*/
|
|
451
|
+
export function componentInks(palette, recipes = {}) {
|
|
452
|
+
const color = (key) => (palette[key] ? diagnosticColor(palette[key]) : undefined);
|
|
453
|
+
const ink = color('ink'), surface = color('surface');
|
|
454
|
+
if (!ink || !surface)
|
|
455
|
+
return [];
|
|
456
|
+
const clear = [0, 0, 0, 0];
|
|
457
|
+
const out = [];
|
|
458
|
+
SERIES.forEach((role, index) => {
|
|
459
|
+
const series = color(role);
|
|
460
|
+
if (!series)
|
|
461
|
+
return;
|
|
462
|
+
if (recipes.board !== 'strip')
|
|
463
|
+
out.push({
|
|
464
|
+
role,
|
|
465
|
+
what: "a board card's category chip",
|
|
466
|
+
fg: mixColor(series, ink, 0.45),
|
|
467
|
+
bg: over(mixColor(series, clear, 0.13), surface),
|
|
468
|
+
needs: 4.5,
|
|
469
|
+
});
|
|
470
|
+
if (index < 7)
|
|
471
|
+
out.push({
|
|
472
|
+
role,
|
|
473
|
+
what: "an avatar's initials",
|
|
474
|
+
fg: mixColor(series, ink, 0.45),
|
|
475
|
+
bg: mixColor(series, mixColor(ink, surface, 0.07), 0.15),
|
|
476
|
+
needs: 4.5,
|
|
477
|
+
});
|
|
478
|
+
if (recipes.stat === 'icon' && index < 4)
|
|
479
|
+
out.push({
|
|
480
|
+
role,
|
|
481
|
+
what: "a stat card's mark",
|
|
482
|
+
fg: mixColor(series, ink, 0.8),
|
|
483
|
+
bg: mixColor(series, surface, 0.12),
|
|
484
|
+
needs: 3,
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
/* The eighth avatar is the second and third series half and half: said by the second. */
|
|
488
|
+
const second = color('chart2'), third = color('chart3');
|
|
489
|
+
if (second && third) {
|
|
490
|
+
const slot = mixColor(second, third, 0.5);
|
|
491
|
+
out.push({
|
|
492
|
+
role: 'chart2',
|
|
493
|
+
what: "the eighth avatar's initials (chart[1] and chart[2] half and half)",
|
|
494
|
+
fg: mixColor(slot, ink, 0.45),
|
|
495
|
+
bg: mixColor(slot, mixColor(ink, surface, 0.07), 0.15),
|
|
496
|
+
needs: 4.5,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
return out.map((item) => ({ ...item, fg: colorHex(over(item.fg, item.bg)), bg: colorHex(item.bg) }));
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* The pairs a role is read in (CONTRAST_PAIRS), measured with `value` drawn in its place: what an author wrote for a
|
|
503
|
+
* colour the page wears as another (a link written as the accent is drawn in the accent's ink), so the colour as
|
|
504
|
+
* written is held to the rule its role is.
|
|
505
|
+
*/
|
|
506
|
+
export function rolePairs(palette, role, value) {
|
|
507
|
+
const foreground = diagnosticColor(value);
|
|
508
|
+
if (!foreground)
|
|
509
|
+
return [];
|
|
510
|
+
return CONTRAST_PAIRS.flatMap(([ink, background, needs, what]) => {
|
|
511
|
+
const ground = palette[background];
|
|
512
|
+
if (ink !== role || !ground || ((background === 'ground' || background === 'field') && ground === palette.surface))
|
|
513
|
+
return [];
|
|
514
|
+
const backgroundColor = diagnosticColor(ground);
|
|
515
|
+
const ratio = backgroundColor && measuredContrast(foreground, backgroundColor);
|
|
516
|
+
return ratio === undefined || !ratio ? [] : [{ background, needs, ...(what ? { what } : {}), ratio }];
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
export function themeWarnings(themes, own = {}) {
|
|
520
|
+
return themeFindings(themes, own).map((finding) => finding.message);
|
|
521
|
+
}
|
|
522
|
+
/** Every failed check of the themes; with `all`, every check measured, passed or not (a fix reads what it reaches). */
|
|
523
|
+
export function themeFindings(themes, own = {}, all = false) {
|
|
524
|
+
return themes.flatMap((theme) => ['default', ...(theme.palettes ?? []).map((item) => item.id)].flatMap((id) => themeModes(theme).flatMap((mode) => {
|
|
525
|
+
const definition = effectiveThemeDefinition(theme, id);
|
|
526
|
+
const palette = withInks(effectiveThemePalette(definition[mode], mode === 'dark', own));
|
|
527
|
+
const name = id === 'default' ? theme.label : `${theme.label} / ${theme.palettes.find((item) => item.id === id).label}`;
|
|
528
|
+
const at = { themeId: theme.id, paletteId: id, mode };
|
|
529
|
+
return CONTRAST_PAIRS.flatMap(([foreground, background, needs, what]) => {
|
|
530
|
+
const a = palette[foreground], b = palette[background];
|
|
531
|
+
// A page the colour of its panels is one ground, and one warning; a field the colour of its panel the same.
|
|
532
|
+
if (!a || !b || ((background === 'ground' || background === 'field') && b === palette.surface))
|
|
533
|
+
return [];
|
|
534
|
+
// With no edge there is no edge to measure: the fill's own distance from its grounds is (stateFindings).
|
|
535
|
+
if (background === 'field' && foreground === 'lineControl' && palette.rulesControl === '0px')
|
|
536
|
+
return [];
|
|
537
|
+
const foregroundColor = diagnosticColor(a), backgroundColor = diagnosticColor(b);
|
|
538
|
+
if (!foregroundColor || !backgroundColor)
|
|
539
|
+
return [];
|
|
540
|
+
const ratio = measuredContrast(foregroundColor, backgroundColor);
|
|
541
|
+
if (ratio === undefined || (!all && ratio >= needs))
|
|
542
|
+
return [];
|
|
543
|
+
const rule = needs === 4.5
|
|
544
|
+
? 'normal text needs 4.5:1'
|
|
545
|
+
: foreground.startsWith('chart')
|
|
546
|
+
? 'a chart series needs 3:1'
|
|
547
|
+
: foreground === 'focus'
|
|
548
|
+
? 'the focus ring needs 3:1'
|
|
549
|
+
: "a form control's edge needs 3:1";
|
|
550
|
+
return [
|
|
551
|
+
{
|
|
552
|
+
...at,
|
|
553
|
+
token: foreground,
|
|
554
|
+
check: `${foreground} on ${background}${what ? ` (${what})` : ''}`,
|
|
555
|
+
ratio,
|
|
556
|
+
needs,
|
|
557
|
+
message: `Theme "${name}" (${mode}): ${what ? `${what}, ` : ''}${rolePath(foreground)}/${rolePath(background)} contrast is ${ratio.toFixed(2)}:1; ${rule}.`,
|
|
558
|
+
},
|
|
559
|
+
];
|
|
560
|
+
}).concat(stateFindings(definition, name, at, palette, all));
|
|
561
|
+
})));
|
|
562
|
+
}
|
|
563
|
+
/** Readers can select any offered look, so App-wide overrides are checked on
|
|
564
|
+
built-ins too. Without overrides retain the registered-theme warning scope. */
|
|
565
|
+
export function appThemeWarnings(themes, own = {}) {
|
|
566
|
+
const warnings = themeWarnings(own.accent ? [...THEME_PRESETS, ...themes] : themes, own);
|
|
567
|
+
const accents = typeof own.accent === 'object'
|
|
568
|
+
? [
|
|
569
|
+
['accent.light', own.accent.light],
|
|
570
|
+
['accent.dark', own.accent.dark],
|
|
571
|
+
]
|
|
572
|
+
: [['accent', own.accent]];
|
|
573
|
+
for (const [key, value] of accents) {
|
|
574
|
+
if (value && /\burl\s*\(/i.test(value))
|
|
575
|
+
warnings.push(`App ui.theme.${key}: a CSS image URL is not a color; use a CSS color value.`);
|
|
576
|
+
else if (value && !diagnosticColor(value))
|
|
577
|
+
warnings.push(`App ui.theme.${key}: contrast was not checked for this legacy CSS color; use a hex color for numeric contrast diagnostics.`);
|
|
578
|
+
}
|
|
579
|
+
if (own.radius) {
|
|
580
|
+
const diagnostic = radiusDiagnostic(own.radius);
|
|
581
|
+
if (diagnostic)
|
|
582
|
+
warnings.push(`App ui.theme.radius: ${diagnostic}`);
|
|
583
|
+
}
|
|
584
|
+
return warnings;
|
|
585
|
+
}
|
|
586
|
+
/** Recognize literal border-radius syntax only. Expressions remain compatible;
|
|
587
|
+
an unmeasured diagnostic is not a declaration that the browser rejects them. */
|
|
588
|
+
function radiusDiagnostic(raw) {
|
|
589
|
+
const value = raw.trim().toLowerCase();
|
|
590
|
+
if (/^(?:initial|inherit|unset|revert|revert-layer)$/.test(value))
|
|
591
|
+
return undefined;
|
|
592
|
+
const unmeasured = 'validity was not checked for this legacy CSS radius expression; verify its computed border-radius in the browser.';
|
|
593
|
+
if (/[()\\]/.test(value))
|
|
594
|
+
return unmeasured;
|
|
595
|
+
const groups = value.split('/').map((group) => group.trim().split(/\s+/));
|
|
596
|
+
if (groups.length > 2 || groups.some((group) => group.length > 4 || !group[0]))
|
|
597
|
+
return unmeasured;
|
|
598
|
+
for (const token of groups.flat()) {
|
|
599
|
+
const literal = /^([+-]?(?:\d*\.\d+|\d+)(?:e[+-]?\d+)?)([a-z]+|%)?$/.exec(token);
|
|
600
|
+
if (!literal) {
|
|
601
|
+
if (/^[a-z-]+$/.test(token))
|
|
602
|
+
return 'an unrecognized bare keyword is not a CSS radius; use nonnegative lengths or percentages.';
|
|
603
|
+
return unmeasured;
|
|
604
|
+
}
|
|
605
|
+
const number = Number(literal[1]);
|
|
606
|
+
const unit = literal[2];
|
|
607
|
+
// Unknown/future units are not rejected by this deliberately bounded check.
|
|
608
|
+
if (unit &&
|
|
609
|
+
!/^(?:%|px|cm|mm|q|in|pt|pc|em|rem|ex|rex|cap|rcap|ch|rch|ic|ric|lh|rlh|(?:s|l|d)?v(?:w|h|i|b|min|max)|cq(?:w|h|i|b|min|max))$/.test(unit))
|
|
610
|
+
return unmeasured;
|
|
611
|
+
if (number < 0)
|
|
612
|
+
return 'a negative radius is not valid CSS; use a nonnegative CSS length.';
|
|
613
|
+
if (!unit && number !== 0)
|
|
614
|
+
return 'a nonzero unitless radius is not valid CSS; include a CSS length unit or percentage.';
|
|
615
|
+
}
|
|
616
|
+
return undefined;
|
|
617
|
+
}
|
|
618
|
+
function stateFindings(theme, name, at, palette, all) {
|
|
619
|
+
const result = [];
|
|
620
|
+
const mode = at.mode;
|
|
621
|
+
const recipes = theme.recipes ?? {};
|
|
622
|
+
const color = (key) => (palette[key] ? diagnosticColor(palette[key]) : undefined);
|
|
623
|
+
const check = (label, foreground, background, needs = 4.5, token, said) => {
|
|
624
|
+
if (!foreground || !background)
|
|
625
|
+
return;
|
|
626
|
+
const ratio = measuredContrast(foreground, background);
|
|
627
|
+
if (ratio === undefined || (!all && ratio >= needs))
|
|
628
|
+
return;
|
|
629
|
+
const rule = said ?? (needs === 4.5 ? 'normal text needs 4.5:1' : 'the focus ring needs 3:1');
|
|
630
|
+
result.push({
|
|
631
|
+
...at,
|
|
632
|
+
token: token ?? /^[a-zA-Z0-9]+/.exec(label)[0],
|
|
633
|
+
check: label,
|
|
634
|
+
ratio,
|
|
635
|
+
needs,
|
|
636
|
+
message: `Theme "${name}" (${mode}): ${label.replace(/^\w+/, rolePath).replace(/\/(\w+)$/, (_, role) => `/${rolePath(role)}`)} contrast is ${ratio.toFixed(2)}:1; ${rule}.`,
|
|
637
|
+
});
|
|
638
|
+
};
|
|
639
|
+
const surface = color('surface'), subtle = color('raised'), text = color('ink');
|
|
640
|
+
/* A field with no edge (shape.border.control 0px) is told from the page by its fill alone, so the fill must stand
|
|
641
|
+
apart from both grounds it sits on (EDGELESS_FIELD_DELTA says how far). A field that names no fill is the surface,
|
|
642
|
+
which is no distance from the surface at all. */
|
|
643
|
+
if (palette.rulesControl === '0px')
|
|
644
|
+
for (const ground of ['ground', 'surface']) {
|
|
645
|
+
if (ground === 'ground' && (!palette.ground || palette.ground === palette.surface))
|
|
646
|
+
continue;
|
|
647
|
+
check(`field/${ground}`, color('field') ?? surface, color(ground), EDGELESS_FIELD_DELTA, 'field', `a field with no edge (shape.border.control 0px) must stand ${EDGELESS_FIELD_DELTA}:1 apart from the ${ground} by its fill, so give colors.field a fill that does or keep a 1px edge`);
|
|
648
|
+
}
|
|
649
|
+
/* The hover and row ground (--raised) carries every kind of text a row
|
|
650
|
+
holds, not only the full ink the pair list checks there: a muted date, a
|
|
651
|
+
faint count, a link. */
|
|
652
|
+
for (const key of ['inkMuted', 'inkSubtle'])
|
|
653
|
+
check(`${key}/raised`, color(key), subtle);
|
|
654
|
+
/* A tone chip (features.css .tone-*) writes 78% of the meaning colour into
|
|
655
|
+
the text ink, on a 12% tint of it over the panel. Neither end of that is
|
|
656
|
+
a palette colour, so no pair above could see it. */
|
|
657
|
+
for (const role of ['statusGood', 'statusWarning', 'statusCritical', 'statusInfo']) {
|
|
658
|
+
const ink = color(role);
|
|
659
|
+
if (!ink || !text || !surface)
|
|
660
|
+
continue;
|
|
661
|
+
check(`${role} tone chip`, mixColor(ink, text, 0.78), over(mixColor(ink, [0, 0, 0, 0], 0.12), surface));
|
|
662
|
+
}
|
|
663
|
+
/* A series is drawn as words and marks too, mixed into the ink (componentInks): measured as each is drawn. */
|
|
664
|
+
for (const item of componentInks(palette, recipes))
|
|
665
|
+
check(`${item.role} as ${item.what}`, diagnosticColor(item.fg), diagnosticColor(item.bg), item.needs, item.role, item.needs === 3 ? 'a mark needs 3:1' : undefined);
|
|
666
|
+
/* A danger banner (a page's errors, pending edits) writes the danger colour
|
|
667
|
+
on a 10% tint of it mixed into the panel. */
|
|
668
|
+
const danger = color('statusCritical');
|
|
669
|
+
if (danger && surface)
|
|
670
|
+
check('statusCritical/danger banner', danger, mixColor(danger, surface, 0.1));
|
|
671
|
+
/* A matrix's heaviest cell says its figure in the ink on the accent's tint, drawn no stronger than heatTop allows:
|
|
672
|
+
only a palette whose ink is too near its accent for even the floor's tint is short here. */
|
|
673
|
+
const accent = color('accent');
|
|
674
|
+
if (accent && surface)
|
|
675
|
+
check("ink/accent's heat tint (a matrix's heaviest cell)", text, over(mixColor(accent, [0, 0, 0, 0], heatTop(palette)), surface), 4.5, 'accent');
|
|
676
|
+
/* A printed page is white paper whatever the page's ground (style.css's print layer), so a light palette's accent
|
|
677
|
+
and links are read there too: one drawn for a dark "light" mode reads on its panel and vanishes on paper (C12).
|
|
678
|
+
A theme that says it is dark has no light palette to be read as paper: its one set of colors is judged as dark. */
|
|
679
|
+
if (mode === 'light' && theme.scheme !== 'dark-only')
|
|
680
|
+
for (const key of ['accentInk', 'link']) {
|
|
681
|
+
/* Darker than its panel, a color reads better on paper than there: the panel's own pair already says it. */
|
|
682
|
+
const value = palette[key];
|
|
683
|
+
const hex = (css) => css !== undefined && /^#[0-9a-f]{6}$/i.test(css);
|
|
684
|
+
if (!hex(value) || !hex(palette.surface) || contrastRatio(value, '#ffffff') < contrastRatio(palette.surface, '#ffffff'))
|
|
685
|
+
check(`${key} on printed paper`, color(key), [1, 1, 1, 1], 4.5, key);
|
|
686
|
+
}
|
|
687
|
+
const soft = color('accentSoft');
|
|
688
|
+
for (const ground of ['surface', 'ground']) {
|
|
689
|
+
const base = color(ground);
|
|
690
|
+
if (ground === 'ground' && palette.ground === palette.surface)
|
|
691
|
+
continue;
|
|
692
|
+
// Selected menu items and pressed controls use accent ink on this tint.
|
|
693
|
+
check(`accentInk/accentSoft over ${ground}`, color('accentInk'), soft && base ? over(soft, base) : undefined);
|
|
694
|
+
}
|
|
695
|
+
// A chosen row, a chosen card and a current step keep their words in the ink on that tint.
|
|
696
|
+
check('ink/accentSoft over surface (a chosen row)', text, soft && surface ? over(soft, surface) : undefined);
|
|
697
|
+
const nav = color('navGround'), navInk = color('navInk');
|
|
698
|
+
// visualThemes.css topbar item hover/current and expanded group overlays.
|
|
699
|
+
const navGround = (weight) => (nav && navInk ? over(mixColor(navInk, [0, 0, 0, 0], weight), nav) : undefined);
|
|
700
|
+
check('navAccent/navigation hover', color('navAccent'), navGround(0.05));
|
|
701
|
+
// The current entry's block, when a theme names one: its words on it (structure.css).
|
|
702
|
+
if (palette.navCurrent)
|
|
703
|
+
check('navOnCurrent/navCurrent', color('navOnCurrent') ?? navInk, color('navCurrent'));
|
|
704
|
+
check('navInk/navigation expanded', navInk, navGround(0.07));
|
|
705
|
+
/* The pill menu marks its current entry with the accent's tint, laid on the menu's own ground, in the menu's
|
|
706
|
+
accent (lumenLook.css --current-ground): a pair neither the menu's nor the page's checks see. A theme that
|
|
707
|
+
names its own block (nav.current) is measured on that instead. */
|
|
708
|
+
if (recipes.nav === 'pill' && !palette.navCurrent)
|
|
709
|
+
check("navAccent/accentSoft over navGround (the pill menu's current entry)", color('navAccent'), soft && nav ? over(soft, nav) : undefined);
|
|
710
|
+
/* The search is a field on the surface in the page's ink (themeFamilies.css), a pair the page's checks read; the
|
|
711
|
+
dark bar alone draws it on an 8% ink tint of its own ground in its muted ink (brandLook.css). */
|
|
712
|
+
if (recipes.bar === 'dark')
|
|
713
|
+
check('navMuted/navigation search', color('navMuted'), navGround(0.08));
|
|
714
|
+
/* The dark bar draws its keys — New, the customer chooser — on the same tint in the menu's ink (brandLook.css), and
|
|
715
|
+
a phone's menu, search and New marks in its muted ink on its ground (visualThemes.css, structure.css): until
|
|
716
|
+
0.9.6 those stayed the page's keys and greys, about 1.5:1 on a green menu, and no pair here read them. */
|
|
717
|
+
if (recipes.bar === 'dark')
|
|
718
|
+
check('navInk/navigation bar button', navInk, navGround(0.08));
|
|
719
|
+
if (recipes.table === 'tinted') {
|
|
720
|
+
const accent = color('accent'), surface = color('surface');
|
|
721
|
+
const hover = accent && surface ? mixColor(accent, surface, 0.04) : undefined;
|
|
722
|
+
check('ink/tinted row hover', color('ink'), hover);
|
|
723
|
+
check('link/tinted row hover', color('link'), hover);
|
|
724
|
+
}
|
|
725
|
+
/* A banded table lays every other row on the band (brandLook.css): whatever a row says is said on it. */
|
|
726
|
+
if (recipes.table === 'banded' && palette.band)
|
|
727
|
+
for (const key of [
|
|
728
|
+
'ink',
|
|
729
|
+
'inkMuted',
|
|
730
|
+
'inkSubtle',
|
|
731
|
+
'link',
|
|
732
|
+
'accentInk',
|
|
733
|
+
'statusGood',
|
|
734
|
+
'statusWarning',
|
|
735
|
+
'statusCritical',
|
|
736
|
+
'statusInfo',
|
|
737
|
+
])
|
|
738
|
+
check(`${key}/band`, color(key), color('band'), 4.5, 'band');
|
|
739
|
+
/* A pattern is the theme's line color at its strength over the ground it is drawn on (surfacesLook.css): the words
|
|
740
|
+
on the page, or in the menu, are measured where a line crosses under them, the darkest point of it. */
|
|
741
|
+
const patterned = (place) => {
|
|
742
|
+
const pattern = theme.surface?.[place];
|
|
743
|
+
if (!pattern)
|
|
744
|
+
return;
|
|
745
|
+
const line = color(place === 'nav' ? 'navLine' : 'line') ?? color('line');
|
|
746
|
+
const ground = place === 'nav' ? color('navGround') : (color('ground') ?? surface);
|
|
747
|
+
if (!line || !ground)
|
|
748
|
+
return;
|
|
749
|
+
const under = over(mixColor(line, [line[0], line[1], line[2], 0], patternStrength(pattern)), ground);
|
|
750
|
+
const where = place === 'nav' ? `the menu's ${pattern.kind} pattern` : `the page's ${pattern.kind} pattern`;
|
|
751
|
+
const inks = place === 'nav' ? ['navInk', 'navMuted'] : ['ink', 'inkMuted', 'inkSubtle', 'link', 'accentInk'];
|
|
752
|
+
for (const key of inks)
|
|
753
|
+
check(`${key} on ${where}`, color(key), under, 4.5, place === 'nav' ? 'navLine' : 'line');
|
|
754
|
+
};
|
|
755
|
+
patterned('ground');
|
|
756
|
+
patterned('nav');
|
|
757
|
+
/* A part's words on its ground (theme-part): its own ink on its own ground, or the one it is drawn on unsaid. */
|
|
758
|
+
for (const [part, style] of Object.entries(theme.parts ?? {})) {
|
|
759
|
+
if (!style || (style.ground === undefined && style.ink === undefined))
|
|
760
|
+
continue;
|
|
761
|
+
const ink = style.ink ? pathRole(style.ink) : partInk(part);
|
|
762
|
+
const ground = style.ground ? pathRole(style.ground) : partGround(part, palette);
|
|
763
|
+
if (!ink || !ground)
|
|
764
|
+
continue;
|
|
765
|
+
const said = (role) => rolePath(role);
|
|
766
|
+
const label = `parts.${part}: ${ink} on ${ground}`;
|
|
767
|
+
const foreground = color(ink), background = color(ground);
|
|
768
|
+
if (!foreground || !background)
|
|
769
|
+
continue;
|
|
770
|
+
const ratio = measuredContrast(foreground, background);
|
|
771
|
+
if (ratio === undefined || (!all && ratio >= 4.5))
|
|
772
|
+
continue;
|
|
773
|
+
result.push({
|
|
774
|
+
...at,
|
|
775
|
+
token: (style.ground ? ground : ink),
|
|
776
|
+
check: label,
|
|
777
|
+
ratio,
|
|
778
|
+
needs: 4.5,
|
|
779
|
+
message: `Theme "${name}" (${mode}): parts.${part}: ${said(ink)} on ${said(ground)} contrast is ${ratio.toFixed(2)}:1; normal text needs 4.5:1.`,
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
return result;
|
|
783
|
+
}
|
|
784
|
+
/** The ink a part's words are drawn in when its style says none: the menu's for the menu's parts, the link's for a
|
|
785
|
+
link; a primary button's is the ink drawn on the accent fill. */
|
|
786
|
+
function partInk(part) {
|
|
787
|
+
if (part === 'button.primary')
|
|
788
|
+
return 'onAccent';
|
|
789
|
+
if (/^(?:nav|bar|logo)\b/.test(part))
|
|
790
|
+
return 'navInk';
|
|
791
|
+
return part === 'link' ? 'link' : 'ink';
|
|
792
|
+
}
|
|
793
|
+
/** The ground a part is drawn on when its style says none. */
|
|
794
|
+
function partGround(part, palette) {
|
|
795
|
+
if (/^(?:nav|bar|logo)\b/.test(part))
|
|
796
|
+
return 'navGround';
|
|
797
|
+
if (part === 'button.primary')
|
|
798
|
+
return 'accent';
|
|
799
|
+
return (part === 'ground' || part === 'page.title') && palette.ground ? 'ground' : 'surface';
|
|
800
|
+
}
|