@openpageflip/react 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -2,35 +2,61 @@
2
2
 
3
3
  React 19 bindings for [`@openpageflip/core`](https://www.npmjs.com/package/@openpageflip/core). Successor to [`react-pageflip`](https://www.npmjs.com/package/react-pageflip).
4
4
 
5
- Pre-1.0. The plan, decisions and status live in the [repository's SPEC.md](https://github.com/benhonda/openpageflip/blob/main/SPEC.md).
5
+ Docs, live demos, the API reference and the changelog: <!-- homepage -->**[openpageflip.shittylittleapps.com](https://openpageflip.shittylittleapps.com)**<!-- /homepage -->
6
6
 
7
7
  ```sh
8
8
  bun add @openpageflip/core @openpageflip/react
9
9
  ```
10
10
 
11
+ Every direct child of `FlipBook` is a page. `Page` marks the hard ones and styles the page element itself. `ref` is the core `Book`.
12
+
13
+ <!-- example: apps/docs/src/examples/react/Quickstart.tsx -->
11
14
  ```tsx
12
15
  import "@openpageflip/core/styles.css";
13
- import { FlipBook, Page, type Book } from "@openpageflip/react";
16
+ import { type Book, FlipBook, Page } from "@openpageflip/react";
14
17
  import { useRef } from "react";
15
18
 
16
- export function Album() {
19
+ export default function Quickstart() {
17
20
  const book = useRef<Book>(null);
18
21
  return (
19
22
  <>
20
- <button onClick={() => book.current?.flipNext()}>Next</button>
21
- <FlipBook ref={book} width={400} height={600} size="stretch" cover onFlip={(e) => console.log(e.page)}>
22
- <Page density="hard">Cover</Page>
23
- <Page>One</Page>
24
- <Photo src="two.jpg" /> {/* any child is a page; no refs needed */}
25
- <Page density="hard">Back</Page>
23
+ <FlipBook ref={book} className="book" width={400} height={560} size="stretch" cover>
24
+ <Page density="hard" className="page page-cover">
25
+ <h2>OpenPageFlip</h2>
26
+ <p>A page-turn effect for React</p>
27
+ </Page>
28
+ <Page className="page">
29
+ <h3>Every child is a page</h3>
30
+ <p>Wrap one in Page to make it hard or to style the page element itself.</p>
31
+ </Page>
32
+ <Page className="page">
33
+ <h3>No refs on pages</h3>
34
+ <p>The book owns the page elements. Your components render inside them.</p>
35
+ </Page>
36
+ <Page className="page">
37
+ <h3>Props are options</h3>
38
+ <p>Change one and the book rebuilds on the same page. Children can change freely.</p>
39
+ </Page>
40
+ <Page className="page">
41
+ <h3>Renders on the server</h3>
42
+ <p>This page was server-rendered by the docs site, then hydrated.</p>
43
+ </Page>
44
+ <Page density="hard" className="page page-cover">
45
+ <p>The end</p>
46
+ </Page>
26
47
  </FlipBook>
48
+ <p className="controls">
49
+ <button type="button" onClick={() => book.current?.flipPrev()}>
50
+ Previous
51
+ </button>
52
+ <button type="button" onClick={() => book.current?.flipNext()}>
53
+ Next
54
+ </button>
55
+ </p>
27
56
  </>
28
57
  );
29
58
  }
30
59
  ```
60
+ <!-- /example -->
31
61
 
32
- - Every direct child is a page. Use `Page` to mark hard pages or to put a class or style on the page element itself.
33
- - `ref` gives you the core `Book` (`flipNext`, `flipTo`, `page`, `on`, ...).
34
- - `page` makes the book controlled: change it and the book flips there. `onFlip` reports the first page of the spread that landed.
35
- - Options are props. Changing one rebuilds the book on the same page. Children can change freely.
36
- - Requires React 19. The component is a client component and ships with `"use client"`; it renders on the server.
62
+ The code above is the docs site's own example and runs in its test suite. Pre-1.0: the plan, decisions and status live in the repository's [SPEC.md](https://github.com/benhonda/openpageflip/blob/main/SPEC.md).
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ import { Book, Book as Book$1, BookEvents, BookEvents as BookEvents$1, BookOptio
2
2
  import { CSSProperties, HTMLAttributes, ReactElement, ReactNode, Ref } from "react";
3
3
  //#region src/FlipBook.d.ts
4
4
  /** `onFlip`, `onChangeState`, ... one prop per core event, typed from the core's event map. */
5
- type EventProps = { [K in keyof BookEvents$1 as `on${Capitalize<K>}`]?: (event: BookEvents$1[K]) => void; };
6
- type FlipBookProps = Omit<BookOptions$1, "pages"> & EventProps & {
5
+ type FlipBookEventProps = { [K in keyof BookEvents$1 as `on${Capitalize<K>}`]?: (event: BookEvents$1[K]) => void; };
6
+ type FlipBookProps = Omit<BookOptions$1, "pages"> & FlipBookEventProps & {
7
7
  /** The book's API, available from the moment the component mounts. */
8
8
  ref?: Ref<Book$1>;
9
9
  /** Each child is a page. Use `Page` to mark hard pages or style the page element. */
@@ -27,5 +27,5 @@ type PageProps = HTMLAttributes<HTMLDivElement> & {
27
27
  */
28
28
  declare function Page({ density, children, ...rest }: PageProps): ReactElement;
29
29
  //#endregion
30
- export { type Book, type BookEvents, type BookOptions, type BookRect, FlipBook, type FlipBookProps, type FlipCorner, type FlipState, type Layout, type Orientation, Page, type PageDensity, type PageProps };
30
+ export { type Book, type BookEvents, type BookOptions, type BookRect, FlipBook, type FlipBookEventProps, type FlipBookProps, type FlipCorner, type FlipState, type Layout, type Orientation, Page, type PageDensity, type PageProps };
31
31
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -93,7 +93,7 @@ function FlipBook({ ref, children, page, className, style, onInit, onUpdate, onF
93
93
  if (book === null || el === null) return;
94
94
  const pages = collectPages(el);
95
95
  if (sameElements(pages, pagesRef.current)) {
96
- book.update();
96
+ book.redraw();
97
97
  return;
98
98
  }
99
99
  pagesRef.current = pages;
@@ -144,10 +144,17 @@ function handleFor(bookRef) {
144
144
  turnPrev: () => live().turnPrev(),
145
145
  setPages: (pages) => live().setPages(pages),
146
146
  update: () => live().update(),
147
+ redraw: () => live().redraw(),
147
148
  destroy: () => live().destroy()
148
149
  };
149
150
  }
150
151
  //#endregion
152
+ //#region src/index.ts
153
+ /**
154
+ * React 19 bindings: `FlipBook`, `Page`, and the core types they use.
155
+ * @module @openpageflip/react
156
+ */
157
+ //#endregion
151
158
  export { FlipBook, Page };
152
159
 
153
160
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/Page.tsx","../src/FlipBook.tsx"],"sourcesContent":["import type { PageDensity } from \"@openpageflip/core\";\nimport type { HTMLAttributes, ReactElement, ReactNode } from \"react\";\n\nexport type PageProps = HTMLAttributes<HTMLDivElement> & {\n /** `hard` pages turn as a rigid sheet. @default \"soft\" */\n density?: PageDensity;\n children?: ReactNode;\n};\n\n/**\n * One page of a `FlipBook`. Any child of `FlipBook` becomes a page; `Page` is the way to say\n * which are hard and to put a class or style on the page element itself.\n */\nexport function Page({ density, children, ...rest }: PageProps): ReactElement {\n return (\n <div data-density={density} {...rest}>\n {children}\n </div>\n );\n}\n","\"use client\";\n\nimport {\n type Book,\n type BookEvents,\n type BookOptions,\n createBook,\n FlipCorner,\n} from \"@openpageflip/core\";\nimport {\n Children,\n type CSSProperties,\n isValidElement,\n type ReactElement,\n type ReactNode,\n type Ref,\n useEffect,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n} from \"react\";\nimport { Page, type PageProps } from \"./Page.tsx\";\n\n/** `onFlip`, `onChangeState`, ... one prop per core event, typed from the core's event map. */\ntype EventProps = {\n [K in keyof BookEvents as `on${Capitalize<K>}`]?: (event: BookEvents[K]) => void;\n};\n\nexport type FlipBookProps = Omit<BookOptions, \"pages\"> &\n EventProps & {\n /** The book's API, available from the moment the component mounts. */\n ref?: Ref<Book>;\n /** Each child is a page. Use `Page` to mark hard pages or style the page element. */\n children: ReactNode;\n /** Controlled page: when it changes, the book flips there. Pair with `onFlip`. */\n page?: number;\n className?: string;\n style?: CSSProperties;\n };\n\nconst PAGE_ATTR = \"data-opf-page\";\n\n/** Page elements rendered by this component, in order, skipping the core's transient clone. */\nfunction collectPages(container: HTMLElement): HTMLElement[] {\n return Array.from(\n container.querySelectorAll<HTMLElement>(`:scope > [${PAGE_ATTR}]:not([data-opf-clone])`),\n );\n}\n\nfunction sameElements(a: readonly HTMLElement[], b: readonly HTMLElement[]): boolean {\n return a.length === b.length && a.every((el, i) => el === b[i]);\n}\n\nfunction renderPage(child: ReactNode): ReactElement | null {\n if (child === null || child === undefined || typeof child === \"boolean\") return null;\n if (isValidElement<PageProps>(child) && child.type === Page) {\n const { density, children, ...rest } = child.props;\n return (\n <div {...rest} {...{ [PAGE_ATTR]: \"\" }} data-density={density}>\n {children}\n </div>\n );\n }\n return <div {...{ [PAGE_ATTR]: \"\" }}>{child}</div>;\n}\n\nexport function FlipBook({\n ref,\n children,\n page,\n className,\n style,\n onInit,\n onUpdate,\n onFlip,\n onChangeState,\n onChangeOrientation,\n ...options\n}: FlipBookProps): ReactElement {\n const container = useRef<HTMLDivElement>(null);\n const bookRef = useRef<Book | null>(null);\n const pagesRef = useRef<HTMLElement[]>([]);\n\n // Callbacks and the easing function are read live, so new closures each render do not rebuild the book.\n const latest = useRef({\n onInit,\n onUpdate,\n onFlip,\n onChangeState,\n onChangeOrientation,\n easing: options.easing,\n });\n useLayoutEffect(() => {\n latest.current = {\n onInit,\n onUpdate,\n onFlip,\n onChangeState,\n onChangeOrientation,\n easing: options.easing,\n };\n });\n\n // Everything else about the book is fixed at creation, so a change means a new book.\n const { easing: _easing, startPage, ...settings } = options;\n const settingsKey = JSON.stringify(settings);\n const initialPage = page ?? startPage;\n\n // `initialPage` only seeds a new book; the controlled `page` effect below handles changes.\n // biome-ignore lint/correctness/useExhaustiveDependencies: settingsKey is the serialised settings\n useLayoutEffect(() => {\n const el = container.current;\n if (el === null) return;\n const pages = collectPages(el);\n pagesRef.current = pages;\n // `settings` is the value from the render that changed `settingsKey`, so it is current here.\n const book = createBook(el, {\n ...settings,\n easing: (t) => latest.current.easing?.(t) ?? t,\n pages,\n // Keep the page a previous book was on when only settings changed.\n startPage: Math.min(bookRef.current?.page ?? initialPage ?? 0, pages.length - 1),\n });\n book.on(\"init\", (e) => latest.current.onInit?.(e));\n book.on(\"update\", (e) => latest.current.onUpdate?.(e));\n book.on(\"flip\", (e) => latest.current.onFlip?.(e));\n book.on(\"changeState\", (e) => latest.current.onChangeState?.(e));\n book.on(\"changeOrientation\", (e) => latest.current.onChangeOrientation?.(e));\n bookRef.current = book;\n return () => {\n book.destroy();\n bookRef.current = null;\n };\n }, [settingsKey]);\n\n // After every commit: adopt added or removed pages, and re-apply the book's own classes and\n // layout in case React rewrote a page's attributes.\n useLayoutEffect(() => {\n const book = bookRef.current;\n const el = container.current;\n if (book === null || el === null) return;\n const pages = collectPages(el);\n if (sameElements(pages, pagesRef.current)) {\n book.update();\n return;\n }\n pagesRef.current = pages;\n book.setPages(pages);\n });\n\n useEffect(() => {\n const book = bookRef.current;\n if (book === null || page === undefined || page === book.page) return;\n void book.flipTo(page);\n }, [page]);\n\n useImperativeHandle(ref, () => handleFor(bookRef), []);\n\n return (\n <div ref={container} className={className} style={style}>\n {Children.map(children, renderPage)}\n </div>\n );\n}\n\n/** A `Book` that always talks to the current instance and says so when there is none. */\nfunction handleFor(bookRef: { current: Book | null }): Book {\n const live = (): Book => {\n if (bookRef.current === null) throw new Error(\"@openpageflip/react: FlipBook is not mounted\");\n return bookRef.current;\n };\n return {\n get page() {\n return live().page;\n },\n get pageCount() {\n return live().pageCount;\n },\n get orientation() {\n return live().orientation;\n },\n get state() {\n return live().state;\n },\n get rect() {\n return live().rect;\n },\n on: (name, listener, options) => live().on(name, listener, options),\n off: (name, listener) => live().off(name, listener),\n flipNext: (corner = FlipCorner.top) => live().flipNext(corner),\n flipPrev: (corner = FlipCorner.top) => live().flipPrev(corner),\n flipTo: (page, corner = FlipCorner.top) => live().flipTo(page, corner),\n turnTo: (page) => live().turnTo(page),\n turnNext: () => live().turnNext(),\n turnPrev: () => live().turnPrev(),\n setPages: (pages) => live().setPages(pages),\n update: () => live().update(),\n destroy: () => live().destroy(),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/Page.tsx","../src/FlipBook.tsx","../src/index.ts"],"sourcesContent":["import type { PageDensity } from \"@openpageflip/core\";\nimport type { HTMLAttributes, ReactElement, ReactNode } from \"react\";\n\nexport type PageProps = HTMLAttributes<HTMLDivElement> & {\n /** `hard` pages turn as a rigid sheet. @default \"soft\" */\n density?: PageDensity;\n children?: ReactNode;\n};\n\n/**\n * One page of a `FlipBook`. Any child of `FlipBook` becomes a page; `Page` is the way to say\n * which are hard and to put a class or style on the page element itself.\n */\nexport function Page({ density, children, ...rest }: PageProps): ReactElement {\n return (\n <div data-density={density} {...rest}>\n {children}\n </div>\n );\n}\n","\"use client\";\n\nimport {\n type Book,\n type BookEvents,\n type BookOptions,\n createBook,\n FlipCorner,\n} from \"@openpageflip/core\";\nimport {\n Children,\n type CSSProperties,\n isValidElement,\n type ReactElement,\n type ReactNode,\n type Ref,\n useEffect,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n} from \"react\";\nimport { Page, type PageProps } from \"./Page.tsx\";\n\n/** `onFlip`, `onChangeState`, ... one prop per core event, typed from the core's event map. */\nexport type FlipBookEventProps = {\n [K in keyof BookEvents as `on${Capitalize<K>}`]?: (event: BookEvents[K]) => void;\n};\n\nexport type FlipBookProps = Omit<BookOptions, \"pages\"> &\n FlipBookEventProps & {\n /** The book's API, available from the moment the component mounts. */\n ref?: Ref<Book>;\n /** Each child is a page. Use `Page` to mark hard pages or style the page element. */\n children: ReactNode;\n /** Controlled page: when it changes, the book flips there. Pair with `onFlip`. */\n page?: number;\n className?: string;\n style?: CSSProperties;\n };\n\nconst PAGE_ATTR = \"data-opf-page\";\n\n/** Page elements rendered by this component, in order, skipping the core's transient clone. */\nfunction collectPages(container: HTMLElement): HTMLElement[] {\n return Array.from(\n container.querySelectorAll<HTMLElement>(`:scope > [${PAGE_ATTR}]:not([data-opf-clone])`),\n );\n}\n\nfunction sameElements(a: readonly HTMLElement[], b: readonly HTMLElement[]): boolean {\n return a.length === b.length && a.every((el, i) => el === b[i]);\n}\n\nfunction renderPage(child: ReactNode): ReactElement | null {\n if (child === null || child === undefined || typeof child === \"boolean\") return null;\n if (isValidElement<PageProps>(child) && child.type === Page) {\n const { density, children, ...rest } = child.props;\n return (\n <div {...rest} {...{ [PAGE_ATTR]: \"\" }} data-density={density}>\n {children}\n </div>\n );\n }\n return <div {...{ [PAGE_ATTR]: \"\" }}>{child}</div>;\n}\n\nexport function FlipBook({\n ref,\n children,\n page,\n className,\n style,\n onInit,\n onUpdate,\n onFlip,\n onChangeState,\n onChangeOrientation,\n ...options\n}: FlipBookProps): ReactElement {\n const container = useRef<HTMLDivElement>(null);\n const bookRef = useRef<Book | null>(null);\n const pagesRef = useRef<HTMLElement[]>([]);\n\n // Callbacks and the easing function are read live, so new closures each render do not rebuild the book.\n const latest = useRef({\n onInit,\n onUpdate,\n onFlip,\n onChangeState,\n onChangeOrientation,\n easing: options.easing,\n });\n useLayoutEffect(() => {\n latest.current = {\n onInit,\n onUpdate,\n onFlip,\n onChangeState,\n onChangeOrientation,\n easing: options.easing,\n };\n });\n\n // Everything else about the book is fixed at creation, so a change means a new book.\n const { easing: _easing, startPage, ...settings } = options;\n const settingsKey = JSON.stringify(settings);\n const initialPage = page ?? startPage;\n\n // `initialPage` only seeds a new book; the controlled `page` effect below handles changes.\n // biome-ignore lint/correctness/useExhaustiveDependencies: settingsKey is the serialised settings\n useLayoutEffect(() => {\n const el = container.current;\n if (el === null) return;\n const pages = collectPages(el);\n pagesRef.current = pages;\n // `settings` is the value from the render that changed `settingsKey`, so it is current here.\n const book = createBook(el, {\n ...settings,\n easing: (t) => latest.current.easing?.(t) ?? t,\n pages,\n // Keep the page a previous book was on when only settings changed.\n startPage: Math.min(bookRef.current?.page ?? initialPage ?? 0, pages.length - 1),\n });\n book.on(\"init\", (e) => latest.current.onInit?.(e));\n book.on(\"update\", (e) => latest.current.onUpdate?.(e));\n book.on(\"flip\", (e) => latest.current.onFlip?.(e));\n book.on(\"changeState\", (e) => latest.current.onChangeState?.(e));\n book.on(\"changeOrientation\", (e) => latest.current.onChangeOrientation?.(e));\n bookRef.current = book;\n return () => {\n book.destroy();\n bookRef.current = null;\n };\n }, [settingsKey]);\n\n // After every commit: adopt added or removed pages, and re-apply the book's own classes and\n // layout in case React rewrote a page's attributes.\n useLayoutEffect(() => {\n const book = bookRef.current;\n const el = container.current;\n if (book === null || el === null) return;\n const pages = collectPages(el);\n if (sameElements(pages, pagesRef.current)) {\n book.redraw();\n return;\n }\n pagesRef.current = pages;\n book.setPages(pages);\n });\n\n useEffect(() => {\n const book = bookRef.current;\n if (book === null || page === undefined || page === book.page) return;\n void book.flipTo(page);\n }, [page]);\n\n useImperativeHandle(ref, () => handleFor(bookRef), []);\n\n return (\n <div ref={container} className={className} style={style}>\n {Children.map(children, renderPage)}\n </div>\n );\n}\n\n/** A `Book` that always talks to the current instance and says so when there is none. */\nfunction handleFor(bookRef: { current: Book | null }): Book {\n const live = (): Book => {\n if (bookRef.current === null) throw new Error(\"@openpageflip/react: FlipBook is not mounted\");\n return bookRef.current;\n };\n return {\n get page() {\n return live().page;\n },\n get pageCount() {\n return live().pageCount;\n },\n get orientation() {\n return live().orientation;\n },\n get state() {\n return live().state;\n },\n get rect() {\n return live().rect;\n },\n on: (name, listener, options) => live().on(name, listener, options),\n off: (name, listener) => live().off(name, listener),\n flipNext: (corner = FlipCorner.top) => live().flipNext(corner),\n flipPrev: (corner = FlipCorner.top) => live().flipPrev(corner),\n flipTo: (page, corner = FlipCorner.top) => live().flipTo(page, corner),\n turnTo: (page) => live().turnTo(page),\n turnNext: () => live().turnNext(),\n turnPrev: () => live().turnPrev(),\n setPages: (pages) => live().setPages(pages),\n update: () => live().update(),\n redraw: () => live().redraw(),\n destroy: () => live().destroy(),\n };\n}\n","/**\n * React 19 bindings: `FlipBook`, `Page`, and the core types they use.\n * @module @openpageflip/react\n */\n\"use client\";\n\nexport type {\n Book,\n BookEvents,\n BookOptions,\n BookRect,\n FlipCorner,\n FlipState,\n Layout,\n Orientation,\n PageDensity,\n} from \"@openpageflip/core\";\nexport { FlipBook, type FlipBookEventProps, type FlipBookProps } from \"./FlipBook.tsx\";\nexport { Page, type PageProps } from \"./Page.tsx\";\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpageflip/react",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "React 19 bindings for @openpageflip/core. Successor to react-pageflip.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,7 +10,7 @@
10
10
  "url": "git+https://github.com/benhonda/openpageflip.git",
11
11
  "directory": "packages/react"
12
12
  },
13
- "homepage": "https://github.com/benhonda/openpageflip#readme",
13
+ "homepage": "https://openpageflip.shittylittleapps.com",
14
14
  "bugs": "https://github.com/benhonda/openpageflip/issues",
15
15
  "keywords": [
16
16
  "react",
@@ -27,7 +27,7 @@
27
27
  "access": "public"
28
28
  },
29
29
  "peerDependencies": {
30
- "@openpageflip/core": "^0.0.1",
30
+ "@openpageflip/core": "^0.2.1",
31
31
  "react": "^19.2.8"
32
32
  },
33
33
  "devDependencies": {