create-rspeedy 0.13.6 → 0.14.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.
Files changed (25) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/index.js +35 -32
  3. package/package.json +5 -5
  4. package/template-react-vitest-rltl/package.json +10 -0
  5. package/template-react-vitest-rltl/src/__tests__/index.test.jsx +17 -0
  6. package/template-react-vitest-rltl-js/lynx.config.js +0 -19
  7. package/template-react-vitest-rltl-js/package.json +0 -26
  8. package/template-react-vitest-rltl-js/src/App.jsx +0 -59
  9. package/template-react-vitest-rltl-js/src/__tests__/index.test.jsx +0 -103
  10. package/template-react-vitest-rltl-js/src/index.jsx +0 -11
  11. package/template-react-vitest-rltl-js/src/lib/flappy.js +0 -58
  12. package/template-react-vitest-rltl-js/src/useFlappy.js +0 -37
  13. package/template-react-vitest-rltl-ts/lynx.config.ts +0 -18
  14. package/template-react-vitest-rltl-ts/package.json +0 -30
  15. package/template-react-vitest-rltl-ts/src/App.tsx +0 -61
  16. package/template-react-vitest-rltl-ts/src/__tests__/index.test.tsx +0 -103
  17. package/template-react-vitest-rltl-ts/src/index.tsx +0 -11
  18. package/template-react-vitest-rltl-ts/src/lib/flappy.ts +0 -76
  19. package/template-react-vitest-rltl-ts/src/rspeedy-env.d.ts +0 -1
  20. package/template-react-vitest-rltl-ts/src/tsconfig.json +0 -15
  21. package/template-react-vitest-rltl-ts/src/useFlappy.ts +0 -49
  22. package/template-react-vitest-rltl-ts/tsconfig.json +0 -17
  23. package/template-react-vitest-rltl-ts/tsconfig.node.json +0 -21
  24. package/template-react-vitest-rltl-ts/vitest.config.ts +0 -9
  25. /package/{template-react-vitest-rltl-js → template-react-vitest-rltl}/vitest.config.js +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # create-rspeedy
2
2
 
3
+ ## 0.14.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix the error when installing Lynx DevTool skill. ([#2427](https://github.com/lynx-family/lynx-stack/pull/2427))
8
+
9
+ ## 0.14.0
10
+
11
+ ### Patch Changes
12
+
13
+ - Add optional Lynx DevTool skill. ([#2421](https://github.com/lynx-family/lynx-stack/pull/2421))
14
+
15
+ - move Vitest integration to create-rstack extraTools and merge the Vitest templates into a single incremental overlay ([#2408](https://github.com/lynx-family/lynx-stack/pull/2408))
16
+
3
17
  ## 0.13.6
4
18
 
5
19
  ## 0.13.5
package/dist/index.js CHANGED
@@ -2,24 +2,18 @@
2
2
  import { createRequire } from "node:module";
3
3
  import node_path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { checkCancel, create, multiselect, select as external_create_rstack_select } from "create-rstack";
5
+ import { checkCancel, copyFolder, create, select as external_create_rstack_select } from "create-rstack";
6
6
  const src_dirname = node_path.dirname(fileURLToPath(import.meta.url));
7
7
  const src_require = createRequire(import.meta.url);
8
8
  const { devDependencies: devDependencies } = src_require('../package.json');
9
- const composeTemplateName = ({ template, tools, lang })=>{
10
- const toolsKeys = (tools ? Object.keys(tools) : []).sort();
11
- const toolsStr = toolsKeys.length > 0 ? `-${toolsKeys.join('-')}` : '';
12
- return `${template}${toolsStr}-${lang}`;
13
- };
9
+ const composeTemplateName = ({ template, lang })=>`${template}-${lang}`;
14
10
  const TEMPLATES = [
15
11
  {
16
12
  template: 'react',
17
- tools: {},
18
13
  lang: 'ts'
19
14
  },
20
15
  {
21
16
  template: 'react',
22
- tools: {},
23
17
  lang: 'js'
24
18
  }
25
19
  ];
@@ -30,7 +24,10 @@ async function getTemplateName({ template }) {
30
24
  if (lang && [
31
25
  'js',
32
26
  'ts'
33
- ].includes(lang)) return template;
27
+ ].includes(lang)) {
28
+ if ('react' === pair[0]) return `react-${lang}`;
29
+ return template;
30
+ }
34
31
  return `${template}-ts`;
35
32
  }
36
33
  const language = checkCancel(await external_create_rstack_select({
@@ -47,41 +44,47 @@ async function getTemplateName({ template }) {
47
44
  }
48
45
  ]
49
46
  }));
50
- const tools = checkCancel(await multiselect({
51
- message: 'Select development tools (Use <space> to select, <enter> to continue)',
52
- required: false,
53
- options: [
54
- {
55
- value: 'vitest-rltl',
56
- label: 'Add ReactLynx Testing Library for unit testing'
57
- }
58
- ],
59
- initialValues: [
60
- 'vitest-rltl'
61
- ]
62
- }));
63
47
  return composeTemplateName({
64
48
  template: 'react',
65
- lang: language,
66
- tools: Object.fromEntries(tools.map((tool)=>[
67
- tool,
68
- tool
69
- ]))
49
+ lang: language
70
50
  });
71
51
  }
