@elytracms/studio 0.0.38 → 0.0.39
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/convex/cms.ts +8 -2
- package/dist/components/app-views/default-modules.d.ts +60 -5
- package/dist/components/app-views/default-modules.js +53 -15
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/app-views/module.d.ts +2 -0
- package/dist/components/app-views/module.js +8 -0
- package/dist/components/app-views/module.js.map +1 -1
- package/dist/components/assets/asset-detail.d.ts +24 -0
- package/dist/components/assets/asset-detail.js +163 -0
- package/dist/components/assets/asset-detail.js.map +1 -0
- package/dist/components/assets/asset-grid.d.ts +28 -0
- package/dist/components/assets/asset-grid.js +68 -0
- package/dist/components/assets/asset-grid.js.map +1 -0
- package/dist/components/assets/asset-metadata.d.ts +19 -0
- package/dist/components/assets/asset-metadata.js +44 -0
- package/dist/components/assets/asset-metadata.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.d.ts +54 -0
- package/dist/components/assets/assets-collection-view-source.js +186 -0
- package/dist/components/assets/assets-collection-view-source.js.map +1 -0
- package/dist/components/assets/assets-module.d.ts +18 -0
- package/dist/components/assets/assets-module.js +94 -0
- package/dist/components/assets/assets-module.js.map +1 -0
- package/dist/components/assets/assets-store.js +2 -7
- package/dist/components/assets/assets-store.js.map +1 -1
- package/dist/components/assets/assets-view-preference.d.ts +15 -0
- package/dist/components/assets/assets-view-preference.js +22 -0
- package/dist/components/assets/assets-view-preference.js.map +1 -0
- package/dist/components/builder/builder-store.d.ts +12 -0
- package/dist/components/builder/builder-store.js +18 -16
- package/dist/components/builder/builder-store.js.map +1 -1
- package/dist/components/builder/clipboard-actions.js +5 -14
- package/dist/components/builder/clipboard-actions.js.map +1 -1
- package/dist/components/builder/composition-field-editor-lazy.d.ts +19 -0
- package/dist/components/builder/composition-field-editor-lazy.js +29 -0
- package/dist/components/builder/composition-field-editor-lazy.js.map +1 -0
- package/dist/components/builder/composition-field-editor.js +1 -4
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/insert-context.d.ts +8 -0
- package/dist/components/builder/insert-context.js +1 -3
- package/dist/components/builder/insert-context.js.map +1 -1
- package/dist/components/builder/page-tree.js +1 -3
- package/dist/components/builder/page-tree.js.map +1 -1
- package/dist/components/builder/property-inspector.js +7 -134
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +15 -4
- package/dist/components/cms/cms-store.js +42 -24
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.d.ts +8 -1
- package/dist/components/cms/collection-table.js +62 -10
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/column-width.d.ts +25 -0
- package/dist/components/cms/column-width.js +47 -0
- package/dist/components/cms/column-width.js.map +1 -0
- package/dist/components/cms/detail-editor.js +101 -68
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/discard-draft.d.ts +32 -0
- package/dist/components/cms/discard-draft.js +13 -0
- package/dist/components/cms/discard-draft.js.map +1 -0
- package/dist/components/cms/document-staging.d.ts +7 -0
- package/dist/components/cms/document-staging.js +44 -0
- package/dist/components/cms/document-staging.js.map +1 -0
- package/dist/components/cms/field-connection.d.ts +22 -0
- package/dist/components/cms/field-connection.js +37 -0
- package/dist/components/cms/field-connection.js.map +1 -0
- package/dist/components/cms/field-control.js +158 -34
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/prop-fields.d.ts +43 -0
- package/dist/components/cms/prop-fields.js +130 -0
- package/dist/components/cms/prop-fields.js.map +1 -0
- package/dist/components/cms/rich-text/component-embed.d.ts +17 -3
- package/dist/components/cms/rich-text/component-embed.js +29 -7
- package/dist/components/cms/rich-text/component-embed.js.map +1 -1
- package/dist/components/cms/rich-text/embed-props.d.ts +49 -0
- package/dist/components/cms/rich-text/embed-props.js +106 -0
- package/dist/components/cms/rich-text/embed-props.js.map +1 -0
- package/dist/components/cms/rich-text/index.d.ts +6 -1
- package/dist/components/cms/rich-text/index.js +6 -1
- package/dist/components/cms/rich-text/index.js.map +1 -1
- package/dist/components/cms/rich-text/lazy.d.ts +19 -0
- package/dist/components/cms/rich-text/lazy.js +26 -0
- package/dist/components/cms/rich-text/lazy.js.map +1 -0
- package/dist/components/cms/row-actions.d.ts +28 -0
- package/dist/components/cms/row-actions.js +20 -0
- package/dist/components/cms/row-actions.js.map +1 -0
- package/dist/components/cms/use-document-versions.d.ts +8 -1
- package/dist/components/cms/use-document-versions.js +31 -4
- package/dist/components/cms/use-document-versions.js.map +1 -1
- package/dist/components/cms/version-compare-lazy.d.ts +1 -0
- package/dist/components/cms/version-compare-lazy.js +23 -0
- package/dist/components/cms/version-compare-lazy.js.map +1 -0
- package/dist/components/cms/version-compare-text.d.ts +46 -0
- package/dist/components/cms/version-compare-text.js +78 -0
- package/dist/components/cms/version-compare-text.js.map +1 -1
- package/dist/components/cms/version-compare.d.ts +9 -5
- package/dist/components/cms/version-compare.js +184 -59
- package/dist/components/cms/version-compare.js.map +1 -1
- package/dist/components/cms/versions-pane.js +10 -4
- package/dist/components/cms/versions-pane.js.map +1 -1
- package/dist/components/components/component-detail.d.ts +5 -3
- package/dist/components/components/component-detail.js +41 -8
- package/dist/components/components/component-detail.js.map +1 -1
- package/dist/components/components/component-preview.d.ts +61 -0
- package/dist/components/components/component-preview.js +103 -0
- package/dist/components/components/component-preview.js.map +1 -0
- package/dist/components/components/components-index.d.ts +5 -3
- package/dist/components/components/components-index.js +19 -24
- package/dist/components/components/components-index.js.map +1 -1
- package/dist/components/components/registry-browser.d.ts +7 -0
- package/dist/components/components/registry-browser.js +20 -36
- package/dist/components/components/registry-browser.js.map +1 -1
- package/dist/components/components/taxonomy.d.ts +51 -0
- package/dist/components/components/taxonomy.js +73 -0
- package/dist/components/components/taxonomy.js.map +1 -0
- package/dist/components/components/transfer-dialog.d.ts +20 -0
- package/dist/components/components/transfer-dialog.js +64 -0
- package/dist/components/components/transfer-dialog.js.map +1 -0
- package/dist/components/components/transfer.d.ts +43 -0
- package/dist/components/components/transfer.js +31 -0
- package/dist/components/components/transfer.js.map +1 -0
- package/dist/components/project-list.js +3 -4
- package/dist/components/project-list.js.map +1 -1
- package/dist/components/project-switcher.js +3 -2
- package/dist/components/project-switcher.js.map +1 -1
- package/dist/components/rail-store-status.d.ts +21 -0
- package/dist/components/rail-store-status.js +21 -0
- package/dist/components/rail-store-status.js.map +1 -0
- package/dist/components/redirects/redirects-module.js +4 -8
- package/dist/components/redirects/redirects-module.js.map +1 -1
- package/dist/components/redirects/redirects-store.js +2 -7
- package/dist/components/redirects/redirects-store.js.map +1 -1
- package/dist/components/sign-in-needed-badge.d.ts +6 -0
- package/dist/components/sign-in-needed-badge.js +13 -0
- package/dist/components/sign-in-needed-badge.js.map +1 -0
- package/dist/components/studio-project-screen.js +1 -4
- package/dist/components/studio-project-screen.js.map +1 -1
- package/dist/components/studio-shell.js +1 -4
- package/dist/components/studio-shell.js.map +1 -1
- package/dist/components/users/users-module.js +5 -7
- package/dist/components/users/users-module.js.map +1 -1
- package/dist/components/users/users-store.js +2 -7
- package/dist/components/users/users-store.js.map +1 -1
- package/dist/lib/auth/index.js +1 -1
- package/dist/lib/auth/index.js.map +1 -1
- package/dist/lib/clipboard/paste-target.js +1 -3
- package/dist/lib/clipboard/paste-target.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +0 -2
- package/dist/lib/cms-views/view-model.js +0 -7
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/diff-view-preference.d.ts +7 -0
- package/dist/lib/diff-view-preference.js +21 -0
- package/dist/lib/diff-view-preference.js.map +1 -0
- package/dist/lib/i18n/messages.en.js +79 -30
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/local-preference.d.ts +11 -0
- package/dist/lib/local-preference.js +26 -0
- package/dist/lib/local-preference.js.map +1 -0
- package/dist/lib/locale/fallback.d.ts +45 -0
- package/dist/lib/locale/fallback.js +85 -0
- package/dist/lib/locale/fallback.js.map +1 -1
- package/dist/lib/persona/labels.d.ts +6 -0
- package/dist/lib/persona/labels.js +8 -0
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/query/index.d.ts +1 -1
- package/dist/lib/query/index.js +1 -1
- package/dist/lib/query/index.js.map +1 -1
- package/dist/lib/query/operations-query.d.ts +14 -2
- package/dist/lib/query/operations-query.js +11 -6
- package/dist/lib/query/operations-query.js.map +1 -1
- package/dist/lib/rail.d.ts +0 -6
- package/dist/lib/rail.js +6 -25
- package/dist/lib/rail.js.map +1 -1
- package/dist/lib/theme.d.ts +0 -6
- package/dist/lib/theme.js +10 -13
- package/dist/lib/theme.js.map +1 -1
- package/dist/router/match.d.ts +1 -0
- package/dist/router/match.js +4 -0
- package/dist/router/match.js.map +1 -1
- package/dist/ui/button.d.ts +1 -1
- package/package.json +22 -25
|
@@ -49,4 +49,89 @@ export function editCreatesVariant(collection, doc, fieldName, locale, config) {
|
|
|
49
49
|
export function localeChipLabel(locale) {
|
|
50
50
|
return locale.toUpperCase();
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* The active-locale value bag: the document's shared `values` overlaid with the
|
|
54
|
+
* active locale's variants (`localized[locale]`). The merged view a surface reads
|
|
55
|
+
* to evaluate conditional-visibility predicates (EC-273) and derive the auto-slug
|
|
56
|
+
* source (EC-315) — deduped between the detail editor and the slug control. Pure.
|
|
57
|
+
*/
|
|
58
|
+
export function activeLocaleValues(doc, locale) {
|
|
59
|
+
return { ...doc.values, ...(doc.localized?.[locale] ?? {}) };
|
|
60
|
+
}
|
|
61
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
62
|
+
// EC-318 — connected fields
|
|
63
|
+
//
|
|
64
|
+
// The Sanity/Contentful "connected field" model, over the SAME present/absent-key
|
|
65
|
+
// storage EC-176 already uses. Connection state is DERIVED, never stored:
|
|
66
|
+
// • connected = NO per-locale key → the field FOLLOWS the base-locale value.
|
|
67
|
+
// • diverged = a per-locale key EXISTS → the field OWNS a locale value.
|
|
68
|
+
// Editing a connected field diverges it (writes the key); "fill from base" seeds a
|
|
69
|
+
// divergent copy; "reset to base" deletes the key to reconnect.
|
|
70
|
+
//
|
|
71
|
+
// Accepted limitation (a deliberate product decision, not an oversight): an
|
|
72
|
+
// intentionally-EMPTY divergent value is not representable. The built-in controls
|
|
73
|
+
// store a cleared value as `undefined`, which DELETES the key — so clearing a
|
|
74
|
+
// localized field reconnects it to base rather than diverging it to empty.
|
|
75
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
76
|
+
/** The "no value" sentinels the built-in controls store (nothing to fill from). */
|
|
77
|
+
function isBlankValue(value) {
|
|
78
|
+
return value === undefined || value === null || value === '';
|
|
79
|
+
}
|
|
80
|
+
/** Derive the connected/diverged state of `field` in `locale` (EC-318). */
|
|
81
|
+
export function fieldConnection(collection, doc, fieldName, locale, config) {
|
|
82
|
+
if (!isFieldLocalized(collection, fieldName))
|
|
83
|
+
return null;
|
|
84
|
+
const baseLocale = documentDefaultLocale(doc, config);
|
|
85
|
+
if (locale === baseLocale)
|
|
86
|
+
return null;
|
|
87
|
+
const baseValue = resolveField(collection, doc, fieldName, baseLocale, config).value;
|
|
88
|
+
return {
|
|
89
|
+
baseLocale,
|
|
90
|
+
connected: !hasLocaleVariant(doc, fieldName, locale),
|
|
91
|
+
baseValue,
|
|
92
|
+
canFillFromBase: !isBlankValue(baseValue),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The present/absent-key write for one field — the FOUNDATION of the connected-
|
|
97
|
+
* field model (EC-176/EC-318). A localized field's value lives under
|
|
98
|
+
* `localized[locale][field]`; writing `undefined` DELETES that key (reconnecting
|
|
99
|
+
* the field to base — the accepted empty-value limitation above). Non-localized
|
|
100
|
+
* fields write the shared `values` bag. Pure — returns a new document.
|
|
101
|
+
*/
|
|
102
|
+
export function setFieldValue(collection, doc, fieldName, locale, value) {
|
|
103
|
+
if (isFieldLocalized(collection, fieldName)) {
|
|
104
|
+
const perLocale = { ...(doc.localized?.[locale] ?? {}) };
|
|
105
|
+
if (value === undefined)
|
|
106
|
+
delete perLocale[fieldName];
|
|
107
|
+
else
|
|
108
|
+
perLocale[fieldName] = value;
|
|
109
|
+
return { ...doc, localized: { ...doc.localized, [locale]: perLocale } };
|
|
110
|
+
}
|
|
111
|
+
const values = { ...doc.values };
|
|
112
|
+
if (value === undefined)
|
|
113
|
+
delete values[fieldName];
|
|
114
|
+
else
|
|
115
|
+
values[fieldName] = value;
|
|
116
|
+
return { ...doc, values };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* "Fill from base" (EC-318) — copy the current base-locale value into the active
|
|
120
|
+
* locale, instantly DISCONNECTING the field with a pre-filled starting point. A
|
|
121
|
+
* no-op when there is nothing to copy (blank base value). Pure.
|
|
122
|
+
*/
|
|
123
|
+
export function fillFieldFromBase(collection, doc, fieldName, locale, config) {
|
|
124
|
+
const connection = fieldConnection(collection, doc, fieldName, locale, config);
|
|
125
|
+
if (!connection || !connection.canFillFromBase)
|
|
126
|
+
return doc;
|
|
127
|
+
return setFieldValue(collection, doc, fieldName, locale, connection.baseValue);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* "Reset to base" / reconnect (EC-318) — delete the active-locale variant so the
|
|
131
|
+
* field follows the base value again. Discards the translation, so gate it behind
|
|
132
|
+
* a confirm. Pure.
|
|
133
|
+
*/
|
|
134
|
+
export function resetFieldToBase(collection, doc, fieldName, locale) {
|
|
135
|
+
return setFieldValue(collection, doc, fieldName, locale, undefined);
|
|
136
|
+
}
|
|
52
137
|
//# sourceMappingURL=fallback.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fallback.js","sourceRoot":"","sources":["../../../src/lib/locale/fallback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAmBhG,iFAAiF;AACjF,MAAM,UAAU,gBAAgB,CAAC,GAAgB,EAAE,SAAiB,EAAE,MAAc;IAClF,MAAM,GAAG,GAAG,GAAG,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,CAAA;IACnC,OAAO,GAAG,KAAK,SAAS,IAAI,SAAS,IAAI,GAAG,CAAA;AAC9C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,UAAyB,EACzB,GAAgB,EAChB,SAAiB,EACjB,MAAc,EACd,MAAoB;IAEpB,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE,SAAS,CAAC;QAAE,OAAO,IAAI,CAAA;IACzD,IAAI,gBAAgB,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC;QAAE,OAAO,IAAI,CAAA;IACzD,MAAM,QAAQ,GAAG,YAAY,CAAC,UAAU,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IACzE,IAAI,CAAC,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IACnE,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,YAAY,EAAE,eAAe,EAAE,MAAM,EAAE,CAAA;AACzE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAyB,EACzB,GAAgB,EAChB,SAAiB,EACjB,MAAc,EACd,MAAoB;IAEpB,OAAO;QACL,KAAK,EAAE,YAAY,CAAC,UAAU,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK;QACrE,QAAQ,EAAE,sBAAsB,CAAC,UAAU,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC;KAC7E,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,UAAyB,EACzB,GAAgB,EAChB,SAAiB,EACjB,MAAc,EACd,MAAoB;IAEpB,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE,SAAS,CAAC;QAAE,OAAO,KAAK,CAAA;IAC1D,IAAI,MAAM,KAAK,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IAC/D,OAAO,CAAC,gBAAgB,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,CAAA;AAClD,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,OAAO,MAAM,CAAC,WAAW,EAAE,CAAA;AAC7B,CAAC","sourcesContent":["import { documentDefaultLocale, isFieldLocalized, resolveField } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef, Locale, LocaleConfig } from '@elytracms/core/cms-core'\n\n/**\n * Fallback visibility (EC-176) — the pure \"is this displayed value a\n * fallback?\" decision the CMS table cells, detail-editor fields, and the\n * builder's Content tab all share. One function answers it, so a value is\n * either labeled everywhere or a real translation everywhere — never\n * silently mixed.\n */\n\n/** A displayed value that came from the fallback locale, not the active one. */\nexport interface DisplayedFallback {\n /** The locale the rendered value was actually read from. */\n sourceLocale: Locale\n /** The locale the surface asked for (the ambient active locale). */\n requestedLocale: Locale\n}\n\n/** True when the document stores an explicit variant of `field` for `locale`. */\nexport function hasLocaleVariant(doc: CmsDocument, fieldName: string, locale: Locale): boolean {\n const bag = doc.localized?.[locale]\n return bag !== undefined && fieldName in bag\n}\n\n/**\n * Whether the value a surface displays for `field` in `locale` is served via\n * EC-018 default-locale fallback. `null` means \"not a fallback\": the field is\n * not localized (locale-independent by design), the active locale has its own\n * variant, or there is nothing to display at all (an empty field is empty,\n * not a fallback).\n */\nexport function displayedFieldFallback(\n collection: CollectionDef,\n doc: CmsDocument,\n fieldName: string,\n locale: Locale,\n config: LocaleConfig,\n): DisplayedFallback | null {\n if (!isFieldLocalized(collection, fieldName)) return null\n if (hasLocaleVariant(doc, fieldName, locale)) return null\n const resolved = resolveField(collection, doc, fieldName, locale, config)\n if (!resolved.fellBack || resolved.value === undefined) return null\n return { sourceLocale: resolved.sourceLocale, requestedLocale: locale }\n}\n\n/**\n * The value a surface should display for `field` in `locale` (explicit\n * variant, else fallback) together with its fallback marker. Display and\n * label come from the same resolution, so they can never disagree.\n */\nexport function resolveDisplayedValue(\n collection: CollectionDef,\n doc: CmsDocument,\n fieldName: string,\n locale: Locale,\n config: LocaleConfig,\n): { value: unknown; fallback: DisplayedFallback | null } {\n return {\n value: resolveField(collection, doc, fieldName, locale, config).value,\n fallback: displayedFieldFallback(collection, doc, fieldName, locale, config),\n }\n}\n\n/**\n * True when editing `field` in `locale` would CREATE a new localized variant\n * (rather than update an existing one): the field is localized and the active\n * locale isn't the document's fallback source and has no variant yet. Drives\n * the \"translating from EN\" affordance on editors.\n */\nexport function editCreatesVariant(\n collection: CollectionDef,\n doc: CmsDocument,\n fieldName: string,\n locale: Locale,\n config: LocaleConfig,\n): boolean {\n if (!isFieldLocalized(collection, fieldName)) return false\n if (locale === documentDefaultLocale(doc, config)) return false\n return !hasLocaleVariant(doc, fieldName, locale)\n}\n\n/** Compact display form of a locale code for chips/badges (\"en\" → \"EN\"). */\nexport function localeChipLabel(locale: Locale): string {\n return locale.toUpperCase()\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fallback.js","sourceRoot":"","sources":["../../../src/lib/locale/fallback.ts"],"names":[],"mappings":"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","sourcesContent":["import { documentDefaultLocale, isFieldLocalized, resolveField } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef, Locale, LocaleConfig } from '@elytracms/core/cms-core'\n\n/**\n * Fallback visibility (EC-176) — the pure \"is this displayed value a\n * fallback?\" decision the CMS table cells, detail-editor fields, and the\n * builder's Content tab all share. One function answers it, so a value is\n * either labeled everywhere or a real translation everywhere — never\n * silently mixed.\n */\n\n/** A displayed value that came from the fallback locale, not the active one. */\nexport interface DisplayedFallback {\n /** The locale the rendered value was actually read from. */\n sourceLocale: Locale\n /** The locale the surface asked for (the ambient active locale). */\n requestedLocale: Locale\n}\n\n/** True when the document stores an explicit variant of `field` for `locale`. */\nexport function hasLocaleVariant(doc: CmsDocument, fieldName: string, locale: Locale): boolean {\n const bag = doc.localized?.[locale]\n return bag !== undefined && fieldName in bag\n}\n\n/**\n * Whether the value a surface displays for `field` in `locale` is served via\n * EC-018 default-locale fallback. `null` means \"not a fallback\": the field is\n * not localized (locale-independent by design), the active locale has its own\n * variant, or there is nothing to display at all (an empty field is empty,\n * not a fallback).\n */\nexport function displayedFieldFallback(\n collection: CollectionDef,\n doc: CmsDocument,\n fieldName: string,\n locale: Locale,\n config: LocaleConfig,\n): DisplayedFallback | null {\n if (!isFieldLocalized(collection, fieldName)) return null\n if (hasLocaleVariant(doc, fieldName, locale)) return null\n const resolved = resolveField(collection, doc, fieldName, locale, config)\n if (!resolved.fellBack || resolved.value === undefined) return null\n return { sourceLocale: resolved.sourceLocale, requestedLocale: locale }\n}\n\n/**\n * The value a surface should display for `field` in `locale` (explicit\n * variant, else fallback) together with its fallback marker. Display and\n * label come from the same resolution, so they can never disagree.\n */\nexport function resolveDisplayedValue(\n collection: CollectionDef,\n doc: CmsDocument,\n fieldName: string,\n locale: Locale,\n config: LocaleConfig,\n): { value: unknown; fallback: DisplayedFallback | null } {\n return {\n value: resolveField(collection, doc, fieldName, locale, config).value,\n fallback: displayedFieldFallback(collection, doc, fieldName, locale, config),\n }\n}\n\n/**\n * True when editing `field` in `locale` would CREATE a new localized variant\n * (rather than update an existing one): the field is localized and the active\n * locale isn't the document's fallback source and has no variant yet. Drives\n * the \"translating from EN\" affordance on editors.\n */\nexport function editCreatesVariant(\n collection: CollectionDef,\n doc: CmsDocument,\n fieldName: string,\n locale: Locale,\n config: LocaleConfig,\n): boolean {\n if (!isFieldLocalized(collection, fieldName)) return false\n if (locale === documentDefaultLocale(doc, config)) return false\n return !hasLocaleVariant(doc, fieldName, locale)\n}\n\n/** Compact display form of a locale code for chips/badges (\"en\" → \"EN\"). */\nexport function localeChipLabel(locale: Locale): string {\n return locale.toUpperCase()\n}\n\n/**\n * The active-locale value bag: the document's shared `values` overlaid with the\n * active locale's variants (`localized[locale]`). The merged view a surface reads\n * to evaluate conditional-visibility predicates (EC-273) and derive the auto-slug\n * source (EC-315) — deduped between the detail editor and the slug control. Pure.\n */\nexport function activeLocaleValues(doc: CmsDocument, locale: Locale): Record<string, unknown> {\n return { ...doc.values, ...(doc.localized?.[locale] ?? {}) }\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n// EC-318 — connected fields\n//\n// The Sanity/Contentful \"connected field\" model, over the SAME present/absent-key\n// storage EC-176 already uses. Connection state is DERIVED, never stored:\n// • connected = NO per-locale key → the field FOLLOWS the base-locale value.\n// • diverged = a per-locale key EXISTS → the field OWNS a locale value.\n// Editing a connected field diverges it (writes the key); \"fill from base\" seeds a\n// divergent copy; \"reset to base\" deletes the key to reconnect.\n//\n// Accepted limitation (a deliberate product decision, not an oversight): an\n// intentionally-EMPTY divergent value is not representable. The built-in controls\n// store a cleared value as `undefined`, which DELETES the key — so clearing a\n// localized field reconnects it to base rather than diverging it to empty.\n// ───────────────────────────────────────────────────────────────────────────\n\n/** The \"no value\" sentinels the built-in controls store (nothing to fill from). */\nfunction isBlankValue(value: unknown): boolean {\n return value === undefined || value === null || value === ''\n}\n\n/**\n * The per-locale CONNECTION state of a localized field (EC-318), derived from the\n * document — never stored. `null` when the connected-field affordance does not\n * apply: the field isn't localized, or the requested locale IS the document's base\n * locale (there is nothing to follow — the base owns the value).\n */\nexport interface FieldConnection {\n /** The document's base/default locale — the value a connected field follows. */\n baseLocale: Locale\n /** True when NO per-locale key exists: the field follows the base value. */\n connected: boolean\n /** The current base-locale value (\"fill from base\"/\"reset\" act against it). */\n baseValue: unknown\n /** True when the base holds a non-empty value there is something to fill from. */\n canFillFromBase: boolean\n}\n\n/** Derive the connected/diverged state of `field` in `locale` (EC-318). */\nexport function fieldConnection(\n collection: CollectionDef,\n doc: CmsDocument,\n fieldName: string,\n locale: Locale,\n config: LocaleConfig,\n): FieldConnection | null {\n if (!isFieldLocalized(collection, fieldName)) return null\n const baseLocale = documentDefaultLocale(doc, config)\n if (locale === baseLocale) return null\n const baseValue = resolveField(collection, doc, fieldName, baseLocale, config).value\n return {\n baseLocale,\n connected: !hasLocaleVariant(doc, fieldName, locale),\n baseValue,\n canFillFromBase: !isBlankValue(baseValue),\n }\n}\n\n/**\n * The present/absent-key write for one field — the FOUNDATION of the connected-\n * field model (EC-176/EC-318). A localized field's value lives under\n * `localized[locale][field]`; writing `undefined` DELETES that key (reconnecting\n * the field to base — the accepted empty-value limitation above). Non-localized\n * fields write the shared `values` bag. Pure — returns a new document.\n */\nexport function setFieldValue(\n collection: CollectionDef,\n doc: CmsDocument,\n fieldName: string,\n locale: Locale,\n value: unknown,\n): CmsDocument {\n if (isFieldLocalized(collection, fieldName)) {\n const perLocale = { ...(doc.localized?.[locale] ?? {}) }\n if (value === undefined) delete perLocale[fieldName]\n else perLocale[fieldName] = value\n return { ...doc, localized: { ...doc.localized, [locale]: perLocale } }\n }\n const values = { ...doc.values }\n if (value === undefined) delete values[fieldName]\n else values[fieldName] = value\n return { ...doc, values }\n}\n\n/**\n * \"Fill from base\" (EC-318) — copy the current base-locale value into the active\n * locale, instantly DISCONNECTING the field with a pre-filled starting point. A\n * no-op when there is nothing to copy (blank base value). Pure.\n */\nexport function fillFieldFromBase(\n collection: CollectionDef,\n doc: CmsDocument,\n fieldName: string,\n locale: Locale,\n config: LocaleConfig,\n): CmsDocument {\n const connection = fieldConnection(collection, doc, fieldName, locale, config)\n if (!connection || !connection.canFillFromBase) return doc\n return setFieldValue(collection, doc, fieldName, locale, connection.baseValue)\n}\n\n/**\n * \"Reset to base\" / reconnect (EC-318) — delete the active-locale variant so the\n * field follows the base value again. Discards the translation, so gate it behind\n * a confirm. Pure.\n */\nexport function resetFieldToBase(\n collection: CollectionDef,\n doc: CmsDocument,\n fieldName: string,\n locale: Locale,\n): CmsDocument {\n return setFieldValue(collection, doc, fieldName, locale, undefined)\n}\n"]}
|
|
@@ -14,6 +14,12 @@ export declare function exportTargetLabel(target: string): string;
|
|
|
14
14
|
export declare function conditionOperatorLabel(operator: ConditionOperator): string;
|
|
15
15
|
/** The leaf of a component id, namespace stripped (`base.Heading` → `Heading`). */
|
|
16
16
|
export declare function componentLeafId(componentId: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Strip the `base.`/`project.` namespace prefix from a component id for compact,
|
|
19
|
+
* editor-facing labels (`base.Heading` → `Heading`). Unlike {@link componentLeafId}
|
|
20
|
+
* it removes only those two known prefixes, so a dotted custom id keeps its shape.
|
|
21
|
+
*/
|
|
22
|
+
export declare function shortId(componentId: string): string;
|
|
17
23
|
/**
|
|
18
24
|
* The human display name of a component for editor-facing surfaces: the
|
|
19
25
|
* manifest's authored `title` when present, else the humanized leaf of its id
|
|
@@ -113,6 +113,14 @@ export function componentLeafId(componentId) {
|
|
|
113
113
|
const dot = componentId.lastIndexOf('.');
|
|
114
114
|
return dot === -1 ? componentId : componentId.slice(dot + 1);
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Strip the `base.`/`project.` namespace prefix from a component id for compact,
|
|
118
|
+
* editor-facing labels (`base.Heading` → `Heading`). Unlike {@link componentLeafId}
|
|
119
|
+
* it removes only those two known prefixes, so a dotted custom id keeps its shape.
|
|
120
|
+
*/
|
|
121
|
+
export function shortId(componentId) {
|
|
122
|
+
return componentId.replace('base.', '').replace('project.', '');
|
|
123
|
+
}
|
|
116
124
|
/**
|
|
117
125
|
* The human display name of a component for editor-facing surfaces: the
|
|
118
126
|
* manifest's authored `title` when present, else the humanized leaf of its id
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"labels.js","sourceRoot":"","sources":["../../../src/lib/persona/labels.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IssueCode, ConditionOperator } from '@elytracms/core/project-graph'\nimport type { CmsIssueCode } from '@elytracms/core/cms-core'\nimport type { OperationErrorCode } from '@elytracms/core/operations'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { humanizeName } from '../utils'\n\n/**\n * Persona-aware labels (EC-192 item 2, Milestone B promise).\n *\n * A non-technical editor must never see a manifest/binding/path-token/validation\n * identifier on — or one click off — the editor path. The engine speaks in stable\n * machine codes (`issueCodeSchema`, `cmsIssueCodeSchema`, `operationErrorCodeSchema`)\n * and machine ids (`base.Heading`, `r-blog-slug-en`); this module is the\n * single translation layer the editor-facing surfaces route through.\n *\n * The maps are exhaustive `Record<…Code, string>` over the Zod enums, so adding a\n * code in `@elytracms/core/project-graph`, `@elytracms/core/cms-core`, or `@elytracms/core/operations`\n * fails typecheck here until it is given an editor-facing label — the persona\n * boundary cannot silently regress. Engineers keep the raw code: every surface that\n * shows a label also carries the code in a `title=`/`data-*` attribute (inspectable\n * via hover/devtools, never rendered as visible copy).\n */\n\nconst GRAPH_ISSUE_LABELS: Record<IssueCode, string> = {\n 'invalid-component-name': 'Invalid component',\n 'unknown-component': 'Unknown component',\n 'duplicate-node-id': 'Duplicate item',\n 'unknown-prop': 'Unknown setting',\n 'invalid-prop': 'Invalid setting',\n 'binding-not-allowed': 'Data not allowed here',\n 'unresolved-binding': 'Missing data',\n 'missing-required-slot': 'Missing content',\n 'unknown-slot': 'Unknown content area',\n 'forbidden-component': 'Not allowed here',\n 'server-only-component': 'Not available in the editor',\n 'invalid-container-direction': 'Invalid layout',\n 'invalid-container-alignment': 'Invalid alignment',\n 'invalid-container-gap': 'Invalid spacing',\n 'invalid-container-columns': 'Invalid columns',\n 'invalid-container-responsive': 'Invalid responsive setting',\n}\n\nconst CMS_ISSUE_LABELS: Record<CmsIssueCode, string> = {\n 'duplicate-collection': 'Duplicate collection',\n 'duplicate-field': 'Duplicate field',\n 'unknown-collection': 'Unknown collection',\n 'invalid-collection-config': 'Invalid collection setup',\n 'invalid-field-config': 'Invalid field setup',\n 'reserved-field-name': 'Reserved field name',\n 'missing-required-field': 'Missing required field',\n 'invalid-field-value': 'Invalid value',\n 'unknown-relation-target': 'Missing reference',\n 'unpublished-relation-target': 'Unpublished reference',\n 'cardinality-violation': 'Wrong number of values',\n 'unknown-asset': 'Missing asset',\n 'duplicate-document': 'Duplicate entry',\n 'unknown-locale': 'Unknown language',\n 'missing-localized-value': 'Missing translation',\n 'route-conflict': 'Conflicting URL',\n 'redirect-loop': 'Redirect loop',\n 'unknown-route-target': 'Missing page',\n 'hierarchy-cycle': 'Page nesting loop',\n 'document-nesting-depth': 'Content nested too deeply',\n 'singleton-document-drift': 'More than one document',\n 'duplicate-alias': 'Slot used twice',\n 'unknown-alias': 'Unknown slot',\n 'unfilled-slot': 'Empty slot',\n 'custom-validation': 'Validation rule',\n 'unknown-version': 'Unknown version',\n}\n\nconst OPERATION_ERROR_LABELS: Record<OperationErrorCode, string> = {\n validation: \"Couldn't save\",\n 'not-found': 'Not found',\n conflict: 'Already exists',\n 'backend-unavailable': 'Connection problem',\n unauthorized: 'Not allowed',\n unknown: 'Something went wrong',\n}\n\nconst EXPORT_TARGET_LABELS: Record<string, string> = {\n nextjs: 'Next.js',\n}\n\n/**\n * Plain-language labels for condition operators. Exhaustive over\n * `conditionOperatorSchema` — a new operator fails typecheck here until it has an\n * editor-facing label. The raw operator (`eq`, `gte`, …) is a machine token an\n * editor must never read in the inspector.\n */\nconst CONDITION_OPERATOR_LABELS: Record<ConditionOperator, string> = {\n truthy: 'Is set',\n exists: 'Exists',\n eq: 'Equals',\n neq: 'Does not equal',\n gt: 'Greater than',\n gte: 'At least',\n lt: 'Less than',\n lte: 'At most',\n}\n\n/** Plain-language title for a graph validation issue code. */\nexport function graphIssueLabel(code: IssueCode): string {\n return GRAPH_ISSUE_LABELS[code]\n}\n\n/** Plain-language title for a CMS validation issue code. */\nexport function cmsIssueLabel(code: CmsIssueCode): string {\n return CMS_ISSUE_LABELS[code]\n}\n\n/** Plain-language title for an operation failure code. */\nexport function operationErrorLabel(code: OperationErrorCode): string {\n return OPERATION_ERROR_LABELS[code] ?? OPERATION_ERROR_LABELS.unknown\n}\n\n/** Friendly name for a project export target (`nextjs` → `Next.js`). */\nexport function exportTargetLabel(target: string): string {\n return EXPORT_TARGET_LABELS[target] ?? humanizeName(target)\n}\n\n/** Plain-language label for a condition operator (`gte` → `At least`). */\nexport function conditionOperatorLabel(operator: ConditionOperator): string {\n return CONDITION_OPERATOR_LABELS[operator]\n}\n\n/** The leaf of a component id, namespace stripped (`base.Heading` → `Heading`). */\nexport function componentLeafId(componentId: string): string {\n const dot = componentId.lastIndexOf('.')\n return dot === -1 ? componentId : componentId.slice(dot + 1)\n}\n\n/**\n * The human display name of a component for editor-facing surfaces: the\n * manifest's authored `title` when present, else the humanized leaf of its id\n * (`base.Heading` → `Heading`, `project.MarketingHero` → `Marketing hero`).\n * Never the raw namespaced id.\n */\nexport function componentDisplayName(\n componentId: string,\n manifest?: ComponentManifest | undefined,\n): string {\n if (manifest?.title) return manifest.title\n return humanizeName(componentLeafId(componentId))\n}\n"]}
|
|
1
|
+
{"version":3,"file":"labels.js","sourceRoot":"","sources":["../../../src/lib/persona/labels.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IssueCode, ConditionOperator } from '@elytracms/core/project-graph'\nimport type { CmsIssueCode } from '@elytracms/core/cms-core'\nimport type { OperationErrorCode } from '@elytracms/core/operations'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { humanizeName } from '../utils'\n\n/**\n * Persona-aware labels (EC-192 item 2, Milestone B promise).\n *\n * A non-technical editor must never see a manifest/binding/path-token/validation\n * identifier on — or one click off — the editor path. The engine speaks in stable\n * machine codes (`issueCodeSchema`, `cmsIssueCodeSchema`, `operationErrorCodeSchema`)\n * and machine ids (`base.Heading`, `r-blog-slug-en`); this module is the\n * single translation layer the editor-facing surfaces route through.\n *\n * The maps are exhaustive `Record<…Code, string>` over the Zod enums, so adding a\n * code in `@elytracms/core/project-graph`, `@elytracms/core/cms-core`, or `@elytracms/core/operations`\n * fails typecheck here until it is given an editor-facing label — the persona\n * boundary cannot silently regress. Engineers keep the raw code: every surface that\n * shows a label also carries the code in a `title=`/`data-*` attribute (inspectable\n * via hover/devtools, never rendered as visible copy).\n */\n\nconst GRAPH_ISSUE_LABELS: Record<IssueCode, string> = {\n 'invalid-component-name': 'Invalid component',\n 'unknown-component': 'Unknown component',\n 'duplicate-node-id': 'Duplicate item',\n 'unknown-prop': 'Unknown setting',\n 'invalid-prop': 'Invalid setting',\n 'binding-not-allowed': 'Data not allowed here',\n 'unresolved-binding': 'Missing data',\n 'missing-required-slot': 'Missing content',\n 'unknown-slot': 'Unknown content area',\n 'forbidden-component': 'Not allowed here',\n 'server-only-component': 'Not available in the editor',\n 'invalid-container-direction': 'Invalid layout',\n 'invalid-container-alignment': 'Invalid alignment',\n 'invalid-container-gap': 'Invalid spacing',\n 'invalid-container-columns': 'Invalid columns',\n 'invalid-container-responsive': 'Invalid responsive setting',\n}\n\nconst CMS_ISSUE_LABELS: Record<CmsIssueCode, string> = {\n 'duplicate-collection': 'Duplicate collection',\n 'duplicate-field': 'Duplicate field',\n 'unknown-collection': 'Unknown collection',\n 'invalid-collection-config': 'Invalid collection setup',\n 'invalid-field-config': 'Invalid field setup',\n 'reserved-field-name': 'Reserved field name',\n 'missing-required-field': 'Missing required field',\n 'invalid-field-value': 'Invalid value',\n 'unknown-relation-target': 'Missing reference',\n 'unpublished-relation-target': 'Unpublished reference',\n 'cardinality-violation': 'Wrong number of values',\n 'unknown-asset': 'Missing asset',\n 'duplicate-document': 'Duplicate entry',\n 'unknown-locale': 'Unknown language',\n 'missing-localized-value': 'Missing translation',\n 'route-conflict': 'Conflicting URL',\n 'redirect-loop': 'Redirect loop',\n 'unknown-route-target': 'Missing page',\n 'hierarchy-cycle': 'Page nesting loop',\n 'document-nesting-depth': 'Content nested too deeply',\n 'singleton-document-drift': 'More than one document',\n 'duplicate-alias': 'Slot used twice',\n 'unknown-alias': 'Unknown slot',\n 'unfilled-slot': 'Empty slot',\n 'custom-validation': 'Validation rule',\n 'unknown-version': 'Unknown version',\n}\n\nconst OPERATION_ERROR_LABELS: Record<OperationErrorCode, string> = {\n validation: \"Couldn't save\",\n 'not-found': 'Not found',\n conflict: 'Already exists',\n 'backend-unavailable': 'Connection problem',\n unauthorized: 'Not allowed',\n unknown: 'Something went wrong',\n}\n\nconst EXPORT_TARGET_LABELS: Record<string, string> = {\n nextjs: 'Next.js',\n}\n\n/**\n * Plain-language labels for condition operators. Exhaustive over\n * `conditionOperatorSchema` — a new operator fails typecheck here until it has an\n * editor-facing label. The raw operator (`eq`, `gte`, …) is a machine token an\n * editor must never read in the inspector.\n */\nconst CONDITION_OPERATOR_LABELS: Record<ConditionOperator, string> = {\n truthy: 'Is set',\n exists: 'Exists',\n eq: 'Equals',\n neq: 'Does not equal',\n gt: 'Greater than',\n gte: 'At least',\n lt: 'Less than',\n lte: 'At most',\n}\n\n/** Plain-language title for a graph validation issue code. */\nexport function graphIssueLabel(code: IssueCode): string {\n return GRAPH_ISSUE_LABELS[code]\n}\n\n/** Plain-language title for a CMS validation issue code. */\nexport function cmsIssueLabel(code: CmsIssueCode): string {\n return CMS_ISSUE_LABELS[code]\n}\n\n/** Plain-language title for an operation failure code. */\nexport function operationErrorLabel(code: OperationErrorCode): string {\n return OPERATION_ERROR_LABELS[code] ?? OPERATION_ERROR_LABELS.unknown\n}\n\n/** Friendly name for a project export target (`nextjs` → `Next.js`). */\nexport function exportTargetLabel(target: string): string {\n return EXPORT_TARGET_LABELS[target] ?? humanizeName(target)\n}\n\n/** Plain-language label for a condition operator (`gte` → `At least`). */\nexport function conditionOperatorLabel(operator: ConditionOperator): string {\n return CONDITION_OPERATOR_LABELS[operator]\n}\n\n/** The leaf of a component id, namespace stripped (`base.Heading` → `Heading`). */\nexport function componentLeafId(componentId: string): string {\n const dot = componentId.lastIndexOf('.')\n return dot === -1 ? componentId : componentId.slice(dot + 1)\n}\n\n/**\n * Strip the `base.`/`project.` namespace prefix from a component id for compact,\n * editor-facing labels (`base.Heading` → `Heading`). Unlike {@link componentLeafId}\n * it removes only those two known prefixes, so a dotted custom id keeps its shape.\n */\nexport function shortId(componentId: string): string {\n return componentId.replace('base.', '').replace('project.', '')\n}\n\n/**\n * The human display name of a component for editor-facing surfaces: the\n * manifest's authored `title` when present, else the humanized leaf of its id\n * (`base.Heading` → `Heading`, `project.MarketingHero` → `Marketing hero`).\n * Never the raw namespaced id.\n */\nexport function componentDisplayName(\n componentId: string,\n manifest?: ComponentManifest | undefined,\n): string {\n if (manifest?.title) return manifest.title\n return humanizeName(componentLeafId(componentId))\n}\n"]}
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* The studio client read seam (EC-308 / AD-17): one operations-backed query cache.
|
|
3
3
|
* See `./operations-query`.
|
|
4
4
|
*/
|
|
5
|
-
export { StudioQueryProvider, useOperationQuery, useOperationCache,
|
|
5
|
+
export { StudioQueryProvider, useOperationQuery, useOperationCache, listStoreState, OPERATION_INVALIDATE_DEBOUNCE_MS, } from './operations-query';
|
|
6
6
|
export type { OperationCache, UseOperationQueryOptions } from './operations-query';
|
package/dist/lib/query/index.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* The studio client read seam (EC-308 / AD-17): one operations-backed query cache.
|
|
3
3
|
* See `./operations-query`.
|
|
4
4
|
*/
|
|
5
|
-
export { StudioQueryProvider, useOperationQuery, useOperationCache,
|
|
5
|
+
export { StudioQueryProvider, useOperationQuery, useOperationCache, listStoreState, OPERATION_INVALIDATE_DEBOUNCE_MS, } from './operations-query';
|
|
6
6
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/query/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/query/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,gCAAgC,GACjC,MAAM,oBAAoB,CAAA","sourcesContent":["/**\n * The studio client read seam (EC-308 / AD-17): one operations-backed query cache.\n * See `./operations-query`.\n */\nexport {\n StudioQueryProvider,\n useOperationQuery,\n useOperationCache,\n listStoreState,\n OPERATION_INVALIDATE_DEBOUNCE_MS,\n} from './operations-query'\nexport type { OperationCache, UseOperationQueryOptions } from './operations-query'\n"]}
|
|
@@ -81,5 +81,17 @@ export interface OperationCache {
|
|
|
81
81
|
invalidate(queryKey: QueryKey): Promise<void>;
|
|
82
82
|
}
|
|
83
83
|
export declare function useOperationCache(): OperationCache;
|
|
84
|
-
/**
|
|
85
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Derive a list store's public `{ status, errorMessage }` from its operation query —
|
|
86
|
+
* the identical mapping the assets/users/redirects rail stores (EC-319) each
|
|
87
|
+
* hand-rolled. `errorMessage` is set only on an error; a store spreads it
|
|
88
|
+
* conditionally onto its public shape.
|
|
89
|
+
*/
|
|
90
|
+
export declare function listStoreState(query: {
|
|
91
|
+
isPending: boolean;
|
|
92
|
+
isError: boolean;
|
|
93
|
+
error: unknown;
|
|
94
|
+
}): {
|
|
95
|
+
status: 'loading' | 'ready' | 'error';
|
|
96
|
+
errorMessage: string | undefined;
|
|
97
|
+
};
|
|
@@ -114,11 +114,16 @@ export function useOperationCache() {
|
|
|
114
114
|
const invalidate = useCallback((queryKey) => queryClient.invalidateQueries({ queryKey }), [queryClient]);
|
|
115
115
|
return useMemo(() => ({ patch, invalidate }), [patch, invalidate]);
|
|
116
116
|
}
|
|
117
|
-
/**
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Derive a list store's public `{ status, errorMessage }` from its operation query —
|
|
119
|
+
* the identical mapping the assets/users/redirects rail stores (EC-319) each
|
|
120
|
+
* hand-rolled. `errorMessage` is set only on an error; a store spreads it
|
|
121
|
+
* conditionally onto its public shape.
|
|
122
|
+
*/
|
|
123
|
+
export function listStoreState(query) {
|
|
124
|
+
return {
|
|
125
|
+
status: query.isPending ? 'loading' : query.isError ? 'error' : 'ready',
|
|
126
|
+
errorMessage: query.isError && query.error instanceof Error ? query.error.message : undefined,
|
|
127
|
+
};
|
|
123
128
|
}
|
|
124
129
|
//# sourceMappingURL=operations-query.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operations-query.js","sourceRoot":"","sources":["../../../src/lib/query/operations-query.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport {\n QueryClient,\n QueryClientProvider,\n useQuery,\n useQueryClient,\n} from '@tanstack/react-query'\nimport type { QueryKey, UseQueryResult } from '@tanstack/react-query'\nimport { getStudioOperations } from '@elytracms/studio/lib/operations'\nimport type { OperationEvent, OperationsClient } from '@elytracms/studio/lib/operations'\n\n/**\n * EC-308 / AD-17 — the ONE studio client read seam.\n *\n * Every studio **server read** goes through {@link useOperationQuery} over the\n * shared operations client, cached by TanStack Query; invalidation rides the\n * operations event bus into the same cache. This replaces the bespoke per-store\n * `useState` + epoch-guard + debounce + reload machinery that each store used to\n * hand-roll (the patchwork AD-17 calls out). See\n * [`docs/client-data-flow.md`](../../../../../docs/client-data-flow.md) and\n * [`docs/principles.md`](../../../../../docs/principles.md) §12.\n *\n * It is also the **Phase-2 prep** (EC-309): reactivity/multiplayer is added by\n * swapping pull → push *behind this hook* (an adapter `subscribe` capability\n * driving `queryClient.setQueryData`), never at the call sites — so call sites\n * never change when push lands.\n *\n * Deliberately NOT here: the optimistic editing session (the draft being typed,\n * live validation, the table working view-state in `cms-store`). That is client\n * state, not server state, and stays local (principle 12).\n */\n\n/**\n * Trailing-debounce window (ms) for event-driven invalidation: a burst of\n * commands — e.g. one `documents.upsert` per keystroke from autosave, or one\n * event per row in a bulk action — coalesces into a single refetch once the\n * writes settle. (The shared replacement for the per-store `*_RELOAD_DEBOUNCE_MS`.)\n */\nexport const OPERATION_INVALIDATE_DEBOUNCE_MS = 300\n\n/** Studio-tuned cache defaults: invalidation (not polling/focus) drives freshness. */\nfunction createStudioQueryClient(): QueryClient {\n return new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5_000,\n refetchOnWindowFocus: false,\n retry: false,\n },\n },\n })\n}\n\n/**\n * Mounts the single studio `QueryClient` (one per studio lifetime, surviving\n * project switches). Mounted package-owned in `StudioProvider`, above every\n * per-project provider, so every studio hook can reach the cache.\n */\nexport function StudioQueryProvider({ children }: { children: ReactNode }): ReactNode {\n const [client] = useState(createStudioQueryClient)\n return <QueryClientProvider client={client}>{children}</QueryClientProvider>\n}\n\nexport interface UseOperationQueryOptions<T> {\n /** Stable query key; the first element is the domain tag used for grouping. */\n queryKey: QueryKey\n /** Loads the data from the shared operations client. */\n loader: (operations: OperationsClient) => Promise<T>\n /**\n * Which succeeded commands invalidate this query — a list of operation names\n * (`'documents.publish'`) and/or domains (`'documents'`). A succeeded command\n * matches when its `operation` OR `domain` is in the list. Keep it a stable\n * (module-level) array. Omit for a query that only loads once / refetches\n * manually.\n */\n invalidateOn?: readonly string[]\n /** Skip the query (e.g. fixture/playground sessions with no backend). */\n enabled?: boolean\n}\n\n/**\n * One operations-backed, cached, self-invalidating query. Thin wrapper over\n * `useQuery` whose `queryFn` resolves the shared operations client and runs the\n * loader, plus one shared invalidation subscriber (the only place that touches\n * the operations event bus for reads).\n */\nexport function useOperationQuery<T>(options: UseOperationQueryOptions<T>): UseQueryResult<T> {\n const { queryKey, loader, invalidateOn, enabled = true } = options\n const queryClient = useQueryClient()\n\n const result = useQuery<T>({\n queryKey,\n queryFn: async () => {\n const { operations } = await getStudioOperations()\n return loader(operations)\n },\n enabled,\n })\n\n // Stable serializations so an inline `queryKey`/`invalidateOn` doesn't churn the\n // subscription every render (re-subscribe only when their CONTENTS change).\n const keyJson = JSON.stringify(queryKey)\n const invalidateJson = JSON.stringify(invalidateOn ?? [])\n\n useEffect(() => {\n const triggers = new Set<string>(JSON.parse(invalidateJson) as string[])\n if (triggers.size === 0) return\n const key = JSON.parse(keyJson) as QueryKey\n let timer: ReturnType<typeof setTimeout> | undefined\n let unsubscribe: (() => void) | undefined\n let disposed = false\n const matches = (event: OperationEvent): boolean =>\n event.kind === 'command' &&\n event.status === 'succeeded' &&\n (triggers.has(event.operation) || triggers.has(event.domain))\n void getStudioOperations().then((studio) => {\n if (disposed) return\n unsubscribe = studio.operations.events.subscribe((event) => {\n if (!matches(event)) return\n // Trailing-debounce: a burst (per-keystroke autosave, bulk actions)\n // collapses into one invalidation; the active query then refetches once.\n if (timer) clearTimeout(timer)\n timer = setTimeout(() => {\n void queryClient.invalidateQueries({ queryKey: key })\n }, OPERATION_INVALIDATE_DEBOUNCE_MS)\n })\n })\n return () => {\n disposed = true\n if (timer) clearTimeout(timer)\n unsubscribe?.()\n }\n }, [keyJson, invalidateJson, queryClient])\n\n return result\n}\n\n/**\n * The write-side companion to {@link useOperationQuery}: an imperative handle on\n * the studio cache for a store that OWNS its writes. Lets a store apply an\n * EC-301-style optimistic patch (drop/replace one record) instead of re-listing\n * the whole collection on every edit — keeping `@tanstack/react-query` out of the\n * call sites so the seam stays the only place that knows the cache exists.\n */\nexport interface OperationCache {\n /**\n * Patch a cached query in place — no refetch. The EC-301 pattern: a focal-point\n * or alt edit patches the one changed record; it does NOT trigger a full\n * `*.list` round-trip. No-op if nothing is cached under `queryKey` yet.\n */\n patch<T>(queryKey: QueryKey, updater: (prev: T | undefined) => T): void\n /**\n * Invalidate a cached query, refetching it if it is currently active. Returns a\n * promise that settles when the refetch does, so a caller that must sequence a\n * read after a local write (e.g. flush-then-reload) can await it.\n */\n invalidate(queryKey: QueryKey): Promise<void>\n}\n\nexport function useOperationCache(): OperationCache {\n const queryClient = useQueryClient()\n const patch = useCallback(\n <T,>(queryKey: QueryKey, updater: (prev: T | undefined) => T): void => {\n queryClient.setQueryData<T>(queryKey, updater)\n },\n [queryClient],\n )\n const invalidate = useCallback(\n (queryKey: QueryKey): Promise<void> => queryClient.invalidateQueries({ queryKey }),\n [queryClient],\n )\n return useMemo(() => ({ patch, invalidate }), [patch, invalidate])\n}\n\n/** Re-export the trigger-set type helper for hooks that build keyed queries. */\nexport function useStableQueryKey(parts: readonly (string | undefined)[]): QueryKey {\n // Memoize on the serialized parts so a hook can pass derived strings without a\n // new key identity each render.\n const json = JSON.stringify(parts)\n return useMemo(() => JSON.parse(json) as QueryKey, [json])\n}\n"]}
|
|
1
|
+
{"version":3,"file":"operations-query.js","sourceRoot":"","sources":["../../../src/lib/query/operations-query.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport {\n QueryClient,\n QueryClientProvider,\n useQuery,\n useQueryClient,\n} from '@tanstack/react-query'\nimport type { QueryKey, UseQueryResult } from '@tanstack/react-query'\nimport { getStudioOperations } from '@elytracms/studio/lib/operations'\nimport type { OperationEvent, OperationsClient } from '@elytracms/studio/lib/operations'\n\n/**\n * EC-308 / AD-17 — the ONE studio client read seam.\n *\n * Every studio **server read** goes through {@link useOperationQuery} over the\n * shared operations client, cached by TanStack Query; invalidation rides the\n * operations event bus into the same cache. This replaces the bespoke per-store\n * `useState` + epoch-guard + debounce + reload machinery that each store used to\n * hand-roll (the patchwork AD-17 calls out). See\n * [`docs/client-data-flow.md`](../../../../../docs/client-data-flow.md) and\n * [`docs/principles.md`](../../../../../docs/principles.md) §12.\n *\n * It is also the **Phase-2 prep** (EC-309): reactivity/multiplayer is added by\n * swapping pull → push *behind this hook* (an adapter `subscribe` capability\n * driving `queryClient.setQueryData`), never at the call sites — so call sites\n * never change when push lands.\n *\n * Deliberately NOT here: the optimistic editing session (the draft being typed,\n * live validation, the table working view-state in `cms-store`). That is client\n * state, not server state, and stays local (principle 12).\n */\n\n/**\n * Trailing-debounce window (ms) for event-driven invalidation: a burst of\n * commands — e.g. one `documents.upsert` per keystroke from autosave, or one\n * event per row in a bulk action — coalesces into a single refetch once the\n * writes settle. (The shared replacement for the per-store `*_RELOAD_DEBOUNCE_MS`.)\n */\nexport const OPERATION_INVALIDATE_DEBOUNCE_MS = 300\n\n/** Studio-tuned cache defaults: invalidation (not polling/focus) drives freshness. */\nfunction createStudioQueryClient(): QueryClient {\n return new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5_000,\n refetchOnWindowFocus: false,\n retry: false,\n },\n },\n })\n}\n\n/**\n * Mounts the single studio `QueryClient` (one per studio lifetime, surviving\n * project switches). Mounted package-owned in `StudioProvider`, above every\n * per-project provider, so every studio hook can reach the cache.\n */\nexport function StudioQueryProvider({ children }: { children: ReactNode }): ReactNode {\n const [client] = useState(createStudioQueryClient)\n return <QueryClientProvider client={client}>{children}</QueryClientProvider>\n}\n\nexport interface UseOperationQueryOptions<T> {\n /** Stable query key; the first element is the domain tag used for grouping. */\n queryKey: QueryKey\n /** Loads the data from the shared operations client. */\n loader: (operations: OperationsClient) => Promise<T>\n /**\n * Which succeeded commands invalidate this query — a list of operation names\n * (`'documents.publish'`) and/or domains (`'documents'`). A succeeded command\n * matches when its `operation` OR `domain` is in the list. Keep it a stable\n * (module-level) array. Omit for a query that only loads once / refetches\n * manually.\n */\n invalidateOn?: readonly string[]\n /** Skip the query (e.g. fixture/playground sessions with no backend). */\n enabled?: boolean\n}\n\n/**\n * One operations-backed, cached, self-invalidating query. Thin wrapper over\n * `useQuery` whose `queryFn` resolves the shared operations client and runs the\n * loader, plus one shared invalidation subscriber (the only place that touches\n * the operations event bus for reads).\n */\nexport function useOperationQuery<T>(options: UseOperationQueryOptions<T>): UseQueryResult<T> {\n const { queryKey, loader, invalidateOn, enabled = true } = options\n const queryClient = useQueryClient()\n\n const result = useQuery<T>({\n queryKey,\n queryFn: async () => {\n const { operations } = await getStudioOperations()\n return loader(operations)\n },\n enabled,\n })\n\n // Stable serializations so an inline `queryKey`/`invalidateOn` doesn't churn the\n // subscription every render (re-subscribe only when their CONTENTS change).\n const keyJson = JSON.stringify(queryKey)\n const invalidateJson = JSON.stringify(invalidateOn ?? [])\n\n useEffect(() => {\n const triggers = new Set<string>(JSON.parse(invalidateJson) as string[])\n if (triggers.size === 0) return\n const key = JSON.parse(keyJson) as QueryKey\n let timer: ReturnType<typeof setTimeout> | undefined\n let unsubscribe: (() => void) | undefined\n let disposed = false\n const matches = (event: OperationEvent): boolean =>\n event.kind === 'command' &&\n event.status === 'succeeded' &&\n (triggers.has(event.operation) || triggers.has(event.domain))\n void getStudioOperations().then((studio) => {\n if (disposed) return\n unsubscribe = studio.operations.events.subscribe((event) => {\n if (!matches(event)) return\n // Trailing-debounce: a burst (per-keystroke autosave, bulk actions)\n // collapses into one invalidation; the active query then refetches once.\n if (timer) clearTimeout(timer)\n timer = setTimeout(() => {\n void queryClient.invalidateQueries({ queryKey: key })\n }, OPERATION_INVALIDATE_DEBOUNCE_MS)\n })\n })\n return () => {\n disposed = true\n if (timer) clearTimeout(timer)\n unsubscribe?.()\n }\n }, [keyJson, invalidateJson, queryClient])\n\n return result\n}\n\n/**\n * The write-side companion to {@link useOperationQuery}: an imperative handle on\n * the studio cache for a store that OWNS its writes. Lets a store apply an\n * EC-301-style optimistic patch (drop/replace one record) instead of re-listing\n * the whole collection on every edit — keeping `@tanstack/react-query` out of the\n * call sites so the seam stays the only place that knows the cache exists.\n */\nexport interface OperationCache {\n /**\n * Patch a cached query in place — no refetch. The EC-301 pattern: a focal-point\n * or alt edit patches the one changed record; it does NOT trigger a full\n * `*.list` round-trip. No-op if nothing is cached under `queryKey` yet.\n */\n patch<T>(queryKey: QueryKey, updater: (prev: T | undefined) => T): void\n /**\n * Invalidate a cached query, refetching it if it is currently active. Returns a\n * promise that settles when the refetch does, so a caller that must sequence a\n * read after a local write (e.g. flush-then-reload) can await it.\n */\n invalidate(queryKey: QueryKey): Promise<void>\n}\n\nexport function useOperationCache(): OperationCache {\n const queryClient = useQueryClient()\n const patch = useCallback(\n <T,>(queryKey: QueryKey, updater: (prev: T | undefined) => T): void => {\n queryClient.setQueryData<T>(queryKey, updater)\n },\n [queryClient],\n )\n const invalidate = useCallback(\n (queryKey: QueryKey): Promise<void> => queryClient.invalidateQueries({ queryKey }),\n [queryClient],\n )\n return useMemo(() => ({ patch, invalidate }), [patch, invalidate])\n}\n\n/**\n * Derive a list store's public `{ status, errorMessage }` from its operation query —\n * the identical mapping the assets/users/redirects rail stores (EC-319) each\n * hand-rolled. `errorMessage` is set only on an error; a store spreads it\n * conditionally onto its public shape.\n */\nexport function listStoreState(query: { isPending: boolean; isError: boolean; error: unknown }): {\n status: 'loading' | 'ready' | 'error'\n errorMessage: string | undefined\n} {\n return {\n status: query.isPending ? 'loading' : query.isError ? 'error' : 'ready',\n errorMessage: query.isError && query.error instanceof Error ? query.error.message : undefined,\n }\n}\n"]}
|
package/dist/lib/rail.d.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Section-rail collapse preference (EC-214). The studio's vertical section rail
|
|
3
|
-
* is user-collapsible; the choice persists across sessions so the shell opens
|
|
4
|
-
* the way the user left it. Best-effort like the theme preference: storage may be
|
|
5
|
-
* unavailable, in which case the rail simply defaults to expanded each session.
|
|
6
|
-
*/
|
|
7
1
|
export declare const RAIL_STORAGE_KEY = "elytra-rail-collapsed";
|
|
8
2
|
/** Read the persisted collapse preference; expanded (false) when unset/unavailable. */
|
|
9
3
|
export declare function readRailCollapsed(): boolean;
|
package/dist/lib/rail.js
CHANGED
|
@@ -4,24 +4,15 @@
|
|
|
4
4
|
* the way the user left it. Best-effort like the theme preference: storage may be
|
|
5
5
|
* unavailable, in which case the rail simply defaults to expanded each session.
|
|
6
6
|
*/
|
|
7
|
+
import { safeReadStorage, safeWriteStorage } from './local-preference';
|
|
7
8
|
export const RAIL_STORAGE_KEY = 'elytra-rail-collapsed';
|
|
8
9
|
/** Read the persisted collapse preference; expanded (false) when unset/unavailable. */
|
|
9
10
|
export function readRailCollapsed() {
|
|
10
|
-
|
|
11
|
-
return localStorage.getItem(RAIL_STORAGE_KEY) === '1';
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
11
|
+
return safeReadStorage(RAIL_STORAGE_KEY) === '1';
|
|
16
12
|
}
|
|
17
13
|
/** Persist the collapse preference (best-effort; storage may be unavailable). */
|
|
18
14
|
export function storeRailCollapsed(collapsed) {
|
|
19
|
-
|
|
20
|
-
localStorage.setItem(RAIL_STORAGE_KEY, collapsed ? '1' : '0');
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
// ignore — preference simply won't persist this session
|
|
24
|
-
}
|
|
15
|
+
safeWriteStorage(RAIL_STORAGE_KEY, collapsed ? '1' : '0');
|
|
25
16
|
}
|
|
26
17
|
/**
|
|
27
18
|
* Per-parent collapse of the rail's nested sub-views (EC-293). We persist the
|
|
@@ -31,21 +22,11 @@ export function storeRailCollapsed(collapsed) {
|
|
|
31
22
|
export const RAIL_COLLAPSED_VIEWS_KEY = 'elytra-rail-collapsed-views';
|
|
32
23
|
/** Read the collapsed parent-view ids; empty (all expanded) when unset/unavailable. */
|
|
33
24
|
export function readCollapsedViews() {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return new Set(raw ? raw.split(',').filter(Boolean) : []);
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return new Set();
|
|
40
|
-
}
|
|
25
|
+
const raw = safeReadStorage(RAIL_COLLAPSED_VIEWS_KEY);
|
|
26
|
+
return new Set(raw ? raw.split(',').filter(Boolean) : []);
|
|
41
27
|
}
|
|
42
28
|
/** Persist the collapsed parent-view ids (best-effort; storage may be unavailable). */
|
|
43
29
|
export function storeCollapsedViews(ids) {
|
|
44
|
-
|
|
45
|
-
localStorage.setItem(RAIL_COLLAPSED_VIEWS_KEY, [...ids].join(','));
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
// ignore — preference simply won't persist this session
|
|
49
|
-
}
|
|
30
|
+
safeWriteStorage(RAIL_COLLAPSED_VIEWS_KEY, [...ids].join(','));
|
|
50
31
|
}
|
|
51
32
|
//# sourceMappingURL=rail.js.map
|
package/dist/lib/rail.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rail.js","sourceRoot":"","sources":["../../src/lib/rail.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,CAAA;AAEvD,uFAAuF;AACvF,MAAM,UAAU,iBAAiB;IAC/B,
|
|
1
|
+
{"version":3,"file":"rail.js","sourceRoot":"","sources":["../../src/lib/rail.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAEtE,MAAM,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,CAAA;AAEvD,uFAAuF;AACvF,MAAM,UAAU,iBAAiB;IAC/B,OAAO,eAAe,CAAC,gBAAgB,CAAC,KAAK,GAAG,CAAA;AAClD,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,kBAAkB,CAAC,SAAkB;IACnD,gBAAgB,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;AAC3D,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,6BAA6B,CAAA;AAErE,uFAAuF;AACvF,MAAM,UAAU,kBAAkB;IAChC,MAAM,GAAG,GAAG,eAAe,CAAC,wBAAwB,CAAC,CAAA;IACrD,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;AAC3D,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,mBAAmB,CAAC,GAAwB;IAC1D,gBAAgB,CAAC,wBAAwB,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAChE,CAAC","sourcesContent":["/**\n * Section-rail collapse preference (EC-214). The studio's vertical section rail\n * is user-collapsible; the choice persists across sessions so the shell opens\n * the way the user left it. Best-effort like the theme preference: storage may be\n * unavailable, in which case the rail simply defaults to expanded each session.\n */\nimport { safeReadStorage, safeWriteStorage } from './local-preference'\n\nexport const RAIL_STORAGE_KEY = 'elytra-rail-collapsed'\n\n/** Read the persisted collapse preference; expanded (false) when unset/unavailable. */\nexport function readRailCollapsed(): boolean {\n return safeReadStorage(RAIL_STORAGE_KEY) === '1'\n}\n\n/** Persist the collapse preference (best-effort; storage may be unavailable). */\nexport function storeRailCollapsed(collapsed: boolean): void {\n safeWriteStorage(RAIL_STORAGE_KEY, collapsed ? '1' : '0')\n}\n\n/**\n * Per-parent collapse of the rail's nested sub-views (EC-293). We persist the\n * exceptions — the set of COLLAPSED parent view ids — so the default (every parent\n * expanded) needs no storage and an unknown/removed id is harmless.\n */\nexport const RAIL_COLLAPSED_VIEWS_KEY = 'elytra-rail-collapsed-views'\n\n/** Read the collapsed parent-view ids; empty (all expanded) when unset/unavailable. */\nexport function readCollapsedViews(): Set<string> {\n const raw = safeReadStorage(RAIL_COLLAPSED_VIEWS_KEY)\n return new Set(raw ? raw.split(',').filter(Boolean) : [])\n}\n\n/** Persist the collapsed parent-view ids (best-effort; storage may be unavailable). */\nexport function storeCollapsedViews(ids: ReadonlySet<string>): void {\n safeWriteStorage(RAIL_COLLAPSED_VIEWS_KEY, [...ids].join(','))\n}\n"]}
|
package/dist/lib/theme.d.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Theme controller (light/dark). The studio renders dark-first (the SSR default
|
|
3
|
-
* and the `<html class="dark">` baseline); a stored preference flips the `dark`
|
|
4
|
-
* class on the document element. Light mode is the absence of that class — the
|
|
5
|
-
* design tokens define `:root` (light) and `.dark` overrides (see tokens.css).
|
|
6
|
-
*/
|
|
7
1
|
export type Theme = 'light' | 'dark';
|
|
8
2
|
export declare const THEME_STORAGE_KEY = "elytra-theme";
|
|
9
3
|
export declare const DEFAULT_THEME: Theme;
|
package/dist/lib/theme.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme controller (light/dark). The studio renders dark-first (the SSR default
|
|
3
|
+
* and the `<html class="dark">` baseline); a stored preference flips the `dark`
|
|
4
|
+
* class on the document element. Light mode is the absence of that class — the
|
|
5
|
+
* design tokens define `:root` (light) and `.dark` overrides (see tokens.css).
|
|
6
|
+
*/
|
|
7
|
+
import { safeReadStorage, safeWriteStorage } from './local-preference';
|
|
1
8
|
export const THEME_STORAGE_KEY = 'elytra-theme';
|
|
2
9
|
export const DEFAULT_THEME = 'dark';
|
|
3
10
|
/** Apply a theme to the document root by toggling the `dark` class. */
|
|
@@ -6,22 +13,12 @@ export function applyTheme(theme, root) {
|
|
|
6
13
|
}
|
|
7
14
|
/** Read the persisted preference, or null when unset/unavailable. */
|
|
8
15
|
export function readStoredTheme() {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return value === 'light' || value === 'dark' ? value : null;
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
+
const value = safeReadStorage(THEME_STORAGE_KEY);
|
|
17
|
+
return value === 'light' || value === 'dark' ? value : null;
|
|
16
18
|
}
|
|
17
19
|
/** Persist the preference (best-effort; storage may be unavailable). */
|
|
18
20
|
export function storeTheme(theme) {
|
|
19
|
-
|
|
20
|
-
localStorage.setItem(THEME_STORAGE_KEY, theme);
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
// ignore — preference simply won't persist this session
|
|
24
|
-
}
|
|
21
|
+
safeWriteStorage(THEME_STORAGE_KEY, theme);
|
|
25
22
|
}
|
|
26
23
|
/**
|
|
27
24
|
* Inline script body that runs before hydration to apply the stored theme,
|
package/dist/lib/theme.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.js","sourceRoot":"","sources":["../../src/lib/theme.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"theme.js","sourceRoot":"","sources":["../../src/lib/theme.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAItE,MAAM,CAAC,MAAM,iBAAiB,GAAG,cAAc,CAAA;AAC/C,MAAM,CAAC,MAAM,aAAa,GAAU,MAAM,CAAA;AAE1C,uEAAuE;AACvE,MAAM,UAAU,UAAU,CAAC,KAAY,EAAE,IAAiB;IACxD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,KAAK,MAAM,CAAC,CAAA;AACjD,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,eAAe;IAC7B,MAAM,KAAK,GAAG,eAAe,CAAC,iBAAiB,CAAC,CAAA;IAChD,OAAO,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;AAC7D,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,UAAU,CAAC,KAAY;IACrC,gBAAgB,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAA;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,+CAA+C,iBAAiB,mFAAmF,CAAA","sourcesContent":["/**\n * Theme controller (light/dark). The studio renders dark-first (the SSR default\n * and the `<html class=\"dark\">` baseline); a stored preference flips the `dark`\n * class on the document element. Light mode is the absence of that class — the\n * design tokens define `:root` (light) and `.dark` overrides (see tokens.css).\n */\nimport { safeReadStorage, safeWriteStorage } from './local-preference'\n\nexport type Theme = 'light' | 'dark'\n\nexport const THEME_STORAGE_KEY = 'elytra-theme'\nexport const DEFAULT_THEME: Theme = 'dark'\n\n/** Apply a theme to the document root by toggling the `dark` class. */\nexport function applyTheme(theme: Theme, root: HTMLElement): void {\n root.classList.toggle('dark', theme === 'dark')\n}\n\n/** Read the persisted preference, or null when unset/unavailable. */\nexport function readStoredTheme(): Theme | null {\n const value = safeReadStorage(THEME_STORAGE_KEY)\n return value === 'light' || value === 'dark' ? value : null\n}\n\n/** Persist the preference (best-effort; storage may be unavailable). */\nexport function storeTheme(theme: Theme): void {\n safeWriteStorage(THEME_STORAGE_KEY, theme)\n}\n\n/**\n * Inline script body that runs before hydration to apply the stored theme,\n * preventing a flash of the wrong theme. Injected as a raw <script> in the\n * document head; defaults to dark when no preference is stored.\n */\nexport const THEME_INIT_SCRIPT = `(function(){try{var t=localStorage.getItem('${THEME_STORAGE_KEY}');document.documentElement.classList.toggle('dark',t!=='light');}catch(e){}})();`\n"]}
|
package/dist/router/match.d.ts
CHANGED
|
@@ -52,6 +52,7 @@ export declare const studioPath: {
|
|
|
52
52
|
readonly index: () => string;
|
|
53
53
|
readonly settings: () => string;
|
|
54
54
|
readonly assets: () => string;
|
|
55
|
+
readonly asset: (assetId: string) => string;
|
|
55
56
|
readonly routes: () => string;
|
|
56
57
|
readonly extensions: () => string;
|
|
57
58
|
readonly builder: () => string;
|
package/dist/router/match.js
CHANGED
|
@@ -56,7 +56,11 @@ export function matchStudioRoute(splat) {
|
|
|
56
56
|
export const studioPath = {
|
|
57
57
|
index: () => '',
|
|
58
58
|
settings: () => 'settings',
|
|
59
|
+
// The assets rail (EC-319) — a list + a per-asset detail, mirroring the CMS
|
|
60
|
+
// arity so the SAME portable list surface (plus a bespoke detail surface,
|
|
61
|
+
// see asset-detail.tsx) drives it (AD-19).
|
|
59
62
|
assets: () => 'assets',
|
|
63
|
+
asset: (assetId) => `assets/${assetId}`,
|
|
60
64
|
routes: () => 'routes',
|
|
61
65
|
extensions: () => 'extensions',
|
|
62
66
|
builder: () => 'builder',
|
package/dist/router/match.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"match.js","sourceRoot":"","sources":["../../src/router/match.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAMH,yFAAyF;AACzF,SAAS,QAAQ,CAAC,KAAa;IAC7B,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;SAChC,MAAM,CAAC,OAAO,CAAC,CAAA;AACpB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACzC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;IACrD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;AACpD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE;IACf,QAAQ,EAAE,GAAG,EAAE,CAAC,UAAU;IAC1B,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ;IACtB,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ;IACtB,UAAU,EAAE,GAAG,EAAE,CAAC,YAAY;IAC9B,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS;IACxB,UAAU,EAAE,GAAG,EAAE,CAAC,YAAY;IAC9B,SAAS,EAAE,CAAC,WAAmB,EAAE,EAAE,CAAC,cAAc,WAAW,EAAE;IAC/D,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK;IAChB,aAAa,EAAE,CAAC,YAAoB,EAAE,EAAE,CAAC,OAAO,YAAY,EAAE;IAC9D,WAAW,EAAE,CAAC,YAAoB,EAAE,UAAkB,EAAE,EAAE,CACxD,OAAO,YAAY,IAAI,UAAU,EAAE;IACrC,4EAA4E;IAC5E,kEAAkE;IAClE,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO;IACpB,IAAI,EAAE,CAAC,MAAc,EAAE,EAAE,CAAC,SAAS,MAAM,EAAE;IAC3C,8EAA8E;IAC9E,4CAA4C;IAC5C,SAAS,EAAE,GAAG,EAAE,CAAC,WAAW;IAC5B,QAAQ,EAAE,CAAC,UAAkB,EAAE,EAAE,CAAC,aAAa,UAAU,EAAE;CACnD,CAAA","sourcesContent":["/**\n * Studio route table (EC-223 Stage 6 — catch-all routing). The studio mounts as\n * a SINGLE splat route per project (`/$projectId/$`); this pure matcher turns the\n * splat's trailing pathname into a typed match that the screen resolver renders.\n * Keeping the URL→screen map inside the engine (instead of in per-consumer route\n * files) is what lets the package own its navigation with a near-empty consumer\n * route tree — the goal of Stage 6.\n *\n * Deterministic and dependency-free: every screen state is reproducible from a\n * pathname string (see `match.test.ts`), satisfying the fixtures-reproducibility\n * convention without needing a router instance.\n *\n * EC-239 (app-view modularization): the route table is no longer a hardcoded\n * per-section switch — the studio's surfaces are a config-extendable VIEW registry\n * derived from the section rail, so the matcher is registry-AGNOSTIC. It splits the\n * splat into a view id (the first segment) + that view's trailing segments; the\n * screen resolver looks the id up in the registry and the view's own `parsePath`\n * interprets the segments (CMS: collection/document; components: id; flat: none).\n * Keeping the matcher free of the surface list is what lets a consuming repo add a\n * routable custom view purely from config.\n *\n * /$projectId → { section: 'index' } (redirect to first view)\n * /$projectId/<id> → { section: 'view', viewId: '<id>', segments: [] }\n * /$projectId/<id>/a/b → { section: 'view', viewId: '<id>', segments: ['a','b'] }\n *\n * The view decides whether its segments resolve; an unknown id or an over-deep\n * path becomes a not-found at the resolver, not here.\n */\n\nexport type StudioRouteMatch =\n | { section: 'index' }\n | { section: 'view'; viewId: string; segments: string[] }\n\n/** Split a splat pathname into clean segments (drops empty/leading/trailing slashes). */\nfunction segments(splat: string): string[] {\n return splat\n .split('/')\n .map((segment) => segment.trim())\n .filter(Boolean)\n}\n\n/**\n * Match the trailing pathname of the project splat (`_splat`) into a view id +\n * its segments. `splat` is everything after `/$projectId/` — e.g. `'cms/page/abc'`\n * for `/p1/cms/page/abc` → `{ viewId: 'cms', segments: ['page', 'abc'] }`. An\n * empty splat is the bare project index (which redirects to the first view).\n */\nexport function matchStudioRoute(splat: string): StudioRouteMatch {\n const [viewId, ...rest] = segments(splat)\n if (viewId === undefined) return { section: 'index' }\n return { section: 'view', viewId, segments: rest }\n}\n\n/**\n * The inverse of {@link matchStudioRoute}: build the `_splat` subpath for a\n * screen. Navigation addresses the single project splat as\n * `to={STUDIO_PROJECT_ROUTE} params={{ projectId, _splat: studioPath.X(...) }}`,\n * so the URL-construction lives in exactly one place — the same module that\n * parses it back. `matchStudioRoute(studioPath.cmsDocument(c, d))` round-trips\n * (pinned in `match.test.ts`), which is what keeps the two directions in sync.\n */\nexport const studioPath = {\n index: () => '',\n settings: () => 'settings',\n assets: () => 'assets',\n routes: () => 'routes',\n extensions: () => 'extensions',\n builder: () => 'builder',\n components: () => 'components',\n component: (componentId: string) => `components/${componentId}`,\n cms: () => 'cms',\n cmsCollection: (collectionId: string) => `cms/${collectionId}`,\n cmsDocument: (collectionId: string, documentId: string) =>\n `cms/${collectionId}/${documentId}`,\n // The users rail (EC-240) — a list + a per-member detail, mirroring the CMS\n // arity so the SAME portable list/edit surfaces drive it (AD-19).\n users: () => 'users',\n user: (userId: string) => `users/${userId}`,\n // The redirects rail (EC-303) — editorial redirects as data, same list+detail\n // arity over the portable surfaces (AD-19).\n redirects: () => 'redirects',\n redirect: (redirectId: string) => `redirects/${redirectId}`,\n} as const\n"]}
|
|
1
|
+
{"version":3,"file":"match.js","sourceRoot":"","sources":["../../src/router/match.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAMH,yFAAyF;AACzF,SAAS,QAAQ,CAAC,KAAa;IAC7B,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;SAChC,MAAM,CAAC,OAAO,CAAC,CAAA;AACpB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACzC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;IACrD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;AACpD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE;IACf,QAAQ,EAAE,GAAG,EAAE,CAAC,UAAU;IAC1B,4EAA4E;IAC5E,0EAA0E;IAC1E,2CAA2C;IAC3C,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ;IACtB,KAAK,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,UAAU,OAAO,EAAE;IAC/C,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ;IACtB,UAAU,EAAE,GAAG,EAAE,CAAC,YAAY;IAC9B,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS;IACxB,UAAU,EAAE,GAAG,EAAE,CAAC,YAAY;IAC9B,SAAS,EAAE,CAAC,WAAmB,EAAE,EAAE,CAAC,cAAc,WAAW,EAAE;IAC/D,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK;IAChB,aAAa,EAAE,CAAC,YAAoB,EAAE,EAAE,CAAC,OAAO,YAAY,EAAE;IAC9D,WAAW,EAAE,CAAC,YAAoB,EAAE,UAAkB,EAAE,EAAE,CACxD,OAAO,YAAY,IAAI,UAAU,EAAE;IACrC,4EAA4E;IAC5E,kEAAkE;IAClE,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO;IACpB,IAAI,EAAE,CAAC,MAAc,EAAE,EAAE,CAAC,SAAS,MAAM,EAAE;IAC3C,8EAA8E;IAC9E,4CAA4C;IAC5C,SAAS,EAAE,GAAG,EAAE,CAAC,WAAW;IAC5B,QAAQ,EAAE,CAAC,UAAkB,EAAE,EAAE,CAAC,aAAa,UAAU,EAAE;CACnD,CAAA","sourcesContent":["/**\n * Studio route table (EC-223 Stage 6 — catch-all routing). The studio mounts as\n * a SINGLE splat route per project (`/$projectId/$`); this pure matcher turns the\n * splat's trailing pathname into a typed match that the screen resolver renders.\n * Keeping the URL→screen map inside the engine (instead of in per-consumer route\n * files) is what lets the package own its navigation with a near-empty consumer\n * route tree — the goal of Stage 6.\n *\n * Deterministic and dependency-free: every screen state is reproducible from a\n * pathname string (see `match.test.ts`), satisfying the fixtures-reproducibility\n * convention without needing a router instance.\n *\n * EC-239 (app-view modularization): the route table is no longer a hardcoded\n * per-section switch — the studio's surfaces are a config-extendable VIEW registry\n * derived from the section rail, so the matcher is registry-AGNOSTIC. It splits the\n * splat into a view id (the first segment) + that view's trailing segments; the\n * screen resolver looks the id up in the registry and the view's own `parsePath`\n * interprets the segments (CMS: collection/document; components: id; flat: none).\n * Keeping the matcher free of the surface list is what lets a consuming repo add a\n * routable custom view purely from config.\n *\n * /$projectId → { section: 'index' } (redirect to first view)\n * /$projectId/<id> → { section: 'view', viewId: '<id>', segments: [] }\n * /$projectId/<id>/a/b → { section: 'view', viewId: '<id>', segments: ['a','b'] }\n *\n * The view decides whether its segments resolve; an unknown id or an over-deep\n * path becomes a not-found at the resolver, not here.\n */\n\nexport type StudioRouteMatch =\n | { section: 'index' }\n | { section: 'view'; viewId: string; segments: string[] }\n\n/** Split a splat pathname into clean segments (drops empty/leading/trailing slashes). */\nfunction segments(splat: string): string[] {\n return splat\n .split('/')\n .map((segment) => segment.trim())\n .filter(Boolean)\n}\n\n/**\n * Match the trailing pathname of the project splat (`_splat`) into a view id +\n * its segments. `splat` is everything after `/$projectId/` — e.g. `'cms/page/abc'`\n * for `/p1/cms/page/abc` → `{ viewId: 'cms', segments: ['page', 'abc'] }`. An\n * empty splat is the bare project index (which redirects to the first view).\n */\nexport function matchStudioRoute(splat: string): StudioRouteMatch {\n const [viewId, ...rest] = segments(splat)\n if (viewId === undefined) return { section: 'index' }\n return { section: 'view', viewId, segments: rest }\n}\n\n/**\n * The inverse of {@link matchStudioRoute}: build the `_splat` subpath for a\n * screen. Navigation addresses the single project splat as\n * `to={STUDIO_PROJECT_ROUTE} params={{ projectId, _splat: studioPath.X(...) }}`,\n * so the URL-construction lives in exactly one place — the same module that\n * parses it back. `matchStudioRoute(studioPath.cmsDocument(c, d))` round-trips\n * (pinned in `match.test.ts`), which is what keeps the two directions in sync.\n */\nexport const studioPath = {\n index: () => '',\n settings: () => 'settings',\n // The assets rail (EC-319) — a list + a per-asset detail, mirroring the CMS\n // arity so the SAME portable list surface (plus a bespoke detail surface,\n // see asset-detail.tsx) drives it (AD-19).\n assets: () => 'assets',\n asset: (assetId: string) => `assets/${assetId}`,\n routes: () => 'routes',\n extensions: () => 'extensions',\n builder: () => 'builder',\n components: () => 'components',\n component: (componentId: string) => `components/${componentId}`,\n cms: () => 'cms',\n cmsCollection: (collectionId: string) => `cms/${collectionId}`,\n cmsDocument: (collectionId: string, documentId: string) =>\n `cms/${collectionId}/${documentId}`,\n // The users rail (EC-240) — a list + a per-member detail, mirroring the CMS\n // arity so the SAME portable list/edit surfaces drive it (AD-19).\n users: () => 'users',\n user: (userId: string) => `users/${userId}`,\n // The redirects rail (EC-303) — editorial redirects as data, same list+detail\n // arity over the portable surfaces (AD-19).\n redirects: () => 'redirects',\n redirect: (redirectId: string) => `redirects/${redirectId}`,\n} as const\n"]}
|
package/dist/ui/button.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type VariantProps } from 'class-variance-authority';
|
|
2
2
|
import type { ButtonHTMLAttributes } from 'react';
|
|
3
3
|
export declare const buttonVariants: (props?: ({
|
|
4
|
-
variant?: "default" | "link" | "
|
|
4
|
+
variant?: "default" | "link" | "secondary" | "outline" | "ghost" | "destructive" | null | undefined;
|
|
5
5
|
size?: "default" | "sm" | "lg" | "icon" | "icon-sm" | null | undefined;
|
|
6
6
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
7
|
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|