@uniweb/kit 0.14.0 → 0.15.1
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "Standard component library for Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"shiki": "^3.0.0",
|
|
45
45
|
"tailwind-merge": "^3.6.0",
|
|
46
46
|
"temml": "^0.13.2",
|
|
47
|
-
"@uniweb/
|
|
48
|
-
"@uniweb/
|
|
47
|
+
"@uniweb/semantic-parser": "^1.4.0",
|
|
48
|
+
"@uniweb/core": "^0.15.0",
|
|
49
49
|
"@uniweb/scene": "^0.1.3"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
package/src/hooks/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { useWebsite, default } from './useWebsite.js'
|
|
|
2
2
|
export { useFetched } from './useFetched.js'
|
|
3
3
|
export { useCacheEntry } from './useCacheEntry.js'
|
|
4
4
|
export { useEntityDetail } from './useEntityDetail.js'
|
|
5
|
-
export {
|
|
5
|
+
export { useQueryable } from './useQueryable.js'
|
|
6
6
|
export { useRouting } from './useRouting.js'
|
|
7
7
|
export { useActiveRoute } from './useActiveRoute.js'
|
|
8
8
|
export { useVersion } from './useVersion.js'
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* useEntityDetail — fetch the full record for a deferred
|
|
2
|
+
* useEntityDetail — fetch the full record for a query with deferred fields.
|
|
3
3
|
*
|
|
4
|
-
* When a
|
|
4
|
+
* When a query declares `deferred: [...]`, the cascade
|
|
5
5
|
* payload omits the deferred fields. The full record (with deferred
|
|
6
6
|
* fields included) lives somewhere — either at a per-record file the
|
|
7
7
|
* build emits, or at an author-declared API endpoint. This hook fetches
|
|
8
8
|
* that full record on demand.
|
|
9
9
|
*
|
|
10
|
-
* Two source patterns, picked automatically from the
|
|
10
|
+
* Two source patterns, picked automatically from the query's
|
|
11
11
|
* declaration:
|
|
12
12
|
*
|
|
13
|
-
* -
|
|
14
|
-
* The build emits `/data/<
|
|
13
|
+
* - File-backed records (a query over `entities/{schema}/`).
|
|
14
|
+
* The build emits `/data/<query>/<slug>.json` per record. The
|
|
15
15
|
* hook fetches that path.
|
|
16
16
|
*
|
|
17
|
-
* -
|
|
17
|
+
* - Remote sources (a query declaring `url:` plus a
|
|
18
18
|
* `detailUrl:` pattern). The hook substitutes `{slug}` in the
|
|
19
19
|
* pattern and fetches that URL.
|
|
20
20
|
*
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* function ArticleCard({ article }) {
|
|
32
32
|
* const [open, setOpen] = useState(false)
|
|
33
33
|
* const { data: full, loading } = useEntityDetail(open ? article : null, {
|
|
34
|
-
*
|
|
34
|
+
* query: 'articles',
|
|
35
35
|
* })
|
|
36
36
|
* return (
|
|
37
37
|
* <div>
|
|
@@ -48,18 +48,18 @@ import { getUniweb, resolveFetchConfigs, buildDetailConfig } from '@uniweb/core'
|
|
|
48
48
|
import { useFetched } from './useFetched.js'
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
* @param {Object|null} record - A record from a cascade-delivered
|
|
51
|
+
* @param {Object|null} record - A record from a cascade-delivered query.
|
|
52
52
|
* Must have a `slug` field. Pass null/undefined to skip the fetch.
|
|
53
53
|
* @param {Object} [options]
|
|
54
|
-
* @param {string} options.
|
|
55
|
-
* Required when record is non-null. Used to look up the
|
|
54
|
+
* @param {string} options.query - The query name (e.g., 'articles').
|
|
55
|
+
* Required when record is non-null. Used to look up the query's
|
|
56
56
|
* `detailUrl:` (if declared) or fall back to the static-file default
|
|
57
57
|
* `/data/<collection>/<slug>.json`.
|
|
58
58
|
* @returns {{ data: any, error: string|null, loading: boolean }}
|
|
59
59
|
*/
|
|
60
60
|
export function useEntityDetail(record, options = {}) {
|
|
61
|
-
const
|
|
62
|
-
const request = buildDetailRequest(record,
|
|
61
|
+
const query = options?.query
|
|
62
|
+
const request = buildDetailRequest(record, query)
|
|
63
63
|
const result = useFetched(request)
|
|
64
64
|
|
|
65
65
|
// No separate detail source for this collection — nothing was stripped from
|
|
@@ -69,7 +69,7 @@ export function useEntityDetail(record, options = {}) {
|
|
|
69
69
|
// guaranteed 404, because that file is only written for a `deferred:`
|
|
70
70
|
// collection. `useFetched` is still called above — unconditionally, as the
|
|
71
71
|
// rules of hooks require — and simply skips on a null request.
|
|
72
|
-
if (record &&
|
|
72
|
+
if (record && query && !request) {
|
|
73
73
|
return { data: record, error: null, loading: false }
|
|
74
74
|
}
|
|
75
75
|
return result
|
|
@@ -79,7 +79,7 @@ export function useEntityDetail(record, options = {}) {
|
|
|
79
79
|
* Build the fetch request for one record's full payload.
|
|
80
80
|
*
|
|
81
81
|
* ⛔ THIS MUST NOT DECIDE THE ADDRESS ITSELF, and it used to. It read
|
|
82
|
-
* `config.
|
|
82
|
+
* `config.queries[name].detailUrl` directly and otherwise composed
|
|
83
83
|
* `/data/<name>/<slug>.json` by hand, which was wrong three ways: it 404'd on
|
|
84
84
|
* any collection without `deferred:` (that file is only written for one), it
|
|
85
85
|
* could not see a host's live record lane at all, and its hand-rolled `{slug}`
|
|
@@ -98,24 +98,24 @@ export function useEntityDetail(record, options = {}) {
|
|
|
98
98
|
* Exported for tests only — not re-exported from the package index.
|
|
99
99
|
*
|
|
100
100
|
* @param {Object|null} record
|
|
101
|
-
* @param {string}
|
|
101
|
+
* @param {string} query
|
|
102
102
|
* @returns {{path?: string, url?: string, endpoint?: string, schema: string}|null}
|
|
103
103
|
*/
|
|
104
|
-
export function buildDetailRequest(record,
|
|
104
|
+
export function buildDetailRequest(record, query) {
|
|
105
105
|
const slug = record?.slug
|
|
106
|
-
if (!slug || !
|
|
106
|
+
if (!slug || !query) return null
|
|
107
107
|
|
|
108
108
|
const website = getUniweb()?.activeWebsite
|
|
109
109
|
const config = website?.config
|
|
110
110
|
|
|
111
111
|
// One synthetic source, resolved by the shared rule — same inputs the
|
|
112
112
|
// EntityStore passes, so the hook cannot disagree with the page it sits on.
|
|
113
|
-
const resolved = resolveFetchConfigs([{
|
|
114
|
-
|
|
113
|
+
const resolved = resolveFetchConfigs([{ query, schema: query }], {
|
|
114
|
+
queries: config?.queries ?? null,
|
|
115
115
|
records: config?.records ?? null,
|
|
116
116
|
locale: website?.getActiveLocale?.() ?? null,
|
|
117
117
|
defaultLocale: website?.getDefaultLocale?.() ?? null,
|
|
118
|
-
}).get(
|
|
118
|
+
}).get(query)
|
|
119
119
|
|
|
120
120
|
if (!resolved) return null
|
|
121
121
|
|
|
@@ -14,7 +14,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
|
14
14
|
*
|
|
15
15
|
* What it deliberately does NOT own is the rendering. Which control a `type`
|
|
16
16
|
* maps to, how it looks, how an error reads — that is the foundation's design
|
|
17
|
-
* and its whole reason for existing. Same split as `
|
|
17
|
+
* and its whole reason for existing. Same split as `useQueryable`:
|
|
18
18
|
* the kit hands over the metadata and the state, the foundation builds the
|
|
19
19
|
* controls against them.
|
|
20
20
|
*
|
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* collection.
|
|
2
|
+
* useQueryable — read the queryable-surface metadata for a named query.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
4
|
+
* ⛔ RENAMED FROM `useQueryable`. An earlier pass kept the old name to
|
|
5
|
+
* avoid breaking foundations — but nothing is published yet, and the word was
|
|
6
|
+
* hiding the conflation this whole change removes: `queryable:` is declared on a
|
|
7
|
+
* QUERY, and a query is not a set of files.
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
9
|
+
* Authors declare the queryable surface on a named QUERY, in `queries.yml`
|
|
10
|
+
* (or under `queries:` in `site.yml`):
|
|
11
|
+
*
|
|
12
|
+
* members:
|
|
13
|
+
* schema: '@std/person'
|
|
14
|
+
* queryable:
|
|
15
|
+
* department:
|
|
16
|
+
* type: enum
|
|
17
|
+
* label: Department
|
|
18
|
+
* options: [biology, physics, chemistry, geology]
|
|
19
|
+
* tenured:
|
|
20
|
+
* type: boolean
|
|
21
|
+
* label: Tenured
|
|
22
|
+
* start_year:
|
|
23
|
+
* type: range
|
|
24
|
+
* label: Start year
|
|
25
|
+
* min: 1800
|
|
26
|
+
* max: 2025
|
|
23
27
|
*
|
|
24
28
|
* Foundations consume this hook to render filter UIs and compose
|
|
25
29
|
* where-objects from user interactions. The kit doesn't ship UI
|
|
@@ -29,7 +33,7 @@
|
|
|
29
33
|
*
|
|
30
34
|
* @example
|
|
31
35
|
* function MemberFilters({ onChange }) {
|
|
32
|
-
* const queryable =
|
|
36
|
+
* const queryable = useQueryable('members')
|
|
33
37
|
* const [values, setValues] = useState({})
|
|
34
38
|
* if (!queryable) return null
|
|
35
39
|
* return (
|
|
@@ -53,22 +57,22 @@
|
|
|
53
57
|
*
|
|
54
58
|
* Returns null when the collection has no `queryable:` declared.
|
|
55
59
|
*
|
|
56
|
-
* @param {string}
|
|
60
|
+
* @param {string} queryName - Name of the collection to read queryable
|
|
57
61
|
* metadata for. Pass null/undefined to skip (returns null).
|
|
58
62
|
* @returns {Object|null} The queryable metadata object, or null.
|
|
59
63
|
*/
|
|
60
64
|
|
|
61
65
|
import { getUniweb } from '@uniweb/core'
|
|
62
66
|
|
|
63
|
-
export function
|
|
64
|
-
if (!
|
|
67
|
+
export function useQueryable(queryName) {
|
|
68
|
+
if (!queryName) return null
|
|
65
69
|
const website = getUniweb()?.activeWebsite
|
|
66
70
|
if (!website) return null
|
|
67
|
-
const config = website.config?.
|
|
71
|
+
const config = website.config?.queries?.[queryName]
|
|
68
72
|
if (!config || typeof config !== 'object') return null
|
|
69
73
|
const queryable = config.queryable
|
|
70
74
|
if (!queryable || typeof queryable !== 'object') return null
|
|
71
75
|
return queryable
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
export default
|
|
78
|
+
export default useQueryable
|
package/src/index.js
CHANGED
|
@@ -93,7 +93,7 @@ export const NOT_RENDERED = {
|
|
|
93
93
|
'carry a component NAME and no refId, so there is nothing here to look up — ' +
|
|
94
94
|
'and kit answering for the name is the shadowing the inset_block case ' +
|
|
95
95
|
'refuses. Consequence worth knowing: a document rendered straight from ' +
|
|
96
|
-
'markdown without that build step (a
|
|
96
|
+
'markdown without that build step (a record body, say) loses its ' +
|
|
97
97
|
'inline component references.',
|
|
98
98
|
form: 'an editor node; its data reaches components as content.data[schemaId]',
|
|
99
99
|
'card-group': 'an editor node, deprecated — maps to content.data[cardType]',
|