@orz/ui-content 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +410 -2
  3. package/dist/attachment/card-content.d.ts +26 -0
  4. package/dist/attachment/card-content.d.ts.map +1 -0
  5. package/dist/attachment/card-content.js +49 -0
  6. package/dist/attachment/card-content.js.map +1 -0
  7. package/dist/attachment/deleted.d.ts +9 -0
  8. package/dist/attachment/deleted.d.ts.map +1 -0
  9. package/dist/attachment/deleted.js +11 -0
  10. package/dist/attachment/deleted.js.map +1 -0
  11. package/dist/attachment/document-card.d.ts +32 -0
  12. package/dist/attachment/document-card.d.ts.map +1 -0
  13. package/dist/attachment/document-card.js +38 -0
  14. package/dist/attachment/document-card.js.map +1 -0
  15. package/dist/attachment/icon.d.ts +13 -0
  16. package/dist/attachment/icon.d.ts.map +1 -0
  17. package/dist/attachment/icon.js +34 -0
  18. package/dist/attachment/icon.js.map +1 -0
  19. package/dist/attachment/image-canvas.d.ts +25 -0
  20. package/dist/attachment/image-canvas.d.ts.map +1 -0
  21. package/dist/attachment/image-canvas.js +301 -0
  22. package/dist/attachment/image-canvas.js.map +1 -0
  23. package/dist/attachment/image-look.d.ts +18 -0
  24. package/dist/attachment/image-look.d.ts.map +1 -0
  25. package/dist/attachment/image-look.js +86 -0
  26. package/dist/attachment/image-look.js.map +1 -0
  27. package/dist/attachment/image-zoom.d.ts +67 -0
  28. package/dist/attachment/image-zoom.d.ts.map +1 -0
  29. package/dist/attachment/image-zoom.js +108 -0
  30. package/dist/attachment/image-zoom.js.map +1 -0
  31. package/dist/attachment/index.d.ts +12 -0
  32. package/dist/attachment/index.d.ts.map +1 -0
  33. package/dist/attachment/index.js +12 -0
  34. package/dist/attachment/index.js.map +1 -0
  35. package/dist/attachment/link.d.ts +18 -0
  36. package/dist/attachment/link.d.ts.map +1 -0
  37. package/dist/attachment/link.js +30 -0
  38. package/dist/attachment/link.js.map +1 -0
  39. package/dist/attachment/list.d.ts +26 -0
  40. package/dist/attachment/list.d.ts.map +1 -0
  41. package/dist/attachment/list.js +53 -0
  42. package/dist/attachment/list.js.map +1 -0
  43. package/dist/attachment/media-content.d.ts +21 -0
  44. package/dist/attachment/media-content.d.ts.map +1 -0
  45. package/dist/attachment/media-content.js +133 -0
  46. package/dist/attachment/media-content.js.map +1 -0
  47. package/dist/attachment/media-player.d.ts +23 -0
  48. package/dist/attachment/media-player.d.ts.map +1 -0
  49. package/dist/attachment/media-player.js +137 -0
  50. package/dist/attachment/media-player.js.map +1 -0
  51. package/dist/attachment/use-media.d.ts +27 -0
  52. package/dist/attachment/use-media.d.ts.map +1 -0
  53. package/dist/attachment/use-media.js +133 -0
  54. package/dist/attachment/use-media.js.map +1 -0
  55. package/dist/attachment/viewer.d.ts +85 -0
  56. package/dist/attachment/viewer.d.ts.map +1 -0
  57. package/dist/attachment/viewer.js +245 -0
  58. package/dist/attachment/viewer.js.map +1 -0
  59. package/dist/file-kind.d.ts +61 -0
  60. package/dist/file-kind.d.ts.map +1 -0
  61. package/dist/file-kind.js +166 -0
  62. package/dist/file-kind.js.map +1 -0
  63. package/dist/index.d.ts +6 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +5 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/registry.d.ts +44 -0
  68. package/dist/registry.d.ts.map +1 -0
  69. package/dist/registry.js +109 -0
  70. package/dist/registry.js.map +1 -0
  71. package/dist/source-text.d.ts +29 -0
  72. package/dist/source-text.d.ts.map +1 -0
  73. package/dist/source-text.js +101 -0
  74. package/dist/source-text.js.map +1 -0
  75. package/dist/title-bar.d.ts +23 -0
  76. package/dist/title-bar.d.ts.map +1 -0
  77. package/dist/title-bar.js +24 -0
  78. package/dist/title-bar.js.map +1 -0
  79. package/dist/types.d.ts +48 -0
  80. package/dist/types.d.ts.map +1 -0
  81. package/dist/types.js +2 -0
  82. package/dist/types.js.map +1 -0
  83. package/package.json +55 -3
  84. package/styles.css +4 -0