72
52
  create({
73
53
  root: node_path.resolve(src_dirname, '..'),
74
54
  name: 'rspeedy',
75
- templates: TEMPLATES.map(({ template, tools, lang })=>composeTemplateName({
55
+ templates: TEMPLATES.map(({ template, lang })=>composeTemplateName({
76
56
  template,
77
- lang,
78
- tools
57
+ lang
79
58
  })),
80
59
  version: devDependencies,
81
60
  getTemplateName: getTemplateName,
61
+ extraTools: [
62
+ {
63
+ value: 'vitest-rltl',
64
+ label: 'ReactLynx Testing Library - unit testing',
65
+ order: 'pre',
66
+ when: (templateName)=>'react-js' === templateName || 'react-ts' === templateName,
67
+ action: ({ distFolder, addAgentsMdSearchDirs })=>{
68
+ const from = node_path.resolve(src_dirname, '..', 'template-react-vitest-rltl');
69
+ copyFolder({
70
+ from,
71
+ to: distFolder,
72
+ isMergePackageJson: true
73
+ });
74
+ addAgentsMdSearchDirs(from);
75
+ }
76
+ }
77
+ ],
78
+ extraSkills: [
79
+ {
80
+ label: 'Lynx DevTool',
81
+ source: 'lynx-community/skills',
82
+ value: 'lynx-devtool'
83
+ }
84
+ ],
82
85
  mapESLintTemplate (templateName) {
83
- const lang = TEMPLATES.find(({ template })=>templateName.startsWith(template))?.lang;
84
- if (!lang) return null;
86
+ const lang = templateName.split('-').at(-1);
87
+ if ('js' !== lang && 'ts' !== lang) return null;
85
88
  switch(lang){
86
89
  case 'js':
87
90
  return 'react-js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-rspeedy",
3
- "version": "0.13.6",
3
+ "version": "0.14.1",
4
4
  "description": "Create Rspeedy-powered ReactLynx apps with one command",
5
5
  "keywords": [
6
6
  "webpack",
@@ -31,14 +31,14 @@
31
31
  "README.md"
32
32
  ],
33
33
  "dependencies": {
34
- "create-rstack": "1.8.0"
34
+ "create-rstack": "1.9.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@rsbuild/plugin-type-check": "1.3.4",
38
38
  "@lynx-js/qrcode-rsbuild-plugin": "^0.4.6",
39
- "@lynx-js/react": "^0.117.0",
40
- "@lynx-js/react-rsbuild-plugin": "^0.13.0",
41
- "@lynx-js/rspeedy": "^0.13.6"
39
+ "@lynx-js/react": "^0.118.0",
40
+ "@lynx-js/react-rsbuild-plugin": "^0.15.0",
41
+ "@lynx-js/rspeedy": "^0.14.1"
42
42
  },
43
43
  "engines": {
44
44
  "node": ">=18"
@@ -0,0 +1,10 @@
1
+ {
2
+ "scripts": {
3
+ "test": "vitest run"
4
+ },
5
+ "devDependencies": {
6
+ "@testing-library/jest-dom": "^6.9.1",
7
+ "jsdom": "^27.4.0",
8
+ "vitest": "^3.2.4"
9
+ }
10
+ }
@@ -0,0 +1,17 @@
1
+ // Copyright 2024 The Lynx Authors. All rights reserved.
2
+ // Licensed under the Apache License Version 2.0 that can be found in the
3
+ // LICENSE file in the root directory of this source tree.
4
+ import '@testing-library/jest-dom'
5
+ import { expect, test } from 'vitest'
6
+ import { getQueriesForElement, render } from '@lynx-js/react/testing-library'
7
+
8
+ import { App } from '../App'
9
+
10
+ test('App', async () => {
11
+ render(<App />)
12
+
13
+ const { findByText } = getQueriesForElement(elementTree.root)
14
+ const element = await findByText('Tap the logo and have fun!')
15
+
16
+ expect(element).toBeInTheDocument()
17
+ })
@@ -1,19 +0,0 @@
1
- import { defineConfig } from '@lynx-js/rspeedy'
2
-
3
- import { pluginQRCode } from '@lynx-js/qrcode-rsbuild-plugin'
4
- import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin'
5
-
6
- export default defineConfig({
7
- source: {
8
- entry: './src/index.jsx',
9
- },
10
- plugins: [
11
- pluginQRCode({
12
- schema(url) {
13
- // We use `?fullscreen=true` to open the page in LynxExplorer in full screen mode
14
- return `${url}?fullscreen=true`
15
- },
16
- }),
17
- pluginReactLynx(),
18
- ],
19
- })
@@ -1,26 +0,0 @@
1
- {
2
- "name": "rspeedy-react-js",
3
- "version": "0.0.0",
4
- "type": "module",
5
- "scripts": {
6
- "build": "rspeedy build",
7
- "dev": "rspeedy dev",
8
- "preview": "rspeedy preview",
9
- "test": "vitest run"
10
- },
11
- "dependencies": {
12
- "@lynx-js/react": "workspace:*"
13
- },
14
- "devDependencies": {
15
- "@lynx-js/preact-devtools": "^5.0.1",
16
- "@lynx-js/qrcode-rsbuild-plugin": "workspace:*",
17
- "@lynx-js/react-rsbuild-plugin": "workspace:*",
18
- "@lynx-js/rspeedy": "workspace:*",
19
- "@testing-library/jest-dom": "^6.9.1",
20
- "jsdom": "^27.4.0",
21
- "vitest": "^3.2.4"
22
- },
23
- "engines": {
24
- "node": ">=18"
25
- }
26
- }
@@ -1,59 +0,0 @@
1
- import { useCallback, useEffect, useState } from '@lynx-js/react'
2
-
3
- import './App.css'
4
- import arrow from './assets/arrow.png'
5
- import lynxLogo from './assets/lynx-logo.png'
6
- import reactLynxLogo from './assets/react-logo.png'
7
- import { useFlappy } from './useFlappy.js'
8
-
9
- export function App(props) {
10
- const [alterLogo, setAlterLogo] = useState(false)
11
- const [logoY, jump] = useFlappy()
12
-
13
- useEffect(() => {
14
- console.info('Hello, ReactLynx')
15
- }, [])
16
- props.onRender?.()
17
-
18
- const onTap = useCallback(() => {
19
- 'background only'
20
- setAlterLogo(prevAlterLogo => !prevAlterLogo)
21
- }, [])
22
-
23
- return (
24
- <view bindtap={jump}>
25
- <view className='Background' />
26
- <view className='App'>
27
- <view className='Banner'>
28
- <view
29
- className='Logo'
30
- style={{ transform: `translateY(${logoY}px)` }}
31
- bindtap={onTap}
32
- >
33
- {alterLogo
34
- ? <image src={reactLynxLogo} className='Logo--react' />
35
- : <image src={lynxLogo} className='Logo--lynx' />}
36
- </view>
37
- <text className='Title'>React</text>
38
- <text className='Subtitle'>on Lynx</text>
39
- </view>
40
- <view className='Content'>
41
- <image src={arrow} className='Arrow' />
42
- <text className='Description'>Tap the logo and have fun!</text>
43
- <text className='Hint'>
44
- Edit<text
45
- style={{
46
- fontStyle: 'italic',
47
- color: 'rgba(255, 255, 255, 0.85)',
48
- }}
49
- >
50
- {' src/App.tsx '}
51
- </text>
52
- to see updates!
53
- </text>
54
- </view>
55
- <view style={{ flex: 1 }} />
56
- </view>
57
- </view>
58
- )
59
- }
@@ -1,103 +0,0 @@
1
- // Copyright 2024 The Lynx Authors. All rights reserved.
2
- // Licensed under the Apache License Version 2.0 that can be found in the
3
- // LICENSE file in the root directory of this source tree.
4
- import '@testing-library/jest-dom'
5
- import { expect, test, vi } from 'vitest'
6
- import { render, getQueriesForElement } from '@lynx-js/react/testing-library'
7
-
8
- import { App } from '../App.jsx'
9
-
10
- test('App', async () => {
11
- const cb = vi.fn()
12
-
13
- render(
14
- <App
15
- onMounted={() => {
16
- cb(`__MAIN_THREAD__: ${__MAIN_THREAD__}`)
17
- }}
18
- />,
19
- )
20
- expect(cb).toBeCalledTimes(1)
21
- expect(cb.mock.calls).toMatchInlineSnapshot(`
22
- [
23
- [
24
- "__MAIN_THREAD__: false",
25
- ],
26
- ]
27
- `)
28
- expect(elementTree.root).toMatchInlineSnapshot(`
29
- <page>
30
- <view>
31
- <view
32
- class="Background"
33
- />
34
- <view
35
- class="App"
36
- >
37
- <view
38
- class="Banner"
39
- >
40
- <view
41
- class="Logo"
42
- style="transform: translateY(0px);"
43
- >
44
- <image
45
- class="Logo--lynx"
46
- src="/src/assets/lynx-logo.png"
47
- />
48
- </view>
49
- <text
50
- class="Title"
51
- >
52
- React
53
- </text>
54
- <text
55
- class="Subtitle"
56
- >
57
- on Lynx
58
- </text>
59
- </view>
60
- <view
61
- class="Content"
62
- >
63
- <image
64
- class="Arrow"
65
- src="/src/assets/arrow.png"
66
- />
67
- <text
68
- class="Description"
69
- >
70
- Tap the logo and have fun!
71
- </text>
72
- <text
73
- class="Hint"
74
- >
75
- Edit
76
- <text
77
- style="font-style:italic;color:rgba(255, 255, 255, 0.85)"
78
- >
79
- src/App.tsx
80
- </text>
81
- to see updates!
82
- </text>
83
- </view>
84
- <view
85
- style="flex:1"
86
- />
87
- </view>
88
- </view>
89
- </page>
90
- `)
91
- const {
92
- findByText,
93
- } = getQueriesForElement(elementTree.root)
94
- const element = await findByText('Tap the logo and have fun!')
95
- expect(element).toBeInTheDocument()
96
- expect(element).toMatchInlineSnapshot(`
97
- <text
98
- class="Description"
99
- >
100
- Tap the logo and have fun!
101
- </text>
102
- `)
103
- })
@@ -1,11 +0,0 @@
1
- import '@lynx-js/preact-devtools'
2
- import '@lynx-js/react/debug'
3
- import { root } from '@lynx-js/react'
4
-
5
- import { App } from './App.jsx'
6
-
7
- root.render(<App />)
8
-
9
- if (import.meta.webpackHot) {
10
- import.meta.webpackHot.accept()
11
- }
@@ -1,58 +0,0 @@
1
- /**
2
- * Framework-agnostic flappy-bird physics engine.
3
- *
4
- * Manages gravity, jump impulse, and a 60fps game loop.
5
- * Wire it up to any UI framework by calling `jump()` on tap
6
- * and reading `getY()` in the loop callback.
7
- *
8
- * @param {(y: number) => void} onUpdate
9
- * @param {object} [options]
10
- * @param {number} [options.gravity=0.6]
11
- * @param {number} [options.jumpForce=-12]
12
- * @param {number} [options.stackFactor=0.6]
13
- * @param {number} [options.frameMs=16]
14
- * @returns {{ jump: () => void, getY: () => number, destroy: () => void }}
15
- */
16
- export function createFlappy(onUpdate, options = {}) {
17
- const {
18
- gravity = 0.6,
19
- jumpForce = -12,
20
- stackFactor = 0.6,
21
- frameMs = 16,
22
- } = options
23
-
24
- let y = 0
25
- let velocity = 0
26
- let timer = null
27
-
28
- function loop() {
29
- velocity += gravity
30
- y += velocity
31
- if (y >= 0) {
32
- y = 0
33
- velocity = 0
34
- timer = null
35
- onUpdate(y)
36
- return
37
- }
38
- onUpdate(y)
39
- timer = setTimeout(loop, frameMs)
40
- }
41
-
42
- function jump() {
43
- // Stack impulse on rapid taps, clamped to one full jumpForce
44
- velocity = Math.max(velocity + jumpForce * stackFactor, jumpForce)
45
- if (!timer) {
46
- loop()
47
- }
48
- }
49
-
50
- function destroy() {
51
- if (timer) {
52
- clearTimeout(timer)
53
- timer = null
54
- }
55
- }
56
-
57
- return { jump, getY: () => y, destroy }
58
- }
@@ -1,37 +0,0 @@
1
- import { useCallback, useEffect, useRef, useState } from '@lynx-js/react'
2
-
3
- import { createFlappy } from './lib/flappy.js'
4
-
5
- /**
6
- * React hook for flappy-bird physics.
7
- *
8
- * Returns `[y, jump]` — a state value and a stable callback.
9
- * The game loop runs automatically; cleanup happens on unmount.
10
- * Options are read once on mount and not reactive to later changes.
11
- *
12
- * @param {object} [options]
13
- * @returns {[number, () => void]}
14
- */
15
- export function useFlappy(options) {
16
- const [y, setY] = useState(0)
17
- const engineRef = useRef(null)
18
-
19
- if (engineRef.current == null) {
20
- engineRef.current = createFlappy((newY) => {
21
- setY(newY)
22
- }, options)
23
- }
24
-
25
- useEffect(() => {
26
- return () => {
27
- engineRef.current?.destroy()
28
- }
29
- }, [])
30
-
31
- const jump = useCallback(() => {
32
- 'background only'
33
- engineRef.current?.jump()
34
- }, [])
35
-
36
- return [y, jump]
37
- }
@@ -1,18 +0,0 @@
1
- import { defineConfig } from '@lynx-js/rspeedy'
2
-
3
- import { pluginQRCode } from '@lynx-js/qrcode-rsbuild-plugin'
4
- import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin'
5
- import { pluginTypeCheck } from '@rsbuild/plugin-type-check'
6
-
7
- export default defineConfig({
8
- plugins: [
9
- pluginQRCode({
10
- schema(url) {
11
- // We use `?fullscreen=true` to open the page in LynxExplorer in full screen mode
12
- return `${url}?fullscreen=true`
13
- },
14
- }),
15
- pluginReactLynx(),
16
- pluginTypeCheck(),
17
- ],
18
- })
@@ -1,30 +0,0 @@
1
- {
2
- "name": "rspeedy-react-ts",
3
- "version": "0.0.0",
4
- "type": "module",
5
- "scripts": {
6
- "build": "rspeedy build",
7
- "dev": "rspeedy dev",
8
- "preview": "rspeedy preview",
9
- "test": "vitest run"
10
- },
11
- "dependencies": {
12
- "@lynx-js/react": "workspace:*"
13
- },
14
- "devDependencies": {
15
- "@lynx-js/preact-devtools": "^5.0.1",
16
- "@lynx-js/qrcode-rsbuild-plugin": "workspace:*",
17
- "@lynx-js/react-rsbuild-plugin": "workspace:*",
18
- "@lynx-js/rspeedy": "workspace:*",
19
- "@lynx-js/types": "3.7.0",
20
- "@rsbuild/plugin-type-check": "1.3.4",
21
- "@testing-library/jest-dom": "^6.9.1",
22
- "@types/react": "^18.3.28",
23
- "jsdom": "^27.4.0",
24
- "typescript": "~5.9.3",
25
- "vitest": "^3.2.4"
26
- },
27
- "engines": {
28
- "node": ">=18"
29
- }
30
- }
@@ -1,61 +0,0 @@
1
- import { useCallback, useEffect, useState } from '@lynx-js/react'
2
-
3
- import './App.css'
4
- import arrow from './assets/arrow.png'
5
- import lynxLogo from './assets/lynx-logo.png'
6
- import reactLynxLogo from './assets/react-logo.png'
7
- import { useFlappy } from './useFlappy.js'
8
-
9
- export function App(props: {
10
- onRender?: () => void
11
- }) {
12
- const [alterLogo, setAlterLogo] = useState(false)
13
- const [logoY, jump] = useFlappy()
14
-
15
- useEffect(() => {
16
- console.info('Hello, ReactLynx')
17
- }, [])
18
- props.onRender?.()
19
-
20
- const onTap = useCallback(() => {
21
- 'background only'
22
- setAlterLogo(prevAlterLogo => !prevAlterLogo)
23
- }, [])
24
-
25
- return (
26
- <view bindtap={jump}>
27
- <view className='Background' />
28
- <view className='App'>
29
- <view className='Banner'>
30
- <view
31
- className='Logo'
32
- style={{ transform: `translateY(${logoY}px)` }}
33
- bindtap={onTap}
34
- >
35
- {alterLogo
36
- ? <image src={reactLynxLogo} className='Logo--react' />
37
- : <image src={lynxLogo} className='Logo--lynx' />}
38
- </view>
39
- <text className='Title'>React</text>
40
- <text className='Subtitle'>on Lynx</text>
41
- </view>
42
- <view className='Content'>
43
- <image src={arrow} className='Arrow' />
44
- <text className='Description'>Tap the logo and have fun!</text>
45
- <text className='Hint'>
46
- Edit<text
47
- style={{
48
- fontStyle: 'italic',
49
- color: 'rgba(255, 255, 255, 0.85)',
50
- }}
51
- >
52
- {' src/App.tsx '}
53
- </text>
54
- to see updates!
55
- </text>
56
- </view>
57
- <view style={{ flex: 1 }} />
58
- </view>
59
- </view>
60
- )
61
- }
@@ -1,103 +0,0 @@
1
- // Copyright 2024 The Lynx Authors. All rights reserved.
2
- // Licensed under the Apache License Version 2.0 that can be found in the
3
- // LICENSE file in the root directory of this source tree.
4
- import '@testing-library/jest-dom'
5
- import { expect, test, vi } from 'vitest'
6
- import { render, getQueriesForElement } from '@lynx-js/react/testing-library'
7
-
8
- import { App } from '../App.jsx'
9
-
10
- test('App', async () => {
11
- const cb = vi.fn()
12
-
13
- render(
14
- <App
15
- onRender={() => {
16
- cb(`__MAIN_THREAD__: ${__MAIN_THREAD__}`)
17
- }}
18
- />,
19
- )
20
- expect(cb).toBeCalledTimes(1)
21
- expect(cb.mock.calls).toMatchInlineSnapshot(`
22
- [
23
- [
24
- "__MAIN_THREAD__: false",
25
- ],
26
- ]
27
- `)
28
- expect(elementTree.root).toMatchInlineSnapshot(`
29
- <page>
30
- <view>
31
- <view
32
- class="Background"
33
- />
34
- <view
35
- class="App"
36
- >
37
- <view
38
- class="Banner"
39
- >
40
- <view
41
- class="Logo"
42
- style="transform: translateY(0px);"
43
- >
44
- <image
45
- class="Logo--lynx"
46
- src="/src/assets/lynx-logo.png"
47
- />
48
- </view>
49
- <text
50
- class="Title"
51
- >
52
- React
53
- </text>
54
- <text
55
- class="Subtitle"
56
- >
57
- on Lynx
58
- </text>
59
- </view>
60
- <view
61
- class="Content"
62
- >
63
- <image
64
- class="Arrow"
65
- src="/src/assets/arrow.png"
66
- />
67
- <text
68
- class="Description"
69
- >
70
- Tap the logo and have fun!
71
- </text>
72
- <text
73
- class="Hint"
74
- >
75
- Edit
76
- <text
77
- style="font-style:italic;color:rgba(255, 255, 255, 0.85)"
78
- >
79
- src/App.tsx
80
- </text>
81
- to see updates!
82
- </text>
83
- </view>
84
- <view
85
- style="flex:1"
86
- />
87
- </view>
88
- </view>
89
- </page>
90
- `)
91
- const {
92
- findByText,
93
- } = getQueriesForElement(elementTree.root!)
94
- const element = await findByText('Tap the logo and have fun!')
95
- expect(element).toBeInTheDocument()
96
- expect(element).toMatchInlineSnapshot(`
97
- <text
98
- class="Description"
99
- >
100
- Tap the logo and have fun!
101
- </text>
102
- `)
103
- })
@@ -1,11 +0,0 @@
1
- import '@lynx-js/preact-devtools'
2
- import '@lynx-js/react/debug'
3
- import { root } from '@lynx-js/react'
4
-
5
- import { App } from './App.jsx'
6
-
7
- root.render(<App />)
8
-
9
- if (import.meta.webpackHot) {
10
- import.meta.webpackHot.accept()
11
- }
@@ -1,76 +0,0 @@
1
- /**
2
- * Framework-agnostic flappy-bird physics engine.
3
- *
4
- * Manages gravity, jump impulse, and a 60fps game loop.
5
- * Wire it up to any UI framework by calling `jump()` on tap
6
- * and reading `getY()` in the loop callback.
7
- */
8
-
9
- export interface FlappyOptions {
10
- /** Downward acceleration per frame (default 0.6) */
11
- gravity?: number
12
- /** Upward impulse per tap — negative value (default -12) */
13
- jumpForce?: number
14
- /** Impulse stacking factor for rapid taps (default 0.6) */
15
- stackFactor?: number
16
- /** Frame interval in ms (default 16 ≈ 60fps) */
17
- frameMs?: number
18
- }
19
-
20
- export type OnUpdate = (y: number) => void
21
-
22
- export interface FlappyEngine {
23
- /** Call on each tap to apply upward impulse. */
24
- jump(): void
25
- /** Current Y offset (0 = ground, negative = airborne). */
26
- getY(): number
27
- /** Stop the game loop and clean up. */
28
- destroy(): void
29
- }
30
-
31
- export function createFlappy(
32
- onUpdate: OnUpdate,
33
- options: FlappyOptions = {},
34
- ): FlappyEngine {
35
- const {
36
- gravity = 0.6,
37
- jumpForce = -12,
38
- stackFactor = 0.6,
39
- frameMs = 16,
40
- } = options
41
-
42
- let y = 0
43
- let velocity = 0
44
- let timer: ReturnType<typeof setTimeout> | null = null
45
-
46
- function loop() {
47
- velocity += gravity
48
- y += velocity
49
- if (y >= 0) {
50
- y = 0
51
- velocity = 0
52
- timer = null
53
- onUpdate(y)
54
- return
55
- }
56
- onUpdate(y)
57
- timer = setTimeout(loop, frameMs)
58
- }
59
-
60
- function jump() {
61
- // Stack impulse on rapid taps, clamped to one full jumpForce
62
- velocity = Math.max(velocity + jumpForce * stackFactor, jumpForce)
63
- if (!timer) {
64
- loop()
65
- }
66
- }
67
-
68
- function destroy() {
69
- if (timer) {
70
- clearTimeout(timer)
71
- timer = null
72
- }
73
- }
74
-
75
- return { jump, getY: () => y, destroy }
76
- }
@@ -1 +0,0 @@
1
- /// <reference types="@lynx-js/rspeedy/client" />
@@ -1,15 +0,0 @@
1
- {
2
- "extends": "../tsconfig.json",
3
- "compilerOptions": {
4
- "composite": true,
5
-
6
- "jsx": "react-jsx",
7
- "jsxImportSource": "@lynx-js/react",
8
-
9
- "module": "ESNext",
10
- "moduleResolution": "Bundler",
11
-
12
- "noEmit": true,
13
- },
14
- "include": ["./**/*.ts", "./**/*.tsx"],
15
- }
@@ -1,49 +0,0 @@
1
- import { useCallback, useEffect, useRef, useState } from '@lynx-js/react'
2
-
3
- import { createFlappy } from './lib/flappy.js'
4
- import type { FlappyEngine, FlappyOptions } from './lib/flappy.js'
5
-
6
- /**
7
- * React hook for flappy-bird physics.
8
- *
9
- * Returns `[y, jump]` — a state value and a stable callback.
10
- * The game loop runs automatically; cleanup happens on unmount.
11
- * Options are read once on mount and not reactive to later changes.
12
- *
13
- * @example
14
- * ```tsx
15
- * function Bird() {
16
- * const [y, jump] = useFlappy()
17
- * return (
18
- * <view bindtap={jump} style={{ transform: `translateY(${y}px)` }}>
19
- * <text>Tap me!</text>
20
- * </view>
21
- * )
22
- * }
23
- * ```
24
- */
25
- export function useFlappy(
26
- options?: FlappyOptions,
27
- ): [number, () => void] {
28
- const [y, setY] = useState(0)
29
- const engineRef = useRef<FlappyEngine | null>(null)
30
-
31
- if (engineRef.current == null) {
32
- engineRef.current = createFlappy((newY) => {
33
- setY(newY)
34
- }, options)
35
- }
36
-
37
- useEffect(() => {
38
- return () => {
39
- engineRef.current?.destroy()
40
- }
41
- }, [])
42
-
43
- const jump = useCallback(() => {
44
- 'background only'
45
- engineRef.current?.jump()
46
- }, [])
47
-
48
- return [y, jump]
49
- }
@@ -1,17 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "strict": true,
4
- "isolatedModules": true,
5
- "verbatimModuleSyntax": true,
6
-
7
- "esModuleInterop": true,
8
- "skipLibCheck": true,
9
-
10
- "noEmit": true,
11
- },
12
- "references": [
13
- { "path": "./tsconfig.node.json" },
14
- { "path": "./src" },
15
- ],
16
- "files": [],
17
- }
@@ -1,21 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "composite": true,
5
-
6
- // Rspeedy uses Node.js's module resolution
7
- "module": "node16",
8
- "moduleResolution": "node16",
9
- "erasableSyntaxOnly": true,
10
-
11
- // Node.js 18+
12
- "lib": ["es2023"],
13
- "target": "es2022",
14
-
15
- "noEmit": true,
16
- },
17
- "include": [
18
- "./lynx.config.ts",
19
- "./vitest.config.ts",
20
- ],
21
- }
@@ -1,9 +0,0 @@
1
- import { defineConfig, mergeConfig } from 'vitest/config'
2
- import { createVitestConfig } from '@lynx-js/react/testing-library/vitest-config'
3
-
4
- const defaultConfig = await createVitestConfig()
5
- const config = defineConfig({
6
- test: {},
7
- })
8
-
9
- export default mergeConfig(defaultConfig, config)