@@ -0,0 +1,109 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useCallback, useContext, useMemo, } from "react";
3
+ /**
4
+ * The fallback, as a component, for renderers that decide to step aside: a plain `<pre><code>`
5
+ * for inline text, and a download link (or the bare name when there is no `url`) for a file.
6
+ */
7
+ export function ContentFallback({ source, className }) {
8
+ if (source.kind === "inline") {
9
+ return (_jsx("pre", { className: className, children: _jsx("code", { className: source.lang === undefined ? undefined : `language-${source.lang}`, children: source.text }) }));
10
+ }
11
+ if (source.url === undefined) {
12
+ return _jsx("span", { className: className, children: source.name });
13
+ }
14
+ return (_jsx("a", { className: className, href: source.url, download: source.name, children: source.name }));
15
+ }
16
+ /**
17
+ * One component per position, built once per list: a renderer's `Next` has to be the same
18
+ * component type on every render, or React remounts everything the renderer drew below it and a
19
+ * live text selection in there collapses (T-60).
20
+ */
21
+ function stepsFor(renderers) {
22
+ const steps = [];
23
+ steps[renderers.length] = ContentFallback;
24
+ for (let i = renderers.length - 1; i >= 0; i--) {
25
+ steps[i] = function ContentStep(props) {
26
+ for (let j = i; j < renderers.length; j++) {
27
+ const renderer = renderers[j];
28
+ if (renderer?.accepts(props.source, props.placement)) {
29
+ const Next = steps[j + 1] ?? ContentFallback;
30
+ return _jsx(renderer.Component, { ...props, Next: Next });
31
+ }
32
+ }
33
+ return _jsx(ContentFallback, { ...props });
34
+ };
35
+ }
36
+ return steps;
37
+ }
38
+ const EMPTY = [];
39
+ const RegistryContext = createContext({
40
+ renderers: EMPTY,
41
+ steps: stepsFor(EMPTY),
42
+ maxDepth: undefined,
43
+ });
44
+ const DepthContext = createContext(0);
45
+ /**
46
+ * Registers renderers for every `<Content>` below. The list is ordered and the first renderer
47
+ * that accepts a source draws it, so a narrow renderer goes before a broad one. A nested provider
48
+ * puts its renderers in front of the outer ones.
49
+ *
50
+ * Keep `renderers` stable (a module constant or a memo): a new array rebuilds every `Next`, and
51
+ * React remounts whatever the renderers drew.
52
+ */
53
+ export function ContentRenderers({ renderers, maxDepth, children, }) {
54
+ const outer = useContext(RegistryContext);
55
+ const merged = useMemo(() => outer.renderers.length === 0
56
+ ? renderers
57
+ : [...renderers, ...outer.renderers], [renderers, outer.renderers]);
58
+ const steps = useMemo(() => stepsFor(merged), [merged]);
59
+ const resolvedMaxDepth = maxDepth ?? outer.maxDepth;
60
+ const value = useMemo(() => ({ renderers: merged, steps, maxDepth: resolvedMaxDepth }), [merged, steps, resolvedMaxDepth]);
61
+ return (_jsx(RegistryContext.Provider, { value: value, children: children }));
62
+ }
63
+ function firstAccepting(renderers, source, placement) {
64
+ return renderers.find((renderer) => renderer.accepts(source, placement));
65
+ }
66
+ /**
67
+ * Draws a source with the first registered renderer that accepts it, or the fallback.
68
+ *
69
+ * A file source a renderer draws raises `useContentDepth()` by one for everything below it, once
70
+ * per `<Content>`: a renderer that wraps the file and hands it on through `Next` does not count
71
+ * it twice.
72
+ */
73
+ export function Content(props) {
74
+ const { renderers, steps } = useContext(RegistryContext);
75
+ const depth = useContext(DepthContext);
76
+ const First = steps[0] ?? ContentFallback;
77
+ const drawn = props.source.kind === "file" &&
78
+ firstAccepting(renderers, props.source, props.placement) !== undefined;
79
+ if (!drawn)
80
+ return _jsx(First, { ...props });
81
+ return (_jsx(DepthContext.Provider, { value: depth + 1, children: _jsx(First, { ...props }) }));
82
+ }
83
+ /**
84
+ * How the renderer that would draw this source lays out, without rendering anything: `"block"`,
85
+ * `"inline"`, or `null` when nothing registered accepts it.
86
+ */
87
+ export function useContentDisplay(source, placement) {
88
+ return useContentDisplayOf()(source, placement);
89
+ }
90
+ /**
91
+ * `useContentDisplay` as a function, for a caller that asks about a varying number of sources in
92
+ * one render, such as a paragraph checking each of its images.
93
+ */
94
+ export function useContentDisplayOf() {
95
+ const { renderers } = useContext(RegistryContext);
96
+ return useCallback((source, placement) => {
97
+ const renderer = firstAccepting(renderers, source, placement);
98
+ return renderer === undefined ? null : (renderer.display ?? "block");
99
+ }, [renderers]);
100
+ }
101
+ /** How many file sources, each drawn by a renderer, enclose this point. */
102
+ export function useContentDepth() {
103
+ return useContext(DepthContext);
104
+ }
105
+ /** The nearest `<ContentRenderers maxDepth>`, for renderers that limit nesting. */
106
+ export function useContentMaxDepth() {
107
+ return useContext(RegistryContext).maxDepth;
108
+ }
109
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { ContentSource } from "./types.ts";
2
+ export type SourceText = ({
3
+ status: "loading";
4
+ text?: undefined;
5
+ error?: undefined;
6
+ } | {
7
+ status: "ready";
8
+ text: string;
9
+ error?: undefined;
10
+ } | {
11
+ status: "error";
12
+ text?: undefined;
13
+ error: Error;
14
+ }) & {
15
+ /** Runs the load again; what a renderer's Retry button calls. */
16
+ retry: () => void;
17
+ };
18
+ /**
19
+ * The text of a source. Inline text is ready at once. A file calls `load` when it has one and
20
+ * fetches `url` otherwise, and the load is aborted on unmount and when the source changes.
21
+ *
22
+ * A file counts as changed when its `name` or `url` does, or, for a file with no `url`, when its
23
+ * `load` function does. A `load` beside a `url` is read from the latest render instead, so a
24
+ * resolver that builds a fresh closure on every render does not restart the load each time.
25
+ *
26
+ * Size caps are the caller's.
27
+ */
28
+ export declare function useSourceText(source: ContentSource): SourceText;
29
+ //# sourceMappingURL=source-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-text.d.ts","sourceRoot":"","sources":["../src/source-text.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,MAAM,UAAU,GAAG,CACrB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,GAC1D;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,GACpD;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,CACtD,GAAG;IACF,iEAAiE;IACjE,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AA+BF;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,UAAU,CAiF/D"}
@@ -0,0 +1,101 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ function sameKey(a, b) {
3
+ return (a.name === b.name &&
4
+ a.url === b.url &&
5
+ a.load === b.load &&
6
+ a.attempt === b.attempt);
7
+ }
8
+ async function fetchText(url, signal) {
9
+ const response = await fetch(url, { signal });
10
+ if (!response.ok) {
11
+ throw new Error(`${response.status} ${response.statusText}`.trim());
12
+ }
13
+ return response.text();
14
+ }
15
+ /**
16
+ * The text of a source. Inline text is ready at once. A file calls `load` when it has one and
17
+ * fetches `url` otherwise, and the load is aborted on unmount and when the source changes.
18
+ *
19
+ * A file counts as changed when its `name` or `url` does, or, for a file with no `url`, when its
20
+ * `load` function does. A `load` beside a `url` is read from the latest render instead, so a
21
+ * resolver that builds a fresh closure on every render does not restart the load each time.
22
+ *
23
+ * Size caps are the caller's.
24
+ */
25
+ export function useSourceText(source) {
26
+ const [attempt, setAttempt] = useState(0);
27
+ const retry = useCallback(() => setAttempt((n) => n + 1), []);
28
+ const file = source.kind === "file" ? source : undefined;
29
+ const latestLoad = useRef(file?.load);
30
+ latestLoad.current = file?.load;
31
+ const key = file === undefined
32
+ ? undefined
33
+ : {
34
+ name: file.name,
35
+ url: file.url,
36
+ load: file.url === undefined ? file.load : undefined,
37
+ attempt,
38
+ };
39
+ const [settled, setSettled] = useState();
40
+ const name = key?.name;
41
+ const url = key?.url;
42
+ const keyLoad = key?.load;
43
+ useEffect(() => {
44
+ if (name === undefined)
45
+ return;
46
+ const forKey = { name, url, load: keyLoad, attempt };
47
+ const load = latestLoad.current ??
48
+ (url === undefined
49
+ ? undefined
50
+ : (signal) => fetchText(url, signal));
51
+ if (load === undefined) {
52
+ setSettled({
53
+ key: forKey,
54
+ result: {
55
+ status: "error",
56
+ error: new Error(`${name}: nothing to load`),
57
+ },
58
+ });
59
+ return;
60
+ }
61
+ const controller = new AbortController();
62
+ let pending;
63
+ try {
64
+ pending = load(controller.signal);
65
+ }
66
+ catch (error) {
67
+ // A load that throws before returning its promise has still failed to
68
+ // load; it must not escape the effect and take the host document down.
69
+ pending = Promise.reject(error);
70
+ }
71
+ pending.then((text) => {
72
+ if (controller.signal.aborted)
73
+ return;
74
+ setSettled({ key: forKey, result: { status: "ready", text } });
75
+ }, (error) => {
76
+ // An abort is this hook letting go of the load, not the load failing.
77
+ if (controller.signal.aborted)
78
+ return;
79
+ setSettled({
80
+ key: forKey,
81
+ result: {
82
+ status: "error",
83
+ error: error instanceof Error ? error : new Error(String(error)),
84
+ },
85
+ });
86
+ });
87
+ return () => controller.abort();
88
+ }, [name, url, keyLoad, attempt]);
89
+ if (source.kind === "inline") {
90
+ return { status: "ready", text: source.text, retry };
91
+ }
92
+ // A result settled for an earlier key is stale: report loading until this key's load settles,
93
+ // rather than the previous file's text for one render.
94
+ if (settled === undefined ||
95
+ key === undefined ||
96
+ !sameKey(settled.key, key)) {
97
+ return { status: "loading", retry };
98
+ }
99
+ return { ...settled.result, retry };
100
+ }
101
+ //# sourceMappingURL=source-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-text.js","sourceRoot":"","sources":["../src/source-text.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { type ReactNode } from "react";
2
+ import type { ContentSource } from "./types.ts";
3
+ /**
4
+ * Lends a spot in a surface's title bar to the renderer that draws `source` below, for controls
5
+ * that belong next to the surface's own (a patch's file list, an image's zoom).
6
+ *
7
+ * Keyed by the source object rather than by nesting: a renderer hands a nested source a new
8
+ * object (a markdown fence, an embedded file), so only the one drawing `source` itself sees the
9
+ * spot, and a `diff` fence inside a markdown card never takes over the card's bar.
10
+ */
11
+ export declare function ContentTitleBar({ source, target, children, }: {
12
+ source: ContentSource;
13
+ /** The element the controls portal into; null until it has mounted. */
14
+ target: HTMLElement | null;
15
+ children?: ReactNode;
16
+ }): import("react").JSX.Element;
17
+ /**
18
+ * Where this source's controls go: an element to portal into, null while the title bar is still
19
+ * mounting, or undefined when no surface lent one for it, in which case the renderer draws a bar
20
+ * of its own if it needs one.
21
+ */
22
+ export declare function useContentTitleBar(source: ContentSource): HTMLElement | null | undefined;
23
+ //# sourceMappingURL=title-bar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"title-bar.d.ts","sourceRoot":"","sources":["../src/title-bar.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAc,MAAM,OAAO,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAMhD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,MAAM,EACN,QAAQ,GACT,EAAE;IACD,MAAM,EAAE,aAAa,CAAC;IACtB,uEAAuE;IACvE,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,+BAMA;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,aAAa,GACpB,WAAW,GAAG,IAAI,GAAG,SAAS,CAGhC"}
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext } from "react";
3
+ const TitleBarContext = createContext(null);
4
+ /**
5
+ * Lends a spot in a surface's title bar to the renderer that draws `source` below, for controls
6
+ * that belong next to the surface's own (a patch's file list, an image's zoom).
7
+ *
8
+ * Keyed by the source object rather than by nesting: a renderer hands a nested source a new
9
+ * object (a markdown fence, an embedded file), so only the one drawing `source` itself sees the
10
+ * spot, and a `diff` fence inside a markdown card never takes over the card's bar.
11
+ */
12
+ export function ContentTitleBar({ source, target, children, }) {
13
+ return (_jsx(TitleBarContext.Provider, { value: { source, target }, children: children }));
14
+ }
15
+ /**
16
+ * Where this source's controls go: an element to portal into, null while the title bar is still
17
+ * mounting, or undefined when no surface lent one for it, in which case the renderer draws a bar
18
+ * of its own if it needs one.
19
+ */
20
+ export function useContentTitleBar(source) {
21
+ const slot = useContext(TitleBarContext);
22
+ return slot !== null && slot.source === source ? slot.target : undefined;
23
+ }
24
+ //# sourceMappingURL=title-bar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"title-bar.js","sourceRoot":"","sources":["../src/title-bar.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAkB,UAAU,EAAE,MAAM,OAAO,CAAC;AAKlE,MAAM,eAAe,GAAG,aAAa,CAAc,IAAI,CAAC,CAAC;AAEzD;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,MAAM,EACN,MAAM,EACN,QAAQ,GAMT;IACC,OAAO,CACL,KAAC,eAAe,CAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,YAChD,QAAQ,GACgB,CAC5B,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAqB;IAErB,MAAM,IAAI,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC;IACzC,OAAO,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { ComponentType } from "react";
2
+ export type ContentSource = {
3
+ kind: "inline";
4
+ text: string;
5
+ lang?: string;
6
+ } | {
7
+ kind: "file";
8
+ name: string;
9
+ mime?: string;
10
+ size?: number;
11
+ /** Where the bytes live; what an `<img>`, an `<iframe>` or the download link uses. */
12
+ url?: string;
13
+ /** The text, for renderers that need it; useSourceText falls back to fetching url. */
14
+ load?: (signal: AbortSignal) => Promise<string>;
15
+ /**
16
+ * The file was deleted. Links and embeds of it draw it struck through, as deleted, instead
17
+ * of a link to nothing or a broken image.
18
+ */
19
+ deleted?: boolean;
20
+ };
21
+ /** "document": inside a flow of other content (a fence, an embed). "viewer": owns the surface. */
22
+ export type Placement = "document" | "viewer";
23
+ export type ContentProps = {
24
+ source: ContentSource;
25
+ placement: Placement;
26
+ /**
27
+ * Put on the renderer's root element. Decoration passes mark the element a source was
28
+ * written as (an embedded `<img>`), and the class has to survive the swap to a renderer.
29
+ */
30
+ className?: string;
31
+ };
32
+ export type ContentDisplay = "block" | "inline";
33
+ export type ContentRenderer = {
34
+ name: string;
35
+ /**
36
+ * Synchronous and pure: decided from the descriptor alone. Layout decisions upstream
37
+ * (a paragraph becoming a `<div>`) are made from it during render and must not flip
38
+ * when data arrives later.
39
+ */
40
+ accepts(source: ContentSource, placement: Placement): boolean;
41
+ /** "block" content cannot sit inside a `<p>`. Default "block". */
42
+ display?: ContentDisplay;
43
+ /** `Next` draws the same source with the renderers after this one. */
44
+ Component: ComponentType<ContentProps & {
45
+ Next: ComponentType<ContentProps>;
46
+ }>;
47
+ };
48
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C;IACE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sFAAsF;IACtF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEN,kGAAkG;AAClG,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE9C,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,EAAE,SAAS,CAAC;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEhD,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC;IAC9D,kEAAkE;IAClE,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,sEAAsE;IACtE,SAAS,EAAE,aAAa,CACtB,YAAY,GAAG;QAAE,IAAI,EAAE,aAAa,CAAC,YAAY,CAAC,CAAA;KAAE,CACrD,CAAC;CACH,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,58 @@
1
1
  {
2
2
  "name": "@orz/ui-content",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "A registry where content packages contribute renderers and any surface asks for content to be drawn",
5
+ "license": "BSD-3-Clause",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/orzFly/ui.git",
9
+ "directory": "packages/ui-content"
10
+ },
11
+ "type": "module",
12
+ "sideEffects": [
13
+ "**/*.css"
14
+ ],
15
+ "exports": {
16
+ ".": {
17
+ "@orz/source": "./src/index.ts",
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "./attachment": {
22
+ "@orz/source": "./src/attachment/index.ts",
23
+ "types": "./dist/attachment/index.d.ts",
24
+ "default": "./dist/attachment/index.js"
25
+ },
26
+ "./styles.css": "./styles.css"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "styles.css"
31
+ ],
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "peerDependencies": {
36
+ "lucide-react": "^1",
37
+ "react": "^19",
38
+ "react-dom": "^19",
39
+ "@orz/ui": "^0.1.0"
40
+ },
41
+ "devDependencies": {
42
+ "@testing-library/react": "^16.3.2",
43
+ "@types/react": "^19.2.18",
44
+ "@types/react-dom": "^19.2.4",
45
+ "happy-dom": "^21.0.3",
46
+ "lucide-react": "^1.31.0",
47
+ "radix-ui": "^1.6.7",
48
+ "react": "^19.2.8",
49
+ "react-dom": "^19.2.8",
50
+ "vitest": "^5.0.5",
51
+ "@orz/ui": "0.1.0"
52
+ },
53
+ "scripts": {
54
+ "build": "tsc -p tsconfig.build.json",
55
+ "typecheck": "tsc --noEmit",
56
+ "test": "vitest run"
57
+ }
6
58
  }
package/styles.css ADDED
@@ -0,0 +1,4 @@
1
+ /* Tailwind resolves an imported file's @source against that file, so a
2
+ consumer importing this stylesheet scans the classes in the package's
3
+ shipped JS without writing an @source of its own. */
4
+ @source "./dist";